diff --git a/lib/main.dart b/lib/main.dart index 0b6b338..bf9da73 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,16 @@ import 'package:flutter/material.dart'; import 'package:smoke_cess_app/pages/main_page.dart'; import 'package:smoke_cess_app/service/database_service.dart'; +import 'package:smoke_cess_app/service/notification_service.dart'; +import 'package:timezone/data/latest.dart' as tz; -void main() async { +void main() { + // to ensure all the widgets are initialized. WidgetsFlutterBinding.ensureInitialized(); //init database DatabaseService.instance; + tz.initializeTimeZones(); + NotificationService().initNotification(); runApp(const MyApp()); } diff --git a/lib/pages/interval_page.dart b/lib/pages/interval_page.dart index a712243..8239d09 100644 --- a/lib/pages/interval_page.dart +++ b/lib/pages/interval_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/widgets/popup_for_start_and_stop.dart'; class IntervalTimerPage extends StatefulWidget { const IntervalTimerPage({Key? key}) : super(key: key); @@ -38,11 +39,18 @@ class _IntervalTimerPageState extends State { super.dispose(); } - void _startTimer() { + void _startTimer() async { + await showDialog( + context: context, + builder: (BuildContext context) { + return const TimerStartStopPopup( + title: 'Motivation vor dem Training', + ); + }, + ); _isPaused = false; - () async { - await AudioPlayer().play(UrlSource('assets/go.mp3')); - }(); + await AudioPlayer().play(UrlSource('assets/go.mp3')); + _timer = Timer.periodic(const Duration(seconds: 1), (_) => _tick()); Future.delayed(const Duration(seconds: 1)).then((value) { _playWarmUpMusic(); @@ -51,9 +59,9 @@ class _IntervalTimerPageState extends State { void _resetTimer() { () async { - await coolDownPlayer.dispose(); - await warmUpPlayer.dispose(); - await workoutPlayer.dispose(); + await coolDownPlayer.stop(); + await warmUpPlayer.stop(); + await workoutPlayer.stop(); }(); _isPaused = true; _timer?.cancel(); @@ -61,6 +69,14 @@ class _IntervalTimerPageState extends State { _currentDuration = _warmupDuration; _totalDuration = const Duration(minutes: 35); setState(() {}); + showDialog( + context: context, + builder: (BuildContext context) { + return const TimerStartStopPopup( + title: 'Motivation nach dem Training', + ); + }, + ); } Future _playWarmUpMusic() async { @@ -69,8 +85,9 @@ class _IntervalTimerPageState extends State { } Future _playWorkoutMusic() async { - await warmUpPlayer.dispose(); + await warmUpPlayer.stop(); Future.delayed(const Duration(microseconds: 600)).then((value) async { + await workoutPlayer.setReleaseMode(ReleaseMode.loop); await workoutPlayer.play(UrlSource('assets/workout.mp3')); }); } @@ -105,7 +122,8 @@ class _IntervalTimerPageState extends State { _currentBlock++; _currentDuration = _cooldownDuration; () async { - await workoutPlayer.dispose(); + await workoutPlayer.stop(); + await coolDownPlayer.setReleaseMode(ReleaseMode.loop); await coolDownPlayer.play(UrlSource('assets/cool_down.mp3')); }(); } else { diff --git a/lib/pages/scanner_page.dart b/lib/pages/scanner_page.dart index 1513ab3..c382990 100644 --- a/lib/pages/scanner_page.dart +++ b/lib/pages/scanner_page.dart @@ -5,6 +5,7 @@ import 'package:smoke_cess_app/models/settings.dart'; import 'package:smoke_cess_app/service/database_service.dart'; import 'package:smoke_cess_app/service/json_service.dart'; import 'package:smoke_cess_app/service/settings_service.dart'; +import 'package:smoke_cess_app/service/notification_service.dart'; import '../models/sleep.dart'; import '../widgets/missing_config_popup.dart'; @@ -70,6 +71,7 @@ class ScannerPageState extends State { textStyle: const TextStyle(fontSize: 20)), onPressed: () { loadSettingsFromLocalJSON(); + NotificationService().setAllNotifications(); }, child: const Text('Read JSON'), ), diff --git a/lib/service/date_service.dart b/lib/service/date_service.dart new file mode 100644 index 0000000..be45fe6 --- /dev/null +++ b/lib/service/date_service.dart @@ -0,0 +1,57 @@ +import 'package:smoke_cess_app/service/settings_service.dart'; +import 'package:timezone/timezone.dart'; + +const int trainingTime = 40; + +const weekDays = { + "Montag": 1, + "Dienstag": 2, + "Mittwoch": 3, + "Donnerstag": 4, + "Freitag": 5, + "Samstag": 6, + "Sonntag": 7, +}; + +Future> getDatesforAll() async { + List allDates = []; + List moodDates = await getDatesforMood(); + List sleepDates = await getDatesforSleep(); + allDates.addAll(moodDates); + allDates.addAll(sleepDates); + return allDates; +} + +Future> getDatesforMood() async { + final List? selectedDays = await getMoodQueryDaysCategories(); + final int? selectedHours = await getMoodQueryHours(); + final int? selectedMinutes = await getMoodQueryMinutes(); + return createTZDateTimes(selectedDays, selectedHours, selectedMinutes); +} + +Future> getDatesforSleep() async { + final List? selectedDays = await getSleepQueryDaysCategories(); + final int? selectedHours = await getSleepQueryHours(); + final int? selectedMinutes = await getSleepQueryMinutes(); + return createTZDateTimes(selectedDays, selectedHours, selectedMinutes); +} + +List createTZDateTimes( + List? selectedDays, int? selectedHours, int? selectedMinutes) { + List tzDateTimes = []; + if (selectedDays == null || + selectedHours == null || + selectedMinutes == null) { + return tzDateTimes; + } + final Iterable selectedDaysInt = + selectedDays.map((day) => weekDays[day]); + for (int i = 0; i < trainingTime; i++) { + final DateTime date = DateTime.now().add(Duration(days: i)); + if (selectedDaysInt.contains(date.weekday)) { + tzDateTimes.add(TZDateTime.local(date.year, date.month, date.day, + selectedHours, selectedMinutes, 0, 0, 0)); + } + } + return tzDateTimes; +} diff --git a/lib/service/notification_service.dart b/lib/service/notification_service.dart new file mode 100644 index 0000000..daff249 --- /dev/null +++ b/lib/service/notification_service.dart @@ -0,0 +1,81 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:smoke_cess_app/service/date_service.dart'; +import 'package:timezone/timezone.dart'; + +class NotificationService { + static final NotificationService _notificationService = + NotificationService._internal(); + + factory NotificationService() { + return _notificationService; + } + + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + NotificationService._internal(); + + Future initNotification() async { + // Android initialization + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + // ios initialization + const IOSInitializationSettings initializationSettingsIOS = + IOSInitializationSettings( + requestAlertPermission: false, + requestBadgePermission: false, + requestSoundPermission: false, + ); + + const InitializationSettings initializationSettings = + InitializationSettings( + android: initializationSettingsAndroid, + iOS: initializationSettingsIOS); + // the initialization settings are initialized after they are setted + await flutterLocalNotificationsPlugin.initialize(initializationSettings); + } + + Future setAllNotifications() async { + List moodDates = await getDatesforMood(); + List sleepDates = await getDatesforSleep(); + for (var date in moodDates) { + setNotification(1, "Mood", "Evaluate your mood", date); + print("mood"); + } + for (var date in sleepDates) { + setNotification(1, "Sleep", "Evaluate your sleep", date); + print("sleep"); + } + } + + Future setNotification( + int id, String title, String body, TZDateTime tzDateTime) async { + await flutterLocalNotificationsPlugin.zonedSchedule( + id, + title, + body, + tzDateTime, //schedule the notification to show after 2 seconds. + const NotificationDetails( + // Android details + android: AndroidNotificationDetails('main_channel', 'Main Channel', + channelDescription: "ashwin", + importance: Importance.max, + priority: Priority.max), + // iOS details + iOS: IOSNotificationDetails( + sound: 'default.wav', + presentAlert: true, + presentBadge: true, + presentSound: true, + ), + ), + + // Type of time interpretation + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + androidAllowWhileIdle: + true, // To show notification even when the app is closed + ); + } +} diff --git a/lib/widgets/elevated_card.dart b/lib/widgets/elevated_card.dart index eba4532..004b837 100644 --- a/lib/widgets/elevated_card.dart +++ b/lib/widgets/elevated_card.dart @@ -18,18 +18,18 @@ class ElevatedCard extends StatelessWidget { borderRadius: BorderRadius.circular(16.0), ), child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, - style: TextStyle( - fontSize: 18.0, + style: const TextStyle( + fontSize: 16.0, fontWeight: FontWeight.bold, ), ), - SizedBox(height: 8.0), + const SizedBox(height: 8.0), child, ], ), diff --git a/lib/widgets/mood_form.dart b/lib/widgets/mood_form.dart index 205e4d6..a13d43e 100644 --- a/lib/widgets/mood_form.dart +++ b/lib/widgets/mood_form.dart @@ -16,7 +16,7 @@ class MoodForm extends StatefulWidget { class _MoodFormState extends State { final GlobalKey _moodFormKey = GlobalKey(); - MySlider slider = const MySlider(''); + MySlider slider = const MySlider(); String _textInput = ""; void submitForm() { diff --git a/lib/widgets/popup_for_start_and_stop.dart b/lib/widgets/popup_for_start_and_stop.dart new file mode 100644 index 0000000..097e587 --- /dev/null +++ b/lib/widgets/popup_for_start_and_stop.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/widgets/slider.dart'; +import 'package:smoke_cess_app/widgets/submit_form_button.dart'; +import 'package:smoke_cess_app/widgets/text_formfield.dart'; + +class TimerStartStopPopup extends StatefulWidget { + final String title; + + const TimerStartStopPopup({Key? key, required this.title}) : super(key: key); + + @override + TimerStartStopPopupState createState() => TimerStartStopPopupState(); +} + +class TimerStartStopPopupState extends State { + final MySlider slider = const MySlider(); + + void submitForm(BuildContext context) { + Navigator.of(context).pop(); + } + + void onFormFieldSave(String? newValue) => newValue!; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(widget.title), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 8), + child: MySlider(labelText: 'Motivation'), + ), + const SizedBox(height: 16), + MyTextFormField('Beschreibe deinen Motivation', onFormFieldSave), + SubmitFormButton(() => submitForm(context)), + ], + ), + ); + } +} diff --git a/lib/widgets/sleep_form.dart b/lib/widgets/sleep_form.dart index 5564c7d..6073166 100644 --- a/lib/widgets/sleep_form.dart +++ b/lib/widgets/sleep_form.dart @@ -16,7 +16,7 @@ class SleepForm extends StatefulWidget { class _SleepFormState extends State { final GlobalKey _sleepFormKey = GlobalKey(); - MySlider slider = const MySlider(''); + MySlider slider = const MySlider(); String _textInput = ""; TimePicker sleepTimePicker = TimePicker( const TimeOfDay(hour: 22, minute: 00), diff --git a/lib/widgets/slider.dart b/lib/widgets/slider.dart index 9d896b7..02f3fb8 100644 --- a/lib/widgets/slider.dart +++ b/lib/widgets/slider.dart @@ -3,8 +3,10 @@ import 'package:flutter/material.dart'; double _currentSliderValue = 50; class MySlider extends StatefulWidget { - final String _title; - const MySlider(this._title, {Key? key}) : super(key: key); + final String _labelText; + const MySlider({Key? key, String labelText = 'Stimmung'}) + : _labelText = labelText, + super(key: key); @override State createState() => SliderState(); @@ -30,7 +32,6 @@ class SliderState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(widget._title), SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -58,7 +59,7 @@ class SliderState extends State { controller: _textFieldController, keyboardType: TextInputType.number, decoration: InputDecoration( - labelText: 'Stimmung', + labelText: widget._labelText, errorText: _errorText, ), onChanged: (text) { diff --git a/pubspec.lock b/pubspec.lock index e044c9f..fea7a33 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" async: dependency: transitive description: @@ -99,6 +106,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + dbus: + dependency: transitive + description: + name: dbus + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.8" fake_async: dependency: transitive description: @@ -132,6 +146,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + url: "https://pub.dartlang.org" + source: hosted + version: "9.9.1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -247,6 +282,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" platform: dependency: transitive description: @@ -385,6 +427,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + timezone: + dependency: "direct main" + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.0" typed_data: dependency: transitive description: @@ -419,7 +468,14 @@ packages: name: xdg_directories url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "0.2.0+3" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" sdks: dart: ">=2.18.2 <3.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index f30d59d..6e3127c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + flutter_local_notifications: ^9.3.1 + timezone: ^0.8.0 shared_preferences: ^2.0.17 audioplayers: ^3.0.1 mobile_scanner: ^3.0.0