Merge branch 'main' into testing_branch

main
Kai Mannweiler 2023-03-06 18:21:30 +01:00
commit b7d5f1d51f
50 changed files with 669 additions and 345 deletions

View File

@ -14,3 +14,13 @@ Die App lässt sich als Android- und iOS App ausführen und zu Debugzwecken eben
## App bedienen ## 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. 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 <code>bool useLocalConfig</code> 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

View File

@ -70,5 +70,7 @@ flutter {
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 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' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
} }

View File

@ -3,5 +3,8 @@ library app.globals;
import 'package:smoke_cess_app/mock/db_mock.dart'; import 'package:smoke_cess_app/mock/db_mock.dart';
import 'package:smoke_cess_app/services/database_service.dart'; import 'package:smoke_cess_app/services/database_service.dart';
DatabaseService databaseService = DatabaseMock(); DatabaseService databaseService = DatabaseMock();
// DatabaseService databaseService = DatabaseService.instance; // DatabaseService databaseService = DatabaseService.instance;
// set this to read settings from local json file instead of scanning a qr code
bool useLocalConfig = false;

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/pages/main_page.dart'; import 'package:smoke_cess_app/pages/main_page.dart';
import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart';
@ -14,6 +15,7 @@ void main() {
//init database //init database
globals.databaseService; globals.databaseService;
tz.initializeTimeZones(); tz.initializeTimeZones();
initializeDateFormatting('de');
NotificationService().initNotification(); NotificationService().initNotification();
runApp(const MyApp()); runApp(const MyApp());
} }

View File

