diff --git a/lib/Activities.dart b/lib/Activities.dart index 82d306b..57f5dea 100644 --- a/lib/Activities.dart +++ b/lib/Activities.dart @@ -8,6 +8,7 @@ import 'newActivity.dart'; import 'Data.dart'; import 'User.dart' as User; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + class Activities extends StatefulWidget { const Activities({Key? key}) : super(key: key); @@ -15,8 +16,6 @@ class Activities extends StatefulWidget { _ActivitiesState createState() => _ActivitiesState(); } - - class _ActivitiesState extends State { //late ProgressDialog progressDialog; TextEditingController searchController = TextEditingController(); @@ -26,13 +25,12 @@ class _ActivitiesState extends State { void _onFocusChange() { print("FOCUS CHANGED! : ${_focus.hasFocus}"); - if(!_focus.hasFocus){ - searching=false; - setState(() { - - }); + if (!_focus.hasFocus) { + searching = false; + setState(() {}); } } + var refreshSub; @override void initState() { @@ -40,10 +38,8 @@ class _ActivitiesState extends State { super.initState(); _focus.addListener(_onFocusChange); refreshSub = User.refreshStream.stream.listen((event) { - if(!event){ - setState(() { - - }); + if (!event) { + setState(() {}); } }); //UpdateList(); @@ -58,6 +54,7 @@ class _ActivitiesState extends State { _focus.dispose(); refreshSub?.closeStream(); } + void UpdateList() async { try { //progressDialog.show(max: 100, msg: 'Loading Activities'); @@ -68,11 +65,12 @@ class _ActivitiesState extends State { // progressDialog.update(value: 100); } catch (e) {} } + Map activitiesGroups = {}; @override Widget build(BuildContext context) { - // progressDialog = ProgressDialog(context: context); - // List activities = PrintTasks(); + // progressDialog = ProgressDialog(context: context); + // List activities = PrintTasks(); activitiesGroups = PrintTasks(); return Scaffold( floatingActionButton: FloatingActionButton.extended( @@ -82,37 +80,45 @@ class _ActivitiesState extends State { label: Text("New Activity"), icon: Icon(Icons.add)), appBar: AppBar( - toolbarHeight: (searching) ? 90 : null, + toolbarHeight: (searching) ? 90 : null, title: (searching) ? Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisSize: MainAxisSize.max, children: [ Expanded( - child: TextField(onChanged: (text){setState(() { - - });},controller: searchController, focusNode: _focus, decoration: InputDecoration( - filled: true, - ),), + child: TextField( + onChanged: (text) { + setState(() {}); + }, + controller: searchController, + focusNode: _focus, + decoration: InputDecoration( + filled: true, + ), + ), ), InkWell( - onTap: (){searching=false; + onTap: () { + searching = false; searchController.clear(); - setState(() { - - });}, + setState(() {}); + }, child: Container( - margin: EdgeInsets.all( 10), + margin: EdgeInsets.all(10), child: Icon(Icons.cancel), ), ) ], ), - Text('searched time : ${Main.MinutesToTimeString(searchTime)}',style: TextStyle(fontSize: 15),) - ], - ) + Text( + 'searched time : ${Main.MinutesToTimeString(searchTime)}', + style: TextStyle(fontSize: 15), + ) + ], + ) : Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -131,14 +137,13 @@ class _ActivitiesState extends State { size: 30, ), )), - InkWell( onTap: () { setState(() { selecting = false; }); }, - child: Container(margin:EdgeInsets.all(10),child: Icon(Icons.close, size: 30)), + child: Container(margin: EdgeInsets.all(10), child: Icon(Icons.close, size: 30)), ) ]) : Row( @@ -147,9 +152,7 @@ class _ActivitiesState extends State { onTap: () { searching = true; _focus.requestFocus(); - setState(() { - - }); + setState(() {}); }, child: Container( margin: EdgeInsets.all(10), @@ -161,11 +164,9 @@ class _ActivitiesState extends State { InkWell( onTap: () { UpdateList(); - setState(() { - - }); + setState(() {}); }, - child: Container(margin: EdgeInsets.all(10),child: Icon(Icons.refresh, size: 30)), + child: Container(margin: EdgeInsets.all(10), child: Icon(Icons.refresh, size: 30)), ) ], ), @@ -175,30 +176,29 @@ class _ActivitiesState extends State { body: Container( padding: EdgeInsets.all(0), child: ScrollablePositionedList.builder( - itemScrollController: scrollController, + itemScrollController: scrollController, itemCount: activitiesGroups.length, - itemBuilder: (context, index){ - // return activities[index]; + itemBuilder: (context, index) { + // return activities[index]; return StickyHeader( - - header:activitiesGroups.values.toList()[index].dateSeperator, - content:Column(children: activitiesGroups.values.toList()[index].activities,) - ); - - }) + header: activitiesGroups.values.toList()[index].dateSeperator, + content: Column( + children: activitiesGroups.values.toList()[index].activities, + )); + }) // SingleChildScrollView( // child: Column( // children: PrintTasks(), // )) - )); + )); } + DateFormat dFormat = DateFormat("yyyy-MM-dd"); ItemScrollController scrollController = ItemScrollController(); ScrollController controller = ScrollController(); int searchTime = 0; - Map PrintTasks() { - - Map _tasks = {}; + Map PrintTasks() { + Map _tasks = {}; print('Priting cats : ' + User.taskTypes.length.toString()); String lastDate = ""; @@ -233,31 +233,30 @@ class _ActivitiesState extends State { } print(productivtyActs); //for (var element in User.activities) { - searchTime=0; - for(int i =0; i < User.activities.length; i++){ - Activity element =User.activities[i]; - if(searching){ + searchTime = 0; + for (int i = 0; i < User.activities.length; i++) { + Activity element = User.activities[i]; + if (searching) { bool matchMetadata = element.metadata!.toLowerCase().contains(searchController.text.toLowerCase()); - bool matchTaskType=element.taskType.name.toLowerCase().contains(searchController.text.toLowerCase()); + bool matchTaskType = element.taskType.name.toLowerCase().contains(searchController.text.toLowerCase()); bool matchCategory = element.taskType.cat!.name.toLowerCase().contains(searchController.text.toLowerCase()); - if(matchMetadata || matchTaskType || matchCategory){ + if (matchMetadata || matchTaskType || matchCategory) { //Good to go searchTime += element.endTime.difference(element.startTime).inMinutes; - }else{ + } else { continue; } } String thisDate = dFormat.format(element.startTime); - if (thisDate != lastDate) { int prodActs = productivtyActs[thisDate] ?? 0; int unProdActs = unproductivtyActs[thisDate] ?? 0; //_tasks.add(DateSeperator(thisDate, prodActs, unProdActs)); - // _tasks.putIfAbsent(DateSeperator(thisDate, prodActs, unProdActs), () => [List.generate(0, (i) => List(2));]); + // _tasks.putIfAbsent(DateSeperator(thisDate, prodActs, unProdActs), () => [List.generate(0, (i) => List(2));]); _tasks.putIfAbsent(thisDate, () => ActivityListDateGroup(DateSeperator(thisDate, prodActs, unProdActs), [])); - // print('adding'); + // print('adding'); lastDate = thisDate; } String name = element.taskType.name; @@ -267,15 +266,15 @@ class _ActivitiesState extends State { Color color = Main.HexColor.fromHex(element.taskType.cat?.color ?? '#000000'); bool productive = element.taskType.cat?.productive ?? true; Widget task = ActivityCard(context, name, element.startTime, element.endTime, productive, color, element, totalMinutes[thisDate] ?? 0); - // print('Activity : ${name} ,sTime: ${element.startTime}, eTime: ${element.endTime}'); - // _tasks.values.toList()[_tasks.length-1].add(task); + // print('Activity : ${name} ,sTime: ${element.startTime}, eTime: ${element.endTime}'); + // _tasks.values.toList()[_tasks.length-1].add(task); _tasks[thisDate]!.activities.add(task); } //Check for gaps - if(i < User.activities.length-1){ - int gap = User.activities[i].trueStartTime.difference(User.activities[i+1].trueEndTime).inMinutes; - if(gap > 10) { - Widget addGap = timeGap(User.activities[i].trueStartTime, User.activities[i+1].trueEndTime); + if (i < User.activities.length - 1) { + int gap = User.activities[i].trueStartTime.difference(User.activities[i + 1].trueEndTime).inMinutes; + if (gap > 10) { + Widget addGap = timeGap(User.activities[i].trueStartTime, User.activities[i + 1].trueEndTime); //_tasks.values.toList()[_tasks.length-1].add(addGap); _tasks[thisDate]!.activities.add(addGap); @@ -286,170 +285,179 @@ class _ActivitiesState extends State { return _tasks; } - Widget timeGap(DateTime sTime, DateTime eTime){ + Widget timeGap(DateTime sTime, DateTime eTime) { DateFormat dateFormat = DateFormat("HH:mm"); String gap = Main.MinutesToTimeString(sTime.difference(eTime).inMinutes); return Row( mainAxisSize: MainAxisSize.max, - children: [ - Container(padding: EdgeInsets.fromLTRB(10, 0, 5, 0), child: Column( - mainAxisSize: MainAxisSize.max,mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Text(dateFormat.format(activity.endTime)), - SizedBox(width: 1, height: 30, child: Container(color: Colors.white)), - Text(dateFormat.format(eTime))])), + Container( + padding: EdgeInsets.fromLTRB(10, 0, 5, 0), + child: Column(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ + // Text(dateFormat.format(activity.endTime)), + SizedBox(width: 1, height: 30, child: Container(color: Colors.white)), + Text(dateFormat.format(eTime)) + ])), Expanded( child: InkWell( - onTap: (){ - Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewActivity(sTime: eTime,eTime: sTime))).then((value) => UpdateList()); + onTap: () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => NewActivity(sTime: eTime, eTime: sTime))) + .then((value) => UpdateList()); }, child: Card( child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Container( - alignment: Alignment.bottomLeft, - padding: const EdgeInsets.all(8), - child: Text('Untracked period $gap'), - ), - Row( - children: [ - Icon(Icons.add,size:30), - Text("Add Activity") - ], - ), - - ], - ) - ), + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + alignment: Alignment.bottomLeft, + padding: const EdgeInsets.all(8), + child: Text('Untracked period $gap'), + ), + Row( + children: [Icon(Icons.add, size: 30), Text("Add Activity")], + ), + ], + )), ), ), ], ); } - void OnJumpToDate(date){ + void OnJumpToDate(date) { int itemId = 0; List keys = activitiesGroups.keys.toList(); List values = activitiesGroups.values.toList(); - for(int i =0; i < activitiesGroups.length; i++){ - - if(keys[i] == dFormat.format(date)){ + for (int i = 0; i < activitiesGroups.length; i++) { + if (keys[i] == dFormat.format(date)) { break; } itemId++; - // itemId+= values[i].activities.length; + // itemId+= values[i].activities.length; } - scrollController.scrollTo(index: itemId, duration: Duration(seconds: 1),curve: Curves.fastOutSlowIn); + scrollController.scrollTo(index: itemId, duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn); } - Widget DateSeperator(date, prodActs, unprodActs,{Function? onTap}) { + Widget DateSeperator(date, prodActs, unprodActs, {Function? onTap}) { // double prodPercentage = (prodActs / (prodActs + unprodActs)) * 100; return FutureBuilder( - future: Settings.getUntrackedUnproductive(), - builder: (context, snapshot) { - double prodPercentage = (User.ParseBool(snapshot.data))? ((prodActs / 1440) * 100) : ((prodActs / unprodActs) * 100); - return Container( - color: Color(0xFF333333), - padding: const EdgeInsets.fromLTRB(0, 10, 10, 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InkWell( - onTap: (){ - DatePicker.showDatePicker(context, - showTitleActions: true, - minTime: User.activities[User.activities.length-1].startTime, - maxTime: User.activities[0].startTime, - theme: DatePickerTheme(), onChanged: (date) { - // print('change $date'); - }, onConfirm: OnJumpToDate, currentTime: DateTime.parse(date), locale: LocaleType.en); - }, - child: Row( + future: Settings.getUntrackedUnproductive(), + builder: (context, snapshot) { + double prodPercentage = (User.ParseBool(snapshot.data)) ? ((prodActs / 1440) * 100) : ((prodActs / unprodActs) * 100); + return Container( + color: Color(0xFF333333), + padding: const EdgeInsets.fromLTRB(0, 10, 10, 10), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InkWell( + onTap: () { + DatePicker.showDatePicker(context, + showTitleActions: true, + minTime: User.activities[User.activities.length - 1].startTime, + maxTime: User.activities[0].startTime, + theme: DatePickerTheme(), onChanged: (date) { + // print('change $date'); + }, onConfirm: OnJumpToDate, currentTime: DateTime.parse(date), locale: LocaleType.en); + }, + child: Row( + children: [ + SizedBox( + width: 15, + ), + Icon(Icons.circle), + SizedBox( + width: 10, + ), + Text( + date, + style: TextStyle(fontSize: 18), + ), + ], + ), + ), + Row( children: [ - SizedBox(width: 15,), - Icon(Icons.circle), + Row( + children: [ + if (prodPercentage < 35) + Text( + Main.MinutesToTimeString(prodActs), + ), + Container( + child: Align( + child: (prodPercentage >= 35) + ? FittedBox( + child: Text( + Main.MinutesToTimeString(prodActs), + )) + : Container(), + alignment: Alignment.center, + ), + width: (prodPercentage) * 1, + height: 20, + decoration: BoxDecoration(color: Colors.green, borderRadius: BorderRadius.horizontal(left: Radius.circular(10))), + ), + Container( + child: Align( + child: (prodPercentage < 35) ? Text(Main.MinutesToTimeString(unprodActs)) : Container(), + alignment: Alignment.center, + ), + width: (100 - prodPercentage) * 1, + height: 20, + decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.horizontal(right: Radius.circular(10))), + ), + if (prodPercentage >= 35) Text(Main.MinutesToTimeString(unprodActs)) + ], + ), SizedBox( width: 10, ), - Text( - date, - style: TextStyle(fontSize: 18), - ), + Text(prodPercentage.toStringAsFixed(1) + "%", + style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold, fontSize: 20)) ], - ), - ), - Row( - children: [ - Row( - children: [ - if(prodPercentage < 35)Text(Main.MinutesToTimeString(prodActs),), - Container( - child: Align( - child: (prodPercentage >= 35) ?FittedBox(child: Text(Main.MinutesToTimeString(prodActs),)) : Container(), - alignment: Alignment.center, - ), - width: (prodPercentage) * 1, - height: 20, - decoration: BoxDecoration(color: Colors.green, borderRadius: BorderRadius.horizontal(left: Radius.circular(10))), - ), - Container( - child: Align( - child: (prodPercentage < 35) ?Text(Main.MinutesToTimeString(unprodActs)) :Container(), - alignment: Alignment.center, - ), - width: (100 - prodPercentage) * 1, - height: 20, - decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.horizontal(right: Radius.circular(10))), - ), - if(prodPercentage >= 35)Text(Main.MinutesToTimeString(unprodActs)) - ], - ), - SizedBox( - width: 10, - ), - Text(prodPercentage.toStringAsFixed(1) + "%", style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold, fontSize: 20)) - ], - ) - // CustomPaint( - // painter: MyPlayerBar(100, prodPercentage.toInt(), - // background: Colors.green, fill: Colors.deepOrange), - // child: Container( - // alignment: Alignment.center, - // height: 25.0, - // width: 200, - // child: Text( - // "Productivity : ${prodPercentage.toStringAsFixed(1)}%", - // style: TextStyle(fontWeight: FontWeight.bold),), - // - // ), - // ), - ], - ) - ], - ), - ); - } - ); + ) + // CustomPaint( + // painter: MyPlayerBar(100, prodPercentage.toInt(), + // background: Colors.green, fill: Colors.deepOrange), + // child: Container( + // alignment: Alignment.center, + // height: 25.0, + // width: 200, + // child: Text( + // "Productivity : ${prodPercentage.toStringAsFixed(1)}%", + // style: TextStyle(fontWeight: FontWeight.bold),), + // + // ), + // ), + ], + ) + ], + ), + ); + }); } bool selecting = false; - Widget ActivityCard(BuildContext context, String name, DateTime sTime, DateTime eTime, bool productive, Color color, Activity activity, int totalMinutes) { + Widget ActivityCard( + BuildContext context, String name, DateTime sTime, DateTime eTime, bool productive, Color color, Activity activity, int totalMinutes) { DateFormat dateFormat = DateFormat("HH:mm"); int thisMinutes = (activity.endTime.difference(activity.startTime).inMinutes); int timePercentage = ((thisMinutes / totalMinutes) * 100).toInt(); // print("$thisMinutes / $totalMinutes"); bool containsMetadata = ((activity.metadata ?? 'null') != 'null') && ((activity.metadata ?? '').isNotEmpty); + bool containsStepData = (containsMetadata && activity.metadata!.contains('[') && activity.metadata!.contains(']')); var _timeSpan = eTime.difference(sTime); - String timeSpan = ((_timeSpan.inHours > 0) ? _timeSpan.inHours.toString() + 'h ' : '') + ((_timeSpan.inMinutes % 60 > 0) ? (_timeSpan.inMinutes % 60).toString() + 'm' : ''); + String timeSpan = ((_timeSpan.inHours > 0) ? _timeSpan.inHours.toString() + 'h ' : '') + + ((_timeSpan.inMinutes % 60 > 0) ? (_timeSpan.inMinutes % 60).toString() + 'm' : ''); return Row(children: [ // Container(), (selecting) @@ -460,14 +468,17 @@ class _ActivitiesState extends State { OnItemSelected(activity); setState(() {}); }) - : Container(padding: EdgeInsets.fromLTRB(10, 0, 5, 0), child: Column( - mainAxisSize: MainAxisSize.max,mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Text(dateFormat.format(activity.endTime)), - SizedBox(width: 1, height: 100, child: Container(color: Colors.white)), - Text(dateFormat.format(activity.startTime)), - ], - )), + : Container( + padding: EdgeInsets.fromLTRB(10, 0, 5, 0), + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Text(dateFormat.format(activity.endTime)), + SizedBox(width: 1, height: 100, child: Container(color: Colors.white)), + Text(dateFormat.format(activity.startTime)), + ], + )), Expanded( child: Column(children: [ Padding( @@ -497,19 +508,20 @@ class _ActivitiesState extends State { children: [ Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ Text(name + " [$timeSpan]", style: TextStyle(fontSize: 17)), - if (containsMetadata)Row(mainAxisAlignment:MainAxisAlignment.start,mainAxisSize: MainAxisSize.max,children: [ - - Icon( - Icons.arrow_forward_outlined, - size: 20, + if (containsMetadata) + Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ + Icon( + Icons.arrow_forward_outlined, + size: 20, + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3, + child: Text( + (containsStepData) + ? activity.metadata!.substring(activity.metadata!.indexOf(']') + 1) + : (activity.metadata ?? ''), ), - SizedBox( - width: MediaQuery.of(context).size.width/3, - child: Text( - activity.metadata ?? '', - ), - ), - + ), ]), // Icon(Icons.analytics, color: color, size: 20,), ]), @@ -523,14 +535,29 @@ class _ActivitiesState extends State { ), Row( children: [ - (activity.taskType.relatedProject!= null) ?Container( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), - child: Text(activity.taskType.relatedProject!.name ?? 'n/a')) : Container(), - SizedBox(width: 10,), + (activity.taskType.relatedProject != null) + ? Row( + children: [ + if (containsStepData) + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), + child: Text(activity.metadata!.substring(1, activity.metadata!.indexOf(']')) ?? 'n/a')), + SizedBox(width: 8 ,), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), + child: Text(activity.taskType.relatedProject!.name ?? 'n/a')), + ], + ) + : Container(), + SizedBox( + width: 10, + ), Container( padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), - decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: (productive) ? Colors.green : Colors.red), + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(10), color: (productive) ? Colors.green : Colors.red), child: Text(activity.taskType.cat?.name ?? 'n/a')), ], ) @@ -540,7 +567,8 @@ class _ActivitiesState extends State { // : Colors.red) ]) ], - )))), + )) + )), ), Container( margin: EdgeInsets.fromLTRB(15, 0, 15, 10), @@ -550,14 +578,25 @@ class _ActivitiesState extends State { )), ]), ), - if(selecting)InkWell(child: Container(margin:EdgeInsets.all(10),child: Icon(Icons.edit)),onTap: (){ - selecting=false; - selectedActivities=[]; - setState(() { - - }); - Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewActivity(sTime: activity.trueStartTime,eTime: activity.trueEndTime,metadata: activity.metadata,selectedTask: activity.taskType.name,))).then((value) => UpdateList()); - },) + if (selecting) + InkWell( + child: Container(margin: EdgeInsets.all(10), child: Icon(Icons.edit)), + onTap: () { + selecting = false; + selectedActivities = []; + setState(() {}); + Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => NewActivity( + sTime: activity.trueStartTime, + eTime: activity.trueEndTime, + metadata: activity.metadata, + selectedTask: activity.taskType.name + + ((activity.taskType.relatedProject != null) ? ' [${activity.taskType.relatedProject!.name}]' : ''), + ))) + .then((value) => UpdateList()); + }, + ) ]); } @@ -581,16 +620,15 @@ class _ActivitiesState extends State { selectedActivities = []; selecting = false; setState(() { - // progressDialog.update(value: 100); + // progressDialog.update(value: 100); }); } } -class ActivityListDateGroup{ +class ActivityListDateGroup { ActivityListDateGroup(this.dateSeperator, this.activities); Widget dateSeperator; List activities; } - List selectedActivities = []; diff --git a/lib/Data.dart b/lib/Data.dart index 0c8e35c..69e5a83 100644 --- a/lib/Data.dart +++ b/lib/Data.dart @@ -155,7 +155,21 @@ class Journal{ static String colDescription = 'Desc'; } +class Todo{ + Todo(this.id, this.category,this.metadata, this.dueDate, {this.notificationTime, this.task}); + String id; + String category; + TaskType? task; + String metadata; + DateTime dueDate; + DateTime? notificationTime; + + static String colCat = 'category'; + static String colMetadata = 'metadata'; + static String colDueDate = 'due_date'; + static String colNotificationTime = 'notification_time'; +} class Settings{ diff --git a/lib/Dialogs.dart b/lib/Dialogs.dart index 67a846b..dd35b20 100644 --- a/lib/Dialogs.dart +++ b/lib/Dialogs.dart @@ -310,6 +310,60 @@ class Dialogs{ } } + static showJournalLink(DateTime date) async{ + context=navigatorKey.currentContext; + + if(context!=null) { + int journalId = -1; + for (int i =0; i < User.journal.length; i++) { + print('${User.journal[i].day } : $date'); + if(DateFormat('yyyy-MM-dd').format(User.journal[i].day) == DateFormat('yyyy-MM-dd').format(date)){ + journalId = i; + break; + } + } + if(journalId < 0){return;} + String title= '${(User.journal[journalId].title!= null && User.journal[journalId].title!.isNotEmpty) ? User.journal[journalId].title : DateFormat('MMMM-dd').format(date)}'; + String description = User.journal[journalId].description ?? ''; + if(description.length > 60){ + description = description.substring(0,60) + '...(click more for more)'; + } + return showDialog( + context: context!, + builder: (BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30)), + backgroundColor: Color(0xFF1C1C1C), + title: Text(title), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(description) + ], + ), + actions: [ + MaterialButton( + onPressed: (){ + Navigator.of(context).pop(); + }, + child: Text('No', style:TextStyle(color: Colors.red)), + ), + MaterialButton( + onPressed: (){ + Navigator.of(context).pop(); + }, + child: Text('Yes', style:TextStyle(color: Colors.green)), + ) + ], + ); + } + ); + }else{ + print('context is null'); + } + } + static hide(){ showing=false; Navigator.of(navigatorKey.currentContext!).popUntil((route){ diff --git a/lib/Journal.dart b/lib/Journal.dart index f34b793..66ca34c 100644 --- a/lib/Journal.dart +++ b/lib/Journal.dart @@ -80,6 +80,9 @@ class _JournalPageState extends State{ }); },child: Container(margin:EdgeInsets.all(8),child: Icon(Icons.cancel))), + if(!selecting)InkWell(onTap:(){setState(() { + + });},child: Container(margin: EdgeInsets.all(8), child: Icon(Icons.refresh),)) ], ) ], diff --git a/lib/NewProject.dart b/lib/NewProject.dart index 484b27a..627de0f 100644 --- a/lib/NewProject.dart +++ b/lib/NewProject.dart @@ -37,11 +37,13 @@ bool editing = false; bool multiLine = true; String selectedCat = User.categories[0].name; +String oldName = '[]'; class _NewProjectState extends State { _NewProjectState({bool? multiline, String? pname, String? selectedCategory, List? m_steps, int? m_eta}){ multiLine=multiline ?? true; nameController.text = pname ?? ''; + if(pname!=null){oldName=pname;} if(selectedCategory!=null){selectedCat = selectedCategory;} if(m_steps!=null){steps=m_steps;}else{print('no steps?');} if(multiline!=null && pname != null && selectedCategory != null && m_steps!=null){ @@ -500,7 +502,12 @@ class _NewProject2State extends State { child: ElevatedButton( style: ElevatedButton.styleFrom(primary: Colors.green, shape: StadiumBorder()), onPressed: () { - OnClickAdd(); + if(editing){ + OnClickEdit(); + }else{ + + OnClickAdd(); + } }, child: Text((editing) ? 'Apply' : 'Add', style: TextStyle(fontSize: 20))))), ], @@ -688,6 +695,16 @@ class _NewProject2State extends State { return route.isFirst; }); } + + void OnClickEdit() async{ + if(projectName==null || deadline.isBefore(DateTime.now())){showAlertDialog(context, 'Error', 'Please make sure you have entered correct information'); return;} + + User.UserOperations.editProject(oldName,projectName!, selectedCat, steps,etaHours, deadline); + + Navigator.of(navigatorKey.currentContext!).popUntil((route){ + return route.isFirst; + }); + } } diff --git a/lib/NewTask.dart b/lib/NewTask.dart index 414fb35..8aa945e 100644 --- a/lib/NewTask.dart +++ b/lib/NewTask.dart @@ -6,7 +6,7 @@ import 'package:tasktracker/NewProject.dart'; import 'Data.dart'; import 'User.dart' as User; -DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); +DateFormat dateTimeFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); DateFormat durationFormat = DateFormat("HH:mm:ss"); class NewTask extends StatefulWidget { diff --git a/lib/NewTodo.dart b/lib/NewTodo.dart new file mode 100644 index 0000000..6fcff10 --- /dev/null +++ b/lib/NewTodo.dart @@ -0,0 +1,337 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/painting.dart'; +import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:intl/intl.dart'; +import 'package:tasktracker/NewTask.dart'; +import 'package:tasktracker/NotificationsManager.dart'; +import 'Data.dart'; +import 'Dialogs.dart'; +import 'User.dart' as User; + +DateFormat dateFormat = DateFormat("yyyy-MM-dd"); +DateFormat dateTimeFormat = DateFormat("yyyy-MM-dd HH:mm"); +DateFormat durationFormat = DateFormat("HH:mm:ss"); + +class NewTodo extends StatefulWidget { + NewTodo({Key? key, this.selectedTask, this.metadata, this.dueDate,this.notificationTime}) : super(key: key); + late String? metadata; + late String? selectedTask; + late DateTime? dueDate; + late DateTime? notificationTime; + @override + _NewActivity createState() => _NewActivity(selectedCat: selectedTask, metadata: metadata); +} + +class _NewActivity extends State { + late String init_selectedTask; + _NewActivity({String? metadata, String? selectedCat, DateTime? DueDate, this.notificationTime}) { + editing =selectedCat != null && DueDate !=null; + dueDate = DueDate ?? DateTime.now().add(Duration(days: 1)); + this.metadataController.text = metadata ?? ""; + if(this.metadataController.text.contains('[') && this.metadataController.text.contains(']') ){ + this.metadataController.text = this.metadataController.text.substring(this.metadataController.text.indexOf(']')+1); + selectedStep = metadata!.substring(1,metadata!.indexOf(']')); + }else{ + selectedStep='None'; + } + this.init_selectedTask = this.selectedCat = selectedCat ?? User.taskTypes[0].name; + print(" meta:$metadata, task: $selectedCat"); + } + + TextEditingController metadataController = TextEditingController(); + late String selectedCat; + late DateTime dueDate; + DateTime? notificationTime; + + bool editing = false; + Map taskTypes = {}; + Map getActivities() { + Map _cats = {}; + _cats.putIfAbsent("+Add New Task Type", () => null); + if (User.taskTypes.isEmpty) { + } else { + print(User.taskTypes[0].name + " : " + selectedCat); + } + User.taskTypes.forEach((element) { + String name = element.name; + if (_cats.keys.toString().contains(element.name)) { + } else { + String displayName = (name + ((element.relatedProject != null) ? ' [${element.relatedProject!.name}]' : '')); + _cats.putIfAbsent(displayName, () => element); + } + }); + return _cats; + } + + String? selectedStep = null; + @override + Widget build(BuildContext context) { + taskTypes = getActivities(); + bool canSelectStep = false; + List steps = ['None']; + if (taskTypes[selectedCat] != null) { + if (taskTypes[selectedCat]!.relatedProject != null) { + //Got a project. But is it multi step? + if (taskTypes[selectedCat]!.relatedProject!.steps.isNotEmpty) { + canSelectStep = true; + bool matchesSelectedStep = false; + taskTypes[selectedCat]!.relatedProject!.steps.forEach((element) { + if (element.finishedDate == null) { + steps.add(element.stepName); + if (selectedStep == null) { + selectedStep = element.stepName; + matchesSelectedStep = true; + } + if (element.stepName == selectedStep) { + matchesSelectedStep = true; + } + } + }); + if (!matchesSelectedStep) { + selectedStep = steps[0]; + } + } + } + } + + return Scaffold( + appBar: AppBar(title: Text((editing) ? 'Edit To-Do' : 'New To-Do')), + body: Column(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Expanded( + flex: 9, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + child: Column( + children: [ + Column(children: [ + Container(padding: EdgeInsets.all(10), child: Text('Task')), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 1), + decoration: BoxDecoration( + color: Colors.black12, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2)), + child: DropdownButton( + dropdownColor: Color(0xFF222222), + iconSize: 30, + elevation: 10, + borderRadius: BorderRadius.circular(10), + value: selectedCat, + isExpanded: true, + items: getActivities().keys.map>((String value) { + print(value); + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? _value) { + if (_value == '+Add New Task Type') { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewTask())).then((val) { + setState(() {}); + }); + } else { + selectedCat = _value ?? 'n/a'; + } + setState(() {}); + })), + if (canSelectStep) Container(padding: EdgeInsets.all(10), child: Text('Step')), + if (canSelectStep) + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 1), + decoration: BoxDecoration( + color: Colors.black12, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2)), + child: DropdownButton( + dropdownColor: Color(0xFF222222), + iconSize: 30, + elevation: 10, + borderRadius: BorderRadius.circular(10), + value: selectedStep, + isExpanded: true, + items: steps.map>((String value) { + print(value); + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? _value) { + selectedStep = _value; + setState(() {}); + })), + Container( + padding: EdgeInsets.all(10), + child: Column( + children: [ + TextField( + controller: metadataController, + decoration: InputDecoration( + hintText: 'Description (required)', + filled: true, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(20))), + ), + ], + )), + Container( + child: Divider( + )), + ListTile( + leading: FaIcon(FontAwesomeIcons.calendarDay), + title: Text('Due Date'), + subtitle: Text('When do you want to do this?'), + onTap:(){setState(() { + DatePicker.showDatePicker(context, showTitleActions: true, onChanged: (date) { + // print('change $date'); + }, onConfirm: (date) { + setState(() { + dueDate=date; + }); + }, currentTime: dueDate, locale: LocaleType.en); + });} , + trailing: InkWell( + child:Text(dateFormat.format(dueDate), style: TextStyle(color: Colors.blue)) + ), + ), + Divider(), + + ListTile( + title: Text('Notification'), + leading: FaIcon((notificationTime==null) ?FontAwesomeIcons.bellSlash :FontAwesomeIcons.bell), + trailing: Switch.adaptive(value: notificationTime!=null, onChanged: (val){ + if(notificationTime==null){ + notificationTime = DateTime.now().add(Duration(days: 1)); + }else{ + notificationTime = null; + } + setState(() { + + }); + }), + ) + ]), + ListTile( + enabled: notificationTime!=null, + leading: FaIcon(FontAwesomeIcons.clock), + title: Text('Notify me at'), + subtitle: Text('When do you want to do this?'), + onTap:(){setState(() { + DatePicker.showDateTimePicker(context, showTitleActions: true, onChanged: (date) { + // print('change $date'); + }, onConfirm: (date) { + setState(() { + notificationTime=date; + }); + }, currentTime: notificationTime, locale: LocaleType.en); + });} , + trailing: InkWell( + child:Text(dateTimeFormat.format(notificationTime ?? DateTime.now()), style: TextStyle(color: Colors.blue)) + ), + ), + ], + )), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 5, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), + child: ElevatedButton( + style: ElevatedButton.styleFrom(primary: Colors.red, shape: StadiumBorder()), + onPressed: () { + Navigator.pop(context); + }, + child: Text('Back', style: TextStyle(fontSize: 20))))), + Expanded( + flex: 6, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), + child: ElevatedButton( + style: ElevatedButton.styleFrom(primary: Colors.green, shape: StadiumBorder()), + onPressed: () { + if (editing) { + edit_action(); + } else { + add_action(); + } + }, + child: Text((editing) ? 'Apply' : 'Add Todo', style: TextStyle(fontSize: 20))))), + ], + )), + ) + ])); + } + + Widget QuickTimeButton(text, {Function}) { + return InkWell( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 15), + height: 30, + decoration: BoxDecoration(color: Colors.blueAccent, borderRadius: BorderRadius.circular(50)), + child: Align( + child: Text(text), + alignment: Alignment.center, + )), + onTap: () { + Function(); + setState(() {}); + }, + ); + } + + void add_action() async { + if(metadataController.text.isEmpty){ + Dialogs.showAlertDialog(context, 'Invalid data', 'Please enter description to add new todo'); + return; + } + + await User.UserOperations.addTodo(selectedCat, metadataController.text, dueDate, notificationTime); + Navigator.of(context).pop(); + } + + void edit_action() async { + + } +} + +String _printDuration(Duration duration) { + String twoDigits(int n) => n.toString().padLeft(2, "0"); + String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); + String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); + return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds"; +} + +showAlertDialog(BuildContext context, String title, String message) { + // set up the button + Widget okButton = TextButton( + child: Text("OK"), + onPressed: () { + Navigator.of(context).pop(); + }, + ); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: Text(title), + content: Text(message), + actions: [ + okButton, + ], + ); + + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return alert; + }, + ); +} diff --git a/lib/Todo.dart b/lib/Todo.dart deleted file mode 100644 index 254d1ed..0000000 --- a/lib/Todo.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'User.dart' as User; -import 'main.dart'; -class TodoPage extends StatefulWidget { - const TodoPage({Key? key}) : super(key: key); - - @override - State createState() => _TodoPageState(); -} - -class _TodoPageState extends State { - @override - Widget build(BuildContext context) { - return SafeArea(child: Scaffold( - floatingActionButton: FloatingActionButton.extended( - onPressed: () { - // Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewTodo())).then((value) => {User.refreshUserData().then((va) => {})}); - }, - label: Text("New Todo"), - icon: Icon(Icons.add)), - appBar: AppBar( - title: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row(children: [Icon(Icons.check, color: Theme.of(context).primaryColor), SizedBox(width: 10), Text('Todo')]), - Row( - children: [ - (User.offline) - ? Icon(Icons.signal_cellular_connected_no_internet_4_bar_outlined) - : InkWell( - onTap: () { - setState(() { - //LoadStats(); - }); - }, - child: Icon(Icons.refresh, size: 30), - ) - ], - ) - ], - ), - //Container(color: Colors.red,child: Text("Offline",style:TextStyle(fontSize: 5))), - ], - )), - drawer: navDrawer(context, 7), - body: Column( - children: PrintTodos(), - ), - )); - } - - List PrintTodos(){ - List todos = []; - - todos.forEach((element) { - - }); - - todos.add(prioritySeperator('- High Priority (0)')); - todos.add(prioritySeperator('- Low Priority (0)')); - - return todos; - } - - Widget todoItem(String name){ - return Row(children:[Text(name)]); - } - - Widget prioritySeperator(String text){ - return Padding( - padding: const EdgeInsets.fromLTRB(15,15,15,0), - child: Row( - children: [Text(text,style:TextStyle(fontSize: 18, color: Colors.grey))], - ), - ); - } -} diff --git a/lib/Todos.dart b/lib/Todos.dart new file mode 100644 index 0000000..60dc4be --- /dev/null +++ b/lib/Todos.dart @@ -0,0 +1,278 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_colorpicker/flutter_colorpicker.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:intl/intl.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:tasktracker/NewJournal.dart'; +import 'package:tasktracker/NewTodo.dart'; +import 'package:tasktracker/main.dart'; +import 'User.dart' as User; +import 'Dialogs.dart'; + +class TodosPage extends StatefulWidget { + const TodosPage({Key? key}) : super(key: key); + + @override + State createState() => _TodosPageState(); +} + +class _TodosPageState extends State { + bool selecting = false; + List selectedIndexes = []; + int expandedIndex = -1; + var refreshStream; + + @override + void initState() { + // TODO: implement initState + super.initState(); + + refreshStream = User.refreshStream.stream.listen((event) { + if (!event) { + setState(() {}); + } + }); + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + refreshStream?.close(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: FloatingActionButton.extended( + onPressed: () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewTodo())).then((val) { + setState(() {}); + }); + }, + label: Text("New To-Do"), + icon: Icon(Icons.add)), + appBar: AppBar( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + FaIcon(FontAwesomeIcons.calendarCheck), + SizedBox( + width: 15, + ), + Text('To-Do') + ], + ), + Row( + children: [ + if (selecting && selectedIndexes.length > 0) + InkWell( + onTap: () async { + selecting = false; + for (int element in selectedIndexes) { + await User.UserOperations.deleteJournal(User.todos[element].id); + } + setState(() {}); + }, + child: Container(margin: EdgeInsets.all(8), child: Icon(Icons.delete))), + if (selecting) + InkWell( + onTap: () { + selecting = false; + setState(() {}); + }, + child: Container(margin: EdgeInsets.all(8), child: Icon(Icons.cancel))), + if (!selecting) + InkWell( + onTap: () { + setState(() {}); + }, + child: Container( + margin: EdgeInsets.all(8), + child: Icon(Icons.refresh), + )) + ], + ) + ], + ), + ), + drawer: navDrawer(context, 9), + body: Container( + padding: EdgeInsets.all(8), + child: ScrollablePositionedList.builder( + itemCount: User.todos.length, + itemBuilder: (context, index) { + int maxCharCount = 100; + bool containsStepData = User.todos[index].metadata.contains('[') && User.todos[index].metadata.contains(']'); + return Container( + child: InkWell( + onTap: () { + if (selecting) { + if (selectedIndexes.contains(index)) { + selectedIndexes.remove(index); + } else { + selectedIndexes.add(index); + } + setState(() {}); + } else { + if (expandedIndex == index) { + expandedIndex = -1; + //_controller..reverse(from: 0.5); + } else { + expandedIndex = index; + // _controller..forward(from: 0); + } + setState(() {}); + } + }, + onLongPress: () { + selecting = !selecting; + if (!selectedIndexes.contains(index)) { + selectedIndexes.add(index); + } + setState(() {}); + }, + child: Row( + children: [ + if (selecting) + Checkbox( + value: selectedIndexes.contains(index), + onChanged: (newVal) { + if (selectedIndexes.contains(index)) { + selectedIndexes.remove(index); + } else { + selectedIndexes.add(index); + } + setState(() {}); + }, + ), + Expanded( + child: Column( + children: [ + Card( + + // color: color, + elevation: 30, + shadowColor: colorFromHex(User.todos[index].task!.cat!.color), + child: InkWell( + onTap: () { + //Open Respective Category + if (selecting) { + //OnItemSelected(activity); + } + setState(() {}); + }, + onLongPress: () { + print('gonna delete'); + selecting = !selecting; + // selectedActivities = [activity]; + setState(() {}); + }, + child: Container( + padding: EdgeInsets.all(15), + child: Column( + children: [ + Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ + Text(User.todos[index].task!.name, style: TextStyle(fontSize: 17)), + Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ + Icon( + Icons.arrow_forward_outlined, + size: 20, + ), + SizedBox( + width: MediaQuery.of(context).size.width / 3, + child: Text( + (containsStepData) + ? User.todos[index].metadata.substring(User.todos[index].metadata!.indexOf(']') + 1) + : (User.todos[index].metadata ?? ''), + ), + ), + ]), + // Icon(Icons.analytics, color: color, size: 20,), + ]), + SizedBox( + height: 5, + ), + Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.blue), + child: Text(DateFormat('yyyy-MM-dd').format(User.todos[index].dueDate))), + SizedBox( + width: 20, + ), + Row( + children: [ + (User.todos[index].task!.relatedProject != null) + ? Row( + children: [ + if (containsStepData) + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), + child: Text(User.todos[index].metadata! + .substring(1, User.todos[index].metadata!.indexOf(']')) ?? + 'n/a')), + SizedBox( + width: 8, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(10), color: Colors.black26), + child: Text(User.todos[index].task!.relatedProject!.name ?? 'n/a')), + ], + ) + : Container(), + SizedBox( + width: 10, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: (User.todos[index].task!.cat!.productive) ? Colors.green : Colors.red), + child: Text(User.todos[index].task!.cat?.name ?? 'n/a')), + ], + ) + // Icon(Icons.circle, + // color: (productive) + // ? Colors.green + // : Colors.red) + ]) + ], + )))), + Container( + margin: EdgeInsets.fromLTRB(15, 0, 15, 10), + height: 2, + child: Row( + children: [ + Expanded(flex: 100, child: Container(color: colorFromHex(User.todos[index].task!.cat!.color))), + Expanded(flex: 0, child: Container()) + ], + )), + ], + ), + ), + if (selecting) + InkWell( + onTap: () { + // Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewJournal(date: User.todos[index].day, title: User.todos[index].title, text: User.todos[index].description,))).then((val) { + // setState(() {}); + // }); + selecting = false; + setState(() {}); + }, + child: Container(margin: EdgeInsets.all(8), child: FaIcon(FontAwesomeIcons.edit))) + ], + ), + )); + }), + ), + ); + } +} diff --git a/lib/User.dart b/lib/User.dart index cd30f40..0529145 100644 --- a/lib/User.dart +++ b/lib/User.dart @@ -29,6 +29,7 @@ List taskTypes = []; List activities = []; List projects = []; List journal = []; +List todos = []; bool offline = true; bool registered = false; @@ -80,83 +81,81 @@ Future initUserData() async { } } -Future BuildBridgeToServer() async{ +Future BuildBridgeToServer() async { final prefs = await SharedPreferences.getInstance(); - while(true){ + while (true) { await UserOperations.executeQueries(); try { - http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/bridge.php'), - body: {"username": username})); + http.Response response = + (await http.post(Uri.parse('http://161.97.127.136/task_tracker/bridge.php'), body: {"username": username})); print("bridge retreive (try): ${response.body}"); List data = response.body.split(","); print('Update :\nactivities_rev=${data[0]} tasks_rev=${data[1]} cats_rev=${data[2]} projects_rev=${data[3]}'); - if(data[4].contains('')){ + if (data[4].contains('')) { List ongoingData = data[4].split(""); - if(!prefs.containsKey('current_activity')) { + if (!prefs.containsKey('current_activity')) { StartActivityTimer(ongoingData[0], ongoingData[1], DateTime.parse(ongoingData[2])); } - }else{ - if(prefs.containsKey('current_activity'))CancelOngoingActivity(); + } else { + if (prefs.containsKey('current_activity')) CancelOngoingActivity(); } - if(prefs.containsKey('rev')){ - if(prefs.getString('rev') == response.body){ + if (prefs.containsKey('rev')) { + if (prefs.getString('rev') == response.body) { print('We are on the same page'); - }else{ + } else { print('Gotta update'); await refreshUserData(); - prefs.setString('rev',response.body); + prefs.setString('rev', response.body); } - }else{ + } else { prefs.setString('rev', response.body); await refreshUserData(); } - - }catch(e){ + } catch (e) { print("Error with bridge : $e"); } await Future.delayed(Duration(seconds: 5)); - } } bool m_refreshing = false; Future refreshUserData({bool forceOffline = false}) async { print('refreshing user data'); - if(m_refreshing){ - print('Called while refreshing. Return');return; + if (m_refreshing) { + print('Called while refreshing. Return'); + return; } - m_refreshing=true; + m_refreshing = true; - if(forceOffline) { + if (forceOffline) { refreshStream.add(true); categories = await GetCategories(true); - projects= await GetProjects(true); + projects = await GetProjects(true); taskTypes = await GetTaskTypes(true); activities = await GetActivities(true); journal = await GetJournals(true); + todos = await GetTodos(true); refreshStream.add(false); - }else{ - + } else { categories = await GetCategories(false); - projects= await GetProjects(false); + projects = await GetProjects(false); taskTypes = await GetTaskTypes(false); activities = await GetActivities(false); journal = await GetJournals(false); - + todos= await GetTodos(false); } - m_refreshing=false; + m_refreshing = false; if (cacheEnabled) { NotificationManager.RescheduleNotifications(); } } - Future cacheDbExist() async { if (Platform.isAndroid || Platform.isIOS) { Directory directory = await getApplicationDocumentsDirectory(); @@ -179,7 +178,7 @@ Future updateActList() async { activities = await GetActivities(false); } -Future updateProjectsList() async{ +Future updateProjectsList() async { projects = await GetProjects(false); } @@ -202,7 +201,8 @@ void onCacheDatabaseCreate(Database db, int newVersion) async { await db.execute(CategoriesTableSQL); print("Initiated Categories Table"); - String TaskTableSQL = 'CREATE TABLE TaskTypes(id TEXT PRIMARY KEY, ${TaskType.colName} TEXT, ${TaskType.colCategory} TEXT, ${TaskType.colRelatedProject} TEXT, ' + String TaskTableSQL = + 'CREATE TABLE TaskTypes(id TEXT PRIMARY KEY, ${TaskType.colName} TEXT, ${TaskType.colCategory} TEXT, ${TaskType.colRelatedProject} TEXT, ' 'FOREIGN KEY (${TaskType.colCategory}) REFERENCES Categories(${Category.colCatId}))'; // print(TaskTableSQL); await db.execute(TaskTableSQL); @@ -219,9 +219,13 @@ void onCacheDatabaseCreate(Database db, int newVersion) async { String JournalTableSQL = 'CREATE TABLE Journal(id TEXT PRIMARY KEY, ${Journal.colTitle} TEXT, ${Journal.colDescription})'; await db.execute(JournalTableSQL); + + String TodoTableSQL = 'CREATE TABLE Todos(id TEXT PRIMARY KEY, ${Todo.colCat} TEXT, ${Todo.colMetadata} TEXT, ${Todo.colDueDate} DATE, ${Todo.colNotificationTime} DATETIME)'; + await db.execute(TodoTableSQL); + String QueriesTableSQL = 'CREATE TABLE Queries(id INTEGER PRIMARY KEY AUTOINCREMENT, ${Queries.colLink} TEXT,${Queries.colData} TEXT)'; -// print(QueriesTableSQL); await db.execute(QueriesTableSQL); + final prefs = await SharedPreferences.getInstance(); if (prefs.getBool("registered") ?? false) { @@ -232,7 +236,7 @@ void onCacheDatabaseCreate(Database db, int newVersion) async { } Future addInitialDataToCache() async { - Dialogs.syncingMessage= "Adding initial data"; + Dialogs.syncingMessage = "Adding initial data"; print("adding init data"); await Future.delayed(const Duration(seconds: 1)); @@ -240,7 +244,7 @@ Future addInitialDataToCache() async { for (Category element in InitialData.getCategories(username)) { await UserOperations.addCategory(element.name, element.color, element.productive, bulk: true); } - Dialogs.syncingMessage= "Just a minute"; + Dialogs.syncingMessage = "Just a minute"; for (TaskType element in InitialData.getTaskTypes(username)) { await UserOperations.addTaskType(element.name, element.category, bulk: true); // Map data = { @@ -249,9 +253,9 @@ Future addInitialDataToCache() async { // }; // await cacheDb.insert('TaskTypes', data); } - Dialogs.syncingMessage= "Syncing"; + Dialogs.syncingMessage = "Syncing"; await UserOperations.executeQueries(); - // await refreshUserData(); + // await refreshUserData(); } void onCacheDatabaseUpgrade(Database db, int oldVersion, int newVersion) async { @@ -333,7 +337,7 @@ Future UpdateCategoriesFromServer() async { print(response.body); List data = response.body.split(""); - await cacheDb.delete("Categories"); + await cacheDb.delete("Categories"); for (var value in data) { Map cat = jsonDecode(value); //print(catData); @@ -390,13 +394,13 @@ Future> GetTaskTypes(bool forceOffline) async { continue; } Project? relatedProject; - if(related_project.isNotEmpty){ + if (related_project.isNotEmpty) { relatedProject = await getProjectFromId(related_project); print('got a tasktype with project'); } // print("name:{$name}, cat:{$category}, prod:{$id}"); - _taskTypes.add(TaskType(id, name, category, cat:cat, relatedProject: relatedProject)); + _taskTypes.add(TaskType(id, name, category, cat: cat, relatedProject: relatedProject)); } taskTypes = _taskTypes; } else { @@ -412,10 +416,10 @@ Future> GetTaskTypes(bool forceOffline) async { for (var value in data) { Map data = jsonDecode(value); Category? cat = await getCatFromId(data['category_id']); - _taskTypes.add(TaskType(data['task_id'], data['name'], data['category_id'], cat:cat,relatedProject: data['related_project'])); + _taskTypes.add(TaskType(data['task_id'], data['name'], data['category_id'], cat: cat, relatedProject: data['related_project'])); //print(cat); } - }catch(e){ + } catch (e) { print("Error while tasks NC $e"); } @@ -437,8 +441,9 @@ Future UpdateTaskTypesFromServer() async { for (var value in data) { Map cat = jsonDecode(value); //print(cat); - await cacheDb.rawInsert("INSERT OR REPLACE INTO TaskTypes (${TaskType.colId},${TaskType.colName},${TaskType.colCategory},${TaskType.colRelatedProject}) " - "VALUES ('${cat['task_id']}','${cat['name']}','${cat['category_id']}', '${cat['related_project']}') "); + await cacheDb + .rawInsert("INSERT OR REPLACE INTO TaskTypes (${TaskType.colId},${TaskType.colName},${TaskType.colCategory},${TaskType.colRelatedProject}) " + "VALUES ('${cat['task_id']}','${cat['name']}','${cat['category_id']}', '${cat['related_project']}') "); print(await cacheDb.query("TaskTypes")); } @@ -447,41 +452,37 @@ Future UpdateTaskTypesFromServer() async { } } - -void StartActivityTimer(String taskType, String metadata, DateTime startTime) async{ +void StartActivityTimer(String taskType, String metadata, DateTime startTime) async { final prefs = await SharedPreferences.getInstance(); prefs.setString('current_activity', "$taskType$metadata${UserOperations.dFormat.format(startTime)}"); NotificationManager.RescheduleNotifications(); UserOperations.startOngoing(prefs.getString('current_activity')!); - } -Future?> getOngoingData() async{ +Future?> getOngoingData() async { final prefs = await SharedPreferences.getInstance(); - if(prefs.containsKey('current_activity')){ + if (prefs.containsKey('current_activity')) { List data = []; - try{ + try { data = prefs.getString('current_activity')!.split(''); return data; - }catch(e){ - } - }else{ - } + } catch (e) {} + } else {} } -void StopActivityTimer() async{ +void StopActivityTimer() async { final prefs = await SharedPreferences.getInstance(); try { List data = prefs.getString('current_activity')!.split(""); - UserOperations.addActivity(data[0], DateTime.parse(data[2]), DateTime.now(),metadata: data[1]); - }catch(e){} + UserOperations.addActivity(data[0], DateTime.parse(data[2]), DateTime.now(), metadata: data[1]); + } catch (e) {} prefs.remove('current_activity'); UserOperations.stopOngoing(); } -void CancelOngoingActivity() async{ +void CancelOngoingActivity() async { final prefs = await SharedPreferences.getInstance(); prefs.remove('current_activity'); UserOperations.stopOngoing(); @@ -623,7 +624,7 @@ Future UpdateActivitiesFromServer() async { //print(cat); await cacheDb.rawInsert( "INSERT OR REPLACE INTO Activities (${Activity.colType}, ${Activity.colStartTime}, ${Activity.colEndTime}, ${Activity.colMetadata}) " - "VALUES ('${cat['task_id']}', '${cat['sTime']}','${cat['eTime']}', '${cat['metadata']}') "); + "VALUES ('${cat['task_id']}', '${cat['sTime']}','${cat['eTime']}', '${cat['metadata'].toString().replaceAll("'", "''")}') "); } } else { print("No activities for now"); @@ -661,16 +662,20 @@ Future> GetProjects(bool forceOffline) async { String? category = element[Project.colCat]; String? stepsJson = element[Project.colSteps]; String? deadline = element[Project.colDeadline]; - int? eta= element[Project.colEta]; + int? eta = element[Project.colEta]; print(name); - if (name == null || category == null || stepsJson == null || deadline == null || eta==null) { + if (name == null || category == null || stepsJson == null || deadline == null || eta == null) { print("Something is null!\nname:${name == null}, cat:${category == null}, steps:${stepsJson == null}, deadline${deadline == null}"); print("TaskType:{$name}, Start Time:{$category}, endTime:{$stepsJson}, metadata:${deadline}"); continue; } Category? cat = await getCatFromId(category); - if(cat==null){print('couldnt find cat');continue;} + print('searching for $category'); + if (cat == null) { + print('couldnt find cat'); + continue; + } print('steps : $stepsJson'); List _steps = jsonDecode(stepsJson); List steps = []; @@ -682,9 +687,9 @@ Future> GetProjects(bool forceOffline) async { print(element); }); eta = (m_eta > 0) ? m_eta : eta; - // print(steps); + // print(steps); - _projects.add(Project(name.replaceAll(username, ""),category,steps,eta,DateTime.parse(deadline),cat: cat)); + _projects.add(Project(name.replaceAll(username, ""), category, steps, eta, DateTime.parse(deadline), cat: cat)); } projects = _projects; } else { @@ -710,7 +715,6 @@ Future> GetProjects(bool forceOffline) async { print('null found'); continue; } - } projects = _projects; @@ -745,7 +749,7 @@ Future UpdateProjectsFromServer() async { print(cat); await cacheDb.rawInsert( "INSERT OR REPLACE INTO Projects (${Project.colName}, ${Project.colCat}, ${Project.colSteps}, ${Project.colDeadline}, ${Project.colEta}) " - "VALUES ('${cat['name']}', '${cat['category']}', '${cat['steps']}', '${cat['deadline']}', ${cat['eta']})"); + "VALUES ('${cat['name']}', '${cat['category']}', '${cat['steps']}', '${cat['deadline']}', ${cat['eta']})"); } } else { print("No activities for now"); @@ -795,7 +799,7 @@ Future> GetJournals(bool forceOffline) async { } DateTime day = DateTime.parse(id.replaceAll(username, '')); // print("name:{$catName}, color:{$catColor}, prod:{$catProductive}"); - _journals.add(Journal(id,day,title: title,description: text)); + _journals.add(Journal(id, day, title: title, description: text)); } journal = _journals; } else { @@ -810,14 +814,15 @@ Future> GetJournals(bool forceOffline) async { for (var value in data) { Map cat = jsonDecode(value); //print(catData); - _categories.add(Journal(cat['id'],DateTime.parse(cat['id'].toString().replaceAll(username, '')), title:cat['title'], description:cat['text'])); + _categories + .add(Journal(cat['id'], DateTime.parse(cat['id'].toString().replaceAll(username, '')), title: cat['title'], description: cat['text'])); } journal = _categories; } catch (e) { print("Error while cats NC: $e"); } } - journal.sort((a,b)=> b.day.compareTo(a.day)); + journal.sort((a, b) => b.day.compareTo(a.day)); return journal; } @@ -833,8 +838,7 @@ Future UpdateJournalsFromServer() async { for (var value in data) { Map cat = jsonDecode(value); //print(catData); - await cacheDb - .rawInsert("INSERT OR REPLACE INTO Journal (id, ${Journal.colTitle},${Journal.colDescription}) " + await cacheDb.rawInsert("INSERT OR REPLACE INTO Journal (id, ${Journal.colTitle},${Journal.colDescription}) " "VALUES ('${cat['id']}','${cat['title'].toString().replaceAll("'", "''")}','${cat['description'].toString().replaceAll("'", "''")}') "); } } catch (e) { @@ -842,6 +846,100 @@ Future UpdateJournalsFromServer() async { } } +Future> GetTodos(bool forceOffline) async { + if (cacheEnabled) { + List _todos = []; + if (offline || forceOffline) { + //Retreive from cacheDB + + } else { + //Check if server got updated, If not go for cache + + //Validate device_id to check updates + + bool catsUpdated = false; + // try { + // http.Response update_response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/check_update.php'), body: {"username": username, "device_id": android_id})); + // final data = update_response.body.split(','); + // catsUpdated = data[0] == '1'; + // } catch (e) { + // print(e); + // } + + //Update CacheDB + if (!catsUpdated) { + await UpdateTodosFromServer(); + } + } + + List cats = await cacheDb.query('Todos'); + print(cats.length); + for (Map element in cats) { + String? id = element['id'].toString(); + String? task_id = element[Todo.colCat]; + String? metadata = element[Todo.colMetadata]; + String? due_date = element[Todo.colDueDate]; + String? notification_time = element[Todo.colNotificationTime]; + + if (id == null || task_id == null || metadata == null || due_date == null) { + print("Something is null!"); + print("id:{$id}, task:{$task_id}, metadata:${metadata}, due_date: ${due_date}"); + continue; + } + TaskType? taskType = await getTaskFromId(task_id); + if(taskType == null){print('got null taask for this todo!');print("id:{$id}, task:{$task_id}, metadata:${metadata}, due_date: ${due_date}");} + DateTime dueDate = DateTime.parse(due_date); + DateTime? notificationTime = (notification_time == null) ? null : ((notification_time.isEmpty || notification_time =='null') ? null :DateTime.parse(notification_time)); + // print("name:{$catName}, color:{$catColor}, prod:{$catProductive}"); + _todos.add(Todo(id,task_id,metadata,dueDate, notificationTime: notificationTime, task:taskType)); + } + todos = _todos; + } else { + print("NC: Updating todos as $username"); + try { + http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_todos.php'), + body: {"username": username, "device_id": await Settings.UUID()})); + + print(response.body); + List data = response.body.split(""); + List _categories = []; + for (var value in data) { + Map cat = jsonDecode(value); + //print(catData); + _categories + .add(Journal(cat['id'], DateTime.parse(cat['id'].toString().replaceAll(username, '')), title: cat['title'], description: cat['text'])); + } + journal = _categories; + } catch (e) { + print("Error while cats NC: $e"); + } + } + // journal.sort((a, b) => b.day.compareTo(a.day)); + return todos; +} + +Future UpdateTodosFromServer() async { + print("Updating Todos as $username"); + try { + http.Response response = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/get_todos.php'), + body: {"username": username, "device_id": await Settings.UUID()})); + + print(response.body); + List data = response.body.split(""); + await cacheDb.delete("Todos"); + for (var value in data) { + Map cat = jsonDecode(value); + //print(catData); + await cacheDb.rawInsert("INSERT OR REPLACE INTO Todos (id, ${Todo.colCat},${Todo.colMetadata},${Todo.colDueDate},${Todo.colNotificationTime}) " + "VALUES ('${cat['id'].toString().replaceAll("'", "''")}', '${cat['task_id']}', '${cat['metadata'].toString().replaceAll("'", "''")}', '${cat['due_date']}', '${cat['notification_time']}') "); + } + } catch (e) { + print("Error while cats $e"); + } +} + + + Future getTaskFromId(String taskId) async { // await GetTaskTypes(false); TaskType? cat = null; @@ -874,7 +972,7 @@ Future getProjectFromId(String projectId) async { // await GetTaskTypes(false); Project? project = null; for (var element in projects) { - if (element.getName() ==projectId.replaceAll(username, "")) { + if (element.getName() == projectId.replaceAll(username, "")) { project = element; } } @@ -885,6 +983,19 @@ Future getProjectFromId(String projectId) async { return project; } +bool journalExists(DateTime date){ + int journalId = -1; + for (int i =0; i < journal.length; i++) { + // print('${journal[i].day } : $date'); + if(DateFormat('yyyy-MM-dd').format(journal[i].day) == DateFormat('yyyy-MM-dd').format(date)){ + journalId = i; + break; + } + } + + return (journalId > 0); +} + //Helpers class Helpers { Future _getId() async { @@ -957,7 +1068,7 @@ class UserOperations { 'device_id': await Settings.UUID(), 'name': name, 'category': username + category, - 'related_project' :(relatedProject==null) ? '' : (username + relatedProject) + 'related_project': (relatedProject == null) ? '' : (username + relatedProject) }; if (cacheEnabled) { @@ -969,8 +1080,14 @@ class UserOperations { await cacheDb.insert('Queries', query); //update Cache - Map data = {TaskType.colId: username + name, Category.colName: name, Category.colCatId: username + category,}; - if(relatedProject!=null || relatedProject =='None'){data.putIfAbsent(TaskType.colRelatedProject, () => relatedProject.toString());} + Map data = { + TaskType.colId: username + name, + Category.colName: name, + Category.colCatId: username + category, + }; + if (relatedProject != null || relatedProject == 'None') { + data.putIfAbsent(TaskType.colRelatedProject, () => relatedProject.toString()); + } await cacheDb.insert('TaskTypes', data); await refreshUserData(forceOffline: true); } else { @@ -996,7 +1113,7 @@ class UserOperations { 'username': username, 'name': name, 'category': username + category, - 'related_project' :(relatedProject==null) ? '' : (username + relatedProject) + 'related_project': (relatedProject == null) ? '' : (username + relatedProject) }; if (cacheEnabled) { @@ -1008,7 +1125,8 @@ class UserOperations { await cacheDb.insert('Queries', query); //update Cache - await cacheDb.rawUpdate("UPDATE TaskTypes SET ${TaskType.colId}='${username+name}', ${TaskType.colName}='$name', ${TaskType.colCategory}='${username+category}', ${TaskType.colRelatedProject}='${(relatedProject == 'None') ? '' : relatedProject}' WHERE id='${username+oldName}'"); + await cacheDb.rawUpdate( + "UPDATE TaskTypes SET ${TaskType.colId}='${username + name}', ${TaskType.colName}='$name', ${TaskType.colCategory}='${username + category}', ${TaskType.colRelatedProject}='${(relatedProject == 'None') ? '' : relatedProject}' WHERE id='${username + oldName}'"); await refreshUserData(forceOffline: true); } else { try { @@ -1193,7 +1311,7 @@ class UserOperations { await executeQueries(); } - static Future addProject(String name, String category, List steps,int eta, DateTime deadline) async { + static Future addProject(String name, String category, List steps, int eta, DateTime deadline) async { Map queryBody = { 'name': username + name, 'username': username, @@ -1213,7 +1331,13 @@ class UserOperations { await cacheDb.insert('Queries', query); //update Cache - Map data = {Project.colName: username+name, Project.colCat: category, Project.colSteps: jsonEncode(steps),Project.colEta: eta, Project.colDeadline: deadline.toString()}; + Map data = { + Project.colName: username + name, + Project.colCat: category, + Project.colSteps: jsonEncode(steps), + Project.colEta: eta, + Project.colDeadline: deadline.toString() + }; await cacheDb.insert('Projects', data); await refreshUserData(forceOffline: true); } else { @@ -1231,16 +1355,60 @@ class UserOperations { await executeQueries(); } - static Future CompleteProjectStep(Project project, ProjectStep step, DateTime finishedDate) async { + static Future editProject(String oldName, String name, String category, List steps, int eta, DateTime deadline) async { + Map queryBody = { + 'oldName': username + oldName, + 'name': username + name, + 'username': username, + 'category_id': username + category, + 'steps': jsonEncode(steps), + 'eta': eta.toString(), + 'deadline': DateFormat("yyyy-MM-dd").format(deadline) + }; + if (cacheEnabled) { + //Add Query + Map query = {Queries.colLink: 'edit_project', Queries.colData: jsonEncode(queryBody)}; + + print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); + + await cacheDb.insert('Queries', query); + + //update Cache + Map data = { + Project.colName: username + name, + Project.colCat: category, + Project.colSteps: jsonEncode(steps), + Project.colEta: eta, + Project.colDeadline: deadline.toString() + }; + await cacheDb.rawUpdate( + "UPDATE Projects SET ${Project.colName}='${username + name}', ${Project.colCat}='${username+category}', ${Project.colSteps}='${jsonEncode(steps)}', ${Project.colEta}='${eta}', ${Project.colDeadline}='${deadline.toString()}' WHERE ${Project.colName}='${username+oldName}'"); + await cacheDb.rawUpdate("UPDATE TaskTypes SET ${TaskType.colRelatedProject}='${username+name}' WHERE ${TaskType.colRelatedProject}='${username+oldName}'"); + await refreshUserData(forceOffline: true); + } else { + try { + http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/edit_project.php'), body: queryBody)); + print("Query executed : Results{${queryResponse.body}"); + if (queryResponse.body.toLowerCase().contains("success")) { + //Success + } + } catch (e) { + print('NC: Error adding prjct $e}'); + } + } + await executeQueries(); + } + + static Future CompleteProjectStep(Project project, ProjectStep step, DateTime finishedDate) async { project.steps.forEach((element) { - if(element.stepName == step.stepName){ + if (element.stepName == step.stepName) { element.finishedDate = finishedDate; } }); Map queryBody = { - 'name': username+project.name, + 'name': username + project.name, 'username': username, 'steps': jsonEncode(project.steps), }; @@ -1273,15 +1441,14 @@ class UserOperations { } static Future UndoProjectStep(Project project, ProjectStep step) async { - project.steps.forEach((element) { - if(element.stepName == step.stepName){ + if (element.stepName == step.stepName) { element.finishedDate = null; } }); Map queryBody = { - 'name': username+project.name, + 'name': username + project.name, 'username': username, 'steps': jsonEncode(project.steps), }; @@ -1314,23 +1481,19 @@ class UserOperations { } static Future addJournal(DateTime day, String title, String text) async { - - bool exist =false; + bool exist = false; for (var element in journal) { - if(element.day== day){ + if (element.day == day) { //Wat! - exist=true; + exist = true; break; } } - if(exist){return;} + if (exist) { + return; + } String id = username + DateFormat('yyyy-MM-dd').format(day); - Map queryBody = { - 'username': username, - 'id': id, - 'title': title, - 'description': text - }; + Map queryBody = {'username': username, 'id': id, 'title': title, 'description': text}; if (cacheEnabled) { //Add Query Map query = {Queries.colLink: 'add_journal', Queries.colData: jsonEncode(queryBody)}; @@ -1340,11 +1503,7 @@ class UserOperations { await cacheDb.insert('Queries', query); //update Cache - Map data = { - 'id':id, - Journal.colTitle: title, - Journal.colDescription:text - }; + Map data = {'id': id, Journal.colTitle: title, Journal.colDescription: text}; await cacheDb.insert('Journal', data); await refreshUserData(forceOffline: true); } else { @@ -1359,20 +1518,14 @@ class UserOperations { } //executeQueries(); } - //Add to server and refresh Cache - await executeQueries(); - + //Add to server and refresh Cache + await executeQueries(); } + static Future editJournal(DateTime oldDay, DateTime day, String title, String text) async { String oldId = username + DateFormat('yyyy-MM-dd').format(oldDay); String id = username + DateFormat('yyyy-MM-dd').format(day); - Map queryBody = { - 'username': username, - 'old_id':oldId, - 'id': id, - 'title': title, - 'description': text - }; + Map queryBody = {'username': username, 'old_id': oldId, 'id': id, 'title': title, 'description': text}; if (cacheEnabled) { //Add Query Map query = {Queries.colLink: 'edit_journal', Queries.colData: jsonEncode(queryBody)}; @@ -1380,14 +1533,11 @@ class UserOperations { print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); await cacheDb.insert('Queries', query); - await cacheDb.rawUpdate("UPDATE Journal SET id='$id', ${Journal.colTitle}='${title.toString().replaceAll("'", "''")}', ${Journal.colDescription}='${text.toString().replaceAll("'", "''")}' WHERE id='$oldId'"); + await cacheDb.rawUpdate( + "UPDATE Journal SET id='$id', ${Journal.colTitle}='${title.toString().replaceAll("'", "''")}', ${Journal.colDescription}='${text.toString().replaceAll("'", "''")}' WHERE id='$oldId'"); //update Cache - Map data = { - 'id':id, - Journal.colTitle: title, - Journal.colDescription:text - }; - // await cacheDb.insert('Journal', data); + Map data = {'id': id, Journal.colTitle: title, Journal.colDescription: text}; + // await cacheDb.insert('Journal', data); await refreshUserData(forceOffline: true); } else { try { @@ -1403,9 +1553,54 @@ class UserOperations { } //Add to server and refresh Cache await executeQueries(); - } + static Future addTodo(String taskType, String metadata, DateTime dueDate, DateTime? notificationTime) async { + + String taskId = username + taskType; + if(taskId.contains('[') && taskId.contains(']')){ + //has a project related + taskId = taskId.substring(0, taskId.indexOf('[') - 1); + } + + String id = username + taskId + metadata; + + Map queryBody = { + 'username': username, + 'task': taskId, + 'metadata': metadata, + 'due_date': DateFormat('yyyy-MM-dd').format(dueDate), + if(notificationTime!=null)'notification_time':DateFormat('yyyy-MM-dd HH:mm').format(notificationTime) + }; + if (cacheEnabled) { + //Add Query + Map query = {Queries.colLink: 'add_todo', Queries.colData: jsonEncode(queryBody)}; + + print("adding new query ${query[Queries.colLink]} : ${jsonEncode(queryBody)}"); + + await cacheDb.insert('Queries', query); + + //update Cache + Map data = {'id': id, Todo.colCat: username+taskType, Todo.colMetadata: metadata, Todo.colDueDate: DateFormat('yyyy-MM-dd').format(dueDate), if(notificationTime!=null)Todo.colNotificationTime:DateFormat('yyyy-MM-dd HH:mm').format(notificationTime)}; + await cacheDb.insert('Todos', data); + await refreshUserData(forceOffline: true); + } else { + try { + http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/add_todo.php'), body: queryBody)); + print("Query executed : Results{${queryResponse.body}"); + if (queryResponse.body.toLowerCase().contains("success")) { + //Success + } + } catch (e) { + print('NC: Error adding journal entry $e}'); + } + //executeQueries(); + } + //Add to server and refresh Cache + await executeQueries(); + } + + static Future deleteTask(String name, {bulk = false}) async { Map queryBody = { 'id': username + name, @@ -1523,7 +1718,7 @@ class UserOperations { static Future deleteProject(String project, {bulk = false}) async { Map queryBody = { 'username': username, - 'name': username+project, + 'name': username + project, }; //Add Query Map query = {Queries.colLink: 'delete_project', Queries.colData: jsonEncode(queryBody)}; @@ -1534,8 +1729,7 @@ class UserOperations { await cacheDb.insert('Queries', query); //update Cache - String deleteQuery = - "DELETE FROM Projects WHERE ${Project.colName}='${username+project}'"; + String deleteQuery = "DELETE FROM Projects WHERE ${Project.colName}='${username + project}'"; print("delteQuery : $deleteQuery"); await cacheDb.rawDelete(deleteQuery); @@ -1571,8 +1765,7 @@ class UserOperations { await cacheDb.insert('Queries', query); //update Cache - String deleteQuery = - "DELETE FROM Journal WHERE id='$id'"; + String deleteQuery = "DELETE FROM Journal WHERE id='$id'"; print("delteQuery : $deleteQuery"); await cacheDb.rawDelete(deleteQuery); @@ -1617,7 +1810,7 @@ class UserOperations { try { http.Response queryResponse = (await http.post(Uri.parse('http://161.97.127.136/task_tracker/$file.php'), body: body)); print("Query executed : Results{${queryResponse.body}"); - if (queryResponse.body.toLowerCase().contains("success")) { + if (queryResponse.body.toLowerCase().contains("+")) { await cacheDb.rawDelete('DELETE FROM Queries WHERE id=$id'); } offline = false; diff --git a/lib/main.dart b/lib/main.dart index 6a50b37..259a1c1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,7 +12,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:tasktracker/Categories.dart'; import 'package:tasktracker/Journal.dart'; import 'package:tasktracker/Projects.dart'; -import 'package:tasktracker/Todo.dart'; +import 'package:tasktracker/Todos.dart'; import 'package:tasktracker/Welcome.dart'; import 'package:tasktracker/splash.dart'; import 'package:tasktracker/theme_provider.dart'; @@ -113,7 +113,8 @@ class MyApp extends StatelessWidget { '/Activities': (context) => const Activities(), '/Settings': (context) => const SettingsPage(), '/Projects':(context)=> const Projects(), - '/Journal': (context)=> const JournalPage() + '/Journal': (context)=> const JournalPage(), + '/Todos':(context)=> const TodosPage() }); }); } @@ -205,7 +206,7 @@ class _MyHomePageState extends State { // } hourglassTime = ((DateTime.now().hour * 60) + DateTime.now().minute) / 1440; // hourglassTime = 1; - print('hourglass time : $hourglassTime'); + // print('hourglass time : $hourglassTime'); hourglassColors =[]; hourglassStops = []; hourglassTotalTime=0; @@ -214,11 +215,11 @@ class _MyHomePageState extends State { hourglassTotalTime+=element.time; // } }); - print('hourglass cat data'); + // print('hourglass cat data'); double stopsTotal = 0; for(int i =0 ; i < hourglassCatData.length; i++) { CatMapData element = hourglassCatData[i]; - print('${element.name} : ${element.time} / $hourglassTotalTime = ${element.time / hourglassTotalTime}'); + // print('${element.name} : ${element.time} / $hourglassTotalTime = ${element.time / hourglassTotalTime}'); double thisStop = ( element.time/hourglassTotalTime); hourglassColors.add(element.color); hourglassStops.add(stopsTotal+thisStop); @@ -228,14 +229,14 @@ class _MyHomePageState extends State { hourglassStops.add(stopsTotal+thisStop + 0.001); } } - print('total Stops ${stopsTotal}'); - print('maxT: $hourglassTotalTime'); + // print('total Stops ${stopsTotal}'); + // print('maxT: $hourglassTotalTime'); if(hourglassColors.isEmpty){ hourglassColors.add(Colors.black); hourglassStops.add(1); } - print('hourglass \n$hourglassColors \n$hourglassStops'); + // print('hourglass \n$hourglassColors \n$hourglassStops'); setState(() { }); @@ -292,6 +293,7 @@ class _MyHomePageState extends State { } } bool loadingStats = false; + DateFormat dFormat = DateFormat("yyyy-MM-dd"); void LoadStats() async { // return; // await User.refreshUserData(); @@ -304,7 +306,7 @@ class _MyHomePageState extends State { } await Refresh(); - DateFormat dFormat = DateFormat("MM/dd"); + Map catTimeMap = {}; Map catBriefMap = {}; @@ -354,13 +356,16 @@ class _MyHomePageState extends State { taskTypesDuration.putIfAbsent(element.taskType, () => thisMinutes); } } - + if (element.taskType.cat?.productive ?? false) { + if (lastProductive == null) { + lastProductive = element.trueEndTime; + }} if ((element.startTime.isAfter(prodRange!.start) && element.startTime.isBefore(prodRange!.end)) || (dFormat.format(element.startTime) == dFormat.format(prodRange!.start) || dFormat.format(element.startTime) == dFormat.format(prodRange!.end))) { if (element.taskType.cat?.productive ?? false) { - if (lastProductive == null) { - lastProductive = element.trueEndTime; - } + // if (lastProductive == null) { + // lastProductive = element.trueEndTime; + // } if (productivtyActs.containsKey(thisDate)) { productivtyActs[thisDate] = (productivtyActs[thisDate]! + thisMinutes); } else { @@ -728,15 +733,19 @@ class _MyHomePageState extends State { series: >[ LineSeries( // Bind data source + markerSettings: MarkerSettings(isVisible: true, shape: DataMarkerType.circle), dataSource: productivityData.reversed.toList(), - xValueMapper: (ProductivityMapData sales, _) => sales.day, + xValueMapper: (ProductivityMapData sales, _) => DateFormat('MM-dd').format(dFormat.parse(sales.day)), yValueMapper: (ProductivityMapData sales, _) => sales.productivity, dataLabelMapper: (ProductivityMapData sales, _) => sales.productivity.toStringAsFixed(1) + "%", dataLabelSettings: DataLabelSettings(overflowMode: OverflowMode.hide, showZeroValue: false, isVisible: true), onPointTap: (ChartPointDetails point){ - showAlertDialog(context, productivityData[point.pointIndex!].day, "I'll show you detailed info about this day in future, When my master creates the feature"); + Dialogs.showJournalLink(dFormat.parse(productivityData[productivityData.length-point.pointIndex!-1].day)); + //showAlertDialog(context, productivityData[point.pointIndex!].day, "I'll show you detailed info about this day in future, When my master creates the feature"); }, - color: Colors.green) + pointColorMapper: (ProductivityMapData sales, _)=> (User.journalExists(dFormat.parse(sales.day)) ? Colors.lightGreenAccent : Colors.green) + ) + //color: User.journalExists(dFormat.parse(productivityData[(productivityData.length-point.pointIndex!-1) as int].day)) ?Colors.green : Colors.red, ]), ), SizedBox(height: 20,), @@ -778,7 +787,6 @@ class _MyHomePageState extends State { child: SfCircularChart(legend: Legend(isVisible: true,position: LegendPosition.bottom,overflowMode: LegendItemOverflowMode.wrap), series: [ // Render pie chart PieSeries( - dataSource: dailyData, pointColorMapper: (CatMapData data, _) => data.color, xValueMapper: (CatMapData data, _) => data.name, @@ -1013,6 +1021,17 @@ Drawer navDrawer(BuildContext context, int pageIndex) { Navigator.of(context).pushReplacementNamed('/Projects'); }, ), + ListTile( + selected: (pageIndex == 9), + title: Text('To-Do'), + leading: FaIcon(FontAwesomeIcons.calendarCheck, color: Theme.of(context).primaryColor), + onTap: () { + if (pageIndex == 9) { + return; + } + Navigator.of(context).pushReplacementNamed('/Todos'); + }, + ), ListTile( selected: (pageIndex == 8), title: Text('Journal'), diff --git a/lib/newActivity.dart b/lib/newActivity.dart index 7ac2da4..605879c 100644 --- a/lib/newActivity.dart +++ b/lib/newActivity.dart @@ -7,7 +7,8 @@ import 'package:tasktracker/NotificationsManager.dart'; import 'package:tasktracker/main.dart'; import 'Data.dart'; import 'User.dart' as User; -DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); + +DateFormat dateTimeFormat = DateFormat("yyyy-MM-dd HH:mm:ss"); DateFormat durationFormat = DateFormat("HH:mm:ss"); class NewActivity extends StatefulWidget { @@ -17,24 +18,27 @@ class NewActivity extends StatefulWidget { late String? metadata; late String? selectedTask; @override - _NewActivity createState() => _NewActivity(eTime: eTime, sTime: sTime,selectedCat: selectedTask, metadata: metadata); + _NewActivity createState() => _NewActivity(eTime: eTime, sTime: sTime, selectedCat: selectedTask, metadata: metadata); } - - class _NewActivity extends State { - late DateTime init_sTime; - late DateTime init_eTime; - late String init_selectedTask; - _NewActivity({DateTime? eTime, DateTime? sTime, String? metadata, String? selectedCat}){ - editing = sTime != null && eTime!=null && selectedCat!=null; + late DateTime init_sTime; + late DateTime init_eTime; + late String init_selectedTask; + _NewActivity({DateTime? eTime, DateTime? sTime, String? metadata, String? selectedCat}) { + editing = sTime != null && eTime != null && selectedCat != null; - this.init_sTime=this.startTime = sTime ?? DateTime.now(); - this.init_eTime=this.endTime = eTime ??DateTime.now().add(Duration(minutes: 30)); + this.init_sTime = this.startTime = sTime ?? DateTime.now(); + this.init_eTime = this.endTime = eTime ?? DateTime.now().add(Duration(minutes: 30)); this.metadataController.text = metadata ?? ""; - this.init_selectedTask=this.selectedCat = selectedCat ?? User.taskTypes[0].name; + if(this.metadataController.text.contains('[') && this.metadataController.text.contains(']') ){ + this.metadataController.text = this.metadataController.text.substring(this.metadataController.text.indexOf(']')+1); + selectedStep = metadata!.substring(1,metadata!.indexOf(']')); + }else{ + selectedStep='None'; + } + this.init_selectedTask = this.selectedCat = selectedCat ?? User.taskTypes[0].name; print("etime:$eTime, sTime:$sTime, meta:$metadata, task: $selectedCat"); - } late DateTime startTime; @@ -42,409 +46,401 @@ class _NewActivity extends State { TextEditingController metadataController = TextEditingController(); late String selectedCat; - - bool editing=false; - - List getActivities(){ - List _cats = []; - _cats.add("+Add New Task Type"); - if(User.taskTypes.isEmpty){ - - }else { + bool editing = false; + Map taskTypes = {}; + Map getActivities() { + Map _cats = {}; + _cats.putIfAbsent("+Add New Task Type", () => null); + if (User.taskTypes.isEmpty) { + } else { print(User.taskTypes[0].name + " : " + selectedCat); } User.taskTypes.forEach((element) { String name = element.name; - if(_cats.contains(element.name)){ - - }else{ - _cats.add(name + ((element.relatedProject !=null) ? ' [${element.relatedProject!.name}]' :'')); + if (_cats.keys.toString().contains(element.name)) { + } else { + String displayName = (name + ((element.relatedProject != null) ? ' [${element.relatedProject!.name}]' : '')); + _cats.putIfAbsent(displayName, () => element); } }); return _cats; } + + String? selectedStep = null; @override Widget build(BuildContext context) { + taskTypes = getActivities(); + bool canSelectStep = false; + List steps = ['None']; + if (taskTypes[selectedCat] != null) { + if (taskTypes[selectedCat]!.relatedProject != null) { + //Got a project. But is it multi step? + if (taskTypes[selectedCat]!.relatedProject!.steps.isNotEmpty) { + canSelectStep = true; + bool matchesSelectedStep = false; + taskTypes[selectedCat]!.relatedProject!.steps.forEach((element) { + if (element.finishedDate == null) { + steps.add(element.stepName); + if (selectedStep == null) { + selectedStep = element.stepName; + matchesSelectedStep = true; + } + if (element.stepName == selectedStep) { + matchesSelectedStep = true; + } + } + }); + if (!matchesSelectedStep) { + selectedStep = steps[0]; + } + } + } + } + return Scaffold( - appBar: AppBar(title: Text((editing) ? 'Edit Activity':'New Activity')), - body:Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 9, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: Padding( - padding: EdgeInsets.fromLTRB(20, 50, 20, 50), - child: Column( - children: [ - Column(children: [ - Container( - padding: EdgeInsets.all(10), - child: Text('Task')), - - Container( - padding: EdgeInsets.symmetric( - horizontal: 12, vertical: 1), - decoration: BoxDecoration( - color: Colors.black12, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.grey, width: 2)), - child: DropdownButton( - dropdownColor: Color(0xFF222222), - iconSize: 30, - elevation: 10, - borderRadius: BorderRadius.circular(10), - value: selectedCat, - isExpanded: true, - items: getActivities().map>( - (String value) { - print(value); - return DropdownMenuItem( - value: value, - child: Text(value), - ); - }).toList(), - onChanged: (String? _value) { - if(_value == '+Add New Task Type'){ - Navigator.of(context).push(MaterialPageRoute(builder: (context)=> NewTask())).then((val){setState(() { - - });}); - }else{ - - selectedCat = _value ?? 'n/a'; - - } - setState(() { - }); - })), - - Container( - padding: EdgeInsets.all(10), - child:Column( - children: [ - - TextField( - controller: metadataController, - decoration: InputDecoration( - hintText: 'Description (optional)', - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(20) - ) - ), - ), - ], - ) - ), - Container( - child: Divider( - height: 30, - )), - Container( - padding: EdgeInsets.all(10), - child: Text('Start Time')), - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickTimeButton('Last', Function: (){ - if(User.activities.length > 0) { - startTime= User.activities[0].endTime; - } - }) - , - Container( - padding: EdgeInsets.symmetric( - horizontal: 12, vertical: 1), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.grey, width: 2)), - child: MaterialButton( - onPressed: () { - setState(() { - DatePicker.showDateTimePicker( - context, - maxTime: endTime, - showTitleActions: true, - onChanged: (date) { - // print('change $date'); - }, onConfirm: (date) { - setState(() { - if(endTime.compareTo(date) < 0){ - const snackBar = SnackBar( - content: Text('You cannot start something after you ended it!'), - ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - }else { - startTime = date; - } - }); - }, - currentTime: startTime, - locale: LocaleType.en); - }); - }, - child: Text( - dateFormat.format(startTime), - style: TextStyle( - color: Colors.blue)))), - QuickTimeButton('Now', Function: (){ - startTime = DateTime.now(); - }) - ], - ), - SizedBox( - height: 10, - ), - Container( - padding: EdgeInsets.all(10), - child: Text('Ended Time')), - - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container(width: 60,), - Container( - padding: EdgeInsets.symmetric( - horizontal: 12, vertical: 1), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: Colors.grey, width: 2)), - child: MaterialButton( - - onPressed: () { - setState(() { - DatePicker.showDateTimePicker( - context, - showTitleActions: true, - minTime: startTime, - onChanged: (date) { - // print('change $date'); - }, onConfirm: (date) { - setState(() { - if(startTime.compareTo(date) > 0){ - const snackBar = SnackBar( - content: Text('You cannot end something before you start it!'), - ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - }else { - endTime = date; - } - }); - }, - currentTime: endTime, - locale: LocaleType.en); - }); - }, - child: Text(dateFormat.format(endTime), - style: TextStyle( - color: Colors.blue)))), - QuickTimeButton('Now', Function: (){ - endTime = DateTime.now(); - }) - ], - ), - SizedBox(height: 15,), - (!editing)?Container( - child: Card( - shadowColor: Colors.blue, - elevation: 20, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), - // color: Color(0xAA51AAFF), - color: Colors.deepPurpleAccent, - child: Column( - mainAxisSize: MainAxisSize.max, - children:[ - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Icon(Icons.lightbulb, color: Colors.yellowAccent), - Text('Not finished yet?', style: TextStyle(fontSize: 18)), - Container(width: 15,), - - ], - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text('Click following button to start tracking an unfinished Activity. Activity will be added when you finish it.', textAlign: TextAlign.center,), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: MaterialButton( - color: Colors.green, - onPressed: (){ - User.StartActivityTimer(selectedCat, metadataController.text, startTime); - //Go home and show card - Navigator.of(context).pop(); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20,vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.access_time), - SizedBox(width: 10,), - Text('Start Timer'), - ], - ), - ), - ), - ) - ] - ) - ), - ) : Container(), - SizedBox( - height: 30, - ), - Text('Duration : ' + - _printDuration( - endTime.difference(startTime)), - style:TextStyle( - fontSize: 20, - )), - Divider( - height: 30, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - - ], - ) - ]), - - ], - - )), - ), - ), - Expanded( - flex: 1, - child: Container( - padding: - EdgeInsets.symmetric(vertical: 10, horizontal: 20), - child: Row( + appBar: AppBar(title: Text((editing) ? 'Edit Activity' : 'New Activity')), + body: Column(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Expanded( + flex: 9, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Padding( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + child: Column( + children: [ + Column(children: [ + Container(padding: EdgeInsets.all(10), child: Text('Task')), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 1), + decoration: BoxDecoration( + color: Colors.black12, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2)), + child: DropdownButton( + dropdownColor: Color(0xFF222222), + iconSize: 30, + elevation: 10, + borderRadius: BorderRadius.circular(10), + value: selectedCat, + isExpanded: true, + items: getActivities().keys.map>((String value) { + print(value); + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? _value) { + if (_value == '+Add New Task Type') { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewTask())).then((val) { + setState(() {}); + }); + } else { + selectedCat = _value ?? 'n/a'; + } + setState(() {}); + })), + if (canSelectStep) Container(padding: EdgeInsets.all(10), child: Text('Step')), + if (canSelectStep) + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 1), + decoration: BoxDecoration( + color: Colors.black12, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2)), + child: DropdownButton( + dropdownColor: Color(0xFF222222), + iconSize: 30, + elevation: 10, + borderRadius: BorderRadius.circular(10), + value: selectedStep, + isExpanded: true, + items: steps.map>((String value) { + print(value); + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + onChanged: (String? _value) { + selectedStep = _value; + setState(() {}); + })), + Container( + padding: EdgeInsets.all(10), + child: Column( + children: [ + TextField( + controller: metadataController, + decoration: InputDecoration( + hintText: 'Description (optional)', + filled: true, + border: OutlineInputBorder(borderRadius: BorderRadius.circular(20))), + ), + ], + )), + Container( + child: Divider( + height: 30, + )), + Container(padding: EdgeInsets.all(10), child: Text('Start Time')), + Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Expanded( - flex: 5, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), - child: ElevatedButton( - - style:ElevatedButton.styleFrom( - primary: Colors.red, - shape: StadiumBorder() - ), - onPressed: () { - Navigator.pop(context); - }, - child: Text('Back', - style: TextStyle(fontSize: 20))))), - Expanded( - flex: 6, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), - child: ElevatedButton( - style:ElevatedButton.styleFrom( - primary: Colors.green, - shape: StadiumBorder() - ), + QuickTimeButton('Last', Function: () { + if (User.activities.length > 0) { + startTime = User.activities[0].endTime; + } + }), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 1), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2)), + child: MaterialButton( onPressed: () { - if(editing){ - edit_action(); - }else { - add_action(); - } + setState(() { + DatePicker.showDateTimePicker(context, maxTime: endTime, showTitleActions: true, onChanged: (date) { + // print('change $date'); + }, onConfirm: (date) { + setState(() { + if (endTime.compareTo(date) < 0) { + const snackBar = SnackBar( + content: Text('You cannot start something after you ended it!'), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } else { + startTime = date; + } + }); + }, currentTime: startTime, locale: LocaleType.en); + }); }, - child: Text((editing) ? 'Apply':'Add Activity', - style: TextStyle(fontSize: 20))))), + child: Text(dateTimeFormat.format(startTime), style: TextStyle(color: Colors.blue)))), + QuickTimeButton('Now', Function: () { + startTime = DateTime.now(); + }) ], - )), - ) - ])); + ), + SizedBox( + height: 10, + ), + Container(padding: EdgeInsets.all(10), child: Text('Ended Time')), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Container( + width: 60, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 1), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey, width: 2)), + child: MaterialButton( + onPressed: () { + setState(() { + DatePicker.showDateTimePicker(context, showTitleActions: true, minTime: startTime, onChanged: (date) { + // print('change $date'); + }, onConfirm: (date) { + setState(() { + if (startTime.compareTo(date) > 0) { + const snackBar = SnackBar( + content: Text('You cannot end something before you start it!'), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } else { + endTime = date; + } + }); + }, currentTime: endTime, locale: LocaleType.en); + }); + }, + child: Text(dateTimeFormat.format(endTime), style: TextStyle(color: Colors.blue)))), + QuickTimeButton('Now', Function: () { + endTime = DateTime.now(); + }) + ], + ), + SizedBox( + height: 15, + ), + (!editing) + ? Container( + child: Card( + shadowColor: Colors.blue, + elevation: 20, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + // color: Color(0xAA51AAFF), + color: Colors.deepPurpleAccent, + child: Column(mainAxisSize: MainAxisSize.max, children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Icon(Icons.lightbulb, color: Colors.yellowAccent), + Text('Not finished yet?', style: TextStyle(fontSize: 18)), + Container( + width: 15, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Click following button to start tracking an unfinished Activity. Activity will be added when you finish it.', + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: MaterialButton( + color: Colors.green, + onPressed: () { + User.StartActivityTimer(selectedCat, metadataController.text, startTime); + //Go home and show card + Navigator.of(context).pop(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.access_time), + SizedBox( + width: 10, + ), + Text('Start Timer'), + ], + ), + ), + ), + ) + ])), + ) + : Container(), + SizedBox( + height: 30, + ), + Text('Duration : ' + _printDuration(endTime.difference(startTime)), + style: TextStyle( + fontSize: 20, + )), + Divider( + height: 30, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [], + ) + ]), + ], + )), + ), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.symmetric(vertical: 10, horizontal: 20), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + flex: 5, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), + child: ElevatedButton( + style: ElevatedButton.styleFrom(primary: Colors.red, shape: StadiumBorder()), + onPressed: () { + Navigator.pop(context); + }, + child: Text('Back', style: TextStyle(fontSize: 20))))), + Expanded( + flex: 6, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 0), + child: ElevatedButton( + style: ElevatedButton.styleFrom(primary: Colors.green, shape: StadiumBorder()), + onPressed: () { + if (editing) { + edit_action(); + } else { + add_action(); + } + }, + child: Text((editing) ? 'Apply' : 'Add Activity', style: TextStyle(fontSize: 20))))), + ], + )), + ) + ])); } - Widget QuickTimeButton(text, {Function}){ + Widget QuickTimeButton(text, {Function}) { return InkWell( child: Container( - padding:EdgeInsets.symmetric(horizontal: 15), - height: 30, - decoration:BoxDecoration( - color: Colors.blueAccent, - borderRadius: BorderRadius.circular(50) - ), - child:Align(child: Text(text),alignment: Alignment.center,) - ), - - onTap: (){ + padding: EdgeInsets.symmetric(horizontal: 15), + height: 30, + decoration: BoxDecoration(color: Colors.blueAccent, borderRadius: BorderRadius.circular(50)), + child: Align( + child: Text(text), + alignment: Alignment.center, + )), + onTap: () { Function(); - setState(() { - - }); - },); + setState(() {}); + }, + ); } - void add_action() async{ - if(startTime.isAfter(endTime)){ + void add_action() async { + if (startTime.isAfter(endTime)) { showAlertDialog(context, 'Really?', 'Start time and end time doesnt make any sense'); } String selectedTasks = selectedCat; - if(selectedTasks.contains('[') && selectedTasks.contains(']')){ - selectedTasks = selectedTasks.substring(0, selectedTasks.indexOf('[')-1); + if (selectedTasks.contains('[') && selectedTasks.contains(']')) { + selectedTasks = selectedTasks.substring(0, selectedTasks.indexOf('[') - 1); print('Project task : $selectedTasks'); } print('adding Task Type : $selectedTasks at $startTime - $endTime'); - bool failed=false; - await User.UserOperations.addActivity(selectedTasks,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){ - showAlertDialog(context, 'Error adding activity', 'Cannot add activity between ${dateFormat.format(startTime)} - ${dateFormat.format(endTime)}, $overlapCount activities are already added within this time range'); - failed=true; + bool failed = false; + await User.UserOperations.addActivity(selectedTasks, startTime, endTime, metadata:((selectedStep!=null && selectedStep != 'None') ? '[$selectedStep]' : '')+ metadataController.text, onOverlap: (overlapCount) { + showAlertDialog(context, 'Error adding activity', + 'Cannot add activity between ${dateTimeFormat.format(startTime)} - ${dateTimeFormat.format(endTime)}, $overlapCount activities are already added within this time range'); + failed = true; }); - if(!failed) { + if (!failed) { print("popping : ${navigatorKey.currentWidget?.toStringShort() ?? "n/a"}"); - Navigator.of(navigatorKey.currentContext!).popUntil((route){ + Navigator.of(navigatorKey.currentContext!).popUntil((route) { return route.isFirst; }); - }else{ + } else { print("Failed adding new activity"); } } - void edit_action() async{ - String selectedTasks = selectedCat; - if(selectedTasks.contains('[') && selectedTasks.contains(']')){ - selectedTasks = selectedTasks.substring(0, selectedTasks.indexOf('[')-1); - print('Project task : $selectedTasks'); - } - print('adding Task Type : $selectedTasks at $startTime - $endTime'); - bool failed=false; - await User.UserOperations.editActivity(init_sTime,init_eTime,selectedTasks,startTime, endTime,metadata:metadataController.text, onOverlap: (overlapCount){ - showAlertDialog(context, 'Error editing activity', 'Cannot add activity between ${dateFormat.format(startTime)} - ${dateFormat.format(endTime)}, $overlapCount activities are already added within this time range'); - failed=true; - }); - - if(!failed) { - print("popping : ${navigatorKey.currentWidget?.toStringShort() ?? "n/a"}"); - Navigator.of(navigatorKey.currentContext!).popUntil((route){ - return route.isFirst; - }); - }else{ - print("Failed editing new activity"); - } + void edit_action() async { + String selectedTasks = selectedCat; + if (selectedTasks.contains('[') && selectedTasks.contains(']')) { + selectedTasks = selectedTasks.substring(0, selectedTasks.indexOf('[') - 1); + print('Project task : $selectedTasks'); } + print('adding Task Type : $selectedTasks at $startTime - $endTime'); + bool failed = false; + await User.UserOperations.editActivity(init_sTime, init_eTime, selectedTasks, startTime, endTime,metadata: ((selectedStep!=null && selectedStep != 'None') ? '[$selectedStep]' : '')+ metadataController.text, + onOverlap: (overlapCount) { + showAlertDialog(context, 'Error editing activity', + 'Cannot add activity between ${dateTimeFormat.format(startTime)} - ${dateTimeFormat.format(endTime)}, $overlapCount activities are already added within this time range'); + failed = true; + }); + + if (!failed) { + print("popping : ${navigatorKey.currentWidget?.toStringShort() ?? "n/a"}"); + Navigator.of(navigatorKey.currentContext!).popUntil((route) { + return route.isFirst; + }); + } else { + print("Failed editing new activity"); + } + } } String _printDuration(Duration duration) { @@ -455,11 +451,12 @@ String _printDuration(Duration duration) { } showAlertDialog(BuildContext context, String title, String message) { - // set up the button Widget okButton = TextButton( child: Text("OK"), - onPressed: () { Navigator.of(context).pop(); }, + onPressed: () { + Navigator.of(context).pop(); + }, ); // set up the AlertDialog @@ -478,4 +475,4 @@ showAlertDialog(BuildContext context, String title, String message) { return alert; }, ); -} \ No newline at end of file +}