import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:intl/intl.dart'; import 'package:sticky_headers/sticky_headers/widget.dart'; import 'main.dart' as Main; 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); @override _ActivitiesState createState() => _ActivitiesState(); } class _ActivitiesState extends State { //late ProgressDialog progressDialog; TextEditingController searchController = TextEditingController(); FocusNode _focus = FocusNode(); bool searching = false; void _onFocusChange() { print("FOCUS CHANGED! : ${_focus.hasFocus}"); if (!_focus.hasFocus) { searching = false; setState(() {}); } } var refreshSub; @override void initState() { // TODO: implement initState super.initState(); _focus.addListener(_onFocusChange); refreshSub = User.refreshStream.stream.listen((event) { if (!event) { setState(() {}); } }); //UpdateList(); //init(context); } @override void dispose() { // TODO: implement dispose super.dispose(); _focus.removeListener(_onFocusChange); _focus.dispose(); refreshSub?.closeStream(); } void UpdateList() async { try { //progressDialog.show(max: 100, msg: 'Loading Activities'); } catch (e) {} await User.refreshUserData(); setState(() {}); try { // progressDialog.update(value: 100); } catch (e) {} } Map activitiesGroups = {}; @override Widget build(BuildContext context) { // progressDialog = ProgressDialog(context: context); // List activities = PrintTasks(); activitiesGroups = PrintTasks(); bool landscape=((MediaQuery.of(context).size.width / MediaQuery.of(context).size.height) > 1); return Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewActivity())).then((value) => UpdateList()); }, label: Text("New Activity"), icon: Icon(Icons.add)), appBar: AppBar( toolbarHeight: (searching) ? 90 : null, title: (searching) ? Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisSize: MainAxisSize.max, children: [ Expanded( child: TextField( onChanged: (text) { setState(() {}); }, controller: searchController, focusNode: _focus, decoration: InputDecoration( filled: true, ), ), ), InkWell( onTap: () { searching = false; searchController.clear(); setState(() {}); }, child: Container( margin: EdgeInsets.all(10), child: Icon(Icons.cancel), ), ) ], ), Text( 'searched time : ${Main.MinutesToTimeString(searchTime)}', style: TextStyle(fontSize: 15), ) ], ) : Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row(children: [Icon(Icons.task, color: Theme.of(context).primaryColor), SizedBox(width: 10), Text('Activities')]), (selecting) ? Row(children: [ InkWell( onTap: () { DeleteSelectedTasks(); }, child: Container( margin: EdgeInsets.all(5), child: Icon( Icons.delete, size: 30, ), )), InkWell( onTap: () { setState(() { selecting = false; }); }, child: Container(margin: EdgeInsets.all(10), child: Icon(Icons.close, size: 30)), ) ]) : Row( children: [ InkWell( onTap: () { searching = true; _focus.requestFocus(); setState(() {}); }, child: Container( margin: EdgeInsets.all(10), child: Icon( Icons.search, size: 30, ), )), InkWell( onTap: () { UpdateList(); setState(() {}); }, child: Container(margin: EdgeInsets.all(10), child: Icon(Icons.refresh, size: 30)), ) ], ), ], )), drawer: landscape ? null : Main.navDrawer(context, 2), body: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ landscape?Main.navDrawer(context, 2) : Container(), Expanded( child: Container( padding: EdgeInsets.all(0), child: ScrollablePositionedList.builder( itemScrollController: scrollController, itemCount: activitiesGroups.length, itemBuilder: (context, index) { // return activities[index]; return StickyHeader( 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 = {}; print('Priting cats : ' + User.taskTypes.length.toString()); String lastDate = ""; Map productivtyActs = {}; Map unproductivtyActs = {}; Map totalMinutes = {}; for (var element in User.activities) { String thisDate = dFormat.format(element.startTime); int thisMinutes = element.endTime.difference(element.startTime).inMinutes; if (totalMinutes.containsKey(thisDate)) { if ((totalMinutes[thisDate] ?? 0) < thisMinutes) { totalMinutes[thisDate] = thisMinutes; } } else { totalMinutes.putIfAbsent(thisDate, () => thisMinutes); } if (element.taskType.cat?.productive ?? false) { if (productivtyActs.containsKey(thisDate)) { productivtyActs[thisDate] = (productivtyActs[thisDate]! + thisMinutes); } else { productivtyActs.putIfAbsent(thisDate, () => thisMinutes); } } else { if (unproductivtyActs.containsKey(thisDate)) { unproductivtyActs[thisDate] = (unproductivtyActs[thisDate]! + thisMinutes); } else { unproductivtyActs.putIfAbsent(thisDate, () => thisMinutes); } } } 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) { bool matchMetadata = element.metadata!.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) { //Good to go searchTime += element.endTime.difference(element.startTime).inMinutes; } 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(thisDate, () => ActivityListDateGroup(DateSeperator(thisDate, prodActs, unProdActs), [])); // print('adding'); lastDate = thisDate; } String name = element.taskType.name; if (element.taskType.cat == null) { print('Got some null cat : ${element.taskType.name}'); } else { 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); _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); //_tasks.values.toList()[_tasks.length-1].add(addGap); _tasks[thisDate]!.activities.add(addGap); } } } return _tasks; } 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)) ])), Expanded( child: InkWell( 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")], ), ], )), ), ), ], ); } 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)) { break; } itemId++; // itemId+= values[i].activities.length; } scrollController.scrollTo(index: itemId, duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn); } 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( children: [ SizedBox( width: 15, ), Icon(Icons.circle), SizedBox( width: 10, ), Text( date, style: TextStyle(fontSize: 18), ), ], ), ), 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),), // // ), // ), ], ) ], ), ); }); } bool selecting = false; 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' : ''); return Row(children: [ // Container(), (selecting) ? Checkbox( value: selectedActivities.contains(activity), onChanged: (value) { print('selected $name'); 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)), ], )), Expanded( child: Column(children: [ Padding( padding: const EdgeInsets.fromLTRB(0, 0, 5, 0), child: Card( // color: color, elevation: 30, shadowColor: 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(name + " [$timeSpan]", style: TextStyle(fontSize: 17)), 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 ?? ''), ), ), ]), // Icon(Icons.analytics, color: color, size: 20,), ]), SizedBox( height: 5, ), Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(dateFormat.format(sTime) + " - " + dateFormat.format(eTime)), SizedBox( width: 20, ), Row( children: [ (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), child: Text(activity.taskType.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: timePercentage, child: Container(color: color)), Expanded(flex: 100 - timePercentage, child: Container())], )), ]), ), 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()); }, ) ]); } void OnItemSelected(Activity activity) { if (!selectedActivities.contains(activity)) { selectedActivities.add(activity); } else { selectedActivities.remove(activity); } } void DeleteSelectedTasks() async { //progressDialog.show(max: 100, msg: 'Deleteing ${selectedActivities.length} Activities'); selectedActivities.forEach((element) async { await User.UserOperations.deleteActivity(element, bulk: true); }); await Future.delayed(Duration(seconds: 2)); await User.UserOperations.executeQueries(); await User.updateActList(); selectedActivities = []; selecting = false; setState(() { // progressDialog.update(value: 100); }); } } class ActivityListDateGroup { ActivityListDateGroup(this.dateSeperator, this.activities); Widget dateSeperator; List activities; } List selectedActivities = [];