Merge branch 'main' into testing_branch

main
Hinrik Ehrenfried 2023-03-06 11:40:11 +01:00
commit baa104d118
78 changed files with 1066 additions and 993 deletions

1
.gitignore vendored
View File

@ -31,6 +31,7 @@ migrate_working_dir/
.pub-cache/ .pub-cache/
.pub/ .pub/
/build/ /build/
pubspec.lock
# Symbolication related # Symbolication related
app.*.symbols app.*.symbols

View File

@ -1,4 +1,4 @@
image: cirrusci/flutter:latest image: cirrusci/flutter:3.7.5
stages: stages:
- analyze - analyze

View File

@ -1,9 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.smoke_cess_app"> package="com.example.smoke_cess_app">
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:label="smoke_cess_app" android:label="ZI SmokeFree"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/launcher_icon">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
assets/ZI_logo.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -7,7 +7,7 @@
}, },
"relapse_categories": ["App stresst mich", "langeweile", "lunge braucht es"], "relapse_categories": ["App stresst mich", "langeweile", "lunge braucht es"],
"mood_query": { "mood_query": {
"days": ["Montag", "Freitag"], "days": ["Montag", "Donnerstag"],
"hours": 10, "hours": 10,
"minutes": 30 "minutes": 30
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Smoke Cess App</string> <string>ZI SmokeFree</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@ -15,7 +15,7 @@
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>smoke_cess_app</string> <string>ZI SmokeFree</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View File

@ -1,9 +1,11 @@
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/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/services/notification_service.dart'; import 'package:smoke_cess_app/services/notification_service.dart';
import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/data/latest.dart' as tz;
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'providers/page_provider.dart';
import 'providers/settings_provider.dart'; import 'providers/settings_provider.dart';
void main() { void main() {
@ -23,11 +25,21 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MultiProvider(
title: _title, providers: [
home: ChangeNotifierProvider( ChangeNotifierProvider(create: (context) => SettingsProvider()),
create: (context) => SettingsProvider(), ChangeNotifierProxyProvider<SettingsProvider, TasksProvider>(
child: const MyHomePage(), create: (context) => TasksProvider(null),
update: (context, value, TasksProvider? previous) =>
TasksProvider(value),
),
ChangeNotifierProvider(
create: (context) => PageProvider(),
),
],
child: const MaterialApp(
title: _title,
home: MyHomePage(),
)); ));
} }
} }

View File