@ -9,6 +9,7 @@ class Mood implements DatabaseRecord {
DateTime get date => _date; DateTime get date => _date;
int get moodValue => _moodValue; int get moodValue => _moodValue;
String get comment => _comment;
@override @override
factory Mood.fromDatabase(Map<String, dynamic> map) { factory Mood.fromDatabase(Map<String, dynamic> map) {

View File

@ -9,6 +9,7 @@ class Relapse implements DatabaseRecord {
String get category => _category; String get category => _category;
DateTime get date => _date; DateTime get date => _date;
String get comment => _comment;
@override @override
factory Relapse.fromDatabase(Map<String, dynamic> map) { factory Relapse.fromDatabase(Map<String, dynamic> map) {

View File

@ -6,9 +6,10 @@ class Settings {
final QueryConfig? moodQuery; final QueryConfig? moodQuery;
final QueryConfig? sleepQuery; final QueryConfig? sleepQuery;
final TimeConfig? chessTime; final TimeConfig? chessTime;
final DateTime startedAt;
Settings(this.group, this.relapseCategories, this.moodQuery, this.sleepQuery, Settings(this.group, this.relapseCategories, this.moodQuery, this.sleepQuery,
this.chessTime); this.chessTime, this.startedAt);
Settings.fromJson(Map<String, dynamic> json) Settings.fromJson(Map<String, dynamic> json)
: group = json['group'] as int, : group = json['group'] as int,
@ -17,7 +18,8 @@ class Settings {
sleepQuery = QueryConfig.fromJson(json['sleep_query']), sleepQuery = QueryConfig.fromJson(json['sleep_query']),
chessTime = json['chess_time'] != null chessTime = json['chess_time'] != null
? TimeConfig.fromJson(json['chess_time']) ? TimeConfig.fromJson(json['chess_time'])
: null; : null,
startedAt = DateTime.parse(json['startedAt']);
} }
class QueryConfig { class QueryConfig {

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/interface/db_record.dart'; import 'package:smoke_cess_app/interface/db_record.dart';
import 'package:smoke_cess_app/utils/timer_util.dart';
class Sleep implements DatabaseRecord { class Sleep implements DatabaseRecord {
final int _sleepQualityValue; final int _sleepQualityValue;
@ -12,7 +13,9 @@ class Sleep implements DatabaseRecord {
this._wokeUpAt); this._wokeUpAt);
DateTime get date => _date; DateTime get date => _date;
String get comment => _comment;
int get sleepQualitiyValue => _sleepQualityValue; int get sleepQualitiyValue => _sleepQualityValue;
TimeOfDay get sleepDuration => _sleptAt.durationBetween(_wokeUpAt);
@override @override
factory Sleep.fromDatabase(Map<String, dynamic> map) { factory Sleep.fromDatabase(Map<String, dynamic> map) {

View File

@ -1,9 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/services/pages_service.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/view_form/workout_form.dart';
import 'package:smoke_cess_app/widgets/workout_view.dart'; import 'package:smoke_cess_app/widgets/view_form/workout_view.dart';
import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart';
import '../widgets/view_form_page.dart';
class IntervalTimerPage extends StatelessWidget { class IntervalTimerPage extends StatelessWidget {
const IntervalTimerPage({super.key}); const IntervalTimerPage({super.key});

View File

@ -8,52 +8,49 @@ import 'package:smoke_cess_app/providers/settings_provider.dart';
import '../widgets/todo_icon.dart'; import '../widgets/todo_icon.dart';
class MyHomePage extends StatefulWidget { class MyHomePage extends StatelessWidget {
const MyHomePage({super.key}); const MyHomePage({super.key});
@override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 4;
bool _isConfigured = false;
void _onItemTapped(int index) {
PageProvider pageProvider = context.read<PageProvider>();
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var settingsModel = context.watch<SettingsProvider>(); SettingsProvider settingsProvider = context.watch<SettingsProvider>();
var tasksModel = context.watch<TasksProvider>(); TasksProvider tasksProvider = context.watch<TasksProvider>();
_isConfigured = settingsModel.initialized; PageProvider pageProvider = context.watch<PageProvider>();
bool isConfigured = settingsProvider.initialized;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Row(
'${pages.values.elementAt(_selectedIndex)['title']} ${_isConfigured ? "Gruppe ${settingsModel.settings?.group}" : ""}')), children: [
body: SingleChildScrollView( Stack(
child: pages.values.elementAt(_selectedIndex)['page'], 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( bottomNavigationBar: NavigationBar(
onDestinationSelected: _onItemTapped, onDestinationSelected: isConfigured
selectedIndex: _selectedIndex, ? 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) { destinations: pages.keys.map((key) {
return NavigationDestination( return NavigationDestination(
icon: tasksModel.tasks[key] ?? false icon: tasksProvider.tasks[key] ?? false
? MyToDoIcon(pages[key]?['icon']) ? MyToDoIcon(pages[key]?['icon'])
: pages[key]!['icon'], : pages[key]!['icon'],
label: pages[key]?['title']); label: pages[key]?['title']);

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/services/pages_service.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/view_form/mood_form.dart';
import 'package:smoke_cess_app/widgets/mood_view.dart'; import 'package:smoke_cess_app/widgets/view_form/mood_view.dart';
import 'package:smoke_cess_app/widgets/view_form_page.dart'; import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart';
class MoodPage extends StatelessWidget { class MoodPage extends StatelessWidget {
const MoodPage({super.key}); const MoodPage({super.key});

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/services/pages_service.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/view_form/relapse_form.dart';
import 'package:smoke_cess_app/widgets/relapse_view.dart'; import 'package:smoke_cess_app/widgets/view_form/relapse_view.dart';
import '../widgets/view_form_page.dart'; import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart';
class RelapsePage extends StatelessWidget { class RelapsePage extends StatelessWidget {
const RelapsePage({super.key}); const RelapsePage({super.key});

View File

@ -4,8 +4,10 @@ import 'package:provider/provider.dart';
import 'package:smoke_cess_app/services/export_service.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/settings_service.dart';
import 'package:smoke_cess_app/services/notification_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 'package:smoke_cess_app/widgets/scanner.dart';
import '../providers/settings_provider.dart'; import '../providers/settings_provider.dart';
import '../globals.dart' as globals;
class ScannerPage extends StatelessWidget { class ScannerPage extends StatelessWidget {
const ScannerPage({super.key}); const ScannerPage({super.key});
@ -16,7 +18,7 @@ class ScannerPage extends StatelessWidget {
} }
void loadJSON(BuildContext context) async { void loadJSON(BuildContext context) async {
var settingsModel = context.read<SettingsProvider>(); SettingsProvider settingsModel = context.read<SettingsProvider>();
await loadSettingsFromLocalJSON(); await loadSettingsFromLocalJSON();
settingsModel.initSettings(); settingsModel.initSettings();
NotificationService().setAllNotifications(); NotificationService().setAllNotifications();
@ -32,25 +34,26 @@ class ScannerPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const MyScanner(), const MyScanner(),
const SizedBox(height: 30), const SizedBox(height: 30),
ElevatedButton( if (!settingsProvider.scanning)
style: ElevatedButton.styleFrom( TextIconButton(
textStyle: const TextStyle(fontSize: 20)), text: 'Export',
onPressed: () => loadJSON(context), onPressed: ExportService().exportData,
child: const Text('Read JSON'), iconData: Icons.upload),
), if (globals.useLocalConfig && !settingsProvider.scanning)
const SizedBox(height: 30), ElevatedButton(
ElevatedButton( style: ElevatedButton.styleFrom(
style: ElevatedButton.styleFrom( textStyle: const TextStyle(fontSize: 20)),
textStyle: const TextStyle(fontSize: 20)), onPressed: () => loadJSON(context),
onPressed: export, child: const Text('Read JSON'),
child: const Text('Export'), ),
)
], ],
)); ));
} }

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/services/pages_service.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/view_form/sleep_form.dart';
import 'package:smoke_cess_app/widgets/sleep_view.dart'; import 'package:smoke_cess_app/widgets/view_form/sleep_view.dart';
import 'package:smoke_cess_app/widgets/view_form_page.dart'; import 'package:smoke_cess_app/widgets/view_form/view_form_page.dart';
class SleepPage extends StatelessWidget { class SleepPage extends StatelessWidget {
const SleepPage({super.key}); const SleepPage({super.key});

View File

@ -1,10 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
class PageProvider extends ChangeNotifier { class PageProvider extends ChangeNotifier {
bool showForm = false; bool showForm = false;
Pages _currentPage = Pages.settings;
void swap() { void swap() {
showForm = !showForm; showForm = !showForm;
notifyListeners(); notifyListeners();
} }
Map<String, dynamic> get currentPageData => pages[_currentPage]!;
int get currentPageIndex => _currentPage.index;
void setCurrentPage(int index) {
showForm = false;
_currentPage = Pages.values[index];
notifyListeners();
}
} }

View File

@ -6,9 +6,16 @@ import '../models/settings.dart';
class SettingsProvider extends ChangeNotifier { class SettingsProvider extends ChangeNotifier {
Settings? _settings; Settings? _settings;
bool _initialized = false; bool _initialized = false;
bool _scanning = false;
Settings? get settings => _settings; Settings? get settings => _settings;
bool get initialized => _initialized; bool get initialized => _initialized;
bool get scanning => _scanning;
set scanning(bool value) {
_scanning = value;
notifyListeners();
}
SettingsProvider() { SettingsProvider() {
initSettings(); initSettings();

View File

@ -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/providers/settings_provider.dart';
import 'package:smoke_cess_app/services/date_service.dart'; import 'package:smoke_cess_app/services/date_service.dart';
import 'package:smoke_cess_app/services/pages_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 '../globals.dart' as globals;
import '../models/mood.dart'; import '../models/mood.dart';

View File

@ -5,13 +5,18 @@ import 'package:flutter/material.dart';
class TimerProvider extends ChangeNotifier { class TimerProvider extends ChangeNotifier {
Timer? _timer; Timer? _timer;
bool started = false; 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) { void startTimer(Duration duration) {
_duration = Duration.zero;
started = true; started = true;
_timer = Timer.periodic(const Duration(seconds: 1), ((timer) { _timer = Timer.periodic(_tickRate, ((timer) {
if (timer.tick >= duration.inSeconds) { _duration += _tickRate;
timer.cancel(); if (elapsedSeconds >= duration.inSeconds) {
_timer?.cancel();
started = false; started = false;
} }
notifyListeners(); notifyListeners();
@ -22,13 +27,12 @@ class TimerProvider extends ChangeNotifier {
started = false; started = false;
_timer?.cancel(); _timer?.cancel();
_timer = null; _timer = null;
_duration = Duration.zero;
} }
@override @override
void dispose() { void dispose() {
started = false; stopTimer();
_timer?.cancel();
_timer = null;
super.dispose(); super.dispose();
} }
} }

View File

@ -8,8 +8,8 @@ import '../pages/sleep_page.dart';
enum Pages { enum Pages {
mood, mood,
sleep, sleep,
relapse,
timer, timer,
relapse,
settings, settings,
} }

View File

@ -23,6 +23,8 @@ Future<int?> getMoodQueryMinutes() => _getIntSetting('mood_query_minutes');
Future<int?> getChessHours() => _getIntSetting('chess_hours'); Future<int?> getChessHours() => _getIntSetting('chess_hours');
Future<int?> getChessMinutes() => _getIntSetting('chess_minutes'); Future<int?> getChessMinutes() => _getIntSetting('chess_minutes');
Future<String?> getStartDay() => _getStringSetting('startedAt');
void _setIntSetting(String settingKey, int settingValue) => void _setIntSetting(String settingKey, int settingValue) =>
SharedPreferences.getInstance() SharedPreferences.getInstance()
.then((pref) => pref.setInt(settingKey, settingValue)); .then((pref) => pref.setInt(settingKey, settingValue));
@ -30,6 +32,13 @@ void _setIntSetting(String settingKey, int settingValue) =>
Future<int?> _getIntSetting(String settingKey) => Future<int?> _getIntSetting(String settingKey) =>
SharedPreferences.getInstance().then((pref) => pref.getInt(settingKey)); SharedPreferences.getInstance().then((pref) => pref.getInt(settingKey));
void _setStringSetting(String settingKey, String settingValue) =>
SharedPreferences.getInstance()
.then((pref) => pref.setString(settingKey, settingValue));
Future<String?> _getStringSetting(String settingKey) =>
SharedPreferences.getInstance().then((pref) => pref.getString(settingKey));
void _setStringListSetting(String settingKey, List<String> list) => void _setStringListSetting(String settingKey, List<String> list) =>
SharedPreferences.getInstance() SharedPreferences.getInstance()
.then((pref) => pref.setStringList(settingKey, list)); .then((pref) => pref.setStringList(settingKey, list));
@ -40,6 +49,7 @@ Future<List<String>?> _getStringListSetting(String settingKey) =>
Future<void> loadSettingsFromLocalJSON() async { Future<void> loadSettingsFromLocalJSON() async {
Map<String, dynamic> configJSON = await loadLocalConfigJSON(); Map<String, dynamic> configJSON = await loadLocalConfigJSON();
configJSON['startedAt'] = DateTime.now().toIso8601String();
Settings settings = Settings.fromJson(configJSON); Settings settings = Settings.fromJson(configJSON);
saveSettings(settings); saveSettings(settings);
} }
@ -53,6 +63,7 @@ void saveSettings(Settings settings) {
_setStringListSetting('sleep_query_days', settings.sleepQuery!.days!); _setStringListSetting('sleep_query_days', settings.sleepQuery!.days!);
_setIntSetting('sleep_query_hours', settings.sleepQuery!.hours!); _setIntSetting('sleep_query_hours', settings.sleepQuery!.hours!);
_setIntSetting('sleep_query_minutes', settings.sleepQuery!.minutes!); _setIntSetting('sleep_query_minutes', settings.sleepQuery!.minutes!);
_setStringSetting('startedAt', DateTime.now().toIso8601String());
if (settings.chessTime != null) { if (settings.chessTime != null) {
_setIntSetting('chess_hours', settings.chessTime!.hours!); _setIntSetting('chess_hours', settings.chessTime!.hours!);
_setIntSetting('chess_minutes', settings.chessTime!.minutes!); _setIntSetting('chess_minutes', settings.chessTime!.minutes!);
@ -70,6 +81,8 @@ Future<Settings?> loadSettings() async {
List<String>? sleepDays = await getSleepQueryDaysCategories(); List<String>? sleepDays = await getSleepQueryDaysCategories();
int? chessHours = await getChessHours(); int? chessHours = await getChessHours();
int? chessMinutes = await getChessMinutes(); int? chessMinutes = await getChessMinutes();
DateTime startedAt =
DateTime.parse(await getStartDay() ?? DateTime.now().toIso8601String());
if (group != null) { if (group != null) {
return Settings( return Settings(
@ -77,7 +90,8 @@ Future<Settings?> loadSettings() async {
relapseCategories, relapseCategories,
QueryConfig(moodHours, moodMinutes, moodDays), QueryConfig(moodHours, moodMinutes, moodDays),
QueryConfig(sleepHours, sleepMinutes, sleepDays), QueryConfig(sleepHours, sleepMinutes, sleepDays),
TimeConfig(chessHours, chessMinutes)); TimeConfig(chessHours, chessMinutes),
startedAt);
} }
return null; return null;
} }

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
String formatTime(int seconds) { String formatTime(int seconds) {
Duration duration = Duration(seconds: seconds); Duration duration = Duration(seconds: seconds);
String formattedTime = ''; String formattedTime = '';
@ -6,9 +8,20 @@ String formatTime(int seconds) {
String hours = twoDigits(duration.inHours.remainder(24)); String hours = twoDigits(duration.inHours.remainder(24));
String minutes = twoDigits(duration.inMinutes.remainder(60)); String minutes = twoDigits(duration.inMinutes.remainder(60));
String formattedSeconds = twoDigits(duration.inSeconds.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:'; if (duration.inHours != 0) formattedTime += '$hours:';
formattedTime += '$minutes:'; formattedTime += '$minutes:';
formattedTime += formattedSeconds; formattedTime += formattedSeconds;
return formattedTime; 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);
}
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../providers/audio_provider.dart';
import '../providers/audio_provider.dart';
class MuteButton extends StatelessWidget { class MuteButton extends StatelessWidget {
const MuteButton({super.key}); const MuteButton({super.key});

View File

@ -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,
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/page_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 { class SubmitFormButton extends StatelessWidget {
final Future<int> Function() submitCallback; final Future<int> Function() submitCallback;
@ -14,7 +15,7 @@ class SubmitFormButton extends StatelessWidget {
PageProvider pageProvider = context.watch<PageProvider>(); PageProvider pageProvider = context.watch<PageProvider>();
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton( child: RoundIconButton(
onPressed: () async { onPressed: () async {
int success = await submitCallback(); int success = await submitCallback();
if (context.mounted) { if (context.mounted) {
@ -37,7 +38,7 @@ class SubmitFormButton extends StatelessWidget {
} }
} }
}, },
child: const Text('Speichern'), iconData: Icons.check_outlined,
), ),
); );
} }

View File

@ -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,
),
);
}
}

View File

@ -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),
)))
],
);
}
}

View File

@ -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,
);
}
}

View File

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:smoke_cess_app/widgets/entry_detail_widget.dart';
class HistoryList<T> extends StatelessWidget {
final List<T> 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()));
}
}

View File

@ -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<T> extends StatelessWidget {
final List<LineSeries<T, DateTime>> series;
const LineChart({
super.key,
required this.series,
});
@override
Widget build(BuildContext context) {
SettingsProvider settingsProvider = context.watch<SettingsProvider>();
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,
);
}
}

View File

@ -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<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SfCartesianChart(
primaryXAxis: DateTimeAxis(),
series: <ChartSeries>[
LineSeries<Mood, DateTime>(
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())
],
);
}
}

View File

@ -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>();
timerProvider.startTimer(duration);
return TimerWidget(duration: duration);
},
),
const SizedBox(
height: 15,
),
],
)).show();
}
}

View File

@ -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>();
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)
]));
}
}

View File

@ -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<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: tasksModel.relapseHistory.map((relapse) {
return Text('${relapse.date}: ${relapse.category}');
}).toList());
}
}

View File

@ -5,80 +5,81 @@ import 'package:provider/provider.dart';
import 'package:smoke_cess_app/models/settings.dart'; import 'package:smoke_cess_app/models/settings.dart';
import 'package:smoke_cess_app/services/json_service.dart'; import 'package:smoke_cess_app/services/json_service.dart';
import 'package:smoke_cess_app/services/settings_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 '../providers/settings_provider.dart';
import '../services/notification_service.dart'; import '../services/notification_service.dart';
class MyScanner extends StatefulWidget { class MyScanner extends StatelessWidget {
const MyScanner({super.key}); const MyScanner({super.key});
@override @override
State<StatefulWidget> createState() => MyScannerState(); Widget build(BuildContext context) {
} SettingsProvider settingsProvider = context.watch<SettingsProvider>();
class MyScannerState extends State<MyScanner> { void handleSucces(String? rawValue) {
bool scanning = false; String qrText = rawValue!;
Map<String, dynamic> 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<String, dynamic> json = stringToJSON(qrText);
Settings settings = Settings.fromJson(json);
saveSettings(settings);
var settingsModel = context.read<SettingsProvider>();
settingsModel.initSettings();
NotificationService().setAllNotifications();
setState(() {
scanning = false;
AwesomeDialog( AwesomeDialog(
context: context, context: context,
dialogType: DialogType.success, dialogType: DialogType.success,
title: 'Geschafft', title: 'Geschafft',
desc: 'Der Code wurde erfolgreich gescannt!', desc: 'Der Code wurde erfolgreich gescannt!',
).show(); ).show();
}); }
}
void handleError() {
settingsProvider.scanning = false;
void handleError() {
setState(() {
scanning = false;
AwesomeDialog( AwesomeDialog(
context: context, context: context,
dialogType: DialogType.error, dialogType: DialogType.error,
title: 'Fehler', title: 'Fehler',
desc: 'Der QR-Code war fehlerhaft!', desc: 'Der QR-Code war fehlerhaft!',
).show(); ).show();
});
}
void onDetect(capture) {
try {
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
if (barcode.rawValue != null) {
return handleSucces(barcode.rawValue);
}
}
} catch (e) {
handleError();
} }
}
@override void onDetect(capture) {
Widget build(BuildContext context) { try {
return scanning final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
if (barcode.rawValue != null) {
return handleSucces(barcode.rawValue);
}
}
} catch (e) {
handleError();
}
}
return settingsProvider.scanning
? Expanded( ? Expanded(
child: MobileScanner( child: Stack(
alignment: Alignment.center,
children: [
MobileScanner(
fit: BoxFit.contain, fit: BoxFit.contain,
controller: MobileScannerController( controller: MobileScannerController(
detectionTimeoutMs: 2000, detectionTimeoutMs: 2000,
), ),
onDetect: onDetect)) onDetect: onDetect,
: ElevatedButton( ),
style: ElevatedButton.styleFrom( ClipRRect(
textStyle: const TextStyle(fontSize: 20)), borderRadius: BorderRadius.circular(20),
onPressed: () { child: Container(
setState(() => scanning = true); height: MediaQuery.of(context).size.height / 3,
}, width: MediaQuery.of(context).size.width * 0.8,
child: const Text('Scan QR Code'), color: Colors.white.withOpacity(0.4))),
); ],
))
: TextIconButton(
text: "Scan",
onPressed: () => settingsProvider.scanning = true,
iconData: Icons.qr_code_scanner_outlined);
} }
} }

