diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/main.dart | 31 | ||||
| -rw-r--r-- | lib/pages/add_shift_page.dart | 142 | ||||
| -rw-r--r-- | lib/pages/agenda_page.dart | 118 | ||||
| -rw-r--r-- | lib/pages/home_page.dart | 62 | ||||
| -rw-r--r-- | lib/pages/logbook_page.dart | 172 | ||||
| -rw-r--r-- | lib/services/ishift_provider_service.dart | 10 | ||||
| -rw-r--r-- | lib/services/mock_program_provider_service.dart | 90 | ||||
| -rw-r--r-- | lib/shift.dart | 140 | ||||
| -rw-r--r-- | lib/style/style.dart | 16 | ||||
| -rw-r--r-- | lib/utils/date.dart | 51 | ||||
| -rw-r--r-- | lib/widgets/agenda_week.dart | 100 | ||||
| -rw-r--r-- | lib/widgets/agenda_week_item.dart | 225 | ||||
| -rw-r--r-- | lib/widgets/agenda_week_title.dart | 49 |
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, + )), + ), + ]), + ]); + } +} |