@ -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/mood.dart';
import 'package:smoke_cess_app/models/relapse.dart'; import 'package:smoke_cess_app/models/relapse.dart';
import 'package:smoke_cess_app/models/sleep.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 'package:smoke_cess_app/services/database_service.dart';
// ignore: depend_on_referenced_packages
import 'package:sqflite_common/sqlite_api.dart'; import 'package:sqflite_common/sqlite_api.dart';
class DatabaseMock implements DatabaseService { class DatabaseMock implements DatabaseService {
@ -15,7 +16,7 @@ class DatabaseMock implements DatabaseService {
final List<Mood> _moodRecords = []; final List<Mood> _moodRecords = [];
final List<Sleep> _sleepRecords = []; final List<Sleep> _sleepRecords = [];
final List<Relapse> _relapseRecords = []; final List<Relapse> _relapseRecords = [];
final List<DatabaseRecord> _workoutRecords = []; final List<Workout> _workoutRecords = [];
@override @override
Future<int> addMood(Mood mood) { Future<int> addMood(Mood mood) {
@ -29,6 +30,12 @@ class DatabaseMock implements DatabaseService {
return Future.value(1); return Future.value(1);
} }
@override
Future<int> addWorkout(Workout workout) {
_workoutRecords.add(workout);
return Future.value(1);
}
@override @override
Future<int> addRelapse(Relapse relapse) { Future<int> addRelapse(Relapse relapse) {
_relapseRecords.add(relapse); _relapseRecords.add(relapse);
@ -36,7 +43,6 @@ class DatabaseMock implements DatabaseService {
} }
@override @override
// TODO: implement database
Future<Database> get database => DatabaseService.instance.database; Future<Database> get database => DatabaseService.instance.database;
@override @override
@ -53,4 +59,9 @@ class DatabaseMock implements DatabaseService {
Future<List<Relapse>> getRelapseRecords() { Future<List<Relapse>> getRelapseRecords() {
return Future.value(_relapseRecords); return Future.value(_relapseRecords);
} }
@override
Future<List<Workout>> getWorkoutRecords() {
return Future.value(_workoutRecords);
}
} }

View File

@ -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<String, dynamic> map) {
return HIITWorkout(map['_workoutDuration'], map['_commentBefore'],
map['_commentAfter'], map['_workoutDate']);
}
@override
String toCSV() =>
"${_workoutDate.toIso8601String()}, $_workoutDuration, $_commentBefore, $_commentAfter";
@override
Map<String, dynamic> toMap() {
return {
'workoutDuration': _workoutDuration,
'commentBefore': _commentBefore,
'commentAfter': _commentAfter,
'workoutDate': _workoutDate,
};
}
}

View File

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

View File

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

View File

@ -11,6 +11,9 @@ class Sleep implements DatabaseRecord {
Sleep(this._sleepQualityValue, this._comment, this._date, this._sleptAt, Sleep(this._sleepQualityValue, this._comment, this._date, this._sleptAt,
this._wokeUpAt); this._wokeUpAt);
DateTime get date => _date;
int get sleepQualitiyValue => _sleepQualityValue;
@override @override
factory Sleep.fromDatabase(Map<String, dynamic> map) { factory Sleep.fromDatabase(Map<String, dynamic> map) {
DateTime date = DateTime.parse(map['date']); DateTime date = DateTime.parse(map['date']);

View File

@ -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<String, dynamic> map) {
return Workout(map['motivationBefore'], map['motivationAfter'],
DateTime.parse(map['workoutDate']));
}
@override
factory Workout.fromMap(Map<String, dynamic> map) {
return Workout(
map['motivationBefore'], map['motivationAfter'], map['workoutDate']);
}
@override
String toCSV() =>
"${_workoutDate.toIso8601String()}, $_motivationBefore, $_motivationAfter";
@override
Map<String, dynamic> toMap() {
return {
'motivationBefore': _motivationBefore,
'motivationAfter': _motivationAfter,
'workoutDate': _workoutDate.toIso8601String(),
};
}
}

View File

@ -1,225 +1,16 @@
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/providers/timer_provider.dart'; import 'package:smoke_cess_app/widgets/workout_form.dart';
import 'package:smoke_cess_app/widgets/popup_for_start_and_stop.dart'; import 'package:smoke_cess_app/widgets/workout_view.dart';
import 'package:smoke_cess_app/widgets/timer_widget.dart';
import '../providers/input_provider.dart'; import '../widgets/view_form_page.dart';
class IntervalTimerPage extends StatefulWidget { class IntervalTimerPage extends StatelessWidget {
const IntervalTimerPage({Key? key}) : super(key: key); const IntervalTimerPage({super.key});
@override
_IntervalTimerPageState createState() => _IntervalTimerPageState();
}
class _IntervalTimerPageState extends State<IntervalTimerPage> {
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<void> _playWarmUpMusic() async {
Source source = AssetSource('warmUp.mp3');
await warmUpPlayer.setReleaseMode(ReleaseMode.loop);
await warmUpPlayer.play(source);
}
Future<void> _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<void> _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));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProvider( return ViewFormPage(
create: (context) => TimerProvider(), form: WorkoutForm(), view: const WorkoutView(), page: Pages.timer);
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();
}
},
),
// ),
],
),
],
)));
} }
} }

View File

