summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/main.dart31
-rw-r--r--lib/pages/add_shift_page.dart142
-rw-r--r--lib/pages/agenda_page.dart118
-rw-r--r--lib/pages/home_page.dart62
-rw-r--r--lib/pages/logbook_page.dart172
-rw-r--r--lib/services/ishift_provider_service.dart10
-rw-r--r--lib/services/mock_program_provider_service.dart90
-rw-r--r--lib/shift.dart140
-rw-r--r--lib/style/style.dart16
-rw-r--r--lib/utils/date.dart51
-rw-r--r--lib/widgets/agenda_week.dart100
-rw-r--r--lib/widgets/agenda_week_item.dart225
-rw-r--r--lib/widgets/agenda_week_title.dart49
13 files changed, 1206 insertions, 0 deletions
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100644
index 0000000..88db8b5
--- /dev/null
+++ b/lib/main.dart
@@ -0,0 +1,31 @@
+import 'package:event_bus/event_bus.dart';
+import 'package:flutter/material.dart';
+import 'package:training_planner/services/ishift_provider_service.dart';
+import 'package:training_planner/services/mock_program_provider_service.dart';
+import 'pages/home_page.dart';
+import 'package:local_auth/local_auth.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+IProgramProviderService shiftProvider = MockShiftProviderService();
+final LocalAuthentication auth = LocalAuthentication();
+
+EventBus eventBus = EventBus();
+
+class MyApp extends StatelessWidget {
+ const MyApp({Key? key}) : super(key: key);
+
+ // This widget is the root of your application.
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'DHL HourTracker',
+ theme: ThemeData(
+ backgroundColor: Color.fromARGB(255, 255, 204, 0),
+ ),
+ home: HomePage(),
+ );
+ }
+}
diff --git a/lib/pages/add_shift_page.dart b/lib/pages/add_shift_page.dart
new file mode 100644
index 0000000..64b6142
--- /dev/null
+++ b/lib/pages/add_shift_page.dart
@@ -0,0 +1,142 @@
+import 'package:flutter/material.dart';
+import 'package:training_planner/pages/agenda_page.dart';
+import 'package:training_planner/pages/home_page.dart';
+import 'package:training_planner/shift.dart';
+import 'package:training_planner/main.dart';
+import 'package:training_planner/style/style.dart';
+
+class AddShiftPage extends StatefulWidget {
+ final int pageNr;
+ final int pageIndex;
+ final DateTime mondayOfWeek;
+ @override
+ _AddShiftPageState createState() => _AddShiftPageState();
+
+ const AddShiftPage({
+ Key? key,
+ required this.pageNr,
+ required this.pageIndex,
+ required this.mondayOfWeek,
+ }) : super(key: key);
+}
+
+class _AddShiftPageState extends State<AddShiftPage> {
+ @override
+ initState() {
+ super.initState();
+ }
+
+ String dropdownValue = 'Maandag';
+ List<bool> isSelected = [false, true, false];
+
+ void addShift() {
+ DateTime startDate = widget.mondayOfWeek;
+ switch (dropdownValue) {
+ case 'Dinsdag':
+ startDate = startDate.add(Duration(days: 1));
+ break;
+ case 'Woensdag':
+ startDate = startDate.add(Duration(days: 2));
+ break;
+ case 'Donderdag':
+ startDate = startDate.add(Duration(days: 3));
+ break;
+ case 'Vrijdag':
+ startDate = startDate.add(Duration(days: 4));
+ break;
+ case 'Zaterdag':
+ startDate = startDate.add(Duration(days: 5));
+ break;
+ case 'Zondag':
+ startDate = startDate.add(Duration(days: 6));
+ break;
+ }
+
+ shiftProvider.addShift(Shift(
+ start: startDate,
+ type: isSelected[0]
+ ? ShiftType.Dagrit
+ : isSelected[1]
+ ? ShiftType.Avondrit
+ : ShiftType.Terugscannen));
+
+ Navigator.pop(context, true);
+
+ // Previous page will not refresh without this.
+ Navigator.pushAndRemoveUntil(
+ context,
+ MaterialPageRoute(builder: (context) => HomePage()),
+ (Route<dynamic> route) => false,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Toevoegen aan planning #' + widget.pageNr.toString()),
+ backgroundColor: Style.background,
+ foregroundColor: Style.titleColor,
+ ),
+ body: Center(
+ child: Column(
+ children: [
+ Padding(padding: const EdgeInsets.all(20)),
+ Container(
+ width: 200,
+ child: DropdownButton<String>(
+ value: dropdownValue,
+ icon: const Icon(Icons.arrow_downward),
+ elevation: 16,
+ style: const TextStyle(color: Colors.black),
+ underline: Container(
+ height: 2,
+ color: Colors.black,
+ ),
+ onChanged: (String? newValue) {
+ setState(() {
+ dropdownValue = newValue!;
+ });
+ },
+ items: <String>[
+ 'Maandag',
+ 'Dinsdag',
+ 'Woensdag',
+ 'Donderdag',
+ 'Vrijdag',
+ 'Zaterdag',
+ 'Zondag'
+ ].map<DropdownMenuItem<String>>((String value) {
+ return DropdownMenuItem<String>(
+ value: value,
+ child: Text(value),
+ );
+ }).toList(),
+ ),
+ ),
+ Padding(padding: const EdgeInsets.all(20)),
+ ToggleButtons(
+ children: <Widget>[
+ Padding(
+ padding: const EdgeInsets.all(15), child: Text('Dagrit')),
+ Padding(
+ padding: const EdgeInsets.all(15), child: Text('Avondrit')),
+ Padding(
+ padding: const EdgeInsets.all(15),
+ child: Text('Terugscan')),
+ ],
+ onPressed: (int index) {
+ setState(() {
+ isSelected[index] = !isSelected[index];
+ });
+ },
+ isSelected: isSelected,
+ ),
+ Padding(
+ padding: const EdgeInsets.all(20),
+ ),
+ TextButton(onPressed: () => {addShift()}, child: Text('Toevoegen')),
+ ],
+ )));
+ }
+}
diff --git a/lib/pages/agenda_page.dart b/lib/pages/agenda_page.dart
new file mode 100644
index 0000000..55c7360
--- /dev/null
+++ b/lib/pages/agenda_page.dart
@@ -0,0 +1,118 @@
+import 'package:carousel_slider/carousel_slider.dart';
+import 'package:flutter/material.dart';
+import 'package:in_date_utils/in_date_utils.dart' as DateUtilities;
+import 'package:training_planner/pages/add_shift_page.dart';
+import 'package:training_planner/shift.dart';
+import 'package:training_planner/style/style.dart';
+import 'package:training_planner/utils/date.dart';
+import 'package:training_planner/widgets/agenda_week.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+
+class AgendaPage extends StatefulWidget {
+ @override
+ _AgendaPageState createState() => _AgendaPageState();
+
+ const AgendaPage({Key? key}) : super(key: key);
+}
+
+class _AgendaPageState extends State<AgendaPage> {
+ int weekToStartAt = 0;
+ List<Widget> weeks = [];
+ List<int> weekNrs = [];
+ List<DateTime> dateTimes = [];
+ int currentSelectedPageIndex = 0;
+ int currentSelectedPageNr = 0;
+ DateTime currentSelectedWeek = DateTime.now();
+
+ @override
+ initState() {
+ super.initState();
+
+ weeks = getWeeks();
+
+ currentSelectedPageIndex = weekToStartAt;
+ currentSelectedPageNr = weekNrs[weekToStartAt];
+ }
+
+ List<Widget> getWeeks() {
+ List<Widget> result = [];
+ List<int> weekNrs = [];
+ DateTime startDate =
+ DateUtilities.DateUtils.firstDayOfWeek(DateTime(2020, 1, 1));
+ DateTime today = DateTime.now();
+ DateTime firstDayOfCurrentWeek =
+ DateUtilities.DateUtils.firstDayOfWeek(today);
+ int difference = today.difference(startDate).inDays;
+
+ int totalWeeks = (difference / 7.0).ceil() + 4;
+
+ for (int i = 0; i < totalWeeks; i++) {
+ DateTime mondayOfWeek = startDate.add(Duration(days: 7 * i));
+ int weekNr = DateUtilities.DateUtils.getWeekNumber(mondayOfWeek);
+
+ bool isCurrentWeek = false;
+ if (mondayOfWeek == firstDayOfCurrentWeek) {
+ weekToStartAt = i;
+ isCurrentWeek = true;
+ }
+
+ result.add(
+ AgendaWeek(
+ weekNr: weekNr,
+ mondayOfWeek: mondayOfWeek,
+ isCurrentWeek: isCurrentWeek,
+ ),
+ );
+ weekNrs.add(weekNr);
+ dateTimes.add(mondayOfWeek);
+ }
+ this.weekNrs = weekNrs;
+
+ return result;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Padding(
+ padding: const EdgeInsets.all(0),
+ child: CarouselSlider(
+ options: CarouselOptions(
+ onPageChanged: (index, _) {
+ currentSelectedPageIndex = index;
+ currentSelectedPageNr = weekNrs[index];
+ currentSelectedWeek = dateTimes[index];
+ },
+ height: MediaQuery.of(context).size.height - 163,
+ viewportFraction: 1.0,
+ enlargeCenterPage: false,
+ enableInfiniteScroll: false,
+ initialPage: weekToStartAt, // Week nr
+ ),
+ items: getWeeks(),
+ ),
+ ),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => AddShiftPage(
+ pageNr: currentSelectedPageNr,
+ pageIndex: currentSelectedPageIndex,
+ mondayOfWeek: currentSelectedWeek,
+ )),
+ ).then((val) => {
+ setState(() {
+ weeks = getWeeks();
+ currentSelectedPageIndex = weekToStartAt;
+ currentSelectedPageNr = weekNrs[weekToStartAt];
+ })
+ });
+ },
+ backgroundColor: Style.titleColor,
+ child: const Icon(Icons.add),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart
new file mode 100644
index 0000000..821caed
--- /dev/null
+++ b/lib/pages/home_page.dart
@@ -0,0 +1,62 @@
+import 'package:flutter/material.dart';
+import 'package:training_planner/pages/agenda_page.dart';
+import 'package:training_planner/pages/logbook_page.dart';
+import 'package:training_planner/shift.dart';
+import 'package:training_planner/main.dart';
+import 'package:training_planner/style/style.dart';
+
+class HomePage extends StatefulWidget {
+ @override
+ _HomePageState createState() => _HomePageState();
+
+ const HomePage({Key? key}) : super(key: key);
+}
+
+class _HomePageState extends State<HomePage> {
+ int _selectedIndex = 0;
+
+ static final List<Widget> _widgetOptions = <Widget>[
+ new AgendaPage(),
+ new LogbookPage(),
+ ];
+
+ @override
+ initState() {
+ super.initState();
+ }
+
+ void _onItemTapped(int index) {
+ setState(() {
+ _selectedIndex = index;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Werkschema'),
+ backgroundColor: Style.background,
+ foregroundColor: Style.titleColor,
+ ),
+ body: Center(
+ child: _widgetOptions.elementAt(_selectedIndex),
+ ),
+ bottomNavigationBar: BottomNavigationBar(
+ items: const <BottomNavigationBarItem>[
+ BottomNavigationBarItem(
+ icon: Icon(Icons.assignment_ind_sharp),
+ label: 'Agenda',
+ ),
+ BottomNavigationBarItem(
+ icon: Icon(Icons.book),
+ label: 'Logboek',
+ ),
+ ],
+ currentIndex: _selectedIndex,
+ selectedItemColor: Style.titleColor,
+ onTap: _onItemTapped,
+ ),
+ );
+ }
+}
diff --git a/lib/pages/logbook_page.dart b/lib/pages/logbook_page.dart
new file mode 100644
index 0000000..bd9bde2
--- /dev/null
+++ b/lib/pages/logbook_page.dart
@@ -0,0 +1,172 @@
+import 'package:carousel_slider/carousel_slider.dart';
+import 'package:flutter/material.dart';
+import 'package:in_date_utils/in_date_utils.dart' as DateUtilities;
+import 'package:training_planner/main.dart';
+import 'package:training_planner/pages/add_shift_page.dart';
+import 'package:training_planner/shift.dart';
+import 'package:training_planner/style/style.dart';
+import 'package:training_planner/utils/date.dart';
+import 'package:training_planner/widgets/agenda_week.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+
+class LogbookPage extends StatefulWidget {
+ @override
+ _LogbookPageState createState() => _LogbookPageState();
+
+ const LogbookPage({Key? key}) : super(key: key);
+}
+
+class MonthData {
+ DateTime firstDayOfMonth;
+ List<Shift> shifts;
+ Duration totalWorkedTime = Duration();
+ double expectedSalary = 0;
+
+ void calculateData() {
+ totalWorkedTime = Duration();
+ for (var shift in shifts) {
+ totalWorkedTime += shift.getElapsedSessionTime();
+ }
+ if (shifts.isNotEmpty) {
+ expectedSalary = totalWorkedTime.inMinutes * shifts[0].getMinutePayRate();
+ }
+ }
+
+ MonthData({required this.firstDayOfMonth, required this.shifts}) {
+ calculateData();
+ }
+}
+
+class _LogbookPageState extends State<LogbookPage> {
+ List<MonthData> months = [];
+
+ void updateMonthData(MonthData month, Shift shift) {
+ month.shifts.add(shift);
+ month.calculateData();
+ }
+
+ void sortShifts(List<Shift> shifts) {
+ for (var shift in shifts) {
+ DateTime firstDayOfMonth =
+ DateUtilities.DateUtils.firstDayOfMonth(shift.start);
+
+ bool found = false;
+ for (var month in months) {
+ if (month.firstDayOfMonth == firstDayOfMonth) {
+ updateMonthData(month, shift);
+ found = true;
+ }
+ }
+
+ if (!found) {
+ months
+ .add(MonthData(firstDayOfMonth: firstDayOfMonth, shifts: [shift]));
+ }
+ }
+
+ months.sort((a, b) => b.firstDayOfMonth.compareTo(a.firstDayOfMonth));
+ }
+
+ @override
+ initState() {
+ super.initState();
+
+ shiftProvider.getPastShifts().then(
+ (value) => setState(
+ () {
+ List<Shift> allShifts = value;
+ sortShifts(allShifts);
+ },
+ ),
+ );
+ }
+
+ List<Widget> createMonthDataWidgets() {
+ List<Widget> result = [];
+
+ for (var month in months) {
+ result.add(Padding(
+ padding: const EdgeInsets.only(bottom: 8, left: 10, right: 10),
+ child: Container(
+ decoration: BoxDecoration(
+ border: Border.all(color: Color.fromARGB(255, 140, 140, 180)),
+ color: Color.fromARGB(255, 180, 180, 200),
+ borderRadius: BorderRadius.all(Radius.circular(8))),
+ child: Padding(
+ padding: const EdgeInsets.all(8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ DateHelper.getMonthName(month.firstDayOfMonth.month) +
+ ' ' +
+ month.firstDayOfMonth.year.toString(),
+ style: TextStyle(
+ color: Colors.white, fontWeight: FontWeight.bold),
+ ),
+ Padding(padding: EdgeInsets.only(left: 5, bottom: 5, right: 5)),
+ Text('Gewerkt: ' +
+ month.totalWorkedTime.inHours.toString() +
+ ' uur'),
+ Text('Verdiend: ' +
+ month.expectedSalary.toStringAsFixed(2) +
+ (month.shifts.isNotEmpty
+ ? ' (' +
+ (month.shifts[0].getMinutePayRate() * 60)
+ .toStringAsFixed(2) +
+ '/uur)'
+ : '')),
+ Padding(padding: EdgeInsets.only(left: 5, bottom: 5, right: 5)),
+ ],
+ ),
+ ),
+ ),
+ ));
+ }
+
+ return result;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ var monthDataWidgets = createMonthDataWidgets();
+
+ return ShaderMask(
+ shaderCallback: (Rect rect) {
+ return LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Style.background,
+ Colors.transparent,
+ Colors.transparent,
+ Style.background
+ ],
+ stops: [
+ 0.0,
+ 0.05,
+ 0.95,
+ 1.0
+ ], // 10% purple, 80% transparent, 10% purple
+ ).createShader(rect);
+ },
+ blendMode: BlendMode.dstOut,
+ child: SafeArea(
+ child: CustomScrollView(
+ physics: null,
+ slivers: [
+ SliverPadding(padding: EdgeInsets.only(top: 20)),
+ SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (BuildContext context, int index) {
+ return monthDataWidgets[index];
+ },
+ childCount: monthDataWidgets.length,
+ )),
+ SliverPadding(padding: EdgeInsets.only(top: 20)),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/services/ishift_provider_service.dart b/lib/services/ishift_provider_service.dart
new file mode 100644
index 0000000..2022536
--- /dev/null
+++ b/lib/services/ishift_provider_service.dart
@@ -0,0 +1,10 @@
+import 'dart:async';
+
+import 'package:training_planner/shift.dart';
+
+abstract class IProgramProviderService {
+ Future<List<Shift>> getPastShifts();
+ Future<List<Shift>> getShiftsForWeek(DateTime firstDayOfWeek);
+ Future<void> updateShift(Shift shift);
+ Future<void> addShift(Shift shift);
+}
diff --git a/lib/services/mock_program_provider_service.dart b/lib/services/mock_program_provider_service.dart
new file mode 100644
index 0000000..5a1f9f4
--- /dev/null
+++ b/lib/services/mock_program_provider_service.dart
@@ -0,0 +1,90 @@
+import 'package:training_planner/services/ishift_provider_service.dart';
+import 'package:training_planner/shift.dart';
+import 'package:uuid/uuid.dart';
+import 'package:in_date_utils/in_date_utils.dart' as DateUtilities;
+
+class MockShiftProviderService extends IProgramProviderService {
+ List<Shift> savedShifts = [];
+
+ MockShiftProviderService() {
+ List<Shift> shifts = [];
+
+ shifts.add(Shift(
+ end: DateTime(2022, 8, 8, 20, 30),
+ start: DateTime(2022, 8, 8, 16, 30),
+ type: ShiftType.Avondrit));
+
+ shifts.add(Shift(
+ end: DateTime(2022, 8, 6, 20, 30),
+ start: DateTime(2022, 8, 6, 16, 30),
+ type: ShiftType.Avondrit));
+
+ shifts.add(Shift(
+ end: DateTime(2022, 8, 5, 20, 30),
+ start: DateTime(2022, 8, 5, 16, 30),
+ type: ShiftType.Avondrit));
+
+ shifts.add(
+ Shift(start: DateTime(2022, 8, 4, 16, 30), type: ShiftType.Avondrit));
+
+ shifts.add(Shift(
+ end: DateTime(2022, 8, 1, 17, 30),
+ start: DateTime(2022, 8, 1, 9, 30),
+ type: ShiftType.Dagrit));
+
+ shifts.add(
+ Shift(start: DateTime(2022, 8, 22, 9, 30), type: ShiftType.Dagrit));
+
+ shifts.add(Shift(
+ start: DateTime.now().subtract(Duration(hours: 2)),
+ type: ShiftType.Dagrit));
+
+ savedShifts = shifts;
+ }
+
+ @override
+ Future<void> updateShift(Shift shift) async {
+ for (var item in savedShifts) {
+ if (DateUtilities.DateUtils.isSameDay(shift.start, item.start)) {
+ item.isActive = shift.isActive;
+ item.start = item.start;
+ item.end = item.end;
+ item.type = item.type;
+ break;
+ }
+ }
+ }
+
+ @override
+ Future<void> addShift(Shift shift) async {
+ for (var item in savedShifts) {
+ if (DateUtilities.DateUtils.isSameDay(shift.start, item.start)) {
+ return;
+ }
+ }
+ savedShifts.add(shift);
+ }
+
+ @override
+ Future<List<Shift>> getPastShifts() async {
+ List<Shift> shifts = savedShifts;
+ shifts.sort((a, b) => a.start.compareTo(b.start));
+
+ return shifts;
+ }
+
+ @override
+ Future<List<Shift>> getShiftsForWeek(DateTime firstDayOfWeek) async {
+ var items = await getPastShifts();
+ List<Shift> result = [];
+
+ for (var item in items) {
+ if (DateUtilities.DateUtils.firstDayOfWeek(item.start) ==
+ firstDayOfWeek) {
+ result.add(item);
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/lib/shift.dart b/lib/shift.dart
new file mode 100644
index 0000000..382b39f
--- /dev/null
+++ b/lib/shift.dart
@@ -0,0 +1,140 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:in_date_utils/in_date_utils.dart' as DateUtilities;
+
+enum ShiftType {
+ Dagrit,
+ Avondrit,
+ Terugscannen,
+}
+
+class Shift {
+ DateTime start;
+ DateTime? end;
+ ShiftType type;
+ bool isActive = false;
+
+ Shift({this.end = null, required this.start, required this.type});
+
+ Widget getStatusIcon() {
+ if (end == null &&
+ start.isBefore(DateTime.now()) &&
+ !DateUtilities.DateUtils.isSameDay(start, DateTime.now())) {
+ return Icon(Icons.pending);
+ }
+ if (getIsActive() && end == null) {
+ return Padding(
+ child: CircularProgressIndicator(
+ strokeWidth: 1,
+ color: Colors.white,
+ ),
+ padding:
+ const EdgeInsets.only(top: 18, left: 10, right: 10, bottom: 18),
+ );
+ }
+ if (canStart()) {
+ return Icon(Icons.today);
+ }
+ if (!getIsActive() && end == null) {
+ return Icon(Icons.cabin);
+ }
+ if (end != null) {
+ return Icon(Icons.check);
+ }
+
+ return Icon(Icons.question_answer);
+ }
+
+ bool canStart() {
+ if (DateUtilities.DateUtils.isSameDay(DateTime.now(), start) &&
+ end == null &&
+ !isActive) {
+ return true;
+ }
+ return false;
+ }
+
+ bool getIsActive() {
+ return this.isActive;
+ }
+
+ bool isDone() {
+ return this.end != null;
+ }
+
+ DateTime expectedEndTime() {
+ switch (type) {
+ case ShiftType.Avondrit:
+ return start.add(Duration(hours: 5));
+ case ShiftType.Dagrit:
+ return start.add(Duration(hours: 8));
+ case ShiftType.Terugscannen:
+ return start.add(Duration(hours: 8));
+ }
+ }
+
+ Duration getElapsedSessionTime() {
+ if (getIsActive()) {
+ return DateTime.now().difference(start);
+ }
+ if (isDone()) {
+ return end?.difference(start) ?? Duration();
+ }
+
+ return expectedEndTime().difference(start);
+ }
+
+ double getMinutePayRate() {
+ return 0.22916666;
+ }
+
+ double getMoneyForActiveSession() {
+ if (getIsActive()) {
+ Duration elapsed = DateTime.now().difference(start);
+
+ return elapsed.inMinutes * getMinutePayRate();
+ }
+ return 0;
+ }
+
+ int getPercentage() {
+ if (getIsActive()) {
+ Duration totalMinutes = expectedEndTime().difference(start);
+ Duration elapsed = DateTime.now().difference(start);
+ int percentage = (elapsed.inMinutes ~/ totalMinutes.inMinutes);
+ if (percentage < 0) percentage = 0;
+ if (percentage > 100) percentage = 100;
+ return percentage;
+ }
+ return 0;
+ }
+
+ void setIsActive(bool active) {
+ isActive = active;
+
+ if (active) {
+ start = DateTime.now();
+ } else {
+ end = DateTime.now();
+ }
+ }
+
+ Color getStatusColor() {
+ if (end == null &&
+ start.isBefore(DateTime.now()) &&
+ !DateUtilities.DateUtils.isSameDay(start, DateTime.now())) {
+ return Colors.orange;
+ }
+ if (getIsActive() && end == null) {
+ return Colors.grey;
+ }
+ if (!getIsActive() && end == null) {
+ return Colors.red;
+ }
+ if (end != null) {
+ return Colors.green;
+ }
+
+ return Colors.pink;
+ }
+}
diff --git a/lib/style/style.dart b/lib/style/style.dart
new file mode 100644
index 0000000..f14f607
--- /dev/null
+++ b/lib/style/style.dart
@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+
+class Style {
+ static const Color background = Color.fromARGB(255, 255, 204, 0);
+ static const Color titleColor = Color.fromARGB(255, 212, 5, 17);
+
+ static const TextStyle bodyNormal =
+ TextStyle(color: Colors.white, fontSize: 14);
+
+ static const TextStyle listItemTitletextBold =
+ TextStyle(color: titleColor, fontSize: 16, fontWeight: FontWeight.bold);
+ static const TextStyle listItemTitletext =
+ TextStyle(color: Colors.red, fontSize: 16, fontWeight: FontWeight.w300);
+
+ static const Color listEntryBackground = background;
+}
diff --git a/lib/utils/date.dart b/lib/utils/date.dart
new file mode 100644
index 0000000..7a66c03
--- /dev/null
+++ b/lib/utils/date.dart
@@ -0,0 +1,51 @@
+class DateHelper {
+ static String getWeekdayName(int nr) {
+ switch (nr) {
+ case DateTime.monday:
+ return 'Ma';
+ case DateTime.tuesday:
+ return 'Di';
+ case DateTime.wednesday:
+ return 'Wo';
+ case DateTime.thursday:
+ return 'Do';
+ case DateTime.friday:
+ return 'Vr';
+ case DateTime.saturday:
+ return 'Za';
+ case DateTime.sunday:
+ return 'Zo';
+ }
+ return '';
+ }
+
+ static String getMonthName(int nr) {
+ switch (nr) {
+ case 1:
+ return 'Jan';
+ case 2:
+ return 'Feb';
+ case 3:
+ return 'Maa';
+ case 4:
+ return 'Apr';
+ case 5:
+ return 'Mei';
+ case 6:
+ return 'Jun';
+ case 7:
+ return 'Jul';
+ case 8:
+ return 'Aug';
+ case 9:
+ return 'Sep';
+ case 10:
+ return 'Okt';
+ case 11:
+ return 'Nov';
+ case 12:
+ return 'Dec';
+ }
+ return '';
+ }
+}
diff --git a/lib/widgets/agenda_week.dart b/lib/widgets/agenda_week.dart
new file mode 100644
index 0000000..4a654ce
--- /dev/null
+++ b/lib/widgets/agenda_week.dart
@@ -0,0 +1,100 @@
+import 'package:flutter/material.dart';
+import 'package:training_planner/main.dart';
+import 'package:training_planner/shift.dart';
+import 'package:training_planner/style/style.dart';
+import 'package:training_planner/utils/date.dart';
+import 'package:training_planner/widgets/agenda_week_item.dart';
+import 'package:training_planner/widgets/agenda_week_title.dart';
+
+class AgendaWeek extends StatefulWidget {
+ final int weekNr;
+ final DateTime mondayOfWeek;
+ final bool isCurrentWeek;
+
+ @override
+ _AgendaWeekState createState() => _AgendaWeekState();
+
+ const AgendaWeek({
+ Key? key,
+ required this.weekNr,
+ required this.mondayOfWeek,
+ required this.isCurrentWeek,
+ }) : super(key: key);
+}
+
+class _AgendaWeekState extends State<AgendaWeek> {
+ List<Widget> weekItems = [];
+
+ @override
+ void initState() {
+ super.initState();
+
+ shiftProvider
+ .getShiftsForWeek(this.widget.mondayOfWeek)
+ .then((value) => setState(() {
+ weekItems = [
+ AgendaWeekTitle(
+ weekNr: this.widget.weekNr,
+ mondayOfWeek: this.widget.mondayOfWeek,
+ isCurrentWeek: this.widget.isCurrentWeek),
+ Padding(
+ padding: const EdgeInsets.all(10),
+ )
+ ];
+
+ for (var item in value) {
+ weekItems.add(new AgendaWeekItem(shift: item));
+ }
+
+ weekItems.add(Padding(
+ padding: const EdgeInsets.all(50),
+ ));
+ }));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return ShaderMask(
+ shaderCallback: (Rect rect) {
+ return LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Style.background,
+ Colors.transparent,
+ Colors.transparent,
+ Style.background
+ ],
+ stops: [
+ 0.0,
+ 0.05,
+ 0.95,
+ 1.0
+ ], // 10% purple, 80% transparent, 10% purple
+ ).createShader(rect);
+ },
+ blendMode: BlendMode.dstOut,
+ child: SafeArea(
+ child: CustomScrollView(
+ physics: null,
+ slivers: [
+ SliverPadding(padding: EdgeInsets.only(top: 20)),
+ SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (BuildContext context, int index) {
+ return weekItems[index];
+ },
+ childCount: weekItems.length,
+ )),
+
+ // Rest day
+ //if (day == null)
+ // createRestDayPage(list)
+
+ SliverPadding(padding: EdgeInsets.only(top: 20)),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/agenda_week_item.dart b/lib/widgets/agenda_week_item.dart
new file mode 100644
index 0000000..ba70083
--- /dev/null
+++ b/lib/widgets/agenda_week_item.dart
@@ -0,0 +1,225 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:training_planner/main.dart';
+import 'package:training_planner/shift.dart';
+import 'package:training_planner/utils/date.dart';
+import '../style/style.dart';
+
+class AgendaWeekItem extends StatefulWidget {
+ final Shift shift;
+ const AgendaWeekItem({
+ Key? key,
+ required this.shift,
+ }) : super(key: key);
+
+ @override
+ _ExerciseEntryState createState() => _ExerciseEntryState();
+}
+
+class _ExerciseEntryState extends State<AgendaWeekItem> {
+ String shiftTypeName = '';
+ String shiftTime = '';
+ String shiftTimeEnd = '';
+ bool canUseLocalAuth = false;
+
+ final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
+ FlutterLocalNotificationsPlugin();
+
+ Future<void> _showOngoingNotification() async {
+ if (widget.shift.isDone()) return;
+
+ AndroidNotificationDetails androidPlatformChannelSpecifics =
+ AndroidNotificationDetails('channel_1', 'Actieve Sessie',
+ channelDescription: '3:45 => \$50',
+ importance: Importance.max,
+ priority: Priority.high,
+ ongoing: true,
+ icon: 'dhl',
+ showProgress: true,
+ onlyAlertOnce: true,
+ maxProgress: 100,
+ channelAction: AndroidNotificationChannelAction.update,
+ progress: widget.shift.getPercentage(),
+ color: Style.background,
+ autoCancel: false);
+ NotificationDetails platformChannelSpecifics =
+ NotificationDetails(android: androidPlatformChannelSpecifics);
+
+ String elapsedTime =
+ "${widget.shift.getElapsedSessionTime().inHours.toString().padLeft(2, '0')}:${(widget.shift.getElapsedSessionTime().inMinutes % 60).toString().padLeft(2, '0')}";
+
+ await flutterLocalNotificationsPlugin.show(
+ 0,
+ 'Sessie actief',
+ '⏱ ' +
+ elapsedTime +
+ ' => €' +
+ widget.shift.getMoneyForActiveSession().toStringAsFixed(2),
+ platformChannelSpecifics);
+ }
+
+ void initState() {
+ super.initState();
+
+ auth.canCheckBiometrics.then((bio) => {
+ auth
+ .isDeviceSupported()
+ .then((supported) => {canUseLocalAuth = bio && supported})
+ });
+
+ setState(() {
+ switch (widget.shift.type) {
+ case ShiftType.Avondrit:
+ shiftTypeName = 'Avondrit';
+ break;
+ case ShiftType.Dagrit:
+ shiftTypeName = 'Dagrit';
+ break;
+ case ShiftType.Terugscannen:
+ shiftTypeName = 'Terugscannen';
+ break;
+ }
+
+ setStartAndEndTime();
+ });
+ }
+
+ void setStartAndEndTime() {
+ shiftTime =
+ "${widget.shift.start.hour.toString().padLeft(2, '0')}:${widget.shift.start.minute.toString().padLeft(2, '0')}";
+
+ DateTime? expectedEndTime = widget.shift.expectedEndTime();
+ if (widget.shift.isDone()) {
+ expectedEndTime = widget.shift.end;
+ }
+
+ shiftTimeEnd = ' - ' +
+ "${expectedEndTime?.hour.toString().padLeft(2, '0')}:${expectedEndTime?.minute.toString().padLeft(2, '0')}";
+ }
+
+ Timer? updateNotificationTimer;
+ void showNotificationForActiveSession() {
+ _showOngoingNotification();
+ updateNotificationTimer = Timer.periodic(
+ Duration(seconds: 10), (Timer t) => _showOngoingNotification());
+ }
+
+ void stopNotificationForActiveSession() {
+ updateNotificationTimer?.cancel();
+ flutterLocalNotificationsPlugin.cancelAll();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ Widget startShiftWidget = Padding(padding: const EdgeInsets.all(0));
+ TextStyle endDateTextStyle = TextStyle(color: Colors.black);
+
+ if (!widget.shift.getIsActive() && widget.shift.canStart()) {
+ startShiftWidget = TextButton(
+ onPressed: () => {
+ setState(() {
+ showNotificationForActiveSession();
+ widget.shift.setIsActive(true);
+ shiftProvider.updateShift(widget.shift);
+ setStartAndEndTime();
+ })
+ },
+ child: Text('Begin'));
+ } else if (widget.shift.getIsActive()) {
+ startShiftWidget = TextButton(
+ onPressed: () {
+ auth
+ .authenticate(
+ localizedReason: 'Weet je zeker dat je wilt eindigen?')
+ .then((value) => {
+ if (value)
+ {
+ setState(() {
+ widget.shift.setIsActive(false);
+ shiftProvider.updateShift(widget.shift);
+ setStartAndEndTime();
+ stopNotificationForActiveSession();
+ })
+ }
+ })
+ .catchError((f) => {});
+ },
+ child: Text('Einde'));
+ }
+
+ if (!widget.shift.isDone()) {
+ endDateTextStyle = TextStyle(color: Color.fromARGB(80, 0, 0, 0));
+ }
+
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8, left: 10, right: 10),
+ child: Container(
+ decoration: const BoxDecoration(
+ color: Style.listEntryBackground,
+ borderRadius: BorderRadius.all(Radius.circular(8))),
+ child: Padding(
+ padding: const EdgeInsets.all(0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ width: double.infinity,
+ decoration: const BoxDecoration(
+ borderRadius: BorderRadius.all(Radius.circular(4))),
+ padding: EdgeInsets.all(0),
+ child: Padding(
+ padding: EdgeInsets.only(right: 5),
+ child: Row(
+ children: [
+ Container(
+ decoration: BoxDecoration(
+ color: widget.shift.getStatusColor(),
+ borderRadius: BorderRadius.only(
+ topLeft: Radius.circular(8),
+ bottomLeft: Radius.circular(8))),
+ height: 48.0,
+ width: 32.0,
+ child: widget.shift.getStatusIcon(),
+ ),
+ Container(
+ padding: const EdgeInsets.only(left: 10),
+ child: Text(
+ DateHelper.getWeekdayName(widget.shift.start.weekday),
+ style: Style.listItemTitletextBold,
+ ),
+ width: 35,
+ ),
+ Container(
+ child: RichText(
+ text: TextSpan(
+ style: TextStyle(color: Colors.black),
+ children: [
+ TextSpan(text: ' | ' + shiftTime),
+ TextSpan(
+ text: shiftTimeEnd, style: endDateTextStyle)
+ ],
+ ),
+ ),
+ width: 95,
+ ),
+ Container(
+ child: Text(
+ '| ' + shiftTypeName,
+ ),
+ width: 100,
+ ),
+ startShiftWidget,
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/agenda_week_title.dart b/lib/widgets/agenda_week_title.dart
new file mode 100644
index 0000000..582b6b8
--- /dev/null
+++ b/lib/widgets/agenda_week_title.dart
@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+import 'package:training_planner/utils/date.dart';
+import '../style/style.dart';
+
+class AgendaWeekTitle extends StatefulWidget {
+ final int weekNr;
+ final DateTime mondayOfWeek;
+ final bool isCurrentWeek;
+
+ const AgendaWeekTitle({
+ Key? key,
+ required this.weekNr,
+ required this.mondayOfWeek,
+ required this.isCurrentWeek,
+ }) : super(key: key);
+
+ @override
+ _AgendaWeekTitleState createState() => _AgendaWeekTitleState();
+}
+
+class _AgendaWeekTitleState extends State<AgendaWeekTitle> {
+ @override
+ Widget build(BuildContext context) {
+ return Column(children: [
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ this.widget.isCurrentWeek
+ ? Icon(Icons.today)
+ : Padding(padding: const EdgeInsets.all(0)),
+ Center(
+ child: Text(
+ " Week #" +
+ this.widget.weekNr.toString() +
+ " | " +
+ this.widget.mondayOfWeek.day.toString() +
+ " " +
+ DateHelper.getMonthName(this.widget.mondayOfWeek.month) +
+ " " +
+ this.widget.mondayOfWeek.year.toString(),
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ )),
+ ),
+ ]),
+ ]);
+ }
+}