Files
TaskTracker/lib/main.dart

1752 lines
86 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' as K;
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/Analytics.dart';
import 'package:tasktracker/Categories.dart';
import 'package:tasktracker/Journal.dart';
import 'package:tasktracker/Projects.dart';
import 'package:tasktracker/Todos.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<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
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);
if (!K.kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
WidgetsFlutterBinding.ensureInitialized();
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<ThemeProvider>(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(),
'/Journal': (context) => const JournalPage(),
'/Todos': (context) => const TodosPage(),
'/Analytics': (context) => const AnalyticsPage()
});
});
}
List<String> days = [];
String curDay = "";
DateTime? firstDay = null;
DateTime? lastDay = null;
DateTimeRange? taskTypeRange = null;
DateTimeRange? catsRange = null;
DateTimeRange? prodRange = null;
List<ProductivityMapData> pastProductivityData = <ProductivityMapData>[
ProductivityMapData('02/24', 32),
ProductivityMapData('02/25', 5),
ProductivityMapData('02/26', 56),
ProductivityMapData('02/27', 33),
ProductivityMapData('02/28', 50)
];
List<ProductivityMapData> productivityData = <ProductivityMapData>[
ProductivityMapData('02/24', 35),
ProductivityMapData('02/25', 28),
ProductivityMapData('02/26', 34),
ProductivityMapData('02/27', 32),
ProductivityMapData('02/28', 40)
];
List<TaskTypeMapData> taskTypesData = <TaskTypeMapData>[
TaskTypeMapData('Eat', 3600, Colors.green),
TaskTypeMapData('Play', 300, Colors.blue)
];
List<CatMapData> catsData = <CatMapData>[
CatMapData('Jan', 35, Colors.green),
CatMapData('Feb', 28, Colors.blueAccent),
CatMapData('Mar', 34, Colors.yellow),
CatMapData('Apr', 32, Colors.grey),
];
List<CatMapData> dailyData = <CatMapData>[
CatMapData('Jan', 35, Colors.green),
CatMapData('Feb', 28, Colors.blueAccent),
CatMapData('Mar', 34, Colors.yellow),
CatMapData('Apr', 32, Colors.grey),
];
List<CatMapData> hourglassCatData = <CatMapData>[];
List<Todo> relativeTodos = [];
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
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<Color> hourglassColors = [Colors.black];
List<double> 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();
// 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<void> Refresh() async {
final prefs = await SharedPreferences.getInstance();
if (prefs.containsKey('current_activity')) {
List<String> data = [];
try {
data = prefs.getString('current_activity')!.split('<td>');
ongoingActName = data[0];
ongoingActStime = DateTime.parse(data[2]);
} catch (e) {
ongoingActName = "";
}
} else {
ongoingActName = "";
}
if (this.mounted) {
setState(() {});
}
}
bool loadingStats = false;
DateFormat dFormat = DateFormat("yyyy-MM-dd");
void LoadStats() async {
// return;
// await User.refreshUserData();
if (loadingStats) {
print('loading stats already');
return;
} else {
loadingStats = true;
}
await Refresh();
Map<Category, int> catTimeMap = <Category, int>{};
Map<Category, int> catBriefMap = <Category, int>{};
Map<String, int> productivtyActs = <String, int>{};
Map<String, int> unproductivtyActs = <String, int>{};
Map<TaskType, int> taskTypesDuration = <TaskType, int>{};
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.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 (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 = <CatMapData>[];
productivityData = <ProductivityMapData>[];
taskTypesData = <TaskTypeMapData>[];
catsData = <CatMapData>[];
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);
}
// }
}
//Past Prod
Map<DateTime, double> pastProdData = AnalyticTools.getProductivities(
DateTimeRange(
start: prodRange!.start
.subtract(Duration(days: prodRange!.duration.inDays + 1)),
end: prodRange!.start.subtract(Duration(days: 1))));
pastProductivityData = [];
pastProdData.forEach((key, value) {
pastProductivityData.add(ProductivityMapData(
dFormat
.format(key.add(Duration(days: prodRange!.duration.inDays + 1))),
value));
});
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));
//relative TOdos
List<String> relativeTodoDays = [];
relativeTodos = [];
for (int i = 0; i < 2; i++) {
relativeTodoDays
.add(dFormat.format(DateTime.now().add(Duration(days: i))));
}
User.todos.forEach((element) {
if (relativeTodoDays.contains(dFormat.format(element.dueDate))) {
//Suitaable
relativeTodos.add(element);
}
});
//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) {
double avgProd = 0;
double pastAvgProd = 0;
int _avgProdCount = 0;
int _pastAvgProdCount = 0;
productivityData.forEach((element) {
avgProd += element.productivity;
_avgProdCount++;
});
pastProductivityData.forEach((element) {
pastAvgProd += element.productivity;
_pastAvgProdCount++;
});
avgProd = avgProd / _avgProdCount;
pastAvgProd = pastAvgProd / _pastAvgProdCount;
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()));
},
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: landscape ? null : navDrawer(context, 0),
body: SafeArea(
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
landscape ? navDrawer(context, 0) : Container(),
(User.activities.isEmpty)
? Container(
width: 1000,
child: 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),
))
]),
)
: Expanded(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
if (User.offline)
Container(
child: Container(
width: 1000,
color: Colors.red,
child: Align(
alignment: Alignment.center,
child: Text("Offline")))),
Expanded(
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')))),
],
),
],
)
],
),
)),
),
if (relativeTodos.isNotEmpty)
Container(
padding: EdgeInsets.all(10),
child: Card(
color: Colors.white10,
elevation: 20,
shadowColor: Colors.black,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
child: Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('Close To-Do',
style: TextStyle(
fontSize: 16)),
MaterialButton(
height: 30,
color: Colors.green,
onPressed: () {
Navigator.of(context)
.pushNamed('/Todos');
},
child: Row(
children: [
Text('More'),
Icon(Icons
.keyboard_arrow_right)
],
),
)
],
),
LimitedBox(
maxHeight: 250,
child: ListView.builder(
shrinkWrap: true,
itemCount:
relativeTodos.length,
itemBuilder:
(context, index) {
return Container(
padding:
EdgeInsets.all(10),
margin:
EdgeInsets.symmetric(
vertical: 2),
decoration: BoxDecoration(
color: Colors.black26,
borderRadius:
BorderRadius
.circular(
10)),
child: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
SizedBox(
width: MediaQuery.of(
context)
.size
.width *
0.7,
child: Text(
'${relativeTodos[index].task!.name} -> ${relativeTodos[index].metadata}')),
Container(
padding: EdgeInsets
.symmetric(
horizontal:
5),
decoration: BoxDecoration(
borderRadius:
BorderRadius
.circular(
20),
color: Colors
.blue),
child: Text(DateFormat(
'MM/dd')
.format(relativeTodos[
index]
.dueDate)))
],
),
);
},
),
)
],
),
)),
),
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: 400,
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 =
DateTimeRange(
start: DateTime(
value.start
.year,
value.start
.month,
value.start
.day),
end: DateTime(
value.end
.year,
value.end
.month,
value.end
.day,
23,
59,
59));
}
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<
ProductivityMapData,
String>>[
LineSeries<
ProductivityMapData,
String>(
// Bind data source
markerSettings: MarkerSettings(
isVisible: true,
shape: DataMarkerType
.circle),
dataSource: productivityData.reversed
.toList(),
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) {
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");
},
pointColorMapper: (ProductivityMapData sales, _) => (User.journalExists(dFormat.parse(sales.day)) ? Colors.lightGreenAccent : Colors.green)),
LineSeries<
ProductivityMapData,
String>(
// Bind data source
// dashArray: [0,0.5,1],
dashArray: [2, 5],
markerSettings:
MarkerSettings(
isVisible: true,
shape:
DataMarkerType
.circle),
dataSource:
pastProductivityData
.reversed
.toList(),
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),
color: Colors.grey
// 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,
]),
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Row(
children: [
Text("Average : "),
Text(
"${(productivityData.length > 0) ? avgProd.toStringAsFixed(1) : 'n/a'}%",
style: TextStyle(
fontSize: 18,
color: (avgProd >
pastAvgProd)
? Colors
.lightGreenAccent
: Colors.red))
],
),
Row(
children: [
Text("Past Average : "),
Text(
"${(productivityData.length > 1) ? pastAvgProd.toStringAsFixed(1) : 'n/a'}%",
style: TextStyle(
fontSize: 18))
],
),
],
),
SizedBox(
height: 20,
),
if (lastProductive != null &&
DateTime.now()
.difference(
lastProductive!)
.inMinutes >
60)
RichText(
text: TextSpan(children: <
TextSpan>[
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: <
CircularSeries>[
// Render pie chart
PieSeries<CatMapData, String>(
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: <
ChartSeries<
TaskTypeMapData,
String>>[
BarSeries<TaskTypeMapData, String>(
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: <
ChartSeries<CatMapData,
String>>[
BarSeries<CatMapData, String>(
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<String> 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('/Analytics');
},
),
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: FaIcon(FontAwesomeIcons.rocket,
color: Theme.of(context).primaryColor),
onTap: () {
if (pageIndex == 7) {
return;
}
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'),
leading: FaIcon(FontAwesomeIcons.bookJournalWhills,
color: Theme.of(context).primaryColor),
onTap: () {
if (pageIndex == 8) {
return;
}
Navigator.of(context).pushReplacementNamed('/Journal');
},
),
// 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 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;
}