View File

@ -3,11 +3,10 @@ import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/tasks_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/services/pages_service.dart';
import 'package:smoke_cess_app/widgets/slider.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/text_formfield.dart';
import 'package:smoke_cess_app/providers/input_provider.dart';
import '../providers/input_provider.dart'; import 'package:smoke_cess_app/widgets/elevated_card.dart';
import 'elevated_card.dart';
class MoodForm extends StatelessWidget { class MoodForm extends StatelessWidget {
const MoodForm({super.key}); const MoodForm({super.key});
@ -16,8 +15,8 @@ class MoodForm extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>(); var inputModel = context.watch<InputProvider>();
var tasksModel = context.watch<TasksProvider>(); var tasksModel = context.watch<TasksProvider>();
return Column( return ListView(
mainAxisAlignment: MainAxisAlignment.center, padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
children: [ children: [
const ElevatedCard( const ElevatedCard(
title: 'Stimmungsbewertung', title: 'Stimmungsbewertung',
@ -28,9 +27,6 @@ class MoodForm extends StatelessWidget {
title: 'Beschreibe deine Stimmung', title: 'Beschreibe deine Stimmung',
child: MyTextFormField('Beschreibe deine Stimmung'), child: MyTextFormField('Beschreibe deine Stimmung'),
), ),
const SizedBox(
height: 80,
),
SubmitFormButton( SubmitFormButton(
submitCallback: inputModel.saveMood, submitCallback: inputModel.saveMood,
updateTasks: () => tasksModel.setTaskDone(Pages.mood), updateTasks: () => tasksModel.setTaskDone(Pages.mood),

View File

@ -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<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LineChart(series: [
LineSeries<Mood, DateTime>(
dataSource: tasksModel.moodHistory,
xValueMapper: (Mood value, _) => value.date,
yValueMapper: (Mood value, _) => value.moodValue)
]),
HistoryList<Mood>(
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,
)
],
);
}
}

View File

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/tasks_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/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 'package:smoke_cess_app/widgets/text_formfield.dart';
import '../providers/input_provider.dart'; import 'package:smoke_cess_app/providers/input_provider.dart';
import '../providers/settings_provider.dart'; import 'package:smoke_cess_app/providers/settings_provider.dart';
import '../services/pages_service.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
import 'elevated_card.dart'; import 'package:smoke_cess_app/widgets/elevated_card.dart';
class RelapseForm extends StatelessWidget { class RelapseForm extends StatelessWidget {
const RelapseForm({super.key}); const RelapseForm({super.key});
@ -17,8 +17,8 @@ class RelapseForm extends StatelessWidget {
var inputModel = context.watch<InputProvider>(); var inputModel = context.watch<InputProvider>();
var settingsModel = context.watch<SettingsProvider>(); var settingsModel = context.watch<SettingsProvider>();
var tasksModel = context.watch<TasksProvider>(); var tasksModel = context.watch<TasksProvider>();
return Column( return ListView(
mainAxisAlignment: MainAxisAlignment.center, padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
children: [ children: [
ElevatedCard( ElevatedCard(
title: 'Rückfallkategorie', title: 'Rückfallkategorie',
@ -28,9 +28,6 @@ class RelapseForm extends StatelessWidget {
title: 'Beschreibe deinen Rückfall', title: 'Beschreibe deinen Rückfall',
child: MyTextFormField('Beschreibe deinen Rückfall'), child: MyTextFormField('Beschreibe deinen Rückfall'),
), ),
const SizedBox(
height: 80,
),
SubmitFormButton( SubmitFormButton(
submitCallback: inputModel.saveRelapse, submitCallback: inputModel.saveRelapse,
updateTasks: () => tasksModel.setTaskDone(Pages.mood), updateTasks: () => tasksModel.setTaskDone(Pages.mood),

View File

@ -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<TasksProvider>();
return Column(children: [
HistoryList<Relapse>(
history: tasksModel.relapseHistory,
dateSelector: (Relapse relapse) => relapse.date,
entryDataSelector: (Relapse relapse) => relapse.category,
entryCommentSelector: (Relapse relapse) =>
'Kommentar: ${relapse.comment}',
icon: Icons.smoke_free_outlined,
)
]);
}
}

View File

@ -3,12 +3,12 @@ import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/tasks_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/elevated_card.dart';
import 'package:smoke_cess_app/widgets/slider.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/text_formfield.dart';
import 'package:smoke_cess_app/widgets/timepicker.dart'; import 'package:smoke_cess_app/widgets/timepicker.dart';
import '../providers/input_provider.dart'; import 'package:smoke_cess_app/providers/input_provider.dart';
import '../services/pages_service.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
class SleepForm extends StatelessWidget { class SleepForm extends StatelessWidget {
const SleepForm({Key? key}) : super(key: key); const SleepForm({Key? key}) : super(key: key);
@ -18,8 +18,8 @@ class SleepForm extends StatelessWidget {
InputProvider inputModel = context.watch<InputProvider>(); InputProvider inputModel = context.watch<InputProvider>();
TasksProvider tasksModel = context.watch<TasksProvider>(); TasksProvider tasksModel = context.watch<TasksProvider>();
return Column( return ListView(
mainAxisAlignment: MainAxisAlignment.center, padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
children: [ children: [
const ElevatedCard( const ElevatedCard(
title: 'Einschlafzeit', title: 'Einschlafzeit',
@ -40,9 +40,6 @@ class SleepForm extends StatelessWidget {
title: 'Schlafbeschreibung', title: 'Schlafbeschreibung',
child: MyTextFormField('Beschreibe deinen Schlaf'), child: MyTextFormField('Beschreibe deinen Schlaf'),
), ),
const SizedBox(
height: 80,
),
SubmitFormButton( SubmitFormButton(
submitCallback: () => submitCallback: () =>
inputModel.saveSleep(SleepTimes.wokeUpAt, SleepTimes.sleptAt), inputModel.saveSleep(SleepTimes.wokeUpAt, SleepTimes.sleptAt),

View File

@ -1,31 +1,36 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/models/sleep.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 'package:syncfusion_flutter_charts/charts.dart';
import '../providers/tasks_provider.dart';
class SleepView extends StatelessWidget { class SleepView extends StatelessWidget {
const SleepView({super.key}); const SleepView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var tasksModel = context.watch<TasksProvider>(); TasksProvider tasksModel = context.watch<TasksProvider>();
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SfCartesianChart( LineChart<Sleep>(
primaryXAxis: DateTimeAxis(), series: [
series: <ChartSeries>[
LineSeries<Sleep, DateTime>( LineSeries<Sleep, DateTime>(
dataSource: tasksModel.sleepHistory, dataSource: tasksModel.sleepHistory,
xValueMapper: (Sleep value, _) => value.date, xValueMapper: (Sleep value, _) => value.date,
yValueMapper: (Sleep value, _) => value.sleepQualitiyValue) yValueMapper: (Sleep value, _) => value.sleepQualitiyValue)
], ],
), ),
Column( HistoryList<Sleep>(
children: tasksModel.sleepHistory.map((sleep) { history: tasksModel.sleepHistory,
return Text('${sleep.date}: ${sleep.sleepQualitiyValue}'); dateSelector: (Sleep sleep) => sleep.date,
}).toList()) entryDataSelector: (Sleep sleep) =>
'${sleep.sleepDuration.hour}:${sleep.sleepDuration.minute}',
entryCommentSelector: (Sleep sleep) => 'Kommentar: ${sleep.comment}',
icon: Icons.bedtime_outlined,
)
], ],
); );
} }

View File

@ -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<PageProvider>();
TasksProvider tasksProvider = context.watch<TasksProvider>();
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),
),
)
]);
}
}

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/audio_provider.dart'; import 'package:smoke_cess_app/providers/audio_provider.dart';
import '../providers/timer_provider.dart'; import 'package:smoke_cess_app/providers/timer_provider.dart';
import '../providers/workout_provider.dart'; import 'package:smoke_cess_app/providers/workout_provider.dart';
import 'mute_button.dart'; import 'package:smoke_cess_app/widgets/buttons/mute_button.dart';
import 'workout_timer_widget.dart'; import 'package:smoke_cess_app/widgets/workout_timer_widget.dart';
class WorkoutForm extends StatelessWidget { class WorkoutForm extends StatelessWidget {
WorkoutForm({super.key}); WorkoutForm({super.key});

View File

@ -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<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
LineChart(series: [
LineSeries<Workout, DateTime>(
dataSource: tasksModel.workoutHistory,
xValueMapper: (Workout value, _) => value.date,
yValueMapper: (Workout value, _) => value.motivationBefore),
LineSeries<Workout, DateTime>(
dataSource: tasksModel.workoutHistory,
xValueMapper: (Workout value, _) => value.date,
yValueMapper: (Workout value, _) => value.motivationAfter)
]),
HistoryList<Workout>(
history: tasksModel.workoutHistory,
dateSelector: (Workout workout) => workout.date,
entryDataSelector: (Workout workout) =>
'${workout.motivationBefore} : ${workout.motivationAfter}',
icon: Icons.sports_score_outlined,
)
],
);
}
}

View File

@ -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<PageProvider>();
TasksProvider tasksProvider = context.watch<TasksProvider>();
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)
]);
}
}

View File

@ -4,13 +4,13 @@ import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../providers/page_provider.dart'; import 'package:smoke_cess_app/providers/page_provider.dart';
import '../providers/tasks_provider.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart';
import '../providers/workout_provider.dart'; import 'package:smoke_cess_app/providers/workout_provider.dart';
import '../services/pages_service.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
import '../widgets/timer_widget.dart'; import 'package:smoke_cess_app/widgets/timer_widget.dart';
import '../providers/timer_provider.dart'; import 'package:smoke_cess_app/providers/timer_provider.dart';
import 'popup_for_start_and_stop.dart'; import 'package:smoke_cess_app/widgets/popup/popup_for_start_and_stop.dart';
class WorkoutTimerWidget extends StatelessWidget { class WorkoutTimerWidget extends StatelessWidget {
const WorkoutTimerWidget({super.key}); const WorkoutTimerWidget({super.key});
@ -74,10 +74,12 @@ class WorkoutTimerWidget extends StatelessWidget {
width: 100, width: 100,
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: workoutProvider.currentPhaseColor, color: workoutProvider.currentPhaseColor,
value: (workoutProvider.currentPhaseDuration.inSeconds value:
.toDouble() - (workoutProvider.currentPhaseDuration.inMilliseconds -
timerProvider.elapsedSeconds) / timerProvider.elapsedMilliseconds)
workoutProvider.currentPhaseDuration.inSeconds)), .toDouble() /
workoutProvider.currentPhaseDuration.inMilliseconds
.toDouble())),
TimerWidget(duration: workoutProvider.currentPhaseDuration), TimerWidget(duration: workoutProvider.currentPhaseDuration),
], ],
), ),

View File

@ -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<TasksProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SfCartesianChart(
primaryXAxis: DateTimeAxis(),
series: <ChartSeries>[
LineSeries<Workout, DateTime>(
dataSource: tasksModel.workoutHistory,
xValueMapper: (Workout value, _) => value.date,
yValueMapper: (Workout value, _) => value.motivationBefore),
LineSeries<Workout, DateTime>(
dataSource: tasksModel.workoutHistory,
xValueMapper: (Workout value, _) => value.date,
yValueMapper: (Workout value, _) => value.motivationAfter)
],
),
],
);
}
}

View File

@ -24,6 +24,7 @@ dependencies:
http: ^0.13.5 http: ^0.13.5
syncfusion_flutter_charts: ^20.4.52 syncfusion_flutter_charts: ^20.4.52
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
intl: ^0.18.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: