diff --git a/.gitignore b/.gitignore index 24476c5..065dc2c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ +pubspec.lock # Symbolication related app.*.symbols diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 562ecfe..4bb6e7e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: cirrusci/flutter:latest +image: cirrusci/flutter:3.7.5 stages: - analyze diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bae31ce..752ea03 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,9 +1,11 @@ + + + android:icon="@mipmap/launcher_icon"> CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Smoke Cess App + ZI SmokeFree CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -15,7 +15,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - smoke_cess_app + ZI SmokeFree CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/main.dart b/lib/main.dart index a1cf5ac..4a49cd4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smoke_cess_app/pages/main_page.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:smoke_cess_app/services/notification_service.dart'; import 'package:timezone/data/latest.dart' as tz; import 'globals.dart' as globals; +import 'providers/page_provider.dart'; import 'providers/settings_provider.dart'; void main() { @@ -23,11 +25,21 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: _title, - home: ChangeNotifierProvider( - create: (context) => SettingsProvider(), - child: const MyHomePage(), + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => SettingsProvider()), + ChangeNotifierProxyProvider( + create: (context) => TasksProvider(null), + update: (context, value, TasksProvider? previous) => + TasksProvider(value), + ), + ChangeNotifierProvider( + create: (context) => PageProvider(), + ), + ], + child: const MaterialApp( + title: _title, + home: MyHomePage(), )); } } diff --git a/lib/mock/db_mock.dart b/lib/mock/db_mock.dart index e7c60c3..4e5d0e6 100644 --- a/lib/mock/db_mock.dart +++ b/lib/mock/db_mock.dart @@ -1,8 +1,9 @@ -import 'package:smoke_cess_app/interface/db_record.dart'; import 'package:smoke_cess_app/models/mood.dart'; import 'package:smoke_cess_app/models/relapse.dart'; import 'package:smoke_cess_app/models/sleep.dart'; +import 'package:smoke_cess_app/models/workout.dart'; import 'package:smoke_cess_app/services/database_service.dart'; +// ignore: depend_on_referenced_packages import 'package:sqflite_common/sqlite_api.dart'; class DatabaseMock implements DatabaseService { @@ -15,7 +16,7 @@ class DatabaseMock implements DatabaseService { final List _moodRecords = []; final List _sleepRecords = []; final List _relapseRecords = []; - final List _workoutRecords = []; + final List _workoutRecords = []; @override Future addMood(Mood mood) { @@ -29,6 +30,12 @@ class DatabaseMock implements DatabaseService { return Future.value(1); } + @override + Future addWorkout(Workout workout) { + _workoutRecords.add(workout); + return Future.value(1); + } + @override Future addRelapse(Relapse relapse) { _relapseRecords.add(relapse); @@ -36,7 +43,6 @@ class DatabaseMock implements DatabaseService { } @override - // TODO: implement database Future get database => DatabaseService.instance.database; @override @@ -53,4 +59,9 @@ class DatabaseMock implements DatabaseService { Future> getRelapseRecords() { return Future.value(_relapseRecords); } + + @override + Future> getWorkoutRecords() { + return Future.value(_workoutRecords); + } } diff --git a/lib/models/hiit_workout.dart b/lib/models/hiit_workout.dart deleted file mode 100644 index c4ec0da..0000000 --- a/lib/models/hiit_workout.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:smoke_cess_app/interface/db_record.dart'; - -class HIITWorkout implements DatabaseRecord { - Duration _workoutDuration; - String _commentBefore; - String _commentAfter; - DateTime _workoutDate; - - HIITWorkout(this._workoutDuration, this._commentBefore, this._commentAfter, - this._workoutDate); - - //TODO Felder anpassen - @override - factory HIITWorkout.fromMap(Map map) { - return HIITWorkout(map['_workoutDuration'], map['_commentBefore'], - map['_commentAfter'], map['_workoutDate']); - } - - @override - String toCSV() => - "${_workoutDate.toIso8601String()}, $_workoutDuration, $_commentBefore, $_commentAfter"; - - @override - Map toMap() { - return { - 'workoutDuration': _workoutDuration, - 'commentBefore': _commentBefore, - 'commentAfter': _commentAfter, - 'workoutDate': _workoutDate, - }; - } -} diff --git a/lib/models/mood.dart b/lib/models/mood.dart index 562b2b4..9d34051 100644 --- a/lib/models/mood.dart +++ b/lib/models/mood.dart @@ -7,6 +7,9 @@ class Mood implements DatabaseRecord { Mood(this._moodValue, this._comment, this._date); + DateTime get date => _date; + int get moodValue => _moodValue; + @override factory Mood.fromDatabase(Map map) { DateTime date = DateTime.parse(map['date']); diff --git a/lib/models/relapse.dart b/lib/models/relapse.dart index 0271d31..1507188 100644 --- a/lib/models/relapse.dart +++ b/lib/models/relapse.dart @@ -7,6 +7,9 @@ class Relapse implements DatabaseRecord { Relapse(this._category, this._comment, this._date); + String get category => _category; + DateTime get date => _date; + @override factory Relapse.fromDatabase(Map map) { DateTime date = DateTime.parse(map['date']); diff --git a/lib/models/sleep.dart b/lib/models/sleep.dart index 3474b03..4447be4 100644 --- a/lib/models/sleep.dart +++ b/lib/models/sleep.dart @@ -11,6 +11,9 @@ class Sleep implements DatabaseRecord { Sleep(this._sleepQualityValue, this._comment, this._date, this._sleptAt, this._wokeUpAt); + DateTime get date => _date; + int get sleepQualitiyValue => _sleepQualityValue; + @override factory Sleep.fromDatabase(Map map) { DateTime date = DateTime.parse(map['date']); diff --git a/lib/models/workout.dart b/lib/models/workout.dart new file mode 100644 index 0000000..dd27105 --- /dev/null +++ b/lib/models/workout.dart @@ -0,0 +1,38 @@ +import 'package:smoke_cess_app/interface/db_record.dart'; + +class Workout implements DatabaseRecord { + int _motivationBefore; + int _motivationAfter; + DateTime _workoutDate; + + Workout(this._motivationBefore, this._motivationAfter, this._workoutDate); + + DateTime get date => _workoutDate; + int get motivationBefore => _motivationBefore; + int get motivationAfter => _motivationAfter; + + @override + factory Workout.fromDatabase(Map map) { + return Workout(map['motivationBefore'], map['motivationAfter'], + DateTime.parse(map['workoutDate'])); + } + + @override + factory Workout.fromMap(Map map) { + return Workout( + map['motivationBefore'], map['motivationAfter'], map['workoutDate']); + } + + @override + String toCSV() => + "${_workoutDate.toIso8601String()}, $_motivationBefore, $_motivationAfter"; + + @override + Map toMap() { + return { + 'motivationBefore': _motivationBefore, + 'motivationAfter': _motivationAfter, + 'workoutDate': _workoutDate.toIso8601String(), + }; + } +} diff --git a/lib/pages/interval_page.dart b/lib/pages/interval_page.dart index f6d2fcd..2326168 100644 --- a/lib/pages/interval_page.dart +++ b/lib/pages/interval_page.dart @@ -1,225 +1,16 @@ -import 'dart:async'; -import 'package:audioplayers/audioplayers.dart'; 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/popup_for_start_and_stop.dart'; -import 'package:smoke_cess_app/widgets/timer_widget.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 '../providers/input_provider.dart'; +import '../widgets/view_form_page.dart'; -class IntervalTimerPage extends StatefulWidget { - const IntervalTimerPage({Key? key}) : super(key: key); - - @override - _IntervalTimerPageState createState() => _IntervalTimerPageState(); -} - -class _IntervalTimerPageState extends State { - final Duration _warmupDuration = const Duration(seconds: 5); - final Duration _cooldownDuration = const Duration(seconds: 5); - final Duration _highIntensityDuration = const Duration(seconds: 4); - final Duration _lowIntensityDuration = const Duration(seconds: 3); - late Duration _totalDuration = const Duration(minutes: 35); - AudioPlayer warmUpPlayer = AudioPlayer(); - AudioPlayer workoutPlayer = AudioPlayer(); - AudioPlayer coolDownPlayer = AudioPlayer(); - final AudioCache _audioCache = AudioCache(); - final int _numHighIntensityBlocks = 4; - final int _numLowIntensityBlocks = 3; - - Timer? _timer; - int _currentBlock = 0; - Duration _currentDuration = const Duration(); - bool _isPaused = true; - - @override - void initState() { - _currentDuration = _warmupDuration; - super.initState(); - } - - @override - void dispose() { - _timer?.cancel(); - super.dispose(); - } - - void _startTimer() async { - await showDialog( - context: context, - builder: (BuildContext context) { - return ChangeNotifierProvider( - create: (context) => InputProvider(), - child: const TimerStartStopPopup( - title: 'Motivation vor dem Training', - )); - }, - ); - _isPaused = false; - Source source = AssetSource('go.mp3'); - await AudioPlayer().play(source); - - _timer = Timer.periodic(const Duration(seconds: 1), (_) => _tick()); - Future.delayed(const Duration(seconds: 1)).then((value) { - _playWarmUpMusic(); - }); - } - - void _resetTimer() { - () async { - await coolDownPlayer.stop(); - await warmUpPlayer.stop(); - await workoutPlayer.stop(); - }(); - _isPaused = true; - _timer?.cancel(); - _currentBlock = 0; - _currentDuration = _warmupDuration; - _totalDuration = const Duration(minutes: 35); - setState(() {}); - showDialog( - context: context, - builder: (BuildContext context) { - return const TimerStartStopPopup( - title: 'Motivation nach dem Training', - ); - }, - ); - } - - Future _playWarmUpMusic() async { - Source source = AssetSource('warmUp.mp3'); - await warmUpPlayer.setReleaseMode(ReleaseMode.loop); - await warmUpPlayer.play(source); - } - - Future _playWorkoutMusic() async { - await warmUpPlayer.stop(); - Future.delayed(const Duration(microseconds: 600)).then((value) async { - Source source = AssetSource('workout.mp3'); - await workoutPlayer.setReleaseMode(ReleaseMode.loop); - await workoutPlayer.play(source); - }); - } - - Future _intervalChange() async { - Source source = AssetSource('beep.mp3'); - await AudioPlayer().play(source); - } - - void _tick() { - setState(() { - _currentDuration = Duration( - seconds: _currentDuration.inSeconds - 1, - ); - _totalDuration = Duration( - seconds: _totalDuration.inSeconds - 1, - ); - if (_currentDuration.inSeconds < 1) { - if (_currentBlock < _numHighIntensityBlocks + _numLowIntensityBlocks) { - _intervalChange(); - if (_currentBlock == 0) { - _playWorkoutMusic(); - } - _currentBlock++; - - if (_currentBlock % 2 == 1) { - _currentDuration = _highIntensityDuration; - } else { - _currentDuration = _lowIntensityDuration; - } - } else if (_currentBlock < _numHighIntensityBlocks * 2) { - _intervalChange(); - _currentBlock++; - _currentDuration = _cooldownDuration; - () async { - await workoutPlayer.stop(); - Source source = AssetSource('cool_down.mp3'); - await coolDownPlayer.setReleaseMode(ReleaseMode.loop); - await coolDownPlayer.play(source); - }(); - } else { - () async { - Future.delayed(const Duration(microseconds: 900)) - .then((value) async { - Source source = AssetSource('finish.mp3'); - await AudioPlayer().play(source); - }); - }(); - _resetTimer(); - } - } - }); - } - - String _formatDuration(Duration duration) { - String twoDigits(int n) => n.toString().padLeft(2, '0'); - final minutes = twoDigits(duration.inMinutes.remainder(60)); - final seconds = twoDigits(duration.inSeconds.remainder(60)); - return '$minutes:$seconds'; - } - - String _formatTotalDuration(Duration duration) { - final minutes = duration.inMinutes; - final seconds = duration.inSeconds.remainder(60); - return _formatDuration(Duration(minutes: minutes, seconds: seconds)); - } +class IntervalTimerPage extends StatelessWidget { + const IntervalTimerPage({super.key}); @override Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => TimerProvider(), - child: TimerWidget( - duration: Duration(seconds: 5), - )); - return Center( - child: ChangeNotifierProvider( - create: (context) => InputProvider(), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - _currentBlock == 0 - ? 'Warm-up' - : _currentBlock % 2 == 1 - ? 'High Intensity' - : _currentBlock < _numHighIntensityBlocks * 2 - ? 'Low Intensity' - : 'Cool-down', - style: const TextStyle(fontSize: 32.0), - ), - const SizedBox(height: 16.0), - Text( - _formatDuration(_currentDuration), - style: const TextStyle(fontSize: 80.0), - ), - const SizedBox(height: 32.0), - Text( - 'Total: ${_formatTotalDuration(_totalDuration)}', - style: const TextStyle(fontSize: 24.0), - ), - const SizedBox(height: 32.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon(_isPaused - ? Icons.play_arrow_rounded - : Icons.stop_rounded), - iconSize: 48.0, - onPressed: () { - if (_isPaused) { - _startTimer(); - } else { - _resetTimer(); - } - }, - ), - // ), - ], - ), - ], - ))); + return ViewFormPage( + form: WorkoutForm(), view: const WorkoutView(), page: Pages.timer); } } diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index b45b479..10ff0ec 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -1,9 +1,13 @@ import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; import 'package:smoke_cess_app/services/pages_service.dart'; import 'package:smoke_cess_app/providers/settings_provider.dart'; +import '../widgets/todo_icon.dart'; + class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @@ -16,38 +20,43 @@ class MyHomePageState extends State { bool _isConfigured = false; void _onItemTapped(int index) { + PageProvider pageProvider = context.read(); setState(() { - _isConfigured - ? _selectedIndex = index - : AwesomeDialog( - context: context, - dialogType: DialogType.info, - title: 'Fehlende Konfiguration', - desc: 'Bitte QR Code Scannen!', - ).show(); + 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 group = settingsModel.settings?.group; + var tasksModel = context.watch(); _isConfigured = settingsModel.initialized; return Scaffold( appBar: AppBar( title: Text( - '${pages.keys.elementAt(_selectedIndex)} ${_isConfigured ? "Gruppe $group" : ""}')), - body: Center( - child: SingleChildScrollView( - child: pages.values.elementAt(_selectedIndex)['page'])), + '${pages.values.elementAt(_selectedIndex)['title']} ${_isConfigured ? "Gruppe ${settingsModel.settings?.group}" : ""}')), + body: SingleChildScrollView( + child: pages.values.elementAt(_selectedIndex)['page'], + ), bottomNavigationBar: NavigationBar( onDestinationSelected: _onItemTapped, selectedIndex: _selectedIndex, destinations: pages.keys.map((key) { return NavigationDestination( - icon: pages[key]!['icon'] ?? - const Icon(Icons.disabled_by_default), - label: key); + icon: tasksModel.tasks[key] ?? false + ? MyToDoIcon(pages[key]?['icon']) + : pages[key]!['icon'], + label: pages[key]?['title']); }).toList()), ); } diff --git a/lib/pages/mood_page.dart b/lib/pages/mood_page.dart index 546c6f1..8c2447d 100644 --- a/lib/pages/mood_page.dart +++ b/lib/pages/mood_page.dart @@ -1,17 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/providers/input_provider.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'; class MoodPage extends StatelessWidget { const MoodPage({super.key}); @override Widget build(BuildContext context) { - return Center( - child: ChangeNotifierProvider( - create: (context) => InputProvider(), - child: const MoodForm(), - )); + return const ViewFormPage( + form: MoodForm(), view: MoodView(), page: Pages.mood); } } diff --git a/lib/pages/relapse_page.dart b/lib/pages/relapse_page.dart index 75e007d..e7b6127 100644 --- a/lib/pages/relapse_page.dart +++ b/lib/pages/relapse_page.dart @@ -1,17 +1,15 @@ 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/relapse_form.dart'; -import '../providers/input_provider.dart'; +import 'package:smoke_cess_app/widgets/relapse_view.dart'; +import '../widgets/view_form_page.dart'; class RelapsePage extends StatelessWidget { const RelapsePage({super.key}); @override Widget build(BuildContext context) { - return Center( - child: ChangeNotifierProvider( - create: (context) => InputProvider(), - child: const RelapseForm(), - )); + return const ViewFormPage( + form: RelapseForm(), view: RelapseView(), page: Pages.relapse); } } diff --git a/lib/pages/scanner_page.dart b/lib/pages/scanner_page.dart index 29cf8a7..ebba252 100644 --- a/lib/pages/scanner_page.dart +++ b/lib/pages/scanner_page.dart @@ -1,31 +1,18 @@ import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/models/mood.dart'; -import 'package:smoke_cess_app/models/relapse.dart'; +import 'package:smoke_cess_app/services/export_service.dart'; import 'package:smoke_cess_app/services/settings_service.dart'; import 'package:smoke_cess_app/services/notification_service.dart'; import 'package:smoke_cess_app/widgets/scanner.dart'; -import '../models/sleep.dart'; import '../providers/settings_provider.dart'; -import '../globals.dart' as globals; class ScannerPage extends StatelessWidget { const ScannerPage({super.key}); void export() async { - List moods = await globals.databaseService.getMoodRecords(); - List sleeps = await globals.databaseService.getSleepRecords(); - List relapses = await globals.databaseService.getRelapseRecords(); - for (Mood mood in moods) { - print(mood.toCSV()); - } - for (Sleep sleep in sleeps) { - print(sleep.toCSV()); - } - for (Relapse relapse in relapses) { - print(relapse.toCSV()); - } + ExportService exportService = ExportService(); + exportService.exportData(); } void loadJSON(BuildContext context) async { @@ -33,12 +20,14 @@ class ScannerPage extends StatelessWidget { await loadSettingsFromLocalJSON(); settingsModel.initSettings(); NotificationService().setAllNotifications(); - AwesomeDialog( - context: context, - dialogType: DialogType.success, - title: 'Geschafft', - desc: 'Die Einstellung wurden erfolgreich gespeichert', - ).show(); + if (context.mounted) { + AwesomeDialog( + context: context, + dialogType: DialogType.success, + title: 'Geschafft', + desc: 'Die Einstellung wurden erfolgreich gespeichert', + ).show(); + } } @override diff --git a/lib/pages/sleep_page.dart b/lib/pages/sleep_page.dart index 22221f3..3cf3fd5 100644 --- a/lib/pages/sleep_page.dart +++ b/lib/pages/sleep_page.dart @@ -1,17 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/providers/input_provider.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'; class SleepPage extends StatelessWidget { const SleepPage({super.key}); @override Widget build(BuildContext context) { - return Center( - child: ChangeNotifierProvider( - create: (context) => InputProvider(), - child: const SleepForm(), - )); + return const ViewFormPage( + form: SleepForm(), view: SleepView(), page: Pages.sleep); } } diff --git a/lib/providers/audio_provider.dart b/lib/providers/audio_provider.dart new file mode 100644 index 0000000..a141cb2 --- /dev/null +++ b/lib/providers/audio_provider.dart @@ -0,0 +1,47 @@ +import 'dart:async'; + +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter/cupertino.dart'; + +class AudioProvider extends ChangeNotifier { + final AudioPlayer _audioPlayer = AudioPlayer(); + bool _isMuted = false; + final Source _finishedSoundSource = AssetSource('finish.mp3'); + final Source _beepSoundSource = AssetSource('beep.mp3'); + StreamSubscription? _onCompleteSubscription; + + bool get isMuted => _isMuted; + + void stop() => _resetPlayer(); + + void playFinishSound() { + _resetPlayer(); + _audioPlayer.play(_finishedSoundSource); + } + + void mutePlayer() { + _isMuted = true; + _audioPlayer.setVolume(0); + notifyListeners(); + } + + void unMutePlayer() { + _isMuted = false; + _audioPlayer.setVolume(1); + notifyListeners(); + } + + //resets player position and delete listening subscription + void _resetPlayer() { + _audioPlayer.stop(); + _onCompleteSubscription?.cancel(); + } + + void playSourceAfterBeep(AssetSource source) { + _resetPlayer(); + _audioPlayer.play(_beepSoundSource); + _onCompleteSubscription = _audioPlayer.onPlayerComplete.listen((event) { + _audioPlayer.play(source); + }); + } +} diff --git a/lib/providers/input_provider.dart b/lib/providers/input_provider.dart index a61514f..b8aed63 100644 --- a/lib/providers/input_provider.dart +++ b/lib/providers/input_provider.dart @@ -4,34 +4,33 @@ import 'package:smoke_cess_app/models/relapse.dart'; import 'package:smoke_cess_app/models/sleep.dart'; import '../globals.dart' as globals; +enum SleepTimes { + wokeUpAt, + sleptAt, +} + class InputProvider extends ChangeNotifier { double _sliderValue = 50; final TextEditingController _textController = TextEditingController(text: ''); - final Map _times = { - 'wokeUpAt': const TimeOfDay(hour: 8, minute: 0), - 'sleptAt': const TimeOfDay(hour: 22, minute: 0), + final Map _times = { + SleepTimes.wokeUpAt: const TimeOfDay(hour: 8, minute: 0), + SleepTimes.sleptAt: const TimeOfDay(hour: 22, minute: 0), }; - String _relapseCategory = ''; + String relapseCategory = ''; double get sliderValue => _sliderValue; TextEditingController get textController => _textController; - String get relapseCategory => _relapseCategory; set sliderValue(double newValue) { _sliderValue = newValue; notifyListeners(); } - set relapseCategory(String newValue) { - _relapseCategory = newValue; - notifyListeners(); - } - - TimeOfDay getTimeEntry(String key) { + TimeOfDay getTimeEntry(SleepTimes key) { return _times[key] ?? const TimeOfDay(hour: 12, minute: 0); } - void setTime(String key, TimeOfDay time) { + void setTime(SleepTimes key, TimeOfDay time) { _times[key] = time; notifyListeners(); } @@ -39,8 +38,8 @@ class InputProvider extends ChangeNotifier { void _resetFields() { _sliderValue = 50; _textController.text = ''; - setTime('wokeUpAt', const TimeOfDay(hour: 8, minute: 0)); - setTime('sleptAt', const TimeOfDay(hour: 22, minute: 0)); + setTime(SleepTimes.wokeUpAt, const TimeOfDay(hour: 8, minute: 0)); + setTime(SleepTimes.sleptAt, const TimeOfDay(hour: 22, minute: 0)); notifyListeners(); } @@ -53,12 +52,12 @@ class InputProvider extends ChangeNotifier { Future saveRelapse() { Relapse relapse = - Relapse(_relapseCategory, _textController.text, DateTime.now()); + Relapse(relapseCategory, _textController.text, DateTime.now()); _resetFields(); return globals.databaseService.addRelapse(relapse); } - Future saveSleep(String wokeUpKey, String sleptKey) { + Future saveSleep(SleepTimes wokeUpKey, SleepTimes sleptKey) { Sleep sleep = Sleep(_sliderValue.toInt(), _textController.text, DateTime.now(), getTimeEntry(sleptKey), getTimeEntry(wokeUpKey)); _resetFields(); diff --git a/lib/providers/page_provider.dart b/lib/providers/page_provider.dart new file mode 100644 index 0000000..a17ff26 --- /dev/null +++ b/lib/providers/page_provider.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class PageProvider extends ChangeNotifier { + bool showForm = false; + + void swap() { + showForm = !showForm; + notifyListeners(); + } +} diff --git a/lib/providers/tasks_provider.dart b/lib/providers/tasks_provider.dart new file mode 100644 index 0000000..d3fc042 --- /dev/null +++ b/lib/providers/tasks_provider.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/models/relapse.dart'; +import 'package:smoke_cess_app/models/sleep.dart'; +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 '../globals.dart' as globals; +import '../models/mood.dart'; + +class TasksProvider extends ChangeNotifier { + Map tasks = { + Pages.mood: true, + Pages.sleep: true, + Pages.timer: true, + }; + + List moodHistory = []; + List sleepHistory = []; + List workoutHistory = []; + List relapseHistory = []; + + TasksProvider(SettingsProvider? settingsProvider) { + initTasks(); + initHistories(); + } + + void setTaskDone(Pages taskName) { + tasks[taskName] = false; + notifyListeners(); + } + + void initHistories() async { + moodHistory = await globals.databaseService.getMoodRecords(); + sleepHistory = await globals.databaseService.getSleepRecords(); + workoutHistory = await globals.databaseService.getWorkoutRecords(); + relapseHistory = await globals.databaseService.getRelapseRecords(); + notifyListeners(); + } + + void initTasks() async { + DateTime now = DateTime.now(); + TZDateTime? moodToday = await getTodayMood(); + if (moodToday != null) { + List moodList = await globals.databaseService.getMoodRecords(); + if (moodList.isNotEmpty) { + Mood mood = moodList.last; + tasks[Pages.mood] = + !isSameDay(moodToday, mood.date) && moodToday.isBefore(now); + } + } else { + tasks[Pages.mood] = false; + } + TZDateTime? sleepToday = await getTodaySleep(); + if (sleepToday != null) { + List sleepList = await globals.databaseService.getSleepRecords(); + if (sleepList.isNotEmpty) { + Sleep sleep = sleepList.last; + tasks[Pages.sleep] = + !isSameDay(sleepToday, sleep.date) && sleepToday.isBefore(now); + } + } else { + tasks[Pages.sleep] = false; + } + List workoutList = + await globals.databaseService.getWorkoutRecords(); + if (workoutList.isNotEmpty) { + Workout mood = workoutList.last; + tasks[Pages.timer] = !isSameDay(DateTime.now(), mood.date); + } + notifyListeners(); + } +} diff --git a/lib/providers/timer_provider.dart b/lib/providers/timer_provider.dart index 0601b39..f355d7d 100644 --- a/lib/providers/timer_provider.dart +++ b/lib/providers/timer_provider.dart @@ -9,7 +9,6 @@ class TimerProvider extends ChangeNotifier { void startTimer(Duration duration) { started = true; - print('starting timer'); _timer = Timer.periodic(const Duration(seconds: 1), ((timer) { if (timer.tick >= duration.inSeconds) { timer.cancel(); @@ -19,5 +18,17 @@ class TimerProvider extends ChangeNotifier { })); } - void stopTimer() => _timer?.cancel(); + void stopTimer() { + started = false; + _timer?.cancel(); + _timer = null; + } + + @override + void dispose() { + started = false; + _timer?.cancel(); + _timer = null; + super.dispose(); + } } diff --git a/lib/providers/workout_provider.dart b/lib/providers/workout_provider.dart new file mode 100644 index 0000000..ebcaf9c --- /dev/null +++ b/lib/providers/workout_provider.dart @@ -0,0 +1,125 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/models/workout.dart'; +import 'package:smoke_cess_app/providers/audio_provider.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; +import '../globals.dart' as globals; + +enum WorkoutPhases { + warmUp, + highIntensity, + lowIntensity, + coolDown, +} + +class WorkoutProvider extends ChangeNotifier { + final TimerProvider timerProvider; + final AudioProvider audioProvider; + + bool isWorkoutStarted = false; + bool isWorkoutComplete = false; + int motivationBefore = 50; + int motivationAfter = 50; + + int _workoutPhaseIndex = 0; + final List _workoutPhases = [ + WorkoutPhases.warmUp, + WorkoutPhases.highIntensity, + WorkoutPhases.lowIntensity, + WorkoutPhases.highIntensity, + WorkoutPhases.lowIntensity, + WorkoutPhases.highIntensity, + WorkoutPhases.lowIntensity, + WorkoutPhases.highIntensity, + WorkoutPhases.coolDown, + ]; + + WorkoutProvider(this.timerProvider, this.audioProvider); + + WorkoutPhases get currentPhase => _workoutPhases[_workoutPhaseIndex]; + Duration get currentPhaseDuration => + _workoutPhaseSettings[currentPhase]!['duration']; + bool get isPhaseComplete => + timerProvider.elapsedSeconds - currentPhaseDuration.inSeconds == 0; + Color get currentPhaseColor => _workoutPhaseSettings[currentPhase]!['color']; + AssetSource get currentPhaseSource => + _workoutPhaseSettings[currentPhase]!['source']; + String get currentPhaseTitle => _workoutPhaseSettings[currentPhase]!['title']; + + void nextPhase() { + if (_workoutPhaseIndex < _workoutPhases.length - 1) { + _workoutPhaseIndex += 1; + audioProvider.playSourceAfterBeep(currentPhaseSource); + timerProvider.startTimer(currentPhaseDuration); + } else { + //workout completed + audioProvider.playFinishSound; + stopWorkout(); + } + } + + void startWorkout() { + isWorkoutStarted = true; + isWorkoutComplete = false; + audioProvider.playSourceAfterBeep(currentPhaseSource); + timerProvider.startTimer(currentPhaseDuration); + } + + void stopWorkout() { + isWorkoutStarted = false; + isWorkoutComplete = true; + _cleanUp(); + notifyListeners(); + } + + void interruptWorkout() { + isWorkoutStarted = false; + isWorkoutComplete = false; + _cleanUp(); + notifyListeners(); + } + + void _cleanUp() { + audioProvider.stop(); + timerProvider.stopTimer(); + } + + void saveWorkout() { + Workout workout = + Workout(motivationBefore, motivationAfter, DateTime.now()); + globals.databaseService.addWorkout(workout); + } + + @override + void dispose() { + _cleanUp(); + super.dispose(); + } +} + +Map> _workoutPhaseSettings = { + WorkoutPhases.warmUp: { + 'title': 'Warm Up', + 'duration': const Duration(seconds: 5), + 'source': AssetSource('warmUp.mp3'), + 'color': Colors.green + }, + WorkoutPhases.highIntensity: { + 'title': 'High Intensity', + 'duration': const Duration(seconds: 4), + 'source': AssetSource('workout.mp3'), + 'color': Colors.red + }, + WorkoutPhases.lowIntensity: { + 'title': 'Low Intensity', + 'duration': const Duration(seconds: 3), + 'source': AssetSource('workout.mp3'), + 'color': Colors.orange + }, + WorkoutPhases.coolDown: { + 'title': 'Cool Down', + 'duration': const Duration(seconds: 5), + 'source': AssetSource('cool_down.mp3'), + 'color': Colors.blue + } +}; diff --git a/lib/services/database_service.dart b/lib/services/database_service.dart index 030a5af..acc3ca0 100644 --- a/lib/services/database_service.dart +++ b/lib/services/database_service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:path/path.dart'; import 'package:smoke_cess_app/models/mood.dart'; +import 'package:smoke_cess_app/models/workout.dart'; import 'package:smoke_cess_app/models/relapse.dart'; import 'package:sqflite/sqflite.dart'; // ignore: depend_on_referenced_packages @@ -35,7 +36,6 @@ class DatabaseService { await db.execute(_createWorkoutTable); } - //TODO use generic function? Future> getMoodRecords() async { Database db = await instance.database; var moodRecords = await db.query('mood'); @@ -63,6 +63,15 @@ class DatabaseService { return relapseList; } + Future> getWorkoutRecords() async { + Database db = await instance.database; + var workoutRecords = await db.query('workout'); + List workoutList = workoutRecords.isNotEmpty + ? workoutRecords.map((e) => Workout.fromDatabase(e)).toList() + : []; + return workoutList; + } + Future addMood(Mood mood) async { Database db = await instance.database; return await db.insert('mood', mood.toMap()); @@ -73,13 +82,17 @@ class DatabaseService { return await db.insert('sleep', sleep.toMap()); } + Future addWorkout(Workout workout) async { + Database db = await instance.database; + return await db.insert('workout', workout.toMap()); + } + Future addRelapse(Relapse relapse) async { Database db = await instance.database; return await db.insert('relapse', relapse.toMap()); } -} -String _createMoodTable = ''' + final String _createMoodTable = ''' CREATE TABLE IF NOT EXISTS mood( id INTEGER PRIMARY KEY, value INTEGER, @@ -88,7 +101,7 @@ String _createMoodTable = ''' ) '''; -String _createSleepTable = ''' + final String _createSleepTable = ''' CREATE TABLE IF NOT EXISTS sleep( id INTEGER PRIMARY KEY, value INTEGER, @@ -101,7 +114,7 @@ String _createSleepTable = ''' ) '''; -String _createRelapseTable = ''' + final String _createRelapseTable = ''' CREATE TABLE IF NOT EXISTS relapse( id INTEGER PRIMARY KEY, date TEXT, @@ -110,14 +123,12 @@ String _createRelapseTable = ''' ) '''; -String _createWorkoutTable = ''' + final String _createWorkoutTable = ''' CREATE TABLE IF NOT EXISTS workout( id INTEGER PRIMARY KEY, date TEXT, motivationBefore INTEGER, - commentBefore TEXT, motivationAfter INTEGER, - commentAfter TEXT, - completed INTEGER ) '''; +} diff --git a/lib/services/date_service.dart b/lib/services/date_service.dart index 0fd108e..79bdd37 100644 --- a/lib/services/date_service.dart +++ b/lib/services/date_service.dart @@ -1,6 +1,8 @@ import 'package:smoke_cess_app/services/settings_service.dart'; import 'package:timezone/timezone.dart'; +import 'pages_service.dart'; + const int trainingTime = 40; const weekDays = { @@ -13,6 +15,12 @@ const weekDays = { "Sonntag": 7, }; +bool isSameDay(DateTime? dateA, DateTime? dateB) { + return dateA?.year == dateB?.year && + dateA?.month == dateB?.month && + dateA?.day == dateB?.day; +} + Future> getDatesforMood() async { final List? selectedDays = await getMoodQueryDaysCategories(); final int? selectedHours = await getMoodQueryHours(); @@ -27,6 +35,59 @@ Future> getDatesforSleep() async { return createTZDateTimes(selectedDays, selectedHours, selectedMinutes); } +Future getTodayMood() async { + List moodDates = await getDatesforMood(); + Iterable today = + moodDates.where((element) => isSameDay(element, DateTime.now())); + return today.isNotEmpty ? today.first : null; +} + +Future getTodaySleep() async { + List sleepDates = await getDatesforSleep(); + Iterable today = + sleepDates.where((element) => isSameDay(element, DateTime.now())); + return today.isNotEmpty ? today.first : null; +} + +Future getTimeTillNextMood() async { + List moodDates = await getDatesforMood(); + Iterable nextDate = + moodDates.where((element) => element.isAfter(DateTime.now())); + Duration duration = nextDate.isNotEmpty + ? nextDate.first.difference(DateTime.now()) + : const Duration(seconds: 0); + return duration; +} + +Future getTimeTillNextSleep() async { + List sleepDates = await getDatesforSleep(); + Iterable nextDate = + sleepDates.where((element) => element.isAfter(DateTime.now())); + Duration duration = nextDate.isNotEmpty + ? nextDate.first.difference(DateTime.now()) + : const Duration(seconds: 0); + return duration; +} + +Future getTimeTillNextWorkout() async { + DateTime now = DateTime.now(); + DateTime tomorrow = + DateTime(now.year, now.month, now.day).add(const Duration(days: 1)); + Duration duration = tomorrow.difference(now); + return duration; +} + +Future getTimeTill(Pages page) { + switch (page) { + case Pages.mood: + return getTimeTillNextMood(); + case Pages.sleep: + return getTimeTillNextSleep(); + default: + return getTimeTillNextWorkout(); + } +} + List createTZDateTimes( List? selectedDays, int? selectedHours, int? selectedMinutes) { final List tzDateTimes = []; diff --git a/lib/services/export_service.dart b/lib/services/export_service.dart new file mode 100644 index 0000000..9758270 --- /dev/null +++ b/lib/services/export_service.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:smoke_cess_app/models/mood.dart'; +import 'package:smoke_cess_app/models/relapse.dart'; +import 'package:smoke_cess_app/models/sleep.dart'; +import 'package:smoke_cess_app/models/workout.dart'; +import 'package:smoke_cess_app/services/database_service.dart'; +import '../globals.dart' as globals; + +class ExportService { + Uri url = Uri.parse('http://localhost:3000/data'); + final DatabaseService _databaseService = globals.databaseService; + + Future>> _loadRecords() async { + List moodRecords = await _databaseService.getMoodRecords(); + List sleepRecords = await _databaseService.getSleepRecords(); + List relapseRecords = await _databaseService.getRelapseRecords(); + List workoutRecords = await _databaseService.getWorkoutRecords(); + return { + 'Stimmung': + moodRecords.map((Mood mood) => jsonEncode(mood.toMap())).toList(), + 'Schlaf': + sleepRecords.map((Sleep sleep) => jsonEncode(sleep.toMap())).toList(), + 'Rückfall': relapseRecords + .map((Relapse relapse) => jsonEncode(relapse.toMap())) + .toList(), + 'Workout': workoutRecords + .map((Workout workout) => jsonEncode(workout.toMap())) + .toList() + }; + } + + Future exportData() async { + Map> body = await _loadRecords(); + final response = await http.post(url, + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: jsonEncode(body)); + return response.statusCode >= 400 ? 0 : 1; + } +} diff --git a/lib/services/pages_service.dart b/lib/services/pages_service.dart index 8f3ab37..0c19383 100644 --- a/lib/services/pages_service.dart +++ b/lib/services/pages_service.dart @@ -5,25 +5,38 @@ import '../pages/relapse_page.dart'; import '../pages/scanner_page.dart'; import '../pages/sleep_page.dart'; -const pages = { - 'Stimmung': { +enum Pages { + mood, + sleep, + relapse, + timer, + settings, +} + +const Map> pages = { + Pages.mood: { + 'title': 'Stimmung', 'page': MoodPage(), - 'icon': Icon(Icons.mood_outlined, color: Colors.black) + 'icon': Icon(Icons.mood_outlined, color: Colors.black), }, - 'Schlaf': { + Pages.sleep: { + 'title': 'Schlaf', 'page': SleepPage(), - 'icon': Icon(Icons.bedtime_outlined, color: Colors.black) + 'icon': Icon(Icons.bedtime_outlined, color: Colors.black), }, - 'Timer': { + Pages.timer: { + 'title': 'Timer', 'page': IntervalTimerPage(), - 'icon': Icon(Icons.timer_outlined, color: Colors.black) + 'icon': Icon(Icons.timer_outlined, color: Colors.black), }, - 'Rückfall': { + Pages.relapse: { + 'title': 'Rückfall', 'page': RelapsePage(), 'icon': Icon(Icons.smoke_free_outlined, color: Colors.black), }, - 'Scanner': { + Pages.settings: { + 'title': 'Scanner', 'page': ScannerPage(), - 'icon': Icon(Icons.camera_alt_outlined, color: Colors.black) + 'icon': Icon(Icons.camera_alt_outlined, color: Colors.black), }, }; diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 8c7e7a7..f28556a 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -23,13 +23,6 @@ Future getMoodQueryMinutes() => _getIntSetting('mood_query_minutes'); Future getChessHours() => _getIntSetting('chess_hours'); Future getChessMinutes() => _getIntSetting('chess_minutes'); -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 _setIntSetting(String settingKey, int settingValue) => SharedPreferences.getInstance() .then((pref) => pref.setInt(settingKey, settingValue)); diff --git a/lib/utils/timer_util.dart b/lib/utils/timer_util.dart index 58d9690..a63030f 100644 --- a/lib/utils/timer_util.dart +++ b/lib/utils/timer_util.dart @@ -1,4 +1,14 @@ String formatTime(int seconds) { Duration duration = Duration(seconds: seconds); - return '${duration.inMinutes.remainder(60).toString().padLeft(2, '0')}:${duration.inSeconds.remainder(60).toString().padLeft(2, '0')}'; + String formattedTime = ''; + String twoDigits(int n) => n.toString().padLeft(2, "0"); + String days = duration.inDays.toString(); + String hours = twoDigits(duration.inHours.remainder(24)); + String minutes = twoDigits(duration.inMinutes.remainder(60)); + String formattedSeconds = twoDigits(duration.inSeconds.remainder(60)); + if (duration.inDays != 0) formattedTime += '$days:'; + if (duration.inHours != 0) formattedTime += '$hours:'; + formattedTime += '$minutes:'; + formattedTime += formattedSeconds; + return formattedTime; } diff --git a/lib/widgets/drop_down.dart b/lib/widgets/drop_down.dart index 5f06d89..ee704cf 100644 --- a/lib/widgets/drop_down.dart +++ b/lib/widgets/drop_down.dart @@ -9,6 +9,7 @@ class DropDown extends StatelessWidget { @override Widget build(BuildContext context) { var inputModel = context.watch(); + inputModel.relapseCategory = _items.isNotEmpty ? _items[0] : ''; return DropdownButtonFormField( value: _items.isEmpty ? null : _items[0], icon: const Icon(Icons.arrow_downward), diff --git a/lib/widgets/mood_form.dart b/lib/widgets/mood_form.dart index b9a0e05..e43e97d 100644 --- a/lib/widgets/mood_form.dart +++ b/lib/widgets/mood_form.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:smoke_cess_app/models/mood.dart'; -import 'package:smoke_cess_app/services/database_service.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/text_formfield.dart'; @@ -15,6 +15,7 @@ class MoodForm extends StatelessWidget { @override Widget build(BuildContext context) { var inputModel = context.watch(); + var tasksModel = context.watch(); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -32,6 +33,7 @@ class MoodForm extends StatelessWidget { ), SubmitFormButton( submitCallback: inputModel.saveMood, + updateTasks: () => tasksModel.setTaskDone(Pages.mood), ) ], ); diff --git a/lib/widgets/mood_view.dart b/lib/widgets/mood_view.dart new file mode 100644 index 0000000..14884b9 --- /dev/null +++ b/lib/widgets/mood_view.dart @@ -0,0 +1,32 @@ +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/mute_button.dart b/lib/widgets/mute_button.dart new file mode 100644 index 0000000..29bebf7 --- /dev/null +++ b/lib/widgets/mute_button.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../providers/audio_provider.dart'; + +class MuteButton extends StatelessWidget { + const MuteButton({super.key}); + + @override + Widget build(BuildContext context) { + AudioProvider workoutProvider = context.watch(); + + return IconButton( + onPressed: workoutProvider.isMuted + ? workoutProvider.unMutePlayer + : workoutProvider.mutePlayer, + icon: Icon(workoutProvider.isMuted + ? Icons.volume_off_outlined + : Icons.volume_up_outlined), + ); + } +} diff --git a/lib/widgets/popup_for_start_and_stop.dart b/lib/widgets/popup_for_start_and_stop.dart index e1af268..77411a4 100644 --- a/lib/widgets/popup_for_start_and_stop.dart +++ b/lib/widgets/popup_for_start_and_stop.dart @@ -1,26 +1,52 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; import 'package:smoke_cess_app/widgets/slider.dart'; -import 'package:smoke_cess_app/widgets/text_formfield.dart'; + +Future showMotivationPopup( + BuildContext context, Function onSave, String title) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ChangeNotifierProvider( + create: (context) => InputProvider(), + child: TimerStartStopPopup( + title: title, + onSaveAction: onSave, + ), + ); + }, + ); +} class TimerStartStopPopup extends StatelessWidget { final String title; - - const TimerStartStopPopup({Key? key, required this.title}) : super(key: key); + final Function onSaveAction; + const TimerStartStopPopup( + {Key? key, required this.title, required this.onSaveAction}) + : super(key: key); @override Widget build(BuildContext context) { + InputProvider inputProvider = context.watch(); + return AlertDialog( title: Text(title), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Padding( - padding: const EdgeInsets.only(top: 8), + children: [ + const Padding( + padding: EdgeInsets.only(top: 8), child: MySlider(), ), - SizedBox(height: 16), - MyTextFormField('Beschreibe deinen Motivation'), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + onSaveAction(inputProvider.sliderValue); + Navigator.pop(context); + }, + child: const Text('Speichern')) ], ), ); diff --git a/lib/widgets/popup_for_task_done.dart b/lib/widgets/popup_for_task_done.dart new file mode 100644 index 0000000..fbaa65d --- /dev/null +++ b/lib/widgets/popup_for_task_done.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; +import 'package:smoke_cess_app/widgets/timer_widget.dart'; +import '../services/date_service.dart'; +import '../services/pages_service.dart'; + +void showTaskDonePopup(BuildContext context, Pages page) async { + Duration duration = await getTimeTill(page); + if (context.mounted) { + await showDialog( + context: context, + builder: (BuildContext context) { + return ChangeNotifierProvider( + create: (context) => TimerProvider(), + child: TaskDonePopup( + duration: duration, + ), + ); + }, + ); + } +} + +class TaskDonePopup extends StatelessWidget { + final Duration duration; + const TaskDonePopup({super.key, required this.duration}); + + @override + Widget build(BuildContext context) { + TimerProvider timerProvider = context.read(); + timerProvider.startTimer(duration); + return AlertDialog( + title: const Text('Schon gemacht'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Nächstes mal wieder:'), + TimerWidget(duration: duration) + ])); + } +} diff --git a/lib/widgets/relapse_form.dart b/lib/widgets/relapse_form.dart index 88e555c..9451277 100644 --- a/lib/widgets/relapse_form.dart +++ b/lib/widgets/relapse_form.dart @@ -1,11 +1,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/text_formfield.dart'; - import '../providers/input_provider.dart'; import '../providers/settings_provider.dart'; +import '../services/pages_service.dart'; import 'elevated_card.dart'; class RelapseForm extends StatelessWidget { @@ -15,6 +16,7 @@ class RelapseForm extends StatelessWidget { Widget build(BuildContext context) { var inputModel = context.watch(); var settingsModel = context.watch(); + var tasksModel = context.watch(); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -29,7 +31,10 @@ class RelapseForm extends StatelessWidget { const SizedBox( height: 80, ), - SubmitFormButton(submitCallback: inputModel.saveRelapse) + SubmitFormButton( + submitCallback: inputModel.saveRelapse, + updateTasks: () => tasksModel.setTaskDone(Pages.mood), + ) ], ); } diff --git a/lib/widgets/relapse_view.dart b/lib/widgets/relapse_view.dart new file mode 100644 index 0000000..48c9829 --- /dev/null +++ b/lib/widgets/relapse_view.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../providers/tasks_provider.dart'; + +class RelapseView extends StatelessWidget { + const RelapseView({super.key}); + + @override + Widget build(BuildContext context) { + var tasksModel = context.watch(); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: tasksModel.relapseHistory.map((relapse) { + return Text('${relapse.date}: ${relapse.category}'); + }).toList()); + } +} diff --git a/lib/widgets/scanner.dart b/lib/widgets/scanner.dart index 46f8c8a..b635d67 100644 --- a/lib/widgets/scanner.dart +++ b/lib/widgets/scanner.dart @@ -37,12 +37,28 @@ class MyScannerState extends State { }); } + void handleError() { + setState(() { + scanning = false; + AwesomeDialog( + context: context, + dialogType: DialogType.error, + title: 'Fehler', + desc: 'Der QR-Code war fehlerhaft!', + ).show(); + }); + } + void onDetect(capture) { - final List barcodes = capture.barcodes; - for (final barcode in barcodes) { - if (barcode.rawValue != null) { - return handleSucces(barcode.rawValue); + try { + final List barcodes = capture.barcodes; + for (final barcode in barcodes) { + if (barcode.rawValue != null) { + return handleSucces(barcode.rawValue); + } } + } catch (e) { + handleError(); } } diff --git a/lib/widgets/sleep_form.dart b/lib/widgets/sleep_form.dart index 8b4389c..4996da7 100644 --- a/lib/widgets/sleep_form.dart +++ b/lib/widgets/sleep_form.dart @@ -1,5 +1,6 @@ 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/elevated_card.dart'; import 'package:smoke_cess_app/widgets/slider.dart'; import 'package:smoke_cess_app/widgets/submit_form_button.dart'; @@ -7,6 +8,7 @@ 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'; class SleepForm extends StatelessWidget { const SleepForm({Key? key}) : super(key: key); @@ -14,20 +16,19 @@ class SleepForm extends StatelessWidget { @override Widget build(BuildContext context) { InputProvider inputModel = context.watch(); - String wokeUpKey = 'wokeUpAt'; - String sleptKey = 'sleptAt'; + TasksProvider tasksModel = context.watch(); return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - ElevatedCard( + const ElevatedCard( title: 'Einschlafzeit', - child: TimePicker(sleptKey), + child: TimePicker(SleepTimes.sleptAt), ), const SizedBox(height: 16), - ElevatedCard( + const ElevatedCard( title: 'Aufwachzeit', - child: TimePicker(wokeUpKey), + child: TimePicker(SleepTimes.wokeUpAt), ), const SizedBox(height: 16), const ElevatedCard( @@ -43,7 +44,9 @@ class SleepForm extends StatelessWidget { height: 80, ), SubmitFormButton( - submitCallback: () => inputModel.saveSleep(wokeUpKey, sleptKey), + submitCallback: () => + inputModel.saveSleep(SleepTimes.wokeUpAt, SleepTimes.sleptAt), + updateTasks: () => tasksModel.setTaskDone(Pages.mood), ) ], ); diff --git a/lib/widgets/sleep_view.dart b/lib/widgets/sleep_view.dart new file mode 100644 index 0000000..90a005c --- /dev/null +++ b/lib/widgets/sleep_view.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/models/sleep.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(); + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SfCartesianChart( + primaryXAxis: DateTimeAxis(), + 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()) + ], + ); + } +} diff --git a/lib/widgets/submit_form_button.dart b/lib/widgets/submit_form_button.dart index 3242b18..f9cb784 100644 --- a/lib/widgets/submit_form_button.dart +++ b/lib/widgets/submit_form_button.dart @@ -1,30 +1,41 @@ import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; class SubmitFormButton extends StatelessWidget { final Future Function() submitCallback; - const SubmitFormButton({super.key, required this.submitCallback}); + final void Function() updateTasks; + const SubmitFormButton( + {super.key, required this.submitCallback, required this.updateTasks}); @override Widget build(BuildContext context) { + PageProvider pageProvider = context.watch(); return Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: ElevatedButton( onPressed: () async { int success = await submitCallback(); - success != 0 - ? AwesomeDialog( - context: context, - dialogType: DialogType.success, - title: 'Gespeichert', - desc: 'Der Eintrag wurde erfolgreich gespeichert', - ).show() - : AwesomeDialog( - context: context, - dialogType: DialogType.error, - title: 'Fehler', - desc: 'Der Eintrag konnte nicht gespeichert werden', - ).show(); + if (context.mounted) { + if (success != 0) { + await AwesomeDialog( + context: context, + dialogType: DialogType.success, + title: 'Gespeichert', + desc: 'Der Eintrag wurde erfolgreich gespeichert', + ).show(); + updateTasks(); + pageProvider.swap(); + } else { + await AwesomeDialog( + context: context, + dialogType: DialogType.error, + title: 'Fehler', + desc: 'Der Eintrag konnte nicht gespeichert werden', + ).show(); + } + } }, child: const Text('Speichern'), ), diff --git a/lib/widgets/timepicker.dart b/lib/widgets/timepicker.dart index 0b0926b..d6ab447 100644 --- a/lib/widgets/timepicker.dart +++ b/lib/widgets/timepicker.dart @@ -3,7 +3,7 @@ import 'package:smoke_cess_app/providers/input_provider.dart'; import 'package:provider/provider.dart'; class TimePicker extends StatelessWidget { - final String keyMap; + final SleepTimes keyMap; const TimePicker(this.keyMap, {super.key}); diff --git a/lib/widgets/timer_widget.dart b/lib/widgets/timer_widget.dart index 875697f..4cf1d51 100644 --- a/lib/widgets/timer_widget.dart +++ b/lib/widgets/timer_widget.dart @@ -14,11 +14,6 @@ class TimerWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text(formatTime(duration.inSeconds - timerProvider.elapsedSeconds)), - ElevatedButton( - onPressed: () => timerProvider.started - ? timerProvider.stopTimer() - : timerProvider.startTimer(duration), - child: Text(timerProvider.started ? 'Stop' : 'Start')) ], ); } diff --git a/lib/widgets/todo_icon.dart b/lib/widgets/todo_icon.dart new file mode 100644 index 0000000..e5c82b6 --- /dev/null +++ b/lib/widgets/todo_icon.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class MyToDoIcon extends StatelessWidget { + final Icon _icon; + const MyToDoIcon(this._icon, {super.key}); + + @override + Widget build(BuildContext context) { + return Stack(children: [ + _icon, + const Positioned( + // draw a red marble + top: 0.0, + right: 0.0, + child: Icon(Icons.brightness_1, size: 10.0, color: Colors.redAccent), + ) + ]); + } +} diff --git a/lib/widgets/view_form_page.dart b/lib/widgets/view_form_page.dart new file mode 100644 index 0000000..57b9271 --- /dev/null +++ b/lib/widgets/view_form_page.dart @@ -0,0 +1,41 @@ +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_form.dart b/lib/widgets/workout_form.dart new file mode 100644 index 0000000..3e75882 --- /dev/null +++ b/lib/widgets/workout_form.dart @@ -0,0 +1,36 @@ +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'; + +class WorkoutForm extends StatelessWidget { + WorkoutForm({super.key}); + + final TimerProvider timerProvider = TimerProvider(); + final AudioProvider audioProvider = AudioProvider(); + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => timerProvider), + ChangeNotifierProvider(create: (context) => audioProvider), + ChangeNotifierProvider( + create: (context) => + WorkoutProvider(timerProvider, audioProvider)), + ], + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Align( + alignment: Alignment.topLeft, + child: MuteButton(), + ), + WorkoutTimerWidget() + ], + )); + } +} diff --git a/lib/widgets/workout_timer_widget.dart b/lib/widgets/workout_timer_widget.dart new file mode 100644 index 0000000..4f1b64c --- /dev/null +++ b/lib/widgets/workout_timer_widget.dart @@ -0,0 +1,93 @@ +import 'dart:async'; + +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'; + +class WorkoutTimerWidget extends StatelessWidget { + const WorkoutTimerWidget({super.key}); + + @override + Widget build(BuildContext context) { + TimerProvider timerProvider = context.watch(); + WorkoutProvider workoutProvider = context.watch(); + TasksProvider tasksProvider = context.read(); + PageProvider pageProvider = context.read(); + + void handleStopWorkout() async { + await showMotivationPopup(context, (double value) { + workoutProvider.motivationAfter = value.toInt(); + workoutProvider.saveWorkout(); + tasksProvider.setTaskDone(Pages.timer); + }, 'Motivation nach dem Training'); + if (context.mounted) { + await AwesomeDialog( + context: context, + dialogType: DialogType.success, + title: 'Gespeichert', + desc: 'Der Eintrag wurde erfolgreich gespeichert', + ).show(); + pageProvider.swap(); + } + } + + if (workoutProvider.isPhaseComplete && !workoutProvider.isWorkoutComplete) { + Timer(const Duration(milliseconds: 1), () => workoutProvider.nextPhase()); + } + + if (workoutProvider.isWorkoutComplete) { + Timer(const Duration(milliseconds: 1), handleStopWorkout); + } + + void handleStartStopWorkout() { + if (!workoutProvider.isWorkoutStarted) { + showMotivationPopup(context, (double value) { + workoutProvider.motivationBefore = value.toInt(); + workoutProvider.startWorkout(); + }, 'Motivation vor dem Training'); + } else { + workoutProvider.interruptWorkout(); + handleStopWorkout(); + } + } + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(workoutProvider.currentPhaseTitle), + const SizedBox( + height: 20, + ), + Stack( + alignment: Alignment.center, + children: [ + SizedBox( + height: 100, + width: 100, + child: CircularProgressIndicator( + color: workoutProvider.currentPhaseColor, + value: (workoutProvider.currentPhaseDuration.inSeconds + .toDouble() - + timerProvider.elapsedSeconds) / + workoutProvider.currentPhaseDuration.inSeconds)), + TimerWidget(duration: workoutProvider.currentPhaseDuration), + ], + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: handleStartStopWorkout, + child: Text(timerProvider.started ? 'Stop' : 'Start')) + ], + ); + } +} diff --git a/lib/widgets/workout_view.dart b/lib/widgets/workout_view.dart new file mode 100644 index 0000000..d0deb4d --- /dev/null +++ b/lib/widgets/workout_view.dart @@ -0,0 +1,32 @@ +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.lock b/pubspec.lock deleted file mode 100644 index 08b0b74..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,516 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.9.0" - audioplayers: - dependency: "direct main" - description: - name: audioplayers - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - audioplayers_android: - dependency: transitive - description: - name: audioplayers_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - audioplayers_darwin: - dependency: transitive - description: - name: audioplayers_darwin - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - audioplayers_linux: - dependency: transitive - description: - name: audioplayers_linux - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.4" - audioplayers_platform_interface: - dependency: transitive - description: - name: audioplayers_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.0" - audioplayers_web: - dependency: transitive - description: - name: audioplayers_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.0" - audioplayers_windows: - dependency: transitive - description: - name: audioplayers_windows - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.3" - awesome_dialog: - dependency: "direct main" - description: - name: awesome_dialog - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.dartlang.org" - source: hosted - version: "1.16.0" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.5" - dbus: - dependency: transitive - description: - name: dbus - url: "https://pub.dartlang.org" - source: hosted - version: "0.7.8" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - ffi: - dependency: transitive - description: - name: ffi - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - file: - dependency: transitive - description: - name: file - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.4" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - url: "https://pub.dartlang.org" - source: hosted - version: "13.0.0" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0+1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" - graphs: - dependency: transitive - description: - name: graphs - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.0" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.5" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.dartlang.org" - source: hosted - version: "4.0.2" - js: - dependency: transitive - description: - name: js - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.4" - lints: - dependency: transitive - description: - name: lints - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.12" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.5" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" - mobile_scanner: - dependency: "direct main" - description: - name: mobile_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.0" - nested: - dependency: transitive - description: - name: nested - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" - path: - dependency: "direct main" - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.2" - path_provider: - dependency: "direct main" - description: - name: path_provider - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.12" - path_provider_android: - dependency: transitive - description: - name: path_provider_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.22" - path_provider_foundation: - dependency: transitive - description: - name: path_provider_foundation - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.8" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.0" - platform: - dependency: transitive - description: - name: platform - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - process: - dependency: transitive - description: - name: process - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.4" - provider: - dependency: "direct main" - description: - name: provider - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.5" - rive: - dependency: transitive - description: - name: rive - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.1" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.17" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.15" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.9.0" - sqflite: - dependency: "direct main" - description: - name: sqflite - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.4+1" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.2+2" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - synchronized: - dependency: transitive - description: - name: synchronized - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.12" - timezone: - dependency: "direct main" - description: - name: timezone - url: "https://pub.dartlang.org" - source: hosted - version: "0.9.1" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - uuid: - dependency: transitive - description: - name: uuid - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.7" - vector_math: - dependency: transitive - description: - name: vector_math - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.2" - win32: - dependency: transitive - description: - name: win32 - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0+3" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "6.1.0" -sdks: - dart: ">=2.18.2 <3.0.0" - flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5c61553..690527f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,33 +1,13 @@ name: smoke_cess_app description: A new Flutter project. -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: sdk: '>=2.18.2 <3.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter @@ -36,38 +16,28 @@ dependencies: path_provider: ^2.0.12 provider: ^6.0.5 awesome_dialog: ^3.0.2 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 timezone: ^0.9.0 shared_preferences: ^2.0.17 audioplayers: ^3.0.1 mobile_scanner: ^3.0.0 flutter_local_notifications: ^13.0.0 + http: ^0.13.5 + syncfusion_flutter_charts: ^20.4.52 + cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^2.0.0 + flutter_launcher_icons: ^0.12.0 +flutter_icons: + android: 'launcher_icon' + ios: true + image_path: 'assets/ZI_logo.png' -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/beep.mp3 - assets/go.mp3 @@ -77,31 +47,3 @@ flutter: - assets/finish.mp3 - assets/group1.json - assets/group3.json - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages