diff --git a/README.md b/README.md index 274732f..ee7623a 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,13 @@ Die App lässt sich als Android- und iOS App ausführen und zu Debugzwecken eben ## App bedienen Um die App nutzen zu können, müssen Settings eingelesen werden. Dazu kann entweder ein in die App integrierter QR-Code Scanner (mit dem beigefügten [QR-Code](./gruppe1_QR.png)) benutzt werden, oder zu Debugzwecken mit dem dafür vorgesehenen Button auf der Scannerseite über eine lokale JSON-Datei. +Es ist zu beachten, dass das verwendete QR-Code Scanner Package für Mobilgeräte optimiert ist. Deshalb kann es zu Abstürzen beim Verwenden des Scanners mit der Web-Version kommen. +Beim Verwenden der Web-Version sollten die Settings mit der lokalen JSON-Datei eingelesen werden. +Der Button zum Einlesen der lokalen JSON-Datei kann über die Variable bool useLocalConfig in der Datei [globals.dart](./lib/globals.dart) ein- und ausgeblendet werden. + +## Authoren + +- Hinrik Ehrenfried 2012537 +- Patrick Meßmer 1911768 +- Kai Mannweiler 2012491 +- Julian Gegner 1922635 diff --git a/android/app/build.gradle b/android/app/build.gradle index af69fb2..24b12bc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -70,5 +70,7 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' } diff --git a/lib/globals.dart b/lib/globals.dart index 58229d9..3cdf823 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -3,5 +3,8 @@ library app.globals; import 'package:smoke_cess_app/mock/db_mock.dart'; import 'package:smoke_cess_app/services/database_service.dart'; -DatabaseService databaseService = DatabaseMock(); +DatabaseService databaseService = DatabaseMock(); // DatabaseService databaseService = DatabaseService.instance; + +// set this to read settings from local json file instead of scanning a qr code +bool useLocalConfig = false; diff --git a/lib/main.dart b/lib/main.dart index 4a49cd4..33f19bc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/date_symbol_data_local.dart'; import 'package:provider/provider.dart'; import 'package:smoke_cess_app/pages/main_page.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart'; @@ -14,6 +15,7 @@ void main() { //init database globals.databaseService; tz.initializeTimeZones(); + initializeDateFormatting('de'); NotificationService().initNotification(); runApp(const MyApp()); } diff --git a/lib/models/mood.dart b/lib/models/mood.dart index 9d34051..27a41d0 100644 --- a/lib/models/mood.dart +++ b/lib/models/mood.dart @@ -9,6 +9,7 @@ class Mood implements DatabaseRecord { DateTime get date => _date; int get moodValue => _moodValue; + String get comment => _comment; @override factory Mood.fromDatabase(Map map) { diff --git a/lib/models/relapse.dart b/lib/models/relapse.dart index 1507188..6bb6ea0 100644 --- a/lib/models/relapse.dart +++ b/lib/models/relapse.dart @@ -9,6 +9,7 @@ class Relapse implements DatabaseRecord { String get category => _category; DateTime get date => _date; + String get comment => _comment; @override factory Relapse.fromDatabase(Map map) { diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 83fad43..dfa79be 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -6,9 +6,10 @@ class Settings { final QueryConfig? moodQuery; final QueryConfig? sleepQuery; final TimeConfig? chessTime; + final DateTime startedAt; Settings(this.group, this.relapseCategories, this.moodQuery, this.sleepQuery, - this.chessTime); + this.chessTime, this.startedAt); Settings.fromJson(Map json) : group = json['group'] as int, @@ -17,7 +18,8 @@ class Settings { sleepQuery = QueryConfig.fromJson(json['sleep_query']), chessTime = json['chess_time'] != null ? TimeConfig.fromJson(json['chess_time']) - : null; + : null, + startedAt = DateTime.parse(json['startedAt']); } class QueryConfig { diff --git a/lib/models/sleep.dart b/lib/models/sleep.dart index 4447be4..e6a0b80 100644 --- a/lib/models/sleep.dart +++ b/lib/models/sleep.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:smoke_cess_app/interface/db_record.dart'; +import 'package:smoke_cess_app/utils/timer_util.dart'; class Sleep implements DatabaseRecord { final int _sleepQualityValue; @@ -12,7 +13,9 @@ class Sleep implements DatabaseRecord { this._wokeUpAt); DateTime get date => _date; + String get comment => _comment; int get sleepQualitiyValue => _sleepQualityValue; + TimeOfDay get sleepDuration => _sleptAt.durationBetween(_wokeUpAt); @override factory Sleep.fromDatabase(Map map) { diff --git a/lib/pages/interval_page.dart b/lib/pages/interval_page.dart index 2326168..484ae20 100644 --- a/lib/pages/interval_page.dart +++ b/lib/pages/interval_page.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:smoke_cess_app/services/pages_service.dart'; -import 'package:smoke_cess_app/widgets/workout_form.dart'; -import 'package:smoke_cess_app/widgets/workout_view.dart'; - -import '../widgets/view_form_page.dart'; +import 'package:smoke_cess_app/widgets/view_form/workout_form.dart'; +import 'package:smoke_cess_app/widgets/view_form/workout_view.dart'; +import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart'; class IntervalTimerPage extends StatelessWidget { const IntervalTimerPage({super.key}); diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 10ff0ec..9d6c77b 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -8,52 +8,49 @@ import 'package:smoke_cess_app/providers/settings_provider.dart'; import '../widgets/todo_icon.dart'; -class MyHomePage extends StatefulWidget { +class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); - @override - MyHomePageState createState() => MyHomePageState(); -} - -class MyHomePageState extends State { - int _selectedIndex = 4; - bool _isConfigured = false; - - void _onItemTapped(int index) { - PageProvider pageProvider = context.read(); - setState(() { - if (_isConfigured) { - pageProvider.showForm = false; - _selectedIndex = index; - return; - } - AwesomeDialog( - context: context, - dialogType: DialogType.info, - title: 'Fehlende Konfiguration', - desc: 'Bitte QR Code Scannen!', - ).show(); - }); - } - @override Widget build(BuildContext context) { - var settingsModel = context.watch(); - var tasksModel = context.watch(); - _isConfigured = settingsModel.initialized; + SettingsProvider settingsProvider = context.watch(); + TasksProvider tasksProvider = context.watch(); + PageProvider pageProvider = context.watch(); + bool isConfigured = settingsProvider.initialized; + return Scaffold( appBar: AppBar( - title: Text( - '${pages.values.elementAt(_selectedIndex)['title']} ${_isConfigured ? "Gruppe ${settingsModel.settings?.group}" : ""}')), - body: SingleChildScrollView( - child: pages.values.elementAt(_selectedIndex)['page'], - ), + title: Row( + children: [ + Stack( + children: [ + const SizedBox( + width: 70, + ), + if (pageProvider.showForm) + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: pageProvider.swap), + ], + ), + Text( + '${pageProvider.currentPageData['title']} ${isConfigured ? "Gruppe ${settingsProvider.settings?.group}" : ""}') + ], + )), + body: pageProvider.currentPageData['page'], bottomNavigationBar: NavigationBar( - onDestinationSelected: _onItemTapped, - selectedIndex: _selectedIndex, + onDestinationSelected: isConfigured + ? pageProvider.setCurrentPage + : (value) => AwesomeDialog( + context: context, + dialogType: DialogType.info, + title: 'Fehlende Konfiguration', + desc: 'Bitte QR Code Scannen!', + ).show(), + selectedIndex: pageProvider.currentPageIndex, destinations: pages.keys.map((key) { return NavigationDestination( - icon: tasksModel.tasks[key] ?? false + icon: tasksProvider.tasks[key] ?? false ? MyToDoIcon(pages[key]?['icon']) : pages[key]!['icon'], label: pages[key]?['title']); diff --git a/lib/pages/mood_page.dart b/lib/pages/mood_page.dart index 8c2447d..f5b8009 100644 --- a/lib/pages/mood_page.dart +++ b/lib/pages/mood_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:smoke_cess_app/services/pages_service.dart'; -import 'package:smoke_cess_app/widgets/mood_form.dart'; -import 'package:smoke_cess_app/widgets/mood_view.dart'; -import 'package:smoke_cess_app/widgets/view_form_page.dart'; +import 'package:smoke_cess_app/widgets/view_form/mood_form.dart'; +import 'package:smoke_cess_app/widgets/view_form/mood_view.dart'; +import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart'; class MoodPage extends StatelessWidget { const MoodPage({super.key}); diff --git a/lib/pages/relapse_page.dart b/lib/pages/relapse_page.dart index e7b6127..db75e86 100644 --- a/lib/pages/relapse_page.dart +++ b/lib/pages/relapse_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:smoke_cess_app/services/pages_service.dart'; -import 'package:smoke_cess_app/widgets/relapse_form.dart'; -import 'package:smoke_cess_app/widgets/relapse_view.dart'; -import '../widgets/view_form_page.dart'; +import 'package:smoke_cess_app/widgets/view_form/relapse_form.dart'; +import 'package:smoke_cess_app/widgets/view_form/relapse_view.dart'; +import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart'; class RelapsePage extends StatelessWidget { const RelapsePage({super.key}); diff --git a/lib/pages/scanner_page.dart b/lib/pages/scanner_page.dart index ebba252..eee7dd0 100644 --- a/lib/pages/scanner_page.dart +++ b/lib/pages/scanner_page.dart @@ -4,8 +4,10 @@ import 'package:provider/provider.dart'; import 'package:smoke_cess_app/services/export_service.dart'; import 'package:smoke_cess_app/services/settings_service.dart'; import 'package:smoke_cess_app/services/notification_service.dart'; +import 'package:smoke_cess_app/widgets/buttons/text_icon_button.dart'; import 'package:smoke_cess_app/widgets/scanner.dart'; import '../providers/settings_provider.dart'; +import '../globals.dart' as globals; class ScannerPage extends StatelessWidget { const ScannerPage({super.key}); @@ -16,7 +18,7 @@ class ScannerPage extends StatelessWidget { } void loadJSON(BuildContext context) async { - var settingsModel = context.read(); + SettingsProvider settingsModel = context.read(); await loadSettingsFromLocalJSON(); settingsModel.initSettings(); NotificationService().setAllNotifications(); @@ -32,25 +34,26 @@ class ScannerPage extends StatelessWidget { @override Widget build(BuildContext context) { + SettingsProvider settingsProvider = context.watch(); + return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const MyScanner(), const SizedBox(height: 30), - ElevatedButton( - style: ElevatedButton.styleFrom( - textStyle: const TextStyle(fontSize: 20)), - onPressed: () => loadJSON(context), - child: const Text('Read JSON'), - ), - const SizedBox(height: 30), - ElevatedButton( - style: ElevatedButton.styleFrom( - textStyle: const TextStyle(fontSize: 20)), - onPressed: export, - child: const Text('Export'), - ) + if (!settingsProvider.scanning) + TextIconButton( + text: 'Export', + onPressed: ExportService().exportData, + iconData: Icons.upload), + if (globals.useLocalConfig && !settingsProvider.scanning) + ElevatedButton( + style: ElevatedButton.styleFrom( + textStyle: const TextStyle(fontSize: 20)), + onPressed: () => loadJSON(context), + child: const Text('Read JSON'), + ), ], )); } diff --git a/lib/pages/sleep_page.dart b/lib/pages/sleep_page.dart index 3cf3fd5..8d9842d 100644 --- a/lib/pages/sleep_page.dart +++ b/lib/pages/sleep_page.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:smoke_cess_app/services/pages_service.dart'; -import 'package:smoke_cess_app/widgets/sleep_form.dart'; -import 'package:smoke_cess_app/widgets/sleep_view.dart'; -import 'package:smoke_cess_app/widgets/view_form_page.dart'; +import 'package:smoke_cess_app/widgets/view_form/sleep_form.dart'; +import 'package:smoke_cess_app/widgets/view_form/sleep_view.dart'; +import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart'; class SleepPage extends StatelessWidget { const SleepPage({super.key}); diff --git a/lib/providers/page_provider.dart b/lib/providers/page_provider.dart index a17ff26..8184ee7 100644 --- a/lib/providers/page_provider.dart +++ b/lib/providers/page_provider.dart @@ -1,10 +1,22 @@ import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; class PageProvider extends ChangeNotifier { bool showForm = false; + Pages _currentPage = Pages.settings; void swap() { showForm = !showForm; notifyListeners(); } + + Map get currentPageData => pages[_currentPage]!; + + int get currentPageIndex => _currentPage.index; + + void setCurrentPage(int index) { + showForm = false; + _currentPage = Pages.values[index]; + notifyListeners(); + } } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index f51c5e1..121ff0d 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -6,9 +6,16 @@ import '../models/settings.dart'; class SettingsProvider extends ChangeNotifier { Settings? _settings; bool _initialized = false; + bool _scanning = false; Settings? get settings => _settings; bool get initialized => _initialized; + bool get scanning => _scanning; + + set scanning(bool value) { + _scanning = value; + notifyListeners(); + } SettingsProvider() { initSettings(); diff --git a/lib/providers/tasks_provider.dart b/lib/providers/tasks_provider.dart index d3fc042..2fed9b8 100644 --- a/lib/providers/tasks_provider.dart +++ b/lib/providers/tasks_provider.dart @@ -5,7 +5,7 @@ import 'package:smoke_cess_app/models/workout.dart'; import 'package:smoke_cess_app/providers/settings_provider.dart'; import 'package:smoke_cess_app/services/date_service.dart'; import 'package:smoke_cess_app/services/pages_service.dart'; -import 'package:timezone/browser.dart'; +import 'package:timezone/timezone.dart'; import '../globals.dart' as globals; import '../models/mood.dart'; diff --git a/lib/providers/timer_provider.dart b/lib/providers/timer_provider.dart index f355d7d..298316a 100644 --- a/lib/providers/timer_provider.dart +++ b/lib/providers/timer_provider.dart @@ -5,13 +5,18 @@ import 'package:flutter/material.dart'; class TimerProvider extends ChangeNotifier { Timer? _timer; bool started = false; - int get elapsedSeconds => _timer != null ? _timer!.tick : 0; + Duration _duration = const Duration(); + int get elapsedSeconds => _duration.inSeconds; + int get elapsedMilliseconds => _duration.inMilliseconds; + final Duration _tickRate = const Duration(milliseconds: 20); void startTimer(Duration duration) { + _duration = Duration.zero; started = true; - _timer = Timer.periodic(const Duration(seconds: 1), ((timer) { - if (timer.tick >= duration.inSeconds) { - timer.cancel(); + _timer = Timer.periodic(_tickRate, ((timer) { + _duration += _tickRate; + if (elapsedSeconds >= duration.inSeconds) { + _timer?.cancel(); started = false; } notifyListeners(); @@ -22,13 +27,12 @@ class TimerProvider extends ChangeNotifier { started = false; _timer?.cancel(); _timer = null; + _duration = Duration.zero; } @override void dispose() { - started = false; - _timer?.cancel(); - _timer = null; + stopTimer(); super.dispose(); } } diff --git a/lib/services/pages_service.dart b/lib/services/pages_service.dart index 0c19383..9ec00d6 100644 --- a/lib/services/pages_service.dart +++ b/lib/services/pages_service.dart @@ -8,8 +8,8 @@ import '../pages/sleep_page.dart'; enum Pages { mood, sleep, - relapse, timer, + relapse, settings, } diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index f28556a..ec8a656 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -23,6 +23,8 @@ Future getMoodQueryMinutes() => _getIntSetting('mood_query_minutes'); Future getChessHours() => _getIntSetting('chess_hours'); Future getChessMinutes() => _getIntSetting('chess_minutes'); +Future getStartDay() => _getStringSetting('startedAt'); + void _setIntSetting(String settingKey, int settingValue) => SharedPreferences.getInstance() .then((pref) => pref.setInt(settingKey, settingValue)); @@ -30,6 +32,13 @@ void _setIntSetting(String settingKey, int settingValue) => Future _getIntSetting(String settingKey) => SharedPreferences.getInstance().then((pref) => pref.getInt(settingKey)); +void _setStringSetting(String settingKey, String settingValue) => + SharedPreferences.getInstance() + .then((pref) => pref.setString(settingKey, settingValue)); + +Future _getStringSetting(String settingKey) => + SharedPreferences.getInstance().then((pref) => pref.getString(settingKey)); + void _setStringListSetting(String settingKey, List list) => SharedPreferences.getInstance() .then((pref) => pref.setStringList(settingKey, list)); @@ -40,6 +49,7 @@ Future?> _getStringListSetting(String settingKey) => Future loadSettingsFromLocalJSON() async { Map configJSON = await loadLocalConfigJSON(); + configJSON['startedAt'] = DateTime.now().toIso8601String(); Settings settings = Settings.fromJson(configJSON); saveSettings(settings); } @@ -53,6 +63,7 @@ void saveSettings(Settings settings) { _setStringListSetting('sleep_query_days', settings.sleepQuery!.days!); _setIntSetting('sleep_query_hours', settings.sleepQuery!.hours!); _setIntSetting('sleep_query_minutes', settings.sleepQuery!.minutes!); + _setStringSetting('startedAt', DateTime.now().toIso8601String()); if (settings.chessTime != null) { _setIntSetting('chess_hours', settings.chessTime!.hours!); _setIntSetting('chess_minutes', settings.chessTime!.minutes!); @@ -70,6 +81,8 @@ Future loadSettings() async { List? sleepDays = await getSleepQueryDaysCategories(); int? chessHours = await getChessHours(); int? chessMinutes = await getChessMinutes(); + DateTime startedAt = + DateTime.parse(await getStartDay() ?? DateTime.now().toIso8601String()); if (group != null) { return Settings( @@ -77,7 +90,8 @@ Future loadSettings() async { relapseCategories, QueryConfig(moodHours, moodMinutes, moodDays), QueryConfig(sleepHours, sleepMinutes, sleepDays), - TimeConfig(chessHours, chessMinutes)); + TimeConfig(chessHours, chessMinutes), + startedAt); } return null; } diff --git a/lib/utils/timer_util.dart b/lib/utils/timer_util.dart index a63030f..57b9f9c 100644 --- a/lib/utils/timer_util.dart +++ b/lib/utils/timer_util.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + String formatTime(int seconds) { Duration duration = Duration(seconds: seconds); String formattedTime = ''; @@ -6,9 +8,20 @@ String formatTime(int seconds) { String hours = twoDigits(duration.inHours.remainder(24)); String minutes = twoDigits(duration.inMinutes.remainder(60)); String formattedSeconds = twoDigits(duration.inSeconds.remainder(60)); - if (duration.inDays != 0) formattedTime += '$days:'; + if (duration.inDays != 0) { + formattedTime += '$days Tag ${duration.inDays > 1 ? "e" : ""}, '; + } if (duration.inHours != 0) formattedTime += '$hours:'; formattedTime += '$minutes:'; formattedTime += formattedSeconds; return formattedTime; } + +extension TimeOfDayExtension on TimeOfDay { + TimeOfDay durationBetween(TimeOfDay time) { + int hourOffset = time.minute - minute < 0 ? 1 : 0; + return TimeOfDay( + hour: (time.hour - hour - hourOffset) % 24, + minute: (time.minute - minute) % 60); + } +} diff --git a/lib/widgets/mute_button.dart b/lib/widgets/buttons/mute_button.dart similarity index 92% rename from lib/widgets/mute_button.dart rename to lib/widgets/buttons/mute_button.dart index 29bebf7..d74eb97 100644 --- a/lib/widgets/mute_button.dart +++ b/lib/widgets/buttons/mute_button.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - -import '../providers/audio_provider.dart'; +import '../../providers/audio_provider.dart'; class MuteButton extends StatelessWidget { const MuteButton({super.key}); diff --git a/lib/widgets/buttons/round_button_widget.dart b/lib/widgets/buttons/round_button_widget.dart new file mode 100644 index 0000000..9e8d7ac --- /dev/null +++ b/lib/widgets/buttons/round_button_widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class RoundIconButton extends StatelessWidget { + final VoidCallback onPressed; + final IconData iconData; + + const RoundIconButton( + {super.key, required this.onPressed, required this.iconData}); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(20), + backgroundColor: Colors.green, // <-- Button color + foregroundColor: Colors.blue, // <-- Splash color + ), + child: Icon( + iconData, + color: Colors.white, + size: MediaQuery.of(context).size.height * 0.05, + ), + ); + } +} diff --git a/lib/widgets/submit_form_button.dart b/lib/widgets/buttons/submit_form_button.dart similarity index 90% rename from lib/widgets/submit_form_button.dart rename to lib/widgets/buttons/submit_form_button.dart index f9cb784..f338339 100644 --- a/lib/widgets/submit_form_button.dart +++ b/lib/widgets/buttons/submit_form_button.dart @@ -2,6 +2,7 @@ import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/widgets/buttons/round_button_widget.dart'; class SubmitFormButton extends StatelessWidget { final Future Function() submitCallback; @@ -14,7 +15,7 @@ class SubmitFormButton extends StatelessWidget { PageProvider pageProvider = context.watch(); return Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), - child: ElevatedButton( + child: RoundIconButton( onPressed: () async { int success = await submitCallback(); if (context.mounted) { @@ -37,7 +38,7 @@ class SubmitFormButton extends StatelessWidget { } } }, - child: const Text('Speichern'), + iconData: Icons.check_outlined, ), ); } diff --git a/lib/widgets/buttons/text_icon_button.dart b/lib/widgets/buttons/text_icon_button.dart new file mode 100644 index 0000000..85534e2 --- /dev/null +++ b/lib/widgets/buttons/text_icon_button.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class TextIconButton extends StatelessWidget { + final String text; + final VoidCallback onPressed; + final IconData iconData; + + const TextIconButton( + {super.key, + required this.text, + required this.onPressed, + required this.iconData}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 0.4, + child: FloatingActionButton.extended( + label: Text(text), + backgroundColor: Theme.of(context).colorScheme.primary, + icon: Icon( + iconData, + size: 24.0, + ), + onPressed: onPressed, + ), + ); + } +} diff --git a/lib/widgets/timer_button.dart b/lib/widgets/buttons/timer_button.dart similarity index 100% rename from lib/widgets/timer_button.dart rename to lib/widgets/buttons/timer_button.dart diff --git a/lib/widgets/entry_detail_title.dart b/lib/widgets/entry_detail_title.dart new file mode 100644 index 0000000..110e38f --- /dev/null +++ b/lib/widgets/entry_detail_title.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class EntryDetailTitle extends StatelessWidget { + final DateTime date; + final String entryData; + + const EntryDetailTitle( + {super.key, required this.date, required this.entryData}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + DateFormat.MMMd('de').format(date), + style: + const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + Flexible( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Text( + entryData, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), + ))) + ], + ); + } +} diff --git a/lib/widgets/entry_detail_widget.dart b/lib/widgets/entry_detail_widget.dart new file mode 100644 index 0000000..0f7a70e --- /dev/null +++ b/lib/widgets/entry_detail_widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/widgets/entry_detail_title.dart'; + +class EntryDetail extends StatelessWidget { + final DateTime date; + final String entryData; + final String? entryComment; + final IconData iconData; + + const EntryDetail( + {super.key, + required this.date, + required this.entryData, + required this.iconData, + required this.entryComment}); + + @override + Widget build(BuildContext context) { + final Icon icon = Icon(iconData, color: Colors.white); + final ShapeBorder shape = RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ); + final Color color = Theme.of(context).colorScheme.primary.withOpacity(0.8); + final Widget title = EntryDetailTitle(date: date, entryData: entryData); + return entryComment != null + ? ExpansionTile( + iconColor: Colors.white, + collapsedIconColor: Colors.white, + collapsedShape: shape, + shape: shape, + leading: icon, + title: title, + collapsedBackgroundColor: color, + backgroundColor: + Theme.of(context).colorScheme.secondary.withOpacity(0.8), + children: entryComment != null + ? [ + Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 10, 10), + child: Text( + entryComment ?? '', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold), + )) + ]) + ] + : [], + ) + : ListTile( + shape: shape, + leading: icon, + title: title, + tileColor: color, + ); + } +} diff --git a/lib/widgets/history_list_widget.dart b/lib/widgets/history_list_widget.dart new file mode 100644 index 0000000..de434e1 --- /dev/null +++ b/lib/widgets/history_list_widget.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/widgets/entry_detail_widget.dart'; + +class HistoryList extends StatelessWidget { + final List history; + final DateTime Function(T) dateSelector; + final String Function(T) entryDataSelector; + final IconData Function(T)? iconDataSelector; + final String Function(T)? entryCommentSelector; + final IconData? icon; + + const HistoryList( + {super.key, + required this.history, + required this.dateSelector, + required this.entryDataSelector, + this.iconDataSelector, + this.icon, + this.entryCommentSelector}); + + IconData _getIcon(T entry) { + if (icon != null) { + return icon!; + } else if (iconDataSelector != null) { + return iconDataSelector!(entry); + } + return Icons.circle; + } + + String? _getComment(T entry) { + if (entryCommentSelector != null) { + return entryCommentSelector!(entry); + } + return null; + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: ListView( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), + children: history.map((T entry) { + return Padding( + padding: const EdgeInsets.only(bottom: 5), + child: EntryDetail( + date: dateSelector(entry), + entryData: entryDataSelector(entry), + entryComment: _getComment(entry), + iconData: _getIcon(entry))); + }).toList())); + } +} diff --git a/lib/widgets/line_chart_widget.dart b/lib/widgets/line_chart_widget.dart new file mode 100644 index 0000000..de24754 --- /dev/null +++ b/lib/widgets/line_chart_widget.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +class LineChart extends StatelessWidget { + final List> series; + const LineChart({ + super.key, + required this.series, + }); + + @override + Widget build(BuildContext context) { + SettingsProvider settingsProvider = context.watch(); + + return SfCartesianChart( + primaryXAxis: DateTimeAxis( + minimum: settingsProvider.settings?.startedAt, + maximum: settingsProvider.settings?.startedAt + .add(const Duration(days: 7 * 6)), + interval: 7, + dateFormat: DateFormat.Md('de'), + ), + primaryYAxis: + NumericAxis(isVisible: false, minimum: 0, maximum: 100, interval: 20), + series: series, + ); + } +} diff --git a/lib/widgets/mood_view.dart b/lib/widgets/mood_view.dart deleted file mode 100644 index 14884b9..0000000 --- a/lib/widgets/mood_view.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../models/mood.dart'; -import '../providers/tasks_provider.dart'; - -class MoodView extends StatelessWidget { - const MoodView({super.key}); - - @override - Widget build(BuildContext context) { - var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SfCartesianChart( - primaryXAxis: DateTimeAxis(), - series: [ - LineSeries( - dataSource: tasksModel.moodHistory, - xValueMapper: (Mood value, _) => value.date, - yValueMapper: (Mood value, _) => value.moodValue) - ], - ), - Column( - children: tasksModel.moodHistory.map((mood) { - return Text('${mood.date}: ${mood.moodValue}'); - }).toList()) - ], - ); - } -} diff --git a/lib/widgets/missing_config_popup.dart b/lib/widgets/popup/missing_config_popup.dart similarity index 100% rename from lib/widgets/missing_config_popup.dart rename to lib/widgets/popup/missing_config_popup.dart diff --git a/lib/widgets/popup_for_start_and_stop.dart b/lib/widgets/popup/popup_for_start_and_stop.dart similarity index 100% rename from lib/widgets/popup_for_start_and_stop.dart rename to lib/widgets/popup/popup_for_start_and_stop.dart diff --git a/lib/widgets/popup/popup_for_task_done.dart b/lib/widgets/popup/popup_for_task_done.dart new file mode 100644 index 0000000..4565aca --- /dev/null +++ b/lib/widgets/popup/popup_for_task_done.dart @@ -0,0 +1,45 @@ +import 'package:awesome_dialog/awesome_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/services/date_service.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; +import 'package:smoke_cess_app/widgets/timer_widget.dart'; + +import '../../providers/timer_provider.dart'; + +void showTaskDonePopup(BuildContext context, Pages page) async { + Duration duration = await getTimeTill(page); + if (context.mounted) { + AwesomeDialog( + context: context, + dialogType: DialogType.info, + body: Column( + children: [ + Text( + '${pages[page]?['title']} schon eingegeben', + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + const SizedBox( + height: 10, + ), + const Text( + 'Nächste Abfrage in', + ), + const SizedBox( + height: 8, + ), + ChangeNotifierProvider( + create: (context) => TimerProvider(), + builder: (context, child) { + TimerProvider timerProvider = context.read(); + timerProvider.startTimer(duration); + return TimerWidget(duration: duration); + }, + ), + const SizedBox( + height: 15, + ), + ], + )).show(); + } +} diff --git a/lib/widgets/popup_for_task_done.dart b/lib/widgets/popup_for_task_done.dart deleted file mode 100644 index fbaa65d..0000000 --- a/lib/widgets/popup_for_task_done.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/providers/timer_provider.dart'; -import 'package:smoke_cess_app/widgets/timer_widget.dart'; -import '../services/date_service.dart'; -import '../services/pages_service.dart'; - -void showTaskDonePopup(BuildContext context, Pages page) async { - Duration duration = await getTimeTill(page); - if (context.mounted) { - await showDialog( - context: context, - builder: (BuildContext context) { - return ChangeNotifierProvider( - create: (context) => TimerProvider(), - child: TaskDonePopup( - duration: duration, - ), - ); - }, - ); - } -} - -class TaskDonePopup extends StatelessWidget { - final Duration duration; - const TaskDonePopup({super.key, required this.duration}); - - @override - Widget build(BuildContext context) { - TimerProvider timerProvider = context.read(); - timerProvider.startTimer(duration); - return AlertDialog( - title: const Text('Schon gemacht'), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Nächstes mal wieder:'), - TimerWidget(duration: duration) - ])); - } -} diff --git a/lib/widgets/relapse_view.dart b/lib/widgets/relapse_view.dart deleted file mode 100644 index 48c9829..0000000 --- a/lib/widgets/relapse_view.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import '../providers/tasks_provider.dart'; - -class RelapseView extends StatelessWidget { - const RelapseView({super.key}); - - @override - Widget build(BuildContext context) { - var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: tasksModel.relapseHistory.map((relapse) { - return Text('${relapse.date}: ${relapse.category}'); - }).toList()); - } -} diff --git a/lib/widgets/scanner.dart b/lib/widgets/scanner.dart index b635d67..c292bf4 100644 --- a/lib/widgets/scanner.dart +++ b/lib/widgets/scanner.dart @@ -5,80 +5,81 @@ import 'package:provider/provider.dart'; import 'package:smoke_cess_app/models/settings.dart'; import 'package:smoke_cess_app/services/json_service.dart'; import 'package:smoke_cess_app/services/settings_service.dart'; +import 'package:smoke_cess_app/widgets/buttons/text_icon_button.dart'; import '../providers/settings_provider.dart'; import '../services/notification_service.dart'; -class MyScanner extends StatefulWidget { +class MyScanner extends StatelessWidget { const MyScanner({super.key}); @override - State createState() => MyScannerState(); -} + Widget build(BuildContext context) { + SettingsProvider settingsProvider = context.watch(); -class MyScannerState extends State { - bool scanning = false; + void handleSucces(String? rawValue) { + String qrText = rawValue!; + Map json = stringToJSON(qrText); + Settings settings = Settings.fromJson(json); + saveSettings(settings); + settingsProvider.initSettings(); + NotificationService().setAllNotifications(); + settingsProvider.scanning = false; - void handleSucces(String? rawValue) { - String qrText = rawValue!; - Map json = stringToJSON(qrText); - Settings settings = Settings.fromJson(json); - saveSettings(settings); - var settingsModel = context.read(); - settingsModel.initSettings(); - NotificationService().setAllNotifications(); - setState(() { - scanning = false; AwesomeDialog( context: context, dialogType: DialogType.success, title: 'Geschafft', desc: 'Der Code wurde erfolgreich gescannt!', ).show(); - }); - } + } + + void handleError() { + settingsProvider.scanning = false; - void handleError() { - setState(() { - scanning = false; AwesomeDialog( context: context, dialogType: DialogType.error, title: 'Fehler', desc: 'Der QR-Code war fehlerhaft!', ).show(); - }); - } - - void onDetect(capture) { - try { - final List barcodes = capture.barcodes; - for (final barcode in barcodes) { - if (barcode.rawValue != null) { - return handleSucces(barcode.rawValue); - } - } - } catch (e) { - handleError(); } - } - @override - Widget build(BuildContext context) { - return scanning + void onDetect(capture) { + try { + final List barcodes = capture.barcodes; + for (final barcode in barcodes) { + if (barcode.rawValue != null) { + return handleSucces(barcode.rawValue); + } + } + } catch (e) { + handleError(); + } + } + + return settingsProvider.scanning ? Expanded( - child: MobileScanner( + child: Stack( + alignment: Alignment.center, + children: [ + MobileScanner( fit: BoxFit.contain, controller: MobileScannerController( detectionTimeoutMs: 2000, ), - onDetect: onDetect)) - : ElevatedButton( - style: ElevatedButton.styleFrom( - textStyle: const TextStyle(fontSize: 20)), - onPressed: () { - setState(() => scanning = true); - }, - child: const Text('Scan QR Code'), - ); + onDetect: onDetect, + ), + ClipRRect( + borderRadius: BorderRadius.circular(20), + child: Container( + height: MediaQuery.of(context).size.height / 3, + width: MediaQuery.of(context).size.width * 0.8, + color: Colors.white.withOpacity(0.4))), + ], + )) + : TextIconButton( + text: "Scan", + onPressed: () => settingsProvider.scanning = true, + iconData: Icons.qr_code_scanner_outlined); } } diff --git a/lib/widgets/mood_form.dart b/lib/widgets/view_form/mood_form.dart similarity index 78% rename from lib/widgets/mood_form.dart rename to lib/widgets/view_form/mood_form.dart index e43e97d..588d542 100644 --- a/lib/widgets/mood_form.dart +++ b/lib/widgets/view_form/mood_form.dart @@ -3,11 +3,10 @@ import 'package:provider/provider.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:smoke_cess_app/services/pages_service.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/buttons/submit_form_button.dart'; import 'package:smoke_cess_app/widgets/text_formfield.dart'; - -import '../providers/input_provider.dart'; -import 'elevated_card.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/widgets/elevated_card.dart'; class MoodForm extends StatelessWidget { const MoodForm({super.key}); @@ -16,8 +15,8 @@ class MoodForm extends StatelessWidget { Widget build(BuildContext context) { var inputModel = context.watch(); var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, + return ListView( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), children: [ const ElevatedCard( title: 'Stimmungsbewertung', @@ -28,9 +27,6 @@ class MoodForm extends StatelessWidget { title: 'Beschreibe deine Stimmung', child: MyTextFormField('Beschreibe deine Stimmung'), ), - const SizedBox( - height: 80, - ), SubmitFormButton( submitCallback: inputModel.saveMood, updateTasks: () => tasksModel.setTaskDone(Pages.mood), diff --git a/lib/widgets/view_form/mood_view.dart b/lib/widgets/view_form/mood_view.dart new file mode 100644 index 0000000..3438f59 --- /dev/null +++ b/lib/widgets/view_form/mood_view.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/widgets/history_list_widget.dart'; +import 'package:smoke_cess_app/widgets/line_chart_widget.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:smoke_cess_app/models/mood.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; + +class MoodView extends StatelessWidget { + const MoodView({super.key}); + + @override + Widget build(BuildContext context) { + var tasksModel = context.watch(); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LineChart(series: [ + LineSeries( + dataSource: tasksModel.moodHistory, + xValueMapper: (Mood value, _) => value.date, + yValueMapper: (Mood value, _) => value.moodValue) + ]), + HistoryList( + history: tasksModel.moodHistory, + dateSelector: (Mood mood) => mood.date, + entryDataSelector: (Mood mood) => 'Stimmung: ${mood.moodValue}', + entryCommentSelector: (Mood mood) => 'Kommentar: ${mood.comment}', + iconDataSelector: (Mood mood) => mood.moodValue >= 50 + ? Icons.mood_outlined + : Icons.mood_bad_outlined, + ) + ], + ); + } +} diff --git a/lib/widgets/relapse_form.dart b/lib/widgets/view_form/relapse_form.dart similarity index 72% rename from lib/widgets/relapse_form.dart rename to lib/widgets/view_form/relapse_form.dart index 9451277..6131880 100644 --- a/lib/widgets/relapse_form.dart +++ b/lib/widgets/view_form/relapse_form.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:smoke_cess_app/widgets/drop_down.dart'; -import 'package:smoke_cess_app/widgets/submit_form_button.dart'; +import 'package:smoke_cess_app/widgets/buttons/submit_form_button.dart'; import 'package:smoke_cess_app/widgets/text_formfield.dart'; -import '../providers/input_provider.dart'; -import '../providers/settings_provider.dart'; -import '../services/pages_service.dart'; -import 'elevated_card.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; +import 'package:smoke_cess_app/widgets/elevated_card.dart'; class RelapseForm extends StatelessWidget { const RelapseForm({super.key}); @@ -17,8 +17,8 @@ class RelapseForm extends StatelessWidget { var inputModel = context.watch(); var settingsModel = context.watch(); var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, + return ListView( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), children: [ ElevatedCard( title: 'Rückfallkategorie', @@ -28,9 +28,6 @@ class RelapseForm extends StatelessWidget { title: 'Beschreibe deinen Rückfall', child: MyTextFormField('Beschreibe deinen Rückfall'), ), - const SizedBox( - height: 80, - ), SubmitFormButton( submitCallback: inputModel.saveRelapse, updateTasks: () => tasksModel.setTaskDone(Pages.mood), diff --git a/lib/widgets/view_form/relapse_view.dart b/lib/widgets/view_form/relapse_view.dart new file mode 100644 index 0000000..98c011b --- /dev/null +++ b/lib/widgets/view_form/relapse_view.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/models/relapse.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/widgets/history_list_widget.dart'; + +class RelapseView extends StatelessWidget { + const RelapseView({super.key}); + + @override + Widget build(BuildContext context) { + TasksProvider tasksModel = context.watch(); + return Column(children: [ + HistoryList( + history: tasksModel.relapseHistory, + dateSelector: (Relapse relapse) => relapse.date, + entryDataSelector: (Relapse relapse) => relapse.category, + entryCommentSelector: (Relapse relapse) => + 'Kommentar: ${relapse.comment}', + icon: Icons.smoke_free_outlined, + ) + ]); + } +} diff --git a/lib/widgets/sleep_form.dart b/lib/widgets/view_form/sleep_form.dart similarity index 84% rename from lib/widgets/sleep_form.dart rename to lib/widgets/view_form/sleep_form.dart index 4996da7..a9998ec 100644 --- a/lib/widgets/sleep_form.dart +++ b/lib/widgets/view_form/sleep_form.dart @@ -3,12 +3,12 @@ import 'package:provider/provider.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:smoke_cess_app/widgets/elevated_card.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/buttons/submit_form_button.dart'; import 'package:smoke_cess_app/widgets/text_formfield.dart'; import 'package:smoke_cess_app/widgets/timepicker.dart'; -import '../providers/input_provider.dart'; -import '../services/pages_service.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; class SleepForm extends StatelessWidget { const SleepForm({Key? key}) : super(key: key); @@ -18,8 +18,8 @@ class SleepForm extends StatelessWidget { InputProvider inputModel = context.watch(); TasksProvider tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, + return ListView( + padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), children: [ const ElevatedCard( title: 'Einschlafzeit', @@ -40,9 +40,6 @@ class SleepForm extends StatelessWidget { title: 'Schlafbeschreibung', child: MyTextFormField('Beschreibe deinen Schlaf'), ), - const SizedBox( - height: 80, - ), SubmitFormButton( submitCallback: () => inputModel.saveSleep(SleepTimes.wokeUpAt, SleepTimes.sleptAt), diff --git a/lib/widgets/sleep_view.dart b/lib/widgets/view_form/sleep_view.dart similarity index 50% rename from lib/widgets/sleep_view.dart rename to lib/widgets/view_form/sleep_view.dart index 90a005c..e53837c 100644 --- a/lib/widgets/sleep_view.dart +++ b/lib/widgets/view_form/sleep_view.dart @@ -1,31 +1,36 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smoke_cess_app/models/sleep.dart'; +import 'package:smoke_cess_app/widgets/history_list_widget.dart'; +import 'package:smoke_cess_app/widgets/line_chart_widget.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; -import '../providers/tasks_provider.dart'; class SleepView extends StatelessWidget { const SleepView({super.key}); @override Widget build(BuildContext context) { - var tasksModel = context.watch(); + TasksProvider tasksModel = context.watch(); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - SfCartesianChart( - primaryXAxis: DateTimeAxis(), - series: [ + LineChart( + series: [ LineSeries( dataSource: tasksModel.sleepHistory, xValueMapper: (Sleep value, _) => value.date, yValueMapper: (Sleep value, _) => value.sleepQualitiyValue) ], ), - Column( - children: tasksModel.sleepHistory.map((sleep) { - return Text('${sleep.date}: ${sleep.sleepQualitiyValue}'); - }).toList()) + HistoryList( + history: tasksModel.sleepHistory, + dateSelector: (Sleep sleep) => sleep.date, + entryDataSelector: (Sleep sleep) => + '${sleep.sleepDuration.hour}:${sleep.sleepDuration.minute}', + entryCommentSelector: (Sleep sleep) => 'Kommentar: ${sleep.comment}', + icon: Icons.bedtime_outlined, + ) ], ); } diff --git a/lib/widgets/view_form/view_form_page.dart b/lib/widgets/view_form/view_form_page.dart new file mode 100644 index 0000000..828b9af --- /dev/null +++ b/lib/widgets/view_form/view_form_page.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; +import 'package:smoke_cess_app/widgets/buttons/round_button_widget.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/widgets/popup/popup_for_task_done.dart'; + +class ViewFormPage extends StatelessWidget { + final Widget form; + final Widget view; + final Pages page; + const ViewFormPage( + {super.key, required this.form, required this.view, required this.page}); + + @override + Widget build(BuildContext context) { + final height = MediaQuery.of(context).size.height; + PageProvider pageProvider = context.watch(); + TasksProvider tasksProvider = context.watch(); + return Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Expanded( + child: Center( + child: pageProvider.showForm + ? ChangeNotifierProvider( + create: (context) => InputProvider(), + child: form, + ) + : view, + )), + if (!pageProvider.showForm) + Container( + margin: EdgeInsets.symmetric(vertical: height * 0.02), + child: RoundIconButton( + iconData: Icons.add_outlined, + onPressed: tasksProvider.tasks[page] ?? true + ? () => pageProvider.swap() + : () => showTaskDonePopup(context, page), + ), + ) + ]); + } +} diff --git a/lib/widgets/workout_form.dart b/lib/widgets/view_form/workout_form.dart similarity index 79% rename from lib/widgets/workout_form.dart rename to lib/widgets/view_form/workout_form.dart index 3e75882..8a678c1 100644 --- a/lib/widgets/workout_form.dart +++ b/lib/widgets/view_form/workout_form.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smoke_cess_app/providers/audio_provider.dart'; -import '../providers/timer_provider.dart'; -import '../providers/workout_provider.dart'; -import 'mute_button.dart'; -import 'workout_timer_widget.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; +import 'package:smoke_cess_app/providers/workout_provider.dart'; +import 'package:smoke_cess_app/widgets/buttons/mute_button.dart'; +import 'package:smoke_cess_app/widgets/workout_timer_widget.dart'; class WorkoutForm extends StatelessWidget { WorkoutForm({super.key}); diff --git a/lib/widgets/view_form/workout_view.dart b/lib/widgets/view_form/workout_view.dart new file mode 100644 index 0000000..3e904e6 --- /dev/null +++ b/lib/widgets/view_form/workout_view.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/models/workout.dart'; +import 'package:smoke_cess_app/widgets/history_list_widget.dart'; +import 'package:smoke_cess_app/widgets/line_chart_widget.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; + +class WorkoutView extends StatelessWidget { + const WorkoutView({super.key}); + + @override + Widget build(BuildContext context) { + TasksProvider tasksModel = context.watch(); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + LineChart(series: [ + LineSeries( + dataSource: tasksModel.workoutHistory, + xValueMapper: (Workout value, _) => value.date, + yValueMapper: (Workout value, _) => value.motivationBefore), + LineSeries( + dataSource: tasksModel.workoutHistory, + xValueMapper: (Workout value, _) => value.date, + yValueMapper: (Workout value, _) => value.motivationAfter) + ]), + HistoryList( + history: tasksModel.workoutHistory, + dateSelector: (Workout workout) => workout.date, + entryDataSelector: (Workout workout) => + '${workout.motivationBefore} : ${workout.motivationAfter}', + icon: Icons.sports_score_outlined, + ) + ], + ); + } +} diff --git a/lib/widgets/view_form_page.dart b/lib/widgets/view_form_page.dart deleted file mode 100644 index 57b9271..0000000 --- a/lib/widgets/view_form_page.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/services/pages_service.dart'; -import '../providers/input_provider.dart'; -import '../providers/page_provider.dart'; -import '../providers/tasks_provider.dart'; -import 'popup_for_task_done.dart'; - -class ViewFormPage extends StatelessWidget { - final Widget form; - final Widget view; - final Pages page; - const ViewFormPage( - {super.key, required this.form, required this.view, required this.page}); - - @override - Widget build(BuildContext context) { - PageProvider pageProvider = context.watch(); - TasksProvider tasksProvider = context.watch(); - return Wrap(children: [ - Align( - alignment: Alignment.topLeft, - child: IconButton( - icon: pageProvider.showForm - ? const Icon(Icons.arrow_back, color: Colors.black) - : const Icon(Icons.add_outlined, color: Colors.black), - onPressed: tasksProvider.tasks[page] ?? true - ? pageProvider.swap - : () => showTaskDonePopup(context, page), - ), - ), - pageProvider.showForm - ? Center( - child: ChangeNotifierProvider( - create: (context) => InputProvider(), - child: form, - )) - : Center(child: view) - ]); - } -} diff --git a/lib/widgets/workout_timer_widget.dart b/lib/widgets/workout_timer_widget.dart index 4f1b64c..51e7f0c 100644 --- a/lib/widgets/workout_timer_widget.dart +++ b/lib/widgets/workout_timer_widget.dart @@ -4,13 +4,13 @@ import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../providers/page_provider.dart'; -import '../providers/tasks_provider.dart'; -import '../providers/workout_provider.dart'; -import '../services/pages_service.dart'; -import '../widgets/timer_widget.dart'; -import '../providers/timer_provider.dart'; -import 'popup_for_start_and_stop.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/providers/workout_provider.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; +import 'package:smoke_cess_app/widgets/timer_widget.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; +import 'package:smoke_cess_app/widgets/popup/popup_for_start_and_stop.dart'; class WorkoutTimerWidget extends StatelessWidget { const WorkoutTimerWidget({super.key}); @@ -74,10 +74,12 @@ class WorkoutTimerWidget extends StatelessWidget { width: 100, child: CircularProgressIndicator( color: workoutProvider.currentPhaseColor, - value: (workoutProvider.currentPhaseDuration.inSeconds - .toDouble() - - timerProvider.elapsedSeconds) / - workoutProvider.currentPhaseDuration.inSeconds)), + value: + (workoutProvider.currentPhaseDuration.inMilliseconds - + timerProvider.elapsedMilliseconds) + .toDouble() / + workoutProvider.currentPhaseDuration.inMilliseconds + .toDouble())), TimerWidget(duration: workoutProvider.currentPhaseDuration), ], ), diff --git a/lib/widgets/workout_view.dart b/lib/widgets/workout_view.dart deleted file mode 100644 index d0deb4d..0000000 --- a/lib/widgets/workout_view.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/models/workout.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; -import '../providers/tasks_provider.dart'; - -class WorkoutView extends StatelessWidget { - const WorkoutView({super.key}); - - @override - Widget build(BuildContext context) { - var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SfCartesianChart( - primaryXAxis: DateTimeAxis(), - series: [ - LineSeries( - dataSource: tasksModel.workoutHistory, - xValueMapper: (Workout value, _) => value.date, - yValueMapper: (Workout value, _) => value.motivationBefore), - LineSeries( - dataSource: tasksModel.workoutHistory, - xValueMapper: (Workout value, _) => value.date, - yValueMapper: (Workout value, _) => value.motivationAfter) - ], - ), - ], - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 690527f..ac1fafe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: http: ^0.13.5 syncfusion_flutter_charts: ^20.4.52 cupertino_icons: ^1.0.2 + intl: ^0.18.0 dev_dependencies: flutter_test: