import 'dart:async'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tasktracker/Categories.dart'; import 'package:tasktracker/Projects.dart'; import 'package:tasktracker/Todo.dart'; import 'package:tasktracker/Welcome.dart'; import 'package:tasktracker/splash.dart'; import 'package:tasktracker/theme_provider.dart'; import 'Settings/Settings.dart'; import 'package:wakelock/wakelock.dart'; import 'Data.dart'; import 'NewTask.dart'; import 'newActivity.dart'; import 'Tasks.dart'; import 'Activities.dart'; import 'User.dart' as User; import 'package:syncfusion_flutter_charts/charts.dart'; import 'Dialogs.dart'; import 'CustomWidgets.dart'; import 'package:firebase_core/firebase_core.dart'; final GlobalKey navigatorKey = new GlobalKey(); 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; }, ); } extension HexColor on Color { /// String is in the format "aabbcc" or "ffaabbcc" with an optional leading "#". static Color fromHex(String hexString) { final buffer = StringBuffer(); if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); buffer.write(hexString.replaceFirst('#', '')); return Color(int.parse(buffer.toString(), radix: 16)); } /// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`)./home/warlock/Desktop/Task Tracker/tasktracker String toHex({bool leadingHashSign = true}) => '${leadingHashSign ? '#' : ''}' '${alpha.toRadixString(16).padLeft(2, '0')}' '${red.toRadixString(16).padLeft(2, '0')}' '${green.toRadixString(16).padLeft(2, '0')}' '${blue.toRadixString(16).padLeft(2, '0')}'; } void main() async { //Wakelock.enable(); // or Wakelock.toggle(on: true); WidgetsFlutterBinding.ensureInitialized(); if(Platform.isAndroid || Platform.isIOS){ await Firebase.initializeApp(); } // // ByteData data = await PlatformAssetBundle().load('assets/ca/certificate.crt'); // SecurityContext.defaultContext.setTrustedCertificatesBytes(data.buffer.asUint8List()); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) => ChangeNotifierProvider( create: (context) => ThemeProvider(), builder: (context, _) { final themeProvider = Provider.of(context); return MaterialApp( title: 'Task Tracker', themeMode: themeProvider.themeMode, theme: ThemeData(accentColor: Colors.redAccent, brightness: Brightness.light, primaryColor: Colors.amber, fontFamily: 'Noto-Sans'), darkTheme: ThemeData(backgroundColor: Colors.black,accentColor: Colors.redAccent, brightness: Brightness.dark, primaryColor: Colors.amber, fontFamily: 'Noto-Sans'), navigatorKey: navigatorKey, //home: const SplashScreen(), initialRoute: '/', routes: { '/': (context) => const SplashScreen(), '/welcome': (context) => const WelcomePage(), '/home': (context) => const MyHomePage(), '/Tasks': (context) => const Tasks(), '/Categories': (context) => const Categories(), '/Activities': (context) => const Activities(), '/Settings': (context) => const SettingsPage(), '/Projects':(context)=> const Projects() }); }); } List days = []; String curDay = ""; DateTime? firstDay = null; DateTime? lastDay = null; DateTimeRange? taskTypeRange = null; DateTimeRange? catsRange = null; DateTimeRange? prodRange = null; List productivityData = [ ProductivityMapData('02/24', 35), ProductivityMapData('02/25', 28), ProductivityMapData('02/26', 34), ProductivityMapData('02/27', 32), ProductivityMapData('02/28', 40) ]; List taskTypesData = [TaskTypeMapData('Eat', 3600, Colors.green), TaskTypeMapData('Play', 300, Colors.blue)]; List catsData = [ CatMapData('Jan', 35, Colors.green), CatMapData('Feb', 28, Colors.blueAccent), CatMapData('Mar', 34, Colors.yellow), CatMapData('Apr', 32, Colors.grey), ]; List dailyData = [ CatMapData('Jan', 35, Colors.green), CatMapData('Feb', 28, Colors.blueAccent), CatMapData('Mar', 34, Colors.yellow), CatMapData('Apr', 32, Colors.grey), ]; List hourglassCatData = []; class MyHomePage extends StatefulWidget { const MyHomePage({Key? key}) : super(key: key); @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { var connectivitySub; var refreshSub; @override void initState() { // TODO: implement initState print("Im home!"); init(context); super.initState(); print("Initializing refresh stream on main dart"); // connectivitySub=Connectivity().onConnectivityChanged.listen((result) { // if (this.mounted) { // setState(() {}); // } // }); refreshSub = User.refreshStream.stream.listen((event) { if(event==false){ //Update done, Lets go update data LoadStats(); } }); LoadStats(); startLoadStatRefresher(); // User.progressDialog=progressDialog; } double hourglassTime = 0; List hourglassColors = [Colors.black]; List hourglassStops = [1]; int hourglassTotalTime = 0; void startLoadStatRefresher() async{ int lastMinute = 0; while(true){ if(DateTime.now().minute != lastMinute){ lastMinute = DateTime.now().minute; LoadStats(); } await Refresh(); // hourglassTime+=0.05; // if(hourglassTime > 1){ // hourglassTime=0; // } hourglassTime = ((DateTime.now().hour * 60) + DateTime.now().minute) / 1440; // hourglassTime = 1; print('hourglass time : $hourglassTime'); hourglassColors =[]; hourglassStops = []; hourglassTotalTime=0; hourglassCatData.forEach((element) { // if(element.time > hourglassTotalTime){ hourglassTotalTime+=element.time; // } }); 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}'); double thisStop = ( element.time/hourglassTotalTime); hourglassColors.add(element.color); hourglassStops.add(stopsTotal+thisStop); stopsTotal += thisStop; if(i < hourglassCatData.length-1){ hourglassColors.add(hourglassCatData[i+1].color); hourglassStops.add(stopsTotal+thisStop + 0.001); } } print('total Stops ${stopsTotal}'); print('maxT: $hourglassTotalTime'); if(hourglassColors.isEmpty){ hourglassColors.add(Colors.black); hourglassStops.add(1); } print('hourglass \n$hourglassColors \n$hourglassStops'); setState(() { }); await Future.delayed(Duration(seconds: 1)); } } void init(BuildContext context) async{ await Future.delayed(Duration(seconds: 1)); refreshSub = User.refreshStream.stream.listen((value) { print("Streaming refresh : $value"); if(value){ Dialogs.waiting("Syncing..."); print("Opening progress dialog"); }else{ Dialogs.hide(); print("Closing progress dialog"); } }); //await User.refreshUserData(); } DateTime? lastProductive = null; @override void dispose() { // TODO: implement dispose super.dispose(); connectivitySub?.cancel(); refreshSub?.closeSteam(); } String ongoingActName = ""; DateTime ongoingActStime=DateTime.now(); Future Refresh() async{ final prefs = await SharedPreferences.getInstance(); if(prefs.containsKey('current_activity')){ List data = []; try{ data = prefs.getString('current_activity')!.split(''); ongoingActName=data[0]; ongoingActStime = DateTime.parse(data[2]); }catch(e){ ongoingActName = ""; } }else{ ongoingActName = ""; } if(mounted) { setState(() { }); } } bool loadingStats = false; void LoadStats() async { // return; // await User.refreshUserData(); if(loadingStats){ print('loading stats already'); return; }else { loadingStats=true; } await Refresh(); DateFormat dFormat = DateFormat("MM/dd"); Map catTimeMap = {}; Map catBriefMap = {}; Map productivtyActs = {}; Map unproductivtyActs = {}; Map taskTypesDuration = {}; hourglassCatData=[]; hourglassColors=[]; firstDay = null; lastDay = null; String lastDate = ""; lastProductive=null; days = []; for (var element in User.activities) { if (lastDay == null) { lastDay = element.endTime; } if (taskTypeRange == null) { print("$lastDay - $firstDay"); taskTypeRange = DateTimeRange(start: lastDay!.subtract(const Duration(days: 0)), end: lastDay!); } if (catsRange == null) { print("$lastDay - $firstDay"); catsRange = DateTimeRange(start: lastDay!.subtract(const Duration(days: 0)), end: lastDay!); } if (prodRange == null) { print("$lastDay - $firstDay"); prodRange = DateTimeRange(start: lastDay!.subtract(const Duration(days: 7)), end: lastDay!); } firstDay = element.startTime; String thisDate = dFormat.format(element.startTime); int thisMinutes = element.endTime.difference(element.startTime).inMinutes; if (!days.contains(thisDate)) { days.add(dFormat.format(element.startTime)); } if (curDay == "") { curDay = dFormat.format(DateTime.now()); } if ((element.startTime.isAfter(taskTypeRange!.start) && element.startTime.isBefore(taskTypeRange!.end)) || (dFormat.format(element.startTime) == dFormat.format(taskTypeRange!.start) || dFormat.format(element.startTime) == dFormat.format(taskTypeRange!.end))) { if (taskTypesDuration.containsKey(element.taskType)) { taskTypesDuration[element.taskType] = taskTypesDuration[element.taskType]! + thisMinutes; } else { taskTypesDuration.putIfAbsent(element.taskType, () => thisMinutes); } } 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 (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); } } } if (thisDate == curDay) { if (element.taskType.cat == null) { continue; } print("Null : ${thisMinutes}"); if (catTimeMap.containsKey(element.taskType.cat)) { catTimeMap[element.taskType.cat!] = (catTimeMap[element.taskType.cat]! + thisMinutes); } else { catTimeMap.putIfAbsent(element.taskType.cat!, () => thisMinutes); } } if ((element.startTime.isAfter(catsRange!.start) && element.startTime.isBefore(catsRange!.end)) || (dFormat.format(element.startTime) == dFormat.format(catsRange!.start) || dFormat.format(element.startTime) == dFormat.format(catsRange!.end))) { if (element.taskType.cat == null) { continue; } print("Null : ${thisMinutes}"); if (catBriefMap.containsKey(element.taskType.cat)) { catBriefMap[element.taskType.cat!] = (catBriefMap[element.taskType.cat]! + thisMinutes); } else { catBriefMap.putIfAbsent(element.taskType.cat!, () => thisMinutes); } } if(dFormat.format(element.startTime) == dFormat.format(DateTime.now())){ if (element.taskType.cat == null) { continue; } print("Null : ${thisMinutes}"); int? existingEntryIndex; for(int i=0; i < hourglassCatData.length; i++){ if(hourglassCatData[i].name == element.taskType.category){ existingEntryIndex=i; } } if (existingEntryIndex==null) { hourglassCatData.add(CatMapData(element.taskType.category, thisMinutes, HexColor.fromHex(element.taskType.cat!.color))); } else { hourglassCatData[existingEntryIndex!].time+=thisMinutes; } } hourglassCatData.sort((a, b) => a.time.compareTo(b.time)); // hourglassCatData = hourglassCatData.reversed.toList(); } dailyData = []; productivityData = []; taskTypesData = []; catsData = []; int trackedTime = 0; catTimeMap.forEach((key, value) { //print(key.name + " : $value"); Color barCol = HexColor.fromHex(key.color); dailyData.add(CatMapData(key.name, value, barCol)); trackedTime += value; }); int untrackedTime = 1440-trackedTime; if(untrackedTime<0){ User.refreshUserData().then((val)=>LoadStats()); print("Shit went wrong!"); } print("Tracked time : $trackedTime"); dailyData.sort((a, b) { return a.name.toLowerCase().compareTo(b.name.toLowerCase()); }); if(untrackedTime> 0){dailyData.add(CatMapData("Untracked",1440-trackedTime, Colors.black));}else{} bool untrackedUnprod = await Settings.getUntrackedUnproductive(); for (var element in days) { // if(productivtyActs.containsKey(element) && unproductivtyActs.containsKey(element)){ int prodActs = (productivtyActs[element] ?? 0); int unprodActs = (unproductivtyActs[element] ?? 0); double prod = (untrackedUnprod) ? ((prodActs / 1440) * 100) : ((prodActs / unprodActs)*100); var newProdData = ProductivityMapData(element, prod); if(prod>0 && !productivityData.contains(newProdData)){ productivityData.add(newProdData); } // } } taskTypesDuration.forEach((key, value) { print("$key : $value"); taskTypesData.add(TaskTypeMapData(key.name, value, HexColor.fromHex(key.cat!.color))); }); taskTypesData.sort((a, b) { return a.time.compareTo(b.time); }); catBriefMap.forEach((key, value) { print(key.name + " : $value"); Color barCol = HexColor.fromHex(key.color); catsData.add(CatMapData(key.name, value, barCol)); }); catsData.sort((a, b) => a.time.compareTo(b.time)); //curDay = days[0]; if (this.mounted) { setState(() { }); } // print('productivity data'); // productivityData.forEach((element) { // print(element.day); // }); loadingStats=false; // loadingStats=false; } void showOfflineSnack() async { await Future.delayed(const Duration(seconds: 1)); if (User.offline) { const SnackBar offlineSnack = SnackBar( content: Text('Offline'), duration: Duration(seconds: 100), ); // ScaffoldMessenger.of(context).showSnackBar(offlineSnack); } } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton.extended( onPressed: () { Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewActivity())); }, label: Text("New Activity"), 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: [FaIcon(FontAwesomeIcons.chartBar), SizedBox(width: 10), Text('Summary')]), Row( children: [ (User.offline) ? Icon(Icons.signal_cellular_connected_no_internet_4_bar_outlined) : InkWell( onTap: () { setState(() async { //await User.refreshUserData(); LoadStats(); }); }, child: Icon(Icons.refresh, size: 30), ) ], ) ], ), //Container(color: Colors.red,child: Text("Offline",style:TextStyle(fontSize: 5))), ], )), drawer: navDrawer(context, 0), body: SafeArea( child: (User.activities.isEmpty) ? Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center ,children:[ Expanded(flex: 1,child: Container(),), Expanded(flex: 2,child: Image(image: AssetImage('images/empty.png'))), Expanded(flex:2,child: Text("Add your first activity to access Summary",style: TextStyle(color: Colors.grey, fontStyle: FontStyle.italic),)) ]) :Column( mainAxisSize: MainAxisSize.max, children: [ if(User.offline)Expanded(flex:1,child: Container(width:1000,color: Colors.red,child: Align(alignment: Alignment.center,child: Text("Offline")))), Expanded( flex:50, child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( children: [ (false) ? Container( padding: EdgeInsets.all(20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ Text("Good\nMorning!", style: TextStyle(fontSize: 23, fontStyle: FontStyle.italic)), Text( "12%", style: TextStyle(fontSize: 30), ), Column( children: [ Text(DateFormat("yy - MM-dd").format(DateTime.now())), Text(DateFormat("HH:mm").format(DateTime.now()), style: TextStyle(fontSize: 40)), ], ) ], ), ) : Container(), //Ongoing activity card (ongoingActName == "") ? Container(): Container( padding: EdgeInsets.all(10), child: Card( color: Colors.white12, elevation: 20, shadowColor: Colors.green, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ SpinKitPouringHourGlass(color: Colors.amber), SizedBox(width: 20,), Flexible(child: Text("You are engaged in '$ongoingActName' since ${DateFormat("hh:mm").format(ongoingActStime)}.", style: TextStyle(fontSize: 19),textAlign: TextAlign.center,)), ], ), SizedBox(height: 10,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( child:Text(MinutesToTimeString(DateTime.now().difference(ongoingActStime).inMinutes)) ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ InkWell( onTap: (){ Dialogs.ongoing(); }, child: Container( decoration: BoxDecoration( color: Colors.green, borderRadius: BorderRadius.circular(10) ), child: Padding( padding: EdgeInsets.symmetric(vertical: 8,horizontal: 15), child:Text('Take Action') ) ) ), ], ), ], ) ], ), )), ), Container( padding: EdgeInsets.all(10), child: Card( color: Colors.white10, elevation: 20, shadowColor: Colors.black, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 20), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Container(width:100,height: 150,child: CustomPaint(painter: HourglassPainter(hourglassTime, hourglassColors, hourglassStops),)), Column( children: [ Text(DateFormat("MMMM-dd").format(DateTime.now()),style:TextStyle(fontSize: 18)), Text(DateFormat("hh:mm a").format(DateTime.now()),style:TextStyle(fontSize: 40, fontWeight: FontWeight.bold)), Container(height: 20,) ], ) ], ) ], ), )), ), Container( height: 350, padding: EdgeInsets.all(10), child: Card( color: Colors.white10, elevation: 20, shadowColor: Colors.black, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 10,vertical: 0), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Productivity", style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold)), InkWell( onTap: () async { DateTimeRange? value = await showDateRangePicker(context: context, firstDate: firstDay ?? DateTime.now(), lastDate: lastDay ?? DateTime.now()); if (value != null) { prodRange = value; } LoadStats(); }, child: Text((prodRange != null) ? (DateFormat("MM/dd").format(prodRange!.start) + " - " + DateFormat("MM/dd").format(prodRange!.end)) : 'n/a'), ) ], ), ), Divider(), Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( children: [ Text("Today : "), Text("${(productivityData.length > 0) ? productivityData[0].productivity.toStringAsFixed(1) : 'n/a'}%", style: TextStyle( fontSize: 20, color: (productivityData.length > 1) ? ((productivityData[0].productivity > productivityData[1].productivity) ? Colors.lightGreenAccent : Colors.red) : Colors.white)) ], ), Row( children: [ Text("Yesterday : "), Text("${(productivityData.length > 1) ? productivityData[1].productivity.toStringAsFixed(1) : 'n/a'}%", style: TextStyle(fontSize: 18)) ], ), ], ), Expanded( child: SfCartesianChart( // Initialize category axis primaryXAxis: CategoryAxis(), series: >[ LineSeries( // Bind data source dataSource: productivityData.reversed.toList(), xValueMapper: (ProductivityMapData sales, _) => 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"); }, color: Colors.green) ]), ), SizedBox(height: 20,), if(lastProductive!=null && DateTime.now().difference(lastProductive!).inMinutes > 60)RichText(text: TextSpan( children: [ TextSpan(text: "You haven't been productive in last",style: TextStyle(color:Colors.orange)), TextSpan(text:" ${MinutesToTimeString(DateTime.now().difference(lastProductive!).inMinutes)}",style: TextStyle(color:Colors.redAccent,fontWeight: FontWeight.bold)) ] )) ], ), )), ), Container( height: 400, padding: EdgeInsets.all(10), child: Card( color: Colors.white10, elevation: 20, shadowColor: Colors.black, child: Padding( padding: EdgeInsets.all(8), child: (!days.isEmpty) ? Column( children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding(padding: EdgeInsets.all(20), child: Text('Daily Briefing', style: TextStyle(fontWeight: FontWeight.bold))), dayPickerWidget(days, value: curDay, onChange: (_value) { print('new val : $_value'); curDay = _value; setState(() { LoadStats(); }); }), ]), Expanded( 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, yValueMapper: (CatMapData data, _) => data.time, dataLabelMapper: (CatMapData sales, _) => MinutesToTimeString(sales.time), dataLabelSettings: DataLabelSettings(isVisible: true, useSeriesColor: true, overflowMode: OverflowMode.shift, showZeroValue: false)) ])) ], ) : Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [CircularProgressIndicator()])))), Container( height: (taskTypesData.length * 45).clamp(350, 1000).toDouble(), padding: EdgeInsets.all(10), child: Card( color: Colors.white10, elevation: 20, shadowColor: Colors.black, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 25), child: Column(children: [ Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Task Types", style: TextStyle(fontWeight: FontWeight.bold)), InkWell( onTap: () async { DateTimeRange? value = await showDateRangePicker(context: context, firstDate: firstDay ?? DateTime.now(), lastDate: lastDay ?? DateTime.now()); if (value != null) { taskTypeRange = value; } LoadStats(); }, child: Text((taskTypeRange != null) ? (DateFormat("MM/dd").format(taskTypeRange!.start) + " - " + DateFormat("MM/dd").format(taskTypeRange!.end)) : 'n/a'), ) ]), Expanded( // maxHeight: 300, // maxWidth: 100, child: SfCartesianChart(primaryXAxis: CategoryAxis(), //primaryYAxis: NumericAxis(minimum: 0, maximum: 40, interval: 10), series: >[ BarSeries( dataSource: taskTypesData, xValueMapper: (TaskTypeMapData data, _) => data.task, yValueMapper: (TaskTypeMapData data, _) => data.time / 60, pointColorMapper: (TaskTypeMapData data, _) => data.color, dataLabelMapper: (TaskTypeMapData data, _) => MinutesToTimeString(data.time), dataLabelSettings: DataLabelSettings(isVisible: true), color: Color.fromRGBO(8, 142, 255, 1)) ]), ) ])))), Container( height: (catsData.length * 45).clamp(350, 1000).toDouble(), padding: EdgeInsets.all(10), child: Card( color: Colors.white10, elevation: 20, shadowColor: Colors.black, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 25), child: Column(children: [ Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Categories", style: TextStyle(fontWeight: FontWeight.bold)), InkWell( onTap: () async { DateTimeRange? value = await showDateRangePicker(context: context, firstDate: firstDay ?? DateTime.now(), lastDate: lastDay ?? DateTime.now()); if (value != null) { catsRange = value; } LoadStats(); }, child: Text((catsRange != null) ? (DateFormat("MM/dd").format(catsRange!.start) + " - " + DateFormat("MM/dd").format(catsRange!.end)) : 'n/a'), ) ]), Expanded( // maxHeight: 300, // maxWidth: 100, child: SfCartesianChart(primaryXAxis: CategoryAxis(), //primaryYAxis: NumericAxis(minimum: 0, maximum: 40, interval: 10), series: >[ BarSeries( dataSource: catsData, xValueMapper: (CatMapData data, _) => data.name, yValueMapper: (CatMapData data, _) => data.time / 60, pointColorMapper: (CatMapData data, _) => data.color, dataLabelMapper: (CatMapData data, _) => MinutesToTimeString(data.time), dataLabelSettings: DataLabelSettings(isVisible: true), color: Color.fromRGBO(8, 142, 255, 1)) ]), ) ])))), ], ), ), ), ], ), ), ); } Widget dayPickerWidget(List list, {required String value, required Function(String value) onChange}) { if (!list.contains(value)) { print("resetting"); onChange(list[0]); } bool nextAvailable = (list.indexOf(value) < (list.length - 1)); bool prevAvailable = (list.indexOf(value) > 0); return Row( children: [ InkWell( onTap: () { if (nextAvailable) { onChange(list[list.indexOf(value) + 1]); } }, child: Container( height: 40, width: 40, child: Icon(Icons.arrow_back_ios, size: 18, color: (nextAvailable) ? Colors.white : Colors.grey), ), ), Text( value, ), InkWell( onTap: () { if (prevAvailable) { onChange(list[list.indexOf(value) - 1]); } }, child: Container( height: 40, width: 40, child: Icon(Icons.arrow_forward_ios, size: 18, color: (prevAvailable) ? Colors.white : Colors.grey), )) ], ); } } Widget moreButton() { return MaterialButton( onPressed: () {}, color: Colors.green, child: Row( children: [Text('More'), Icon(Icons.keyboard_arrow_right)], )); } Drawer navDrawer(BuildContext context, int pageIndex) { return Drawer( child: ListView( children: [ Padding( padding: EdgeInsets.all(16), child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Time Tracker", style: TextStyle(fontSize: 25, color: Theme.of(context).accentColor, fontWeight: FontWeight.bold)), Icon( Icons.more_time, size: 30, ), ])), Divider(), ListTile( selected: (pageIndex == 0), title: Text('Summary'), leading: Icon(Icons.article_outlined, color: Theme.of(context).primaryColor), onTap: () { if (pageIndex == 0) { return; } Navigator.of(context).pushReplacementNamed('/home'); }, ), // ListTile( // selected: (pageIndex == 1), // title: Text('Analytics'), // leading: Icon(Icons.analytics_outlined, // color: Theme.of(context).primaryColor), // onTap: () { // if (pageIndex == 1) { // return; // } // // Navigator.of(context).pushReplacementNamed('/'); // }, // ), Divider(), ListTile( selected: (pageIndex == 2), title: Text('Activities'), leading: Icon(Icons.task, color: Theme.of(context).primaryColor), onTap: () { if (pageIndex == 2) { return; } Navigator.of(context).pushReplacementNamed('/Activities'); }, ), ListTile( selected: (pageIndex == 3), title: Text('Task Types'), leading: Icon(Icons.task, color: Theme.of(context).primaryColor), onTap: () { if (pageIndex == 3) { return; } Navigator.of(context).pushReplacementNamed('/Tasks'); }, ), ListTile( selected: (pageIndex == 4), title: Text('Categories'), leading: Icon(Icons.account_tree_outlined, color: Theme.of(context).primaryColor), onTap: () { if (pageIndex == 4) { return; } Navigator.of(context).pushReplacementNamed('/Categories'); }, ), Divider(), ListTile( selected: (pageIndex == 7), title: Text('Projects'), leading: Icon(Icons.work_outline_sharp, color: Theme.of(context).primaryColor), onTap: () { if (pageIndex == 7) { return; } Navigator.of(context).pushReplacementNamed('/Projects'); }, ), // ListTile( // selected: (pageIndex == 7), // title: Text('TODO'), // leading: Icon(Icons.check, color: Theme.of(context).primaryColor), // onTap: () { // if (pageIndex == 7) { // return; // } // // Navigator.of(context).pushReplacementNamed('/Todo'); // }, // ), Divider(), ListTile( selected: (pageIndex == 5), title: Text('Settings'), leading: Icon(Icons.settings, color: Colors.blueGrey), onTap: () { if (pageIndex == 5) { return; } Navigator.of(context).pushNamed('/Settings'); }, ), ListTile( selected: (pageIndex == 6), title: Text('About'), leading: Icon(Icons.help_outline_outlined), onTap: () { showAboutDialog(context: context); }, ), ], )); } class MyPlayerBar extends CustomPainter { MyPlayerBar(this.max, this.value, {this.background = Colors.lightBlue, this.fill = Colors.blue}); Color background = Colors.lightBlue; Color fill = Colors.blue; final int max; final int value; @override void paint(Canvas canvas, Size size) { Paint paint = Paint(); double cursor = (value * size.width) / max; Radius cornerRadius = Radius.circular(10.0); // Already played half color (your darker orange) paint.color = background; // Painting already played half canvas.drawRRect(RRect.fromRectAndCorners(Rect.fromLTWH(0.0, 0.0, cursor, size.height), topLeft: cornerRadius, bottomLeft: cornerRadius), paint); // Yet to play half color (your lighter orange) paint.color = fill; // Painting the remaining space canvas.drawRRect(RRect.fromRectAndCorners(Rect.fromLTWH(cursor, 0.0, size.width - cursor, size.height), bottomRight: cornerRadius, topRight: cornerRadius), paint); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; } class CatMapData { CatMapData(this.name, this.time, this.color); final String name; int time; final Color color; } class ProductivityMapData { ProductivityMapData(this.day, this.productivity); final String day; final double productivity; } class TaskTypeMapData { TaskTypeMapData(this.task, this.time, this.color); final Color color; final String task; final int time; } String MinutesToTimeString(minutes) { int hours = (minutes / 60).floor(); int mins = minutes % 60; String str = ""; if (hours > 0) { str += hours.toString() + "h"; } if (mins > 0) { str += ((hours > 0) ? " " : "") + mins.toString() + "m"; } return str; }