@ -1,9 +1,13 @@
import 'package:awesome_dialog/awesome_dialog.dart'; 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/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/providers/settings_provider.dart'; import 'package:smoke_cess_app/providers/settings_provider.dart';
import '../widgets/todo_icon.dart';
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); const MyHomePage({super.key});
@ -16,38 +20,43 @@ class MyHomePageState extends State<MyHomePage> {
bool _isConfigured = false; bool _isConfigured = false;
void _onItemTapped(int index) { void _onItemTapped(int index) {
PageProvider pageProvider = context.read<PageProvider>();
setState(() { setState(() {
_isConfigured if (_isConfigured) {
? _selectedIndex = index pageProvider.showForm = false;
: AwesomeDialog( _selectedIndex = index;
context: context, return;
dialogType: DialogType.info, }
title: 'Fehlende Konfiguration', AwesomeDialog(
desc: 'Bitte QR Code Scannen!', context: context,
).show(); 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>(); var settingsModel = context.watch<SettingsProvider>();
var group = settingsModel.settings?.group; var tasksModel = context.watch<TasksProvider>();
_isConfigured = settingsModel.initialized; _isConfigured = settingsModel.initialized;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
'${pages.keys.elementAt(_selectedIndex)} ${_isConfigured ? "Gruppe $group" : ""}')), '${pages.values.elementAt(_selectedIndex)['title']} ${_isConfigured ? "Gruppe ${settingsModel.settings?.group}" : ""}')),
body: Center( body: SingleChildScrollView(
child: SingleChildScrollView( child: pages.values.elementAt(_selectedIndex)['page'],
child: pages.values.elementAt(_selectedIndex)['page'])), ),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
onDestinationSelected: _onItemTapped, onDestinationSelected: _onItemTapped,
selectedIndex: _selectedIndex, selectedIndex: _selectedIndex,
destinations: pages.keys.map((key) { destinations: pages.keys.map((key) {
return NavigationDestination( return NavigationDestination(
icon: pages[key]!['icon'] ?? icon: tasksModel.tasks[key] ?? false
const Icon(Icons.disabled_by_default), ? MyToDoIcon(pages[key]?['icon'])
label: key); : pages[key]!['icon'],
label: pages[key]?['title']);
}).toList()), }).toList()),
); );
} }

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/providers/input_provider.dart';
import 'package:smoke_cess_app/widgets/mood_form.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 { class MoodPage extends StatelessWidget {
const MoodPage({super.key}); const MoodPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return const ViewFormPage(
child: ChangeNotifierProvider( form: MoodForm(), view: MoodView(), page: Pages.mood);
create: (context) => InputProvider(),
child: const MoodForm(),
));
} }
} }

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart'; 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 '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 { class RelapsePage extends StatelessWidget {
const RelapsePage({super.key}); const RelapsePage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return const ViewFormPage(
child: ChangeNotifierProvider( form: RelapseForm(), view: RelapseView(), page: Pages.relapse);
create: (context) => InputProvider(),
child: const RelapseForm(),
));
} }
} }

View File

@ -1,31 +1,18 @@
import 'package:awesome_dialog/awesome_dialog.dart'; 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/models/mood.dart'; import 'package:smoke_cess_app/services/export_service.dart';
import 'package:smoke_cess_app/models/relapse.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/scanner.dart'; import 'package:smoke_cess_app/widgets/scanner.dart';
import '../models/sleep.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});
void export() async { void export() async {
List<Mood> moods = await globals.databaseService.getMoodRecords(); ExportService exportService = ExportService();
List<Sleep> sleeps = await globals.databaseService.getSleepRecords(); exportService.exportData();
List<Relapse> 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());
}
} }
void loadJSON(BuildContext context) async { void loadJSON(BuildContext context) async {
@ -33,12 +20,14 @@ class ScannerPage extends StatelessWidget {
await loadSettingsFromLocalJSON(); await loadSettingsFromLocalJSON();
settingsModel.initSettings(); settingsModel.initSettings();
NotificationService().setAllNotifications(); NotificationService().setAllNotifications();
AwesomeDialog( if (context.mounted) {
context: context, AwesomeDialog(
dialogType: DialogType.success, context: context,
title: 'Geschafft', dialogType: DialogType.success,
desc: 'Die Einstellung wurden erfolgreich gespeichert', title: 'Geschafft',
).show(); desc: 'Die Einstellung wurden erfolgreich gespeichert',
).show();
}
} }
@override @override

View File

@ -1,17 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/providers/input_provider.dart';
import 'package:smoke_cess_app/widgets/sleep_form.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 { class SleepPage extends StatelessWidget {
const SleepPage({super.key}); const SleepPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return const ViewFormPage(
child: ChangeNotifierProvider( form: SleepForm(), view: SleepView(), page: Pages.sleep);
create: (context) => InputProvider(),
child: const SleepForm(),
));
} }
} }

View File

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

View File

