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/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..991de20 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; @@ -13,6 +14,7 @@ class Sleep implements DatabaseRecord { DateTime get date => _date; 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/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/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..42a9180 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 = ''; @@ -12,3 +14,12 @@ String formatTime(int seconds) { 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..14efe2d --- /dev/null +++ b/lib/widgets/buttons/round_button_widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class RoundAddButton extends StatelessWidget { + final VoidCallback onPressed; + final IconData iconData; + + const RoundAddButton( + {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 100% rename from lib/widgets/submit_form_button.dart rename to lib/widgets/buttons/submit_form_button.dart 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_widget.dart b/lib/widgets/entry_detail_widget.dart new file mode 100644 index 0000000..10afacc --- /dev/null +++ b/lib/widgets/entry_detail_widget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class EntryDetail extends StatelessWidget { + final DateTime date; + final String entryData; + final IconData icon; + + const EntryDetail( + {super.key, + required this.date, + required this.entryData, + required this.icon}); + + @override + Widget build(BuildContext context) { + return Card( + child: ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + leading: Icon(icon, color: Colors.white), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + DateFormat.MMMd('de').format(date), + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), + ), + Text( + entryData, + style: const TextStyle( + color: Colors.white, fontWeight: FontWeight.bold), + ) + ], + ), + tileColor: Theme.of(context).colorScheme.primary.withOpacity(0.8), + )); + } +} diff --git a/lib/widgets/history_list_widget.dart b/lib/widgets/history_list_widget.dart new file mode 100644 index 0000000..ff63e49 --- /dev/null +++ b/lib/widgets/history_list_widget.dart @@ -0,0 +1,39 @@ +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 IconData? icon; + + const HistoryList( + {super.key, + required this.history, + required this.dateSelector, + required this.entryDataSelector, + this.iconDataSelector, + this.icon}); + + IconData _getIcon(T entry) { + if (icon != null) { + return icon!; + } else if (iconDataSelector != null) { + return iconDataSelector!(entry); + } + return Icons.circle; + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: ListView( + children: history.map((T entry) { + return EntryDetail( + date: dateSelector(entry), + entryData: entryDataSelector(entry), + icon: _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_for_task_done.dart b/lib/widgets/popup/popup_for_task_done.dart similarity index 91% rename from lib/widgets/popup_for_task_done.dart rename to lib/widgets/popup/popup_for_task_done.dart index fbaa65d..cf7f7ed 100644 --- a/lib/widgets/popup_for_task_done.dart +++ b/lib/widgets/popup/popup_for_task_done.dart @@ -2,8 +2,8 @@ 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'; +import 'package:smoke_cess_app/services/date_service.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; void showTaskDonePopup(BuildContext context, Pages page) async { Duration duration = await getTimeTill(page); 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/mood_form.dart b/lib/widgets/view_form/mood_form.dart similarity index 83% rename from lib/widgets/mood_form.dart rename to lib/widgets/view_form/mood_form.dart index e43e97d..9985b9b 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,7 @@ class MoodForm extends StatelessWidget { Widget build(BuildContext context) { var inputModel = context.watch(); var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, + return ListView( children: [ const ElevatedCard( title: 'Stimmungsbewertung', diff --git a/lib/widgets/view_form/mood_view.dart b/lib/widgets/view_form/mood_view.dart new file mode 100644 index 0000000..92017f0 --- /dev/null +++ b/lib/widgets/view_form/mood_view.dart @@ -0,0 +1,35 @@ +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}', + 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 76% rename from lib/widgets/relapse_form.dart rename to lib/widgets/view_form/relapse_form.dart index 9451277..3d1a1ab 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,7 @@ class RelapseForm extends StatelessWidget { var inputModel = context.watch(); var settingsModel = context.watch(); var tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, + return ListView( children: [ ElevatedCard( title: 'Rückfallkategorie', diff --git a/lib/widgets/view_form/relapse_view.dart b/lib/widgets/view_form/relapse_view.dart new file mode 100644 index 0000000..92072f4 --- /dev/null +++ b/lib/widgets/view_form/relapse_view.dart @@ -0,0 +1,20 @@ +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 HistoryList( + history: tasksModel.relapseHistory, + dateSelector: (Relapse relapse) => relapse.date, + entryDataSelector: (Relapse relapse) => 'Grund: ${relapse.category}', + icon: Icons.smoke_free_outlined, + ); + } +} diff --git a/lib/widgets/sleep_form.dart b/lib/widgets/view_form/sleep_form.dart similarity index 87% rename from lib/widgets/sleep_form.dart rename to lib/widgets/view_form/sleep_form.dart index 4996da7..bb01e37 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,7 @@ class SleepForm extends StatelessWidget { InputProvider inputModel = context.watch(); TasksProvider tasksModel = context.watch(); - return Column( - mainAxisAlignment: MainAxisAlignment.center, + return ListView( children: [ const ElevatedCard( title: 'Einschlafzeit', diff --git a/lib/widgets/sleep_view.dart b/lib/widgets/view_form/sleep_view.dart similarity index 53% rename from lib/widgets/sleep_view.dart rename to lib/widgets/view_form/sleep_view.dart index 90a005c..4e1e90d 100644 --- a/lib/widgets/sleep_view.dart +++ b/lib/widgets/view_form/sleep_view.dart @@ -1,31 +1,35 @@ 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}', + 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..0b348b6 --- /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: RoundAddButton( + 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: