Files
TaskTracker/lib/Activities.dart
2022-04-25 15:01:05 +05:30

645 lines
27 KiB
Dart

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<Activities> {
//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<String, ActivityListDateGroup> activitiesGroups = <String, ActivityListDateGroup>{};
@override
Widget build(BuildContext context) {
// progressDialog = ProgressDialog(context: context);
// List<Widget> 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<String, ActivityListDateGroup> PrintTasks() {
Map<String, ActivityListDateGroup> _tasks = <String, ActivityListDateGroup>{};
print('Priting cats : ' + User.taskTypes.length.toString());
String lastDate = "";
Map<String, int> productivtyActs = <String, int>{};
Map<String, int> unproductivtyActs = <String, int>{};
Map<String, int> totalMinutes = <String, int>{};
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<String> keys = activitiesGroups.keys.toList();
List<ActivityListDateGroup> 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<Widget> activities;
}
List<Activity> selectedActivities = [];