@ -4,34 +4,33 @@ import 'package:smoke_cess_app/models/relapse.dart';
import 'package:smoke_cess_app/models/sleep.dart'; import 'package:smoke_cess_app/models/sleep.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
enum SleepTimes {
wokeUpAt,
sleptAt,
}
class InputProvider extends ChangeNotifier { class InputProvider extends ChangeNotifier {
double _sliderValue = 50; double _sliderValue = 50;
final TextEditingController _textController = TextEditingController(text: ''); final TextEditingController _textController = TextEditingController(text: '');
final Map<String, TimeOfDay> _times = { final Map<SleepTimes, TimeOfDay> _times = {
'wokeUpAt': const TimeOfDay(hour: 8, minute: 0), SleepTimes.wokeUpAt: const TimeOfDay(hour: 8, minute: 0),
'sleptAt': const TimeOfDay(hour: 22, minute: 0), SleepTimes.sleptAt: const TimeOfDay(hour: 22, minute: 0),
}; };
String _relapseCategory = ''; String relapseCategory = '';
double get sliderValue => _sliderValue; double get sliderValue => _sliderValue;
TextEditingController get textController => _textController; TextEditingController get textController => _textController;
String get relapseCategory => _relapseCategory;
set sliderValue(double newValue) { set sliderValue(double newValue) {
_sliderValue = newValue; _sliderValue = newValue;
notifyListeners(); notifyListeners();
} }
set relapseCategory(String newValue) { TimeOfDay getTimeEntry(SleepTimes key) {
_relapseCategory = newValue;
notifyListeners();
}
TimeOfDay getTimeEntry(String key) {
return _times[key] ?? const TimeOfDay(hour: 12, minute: 0); return _times[key] ?? const TimeOfDay(hour: 12, minute: 0);
} }
void setTime(String key, TimeOfDay time) { void setTime(SleepTimes key, TimeOfDay time) {
_times[key] = time; _times[key] = time;
notifyListeners(); notifyListeners();
} }
@ -39,8 +38,8 @@ class InputProvider extends ChangeNotifier {
void _resetFields() { void _resetFields() {
_sliderValue = 50; _sliderValue = 50;
_textController.text = ''; _textController.text = '';
setTime('wokeUpAt', const TimeOfDay(hour: 8, minute: 0)); setTime(SleepTimes.wokeUpAt, const TimeOfDay(hour: 8, minute: 0));
setTime('sleptAt', const TimeOfDay(hour: 22, minute: 0)); setTime(SleepTimes.sleptAt, const TimeOfDay(hour: 22, minute: 0));
notifyListeners(); notifyListeners();
} }
@ -53,12 +52,12 @@ class InputProvider extends ChangeNotifier {
Future<int> saveRelapse() { Future<int> saveRelapse() {
Relapse relapse = Relapse relapse =
Relapse(_relapseCategory, _textController.text, DateTime.now()); Relapse(relapseCategory, _textController.text, DateTime.now());
_resetFields(); _resetFields();
return globals.databaseService.addRelapse(relapse); return globals.databaseService.addRelapse(relapse);
} }
Future<int> saveSleep(String wokeUpKey, String sleptKey) { Future<int> saveSleep(SleepTimes wokeUpKey, SleepTimes sleptKey) {
Sleep sleep = Sleep(_sliderValue.toInt(), _textController.text, Sleep sleep = Sleep(_sliderValue.toInt(), _textController.text,
DateTime.now(), getTimeEntry(sleptKey), getTimeEntry(wokeUpKey)); DateTime.now(), getTimeEntry(sleptKey), getTimeEntry(wokeUpKey));
_resetFields(); _resetFields();

View File

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class PageProvider extends ChangeNotifier {
bool showForm = false;
void swap() {
showForm = !showForm;
notifyListeners();
}
}

View File

@ -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<Pages, bool> tasks = {
Pages.mood: true,
Pages.sleep: true,
Pages.timer: true,
};
List<Mood> moodHistory = [];
List<Sleep> sleepHistory = [];
List<Workout> workoutHistory = [];
List<Relapse> 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<Mood> 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<Sleep> 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<Workout> workoutList =
await globals.databaseService.getWorkoutRecords();
if (workoutList.isNotEmpty) {
Workout mood = workoutList.last;
tasks[Pages.timer] = !isSameDay(DateTime.now(), mood.date);
}
notifyListeners();
}
}

View File

@ -9,7 +9,6 @@ class TimerProvider extends ChangeNotifier {
void startTimer(Duration duration) { void startTimer(Duration duration) {
started = true; started = true;
print('starting timer');
_timer = Timer.periodic(const Duration(seconds: 1), ((timer) { _timer = Timer.periodic(const Duration(seconds: 1), ((timer) {
if (timer.tick >= duration.inSeconds) { if (timer.tick >= duration.inSeconds) {
timer.cancel(); 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();
}
} }

View File

@ -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 = [
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<WorkoutPhases, Map<String, dynamic>> _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
}
};

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:smoke_cess_app/models/mood.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:smoke_cess_app/models/relapse.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
// ignore: depend_on_referenced_packages // ignore: depend_on_referenced_packages
@ -35,7 +36,6 @@ class DatabaseService {
await db.execute(_createWorkoutTable); await db.execute(_createWorkoutTable);
} }
//TODO use generic function?
Future<List<Mood>> getMoodRecords() async { Future<List<Mood>> getMoodRecords() async {
Database db = await instance.database; Database db = await instance.database;
var moodRecords = await db.query('mood'); var moodRecords = await db.query('mood');
@ -63,6 +63,15 @@ class DatabaseService {
return relapseList; return relapseList;
} }
Future<List<Workout>> getWorkoutRecords() async {
Database db = await instance.database;
var workoutRecords = await db.query('workout');
List<Workout> workoutList = workoutRecords.isNotEmpty
? workoutRecords.map((e) => Workout.fromDatabase(e)).toList()
: [];
return workoutList;
}
Future<int> addMood(Mood mood) async { Future<int> addMood(Mood mood) async {
Database db = await instance.database; Database db = await instance.database;
return await db.insert('mood', mood.toMap()); return await db.insert('mood', mood.toMap());
@ -73,13 +82,17 @@ class DatabaseService {
return await db.insert('sleep', sleep.toMap()); return await db.insert('sleep', sleep.toMap());
} }
Future<int> addWorkout(Workout workout) async {
Database db = await instance.database;
return await db.insert('workout', workout.toMap());
}
Future<int> addRelapse(Relapse relapse) async { Future<int> addRelapse(Relapse relapse) async {
Database db = await instance.database; Database db = await instance.database;
return await db.insert('relapse', relapse.toMap()); return await db.insert('relapse', relapse.toMap());
} }
}
String _createMoodTable = ''' final String _createMoodTable = '''
CREATE TABLE IF NOT EXISTS mood( CREATE TABLE IF NOT EXISTS mood(
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
value INTEGER, value INTEGER,
@ -88,7 +101,7 @@ String _createMoodTable = '''
) )
'''; ''';
String _createSleepTable = ''' final String _createSleepTable = '''
CREATE TABLE IF NOT EXISTS sleep( CREATE TABLE IF NOT EXISTS sleep(
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
value INTEGER, value INTEGER,
@ -101,7 +114,7 @@ String _createSleepTable = '''
) )
'''; ''';
String _createRelapseTable = ''' final String _createRelapseTable = '''
CREATE TABLE IF NOT EXISTS relapse( CREATE TABLE IF NOT EXISTS relapse(
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
date TEXT, date TEXT,
@ -110,14 +123,12 @@ String _createRelapseTable = '''
) )
'''; ''';
String _createWorkoutTable = ''' final String _createWorkoutTable = '''
CREATE TABLE IF NOT EXISTS workout( CREATE TABLE IF NOT EXISTS workout(
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
date TEXT, date TEXT,
motivationBefore INTEGER, motivationBefore INTEGER,
commentBefore TEXT,
motivationAfter INTEGER, motivationAfter INTEGER,
commentAfter TEXT,
completed INTEGER
) )
'''; ''';
}

View File

@ -1,6 +1,8 @@
import 'package:smoke_cess_app/services/settings_service.dart'; import 'package:smoke_cess_app/services/settings_service.dart';
import 'package:timezone/timezone.dart'; import 'package:timezone/timezone.dart';
import 'pages_service.dart';
const int trainingTime = 40; const int trainingTime = 40;
const weekDays = { const weekDays = {
@ -13,6 +15,12 @@ const weekDays = {
"Sonntag": 7, "Sonntag": 7,
}; };
bool isSameDay(DateTime? dateA, DateTime? dateB) {
return dateA?.year == dateB?.year &&
dateA?.month == dateB?.month &&
dateA?.day == dateB?.day;
}
Future<List<TZDateTime>> getDatesforMood() async { Future<List<TZDateTime>> getDatesforMood() async {
final List<String>? selectedDays = await getMoodQueryDaysCategories(); final List<String>? selectedDays = await getMoodQueryDaysCategories();
final int? selectedHours = await getMoodQueryHours(); final int? selectedHours = await getMoodQueryHours();
@ -27,6 +35,59 @@ Future<List<TZDateTime>> getDatesforSleep() async {
return createTZDateTimes(selectedDays, selectedHours, selectedMinutes); return createTZDateTimes(selectedDays, selectedHours, selectedMinutes);
} }
Future<TZDateTime?> getTodayMood() async {
List<TZDateTime> moodDates = await getDatesforMood();
Iterable<TZDateTime> today =
moodDates.where((element) => isSameDay(element, DateTime.now()));
return today.isNotEmpty ? today.first : null;
}
Future<TZDateTime?> getTodaySleep() async {
List<TZDateTime> sleepDates = await getDatesforSleep();
Iterable<TZDateTime> today =
sleepDates.where((element) => isSameDay(element, DateTime.now()));
return today.isNotEmpty ? today.first : null;
}
Future<Duration> getTimeTillNextMood() async {
List<TZDateTime> moodDates = await getDatesforMood();
Iterable<TZDateTime> nextDate =
moodDates.where((element) => element.isAfter(DateTime.now()));
Duration duration = nextDate.isNotEmpty
? nextDate.first.difference(DateTime.now())
: const Duration(seconds: 0);
return duration;
}
Future<Duration> getTimeTillNextSleep() async {
List<TZDateTime> sleepDates = await getDatesforSleep();
Iterable<TZDateTime> nextDate =
sleepDates.where((element) => element.isAfter(DateTime.now()));
Duration duration = nextDate.isNotEmpty
? nextDate.first.difference(DateTime.now())
: const Duration(seconds: 0);
return duration;
}
Future<Duration> 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<Duration> getTimeTill(Pages page) {
switch (page) {
case Pages.mood:
return getTimeTillNextMood();
case Pages.sleep:
return getTimeTillNextSleep();
default:
return getTimeTillNextWorkout();
}
}
List<TZDateTime> createTZDateTimes( List<TZDateTime> createTZDateTimes(
List<String>? selectedDays, int? selectedHours, int? selectedMinutes) { List<String>? selectedDays, int? selectedHours, int? selectedMinutes) {
final List<TZDateTime> tzDateTimes = []; final List<TZDateTime> tzDateTimes = [];

View File

@ -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<Map<String, List<String>>> _loadRecords() async {
List<Mood> moodRecords = await _databaseService.getMoodRecords();
List<Sleep> sleepRecords = await _databaseService.getSleepRecords();
List<Relapse> relapseRecords = await _databaseService.getRelapseRecords();
List<Workout> 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<int> exportData() async {
Map<String, List<String>> body = await _loadRecords();
final response = await http.post(url,
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(body));
return response.statusCode >= 400 ? 0 : 1;
}
}

View File

@ -5,25 +5,38 @@ import '../pages/relapse_page.dart';
import '../pages/scanner_page.dart'; import '../pages/scanner_page.dart';
import '../pages/sleep_page.dart'; import '../pages/sleep_page.dart';
const pages = { enum Pages {
'Stimmung': { mood,
sleep,
relapse,
timer,
settings,
}
const Map<Pages, Map<String, dynamic>> pages = {
Pages.mood: {
'title': 'Stimmung',
'page': MoodPage(), '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(), '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(), '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(), 'page': RelapsePage(),
'icon': Icon(Icons.smoke_free_outlined, color: Colors.black), 'icon': Icon(Icons.smoke_free_outlined, color: Colors.black),
}, },
'Scanner': { Pages.settings: {
'title': 'Scanner',
'page': ScannerPage(), 'page': ScannerPage(),
'icon': Icon(Icons.camera_alt_outlined, color: Colors.black) 'icon': Icon(Icons.camera_alt_outlined, color: Colors.black),
}, },
}; };

View File

@ -23,13 +23,6 @@ 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');
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 _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));

View File

@ -1,4 +1,14 @@
String formatTime(int seconds) { String formatTime(int seconds) {
Duration duration = Duration(seconds: 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;
} }

View File

@ -9,6 +9,7 @@ class DropDown extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>(); var inputModel = context.watch<InputProvider>();
inputModel.relapseCategory = _items.isNotEmpty ? _items[0] : '';
return DropdownButtonFormField<String>( return DropdownButtonFormField<String>(
value: _items.isEmpty ? null : _items[0], value: _items.isEmpty ? null : _items[0],
icon: const Icon(Icons.arrow_downward), icon: const Icon(Icons.arrow_downward),

View File

@ -1,7 +1,7 @@
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/mood.dart'; import 'package:smoke_cess_app/providers/tasks_provider.dart';
import 'package:smoke_cess_app/services/database_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/submit_form_button.dart';
import 'package:smoke_cess_app/widgets/text_formfield.dart'; import 'package:smoke_cess_app/widgets/text_formfield.dart';
@ -15,6 +15,7 @@ class MoodForm extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>(); var inputModel = context.watch<InputProvider>();
var tasksModel = context.watch<TasksProvider>();
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -32,6 +33,7 @@ class MoodForm extends StatelessWidget {
), ),
SubmitFormButton( SubmitFormButton(
submitCallback: inputModel.saveMood, submitCallback: inputModel.saveMood,
updateTasks: () => tasksModel.setTaskDone(Pages.mood),
) )
], ],
); );

View File

@ -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<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,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<AudioProvider>();
return IconButton(
onPressed: workoutProvider.isMuted
? workoutProvider.unMutePlayer
: workoutProvider.mutePlayer,
icon: Icon(workoutProvider.isMuted
? Icons.volume_off_outlined
: Icons.volume_up_outlined),
);
}
}

View File

@ -1,26 +1,52 @@
import 'package:flutter/material.dart'; 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/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 { class TimerStartStopPopup extends StatelessWidget {
final String title; final String title;
final Function onSaveAction;
const TimerStartStopPopup({Key? key, required this.title}) : super(key: key); const TimerStartStopPopup(
{Key? key, required this.title, required this.onSaveAction})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
InputProvider inputProvider = context.watch<InputProvider>();
return AlertDialog( return AlertDialog(
title: Text(title), title: Text(title),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: const [ children: [
Padding( const Padding(
padding: const EdgeInsets.only(top: 8), padding: EdgeInsets.only(top: 8),
child: MySlider(), child: MySlider(),
), ),
SizedBox(height: 16), const SizedBox(height: 16),
MyTextFormField('Beschreibe deinen Motivation'), ElevatedButton(
onPressed: () {
onSaveAction(inputProvider.sliderValue);
Navigator.pop(context);
},
child: const Text('Speichern'))
], ],
), ),
); );

View File

@ -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>();
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,11 +1,12 @@
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/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/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 '../providers/input_provider.dart';
import '../providers/settings_provider.dart'; import '../providers/settings_provider.dart';
import '../services/pages_service.dart';
import 'elevated_card.dart'; import 'elevated_card.dart';
class RelapseForm extends StatelessWidget { class RelapseForm extends StatelessWidget {
@ -15,6 +16,7 @@ class RelapseForm extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
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>();
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -29,7 +31,10 @@ class RelapseForm extends StatelessWidget {
const SizedBox( const SizedBox(
height: 80, height: 80,
), ),
SubmitFormButton(submitCallback: inputModel.saveRelapse) SubmitFormButton(
submitCallback: inputModel.saveRelapse,
updateTasks: () => tasksModel.setTaskDone(Pages.mood),
)
], ],
); );
} }

View File

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

View File

@ -37,12 +37,28 @@ class MyScannerState extends State<MyScanner> {
}); });
} }
void handleError() {
setState(() {
scanning = false;
AwesomeDialog(
context: context,
dialogType: DialogType.error,
title: 'Fehler',
desc: 'Der QR-Code war fehlerhaft!',
).show();
});
}
void onDetect(capture) { void onDetect(capture) {
final List<Barcode> barcodes = capture.barcodes; try {
for (final barcode in barcodes) { final List<Barcode> barcodes = capture.barcodes;
if (barcode.rawValue != null) { for (final barcode in barcodes) {
return handleSucces(barcode.rawValue); if (barcode.rawValue != null) {
return handleSucces(barcode.rawValue);
}
} }
} catch (e) {
handleError();
} }
} }

View File

@ -1,5 +1,6 @@
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/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/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 'package:smoke_cess_app/widgets/timepicker.dart';
import '../providers/input_provider.dart'; import '../providers/input_provider.dart';
import '../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);
@ -14,20 +16,19 @@ class SleepForm extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
InputProvider inputModel = context.watch<InputProvider>(); InputProvider inputModel = context.watch<InputProvider>();
String wokeUpKey = 'wokeUpAt'; TasksProvider tasksModel = context.watch<TasksProvider>();
String sleptKey = 'sleptAt';
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedCard( const ElevatedCard(
title: 'Einschlafzeit', title: 'Einschlafzeit',
child: TimePicker(sleptKey), child: TimePicker(SleepTimes.sleptAt),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
ElevatedCard( const ElevatedCard(
title: 'Aufwachzeit', title: 'Aufwachzeit',
child: TimePicker(wokeUpKey), child: TimePicker(SleepTimes.wokeUpAt),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const ElevatedCard( const ElevatedCard(
@ -43,7 +44,9 @@ class SleepForm extends StatelessWidget {
height: 80, height: 80,
), ),
SubmitFormButton( SubmitFormButton(
submitCallback: () => inputModel.saveSleep(wokeUpKey, sleptKey), submitCallback: () =>
inputModel.saveSleep(SleepTimes.wokeUpAt, SleepTimes.sleptAt),
updateTasks: () => tasksModel.setTaskDone(Pages.mood),
) )
], ],
); );

View File

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

View File

@ -1,30 +1,41 @@
import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/providers/page_provider.dart';
class SubmitFormButton extends StatelessWidget { class SubmitFormButton extends StatelessWidget {
final Future<int> Function() submitCallback; final Future<int> Function() submitCallback;
const SubmitFormButton({super.key, required this.submitCallback}); final void Function() updateTasks;
const SubmitFormButton(
{super.key, required this.submitCallback, required this.updateTasks});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
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: ElevatedButton(
onPressed: () async { onPressed: () async {
int success = await submitCallback(); int success = await submitCallback();
success != 0 if (context.mounted) {
? AwesomeDialog( if (success != 0) {
context: context, await AwesomeDialog(
dialogType: DialogType.success, context: context,
title: 'Gespeichert', dialogType: DialogType.success,
desc: 'Der Eintrag wurde erfolgreich gespeichert', title: 'Gespeichert',
).show() desc: 'Der Eintrag wurde erfolgreich gespeichert',
: AwesomeDialog( ).show();
context: context, updateTasks();
dialogType: DialogType.error, pageProvider.swap();
title: 'Fehler', } else {
desc: 'Der Eintrag konnte nicht gespeichert werden', await AwesomeDialog(
).show(); context: context,
dialogType: DialogType.error,
title: 'Fehler',
desc: 'Der Eintrag konnte nicht gespeichert werden',
).show();
}
}
}, },
child: const Text('Speichern'), child: const Text('Speichern'),
), ),

View File

@ -3,7 +3,7 @@ import 'package:smoke_cess_app/providers/input_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class TimePicker extends StatelessWidget { class TimePicker extends StatelessWidget {
final String keyMap; final SleepTimes keyMap;
const TimePicker(this.keyMap, {super.key}); const TimePicker(this.keyMap, {super.key});

View File

@ -14,11 +14,6 @@ class TimerWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text(formatTime(duration.inSeconds - timerProvider.elapsedSeconds)), Text(formatTime(duration.inSeconds - timerProvider.elapsedSeconds)),
ElevatedButton(
onPressed: () => timerProvider.started
? timerProvider.stopTimer()
: timerProvider.startTimer(duration),
child: Text(timerProvider.started ? 'Stop' : 'Start'))
], ],
); );
} }

View File

@ -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: <Widget>[
_icon,
const Positioned(
// draw a red marble
top: 0.0,
right: 0.0,
child: Icon(Icons.brightness_1, size: 10.0, color: Colors.redAccent),
)
]);
}
}

View File

@ -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<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

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

View File

@ -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<TimerProvider>();
WorkoutProvider workoutProvider = context.watch<WorkoutProvider>();
TasksProvider tasksProvider = context.read<TasksProvider>();
PageProvider pageProvider = context.read<PageProvider>();
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'))
],
);
}
}

View File

@ -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<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

@ -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"

View File

@ -1,33 +1,13 @@
name: smoke_cess_app name: smoke_cess_app
description: A new Flutter project. description: A new Flutter project.
# The following line prevents the package from being accidentally published to publish_to: 'none'
# 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
# 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 version: 1.0.0+1
environment: environment:
sdk: '>=2.18.2 <3.0.0' 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: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
@ -36,38 +16,28 @@ dependencies:
path_provider: ^2.0.12 path_provider: ^2.0.12
provider: ^6.0.5 provider: ^6.0.5
awesome_dialog: ^3.0.2 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 timezone: ^0.9.0
shared_preferences: ^2.0.17 shared_preferences: ^2.0.17
audioplayers: ^3.0.1 audioplayers: ^3.0.1
mobile_scanner: ^3.0.0 mobile_scanner: ^3.0.0
flutter_local_notifications: ^13.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: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter 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_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: 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 uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets: assets:
- assets/beep.mp3 - assets/beep.mp3
- assets/go.mp3 - assets/go.mp3
@ -77,31 +47,3 @@ flutter:
- assets/finish.mp3 - assets/finish.mp3
- assets/group1.json - assets/group1.json
- assets/group3.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