Merge branch 'main' into 29-provider-refactoring

main
Julian Gegner 2023-03-02 16:09:24 +01:00
commit da0bf43087
20 changed files with 426 additions and 163 deletions

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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/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/settings_provider.dart';
void main() { void main() {
// to ensure all the widgets are initialized. // to ensure all the widgets are initialized.
@ -21,6 +23,11 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const MaterialApp(title: _title, home: MyHomePage()); return MaterialApp(
title: _title,
home: ChangeNotifierProvider(
create: (context) => SettingsProvider(),
child: const MyHomePage(),
));
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:smoke_cess_app/interface/db_record.dart'; 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/sleep.dart'; import 'package:smoke_cess_app/models/sleep.dart';
import 'package:smoke_cess_app/models/workout.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';
@ -14,6 +15,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<DatabaseRecord> _workoutRecords = []; final List<DatabaseRecord> _workoutRecords = [];
@override @override
@ -34,6 +36,12 @@ class DatabaseMock implements DatabaseService {
return Future.value(1); return Future.value(1);
} }
@override
Future<int> addRelapse(Relapse relapse) {
_relapseRecords.add(relapse);
return Future.value(1);
}
@override @override
Future<Database> get database => DatabaseService.instance.database; Future<Database> get database => DatabaseService.instance.database;
@ -46,4 +54,9 @@ class DatabaseMock implements DatabaseService {
Future<List<Sleep>> getSleepRecords() { Future<List<Sleep>> getSleepRecords() {
return Future.value(_sleepRecords); return Future.value(_sleepRecords);
} }
@override
Future<List<Relapse>> getRelapseRecords() {
return Future.value(_relapseRecords);
}
} }

View File

@ -0,0 +1,29 @@
import 'package:smoke_cess_app/interface/db_record.dart';
class Relapse implements DatabaseRecord {
final String _category;
final String _comment;
final DateTime _date;
Relapse(this._category, this._comment, this._date);
@override
factory Relapse.fromDatabase(Map<String, dynamic> map) {
DateTime date = DateTime.parse(map['date']);
return Relapse(map['category'], map['comment'], date);
}
@override
String toCSV() {
return "${_date.toIso8601String()}, $_category, $_comment";
}
@override
Map<String, dynamic> toMap() {
return {
'category': _category,
'comment': _comment,
'date': _date.toIso8601String(),
};
}
}

View File

@ -3,8 +3,8 @@ import 'package:smoke_cess_app/services/json_service.dart';
class Settings { class Settings {
final int group; final int group;
final List<String>? relapseCategories; final List<String>? relapseCategories;
final QueryConfig moodQuery; final QueryConfig? moodQuery;
final QueryConfig sleepQuery; final QueryConfig? sleepQuery;
final TimeConfig? chessTime; final TimeConfig? chessTime;
Settings(this.group, this.relapseCategories, this.moodQuery, this.sleepQuery, Settings(this.group, this.relapseCategories, this.moodQuery, this.sleepQuery,
@ -21,8 +21,8 @@ class Settings {
} }
class QueryConfig { class QueryConfig {
final int hours; final int? hours;
final int minutes; final int? minutes;
final List<String>? days; final List<String>? days;
QueryConfig(this.hours, this.minutes, this.days); QueryConfig(this.hours, this.minutes, this.days);
@ -34,8 +34,8 @@ class QueryConfig {
} }
class TimeConfig { class TimeConfig {
final int hours; final int? hours;
final int minutes; final int? minutes;
TimeConfig(this.hours, this.minutes); TimeConfig(this.hours, this.minutes);

View File

@ -1,11 +1,8 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/pages/mood_page.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/pages/relapse_page.dart'; import 'package:smoke_cess_app/services/pages_service.dart';
import 'package:smoke_cess_app/pages/scanner_page.dart'; import 'package:smoke_cess_app/providers/settings_provider.dart';
import 'package:smoke_cess_app/pages/sleep_page.dart';
import 'package:smoke_cess_app/pages/interval_page.dart';
import 'package:smoke_cess_app/services/settings_service.dart';
import 'package:smoke_cess_app/widgets/missing_config_popup.dart';
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key}); const MyHomePage({super.key});
@ -15,74 +12,43 @@ class MyHomePage extends StatefulWidget {
} }
class MyHomePageState extends State<MyHomePage> { class MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 2; int _selectedIndex = 4;
int? _gruppe; bool _isConfigured = false;
final List<String> _titles = [ void _onItemTapped(int index) {
'Stimmung',
'Schlaf',
'Timer',
'Rückfall',
'Scanner'
];
static const List<Widget> _widgetOptions = <Widget>[
MoodPage(),
SleepPage(),
IntervalTimerPage(),
RelapsePage(),
ScannerPage(),
];
Future<void> _onItemTapped(int index) async {
_gruppe = await getGroup();
bool isConfigured = _gruppe != null;
setState(() { setState(() {
isConfigured _isConfigured
? _selectedIndex = index ? _selectedIndex = index
: showDialog( : AwesomeDialog(
context: context, context: context,
builder: (BuildContext context) { dialogType: DialogType.info,
return const MissingConfigPopup( title: 'Fehlende Konfiguration',
title: 'Fehlende Konfiguration', desc: 'Bitte QR Code Scannen!',
text: 'Bitte QR Code Scannen!', ).show();
);
});
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var settingsModel = context.watch<SettingsProvider>();
var group = settingsModel.settings?.group;
_isConfigured = settingsModel.initialized;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
'${_titles[_selectedIndex]} ${_gruppe != null ? "Gruppe $_gruppe" : ""}')), '${pages.keys.elementAt(_selectedIndex)} ${_isConfigured ? "Gruppe $group" : ""}')),
body: _widgetOptions.elementAt(_selectedIndex), body: Center(
child: SingleChildScrollView(
child: pages.values.elementAt(_selectedIndex)['page'])),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
onDestinationSelected: _onItemTapped, onDestinationSelected: _onItemTapped,
selectedIndex: _selectedIndex, selectedIndex: _selectedIndex,
destinations: const <Widget>[ destinations: pages.keys.map((key) {
NavigationDestination( return NavigationDestination(
icon: Icon(Icons.mood_outlined, color: Colors.black), icon: pages[key]!['icon'] ??
label: 'Stimmung'), const Icon(Icons.disabled_by_default),
NavigationDestination( label: key);
icon: Icon(Icons.bedtime_outlined, color: Colors.black), }).toList()),
label: 'Schlaf'),
NavigationDestination(
icon: Icon(
Icons.timer_outlined,
color: Colors.black,
),
label: 'Timer'),
NavigationDestination(
icon: Icon(Icons.smoke_free_outlined, color: Colors.black),
label: 'Rückfall'),
NavigationDestination(
icon: Icon(Icons.camera_alt_outlined, color: Colors.black),
label: 'Settings'),
],
//onTap: _onItemTapped,
),
); );
} }
} }

View File

@ -1,10 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/widgets/relapse_form.dart';
import '../providers/input_provider.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 const Center(child: Text('Hier werden Rückfälle dokumentiert')); return Center(
child: ChangeNotifierProvider(
create: (context) => InputProvider(),
child: const RelapseForm(),
));
} }
} }

View File

@ -1,25 +1,45 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:provider/provider.dart';
import 'package:smoke_cess_app/models/mood.dart'; import 'package:smoke_cess_app/models/mood.dart';
import 'package:smoke_cess_app/models/settings.dart'; import 'package:smoke_cess_app/models/relapse.dart';
import 'package:smoke_cess_app/services/database_service.dart';
import 'package:smoke_cess_app/services/json_service.dart';
import 'package:smoke_cess_app/services/settings_service.dart'; import 'package:smoke_cess_app/services/settings_service.dart';
import 'package:smoke_cess_app/services/notification_service.dart'; import 'package:smoke_cess_app/services/notification_service.dart';
import 'package:smoke_cess_app/widgets/scanner.dart';
import '../models/sleep.dart'; import '../models/sleep.dart';
import '../widgets/missing_config_popup.dart'; import '../providers/settings_provider.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
class ScannerPage extends StatefulWidget { class ScannerPage extends StatelessWidget {
const ScannerPage({super.key}); const ScannerPage({super.key});
@override void export() async {
State<StatefulWidget> createState() => ScannerPageState(); List<Mood> moods = await globals.databaseService.getMoodRecords();
} List<Sleep> sleeps = await globals.databaseService.getSleepRecords();
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());
}
}
class ScannerPageState extends State<ScannerPage> { void loadJSON(BuildContext context) async {
bool scanning = false; var settingsModel = context.read<SettingsProvider>();
await loadSettingsFromLocalJSON();
settingsModel.initSettings();
NotificationService().setAllNotifications();
AwesomeDialog(
context: context,
dialogType: DialogType.success,
title: 'Geschafft',
desc: 'Die Einstellung wurden erfolgreich gespeichert',
).show();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,70 +47,19 @@ class ScannerPageState extends State<ScannerPage> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
scanning const MyScanner(),
? Expanded(
child: MobileScanner(
fit: BoxFit.contain,
controller: MobileScannerController(
detectionTimeoutMs: 2000,
),
onDetect: (capture) {
//TODO Errorhandling!!
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
if (barcode.rawValue != null) {
String qrText = barcode.rawValue!;
Map<String, dynamic> json = stringToJSON(qrText);
Settings settings = Settings.fromJson(json);
saveSettings(settings);
setState(() {
scanning = false;
showDialog(
context: context,
builder: (BuildContext context) {
return MissingConfigPopup(
title: 'Konfiguration erfolgreich',
text: 'Du gehörst zu Gruppe ${settings.group}',
);
});
});
}
}
},
))
: ElevatedButton(
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20)),
onPressed: () {
setState(() => scanning = true);
},
child: const Text('Scan QR Code'),
),
const SizedBox(height: 30), const SizedBox(height: 30),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20)), textStyle: const TextStyle(fontSize: 20)),
onPressed: () { onPressed: () => loadJSON(context),
loadSettingsFromLocalJSON();
NotificationService().setAllNotifications();
},
child: const Text('Read JSON'), child: const Text('Read JSON'),
), ),
const SizedBox(height: 30), const SizedBox(height: 30),
ElevatedButton( ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20)), textStyle: const TextStyle(fontSize: 20)),
onPressed: () async { onPressed: export,
List<Mood> moods = await globals.databaseService.getMoodRecords();
List<Sleep> sleeps =
await globals.databaseService.getSleepRecords();
for (Mood mood in moods) {
print(mood.toCSV());
}
for (Sleep sleep in sleeps) {
print(sleep.toCSV());
}
},
child: const Text('Export'), child: const Text('Export'),
) )
], ],

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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/sleep.dart'; import 'package:smoke_cess_app/models/sleep.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
@ -10,15 +11,22 @@ class InputProvider extends ChangeNotifier {
'wokeUpAt': const TimeOfDay(hour: 8, minute: 0), 'wokeUpAt': const TimeOfDay(hour: 8, minute: 0),
'sleptAt': const TimeOfDay(hour: 22, minute: 0), 'sleptAt': const TimeOfDay(hour: 22, minute: 0),
}; };
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) {
_relapseCategory = newValue;
notifyListeners();
}
TimeOfDay getTimeEntry(String key) { TimeOfDay getTimeEntry(String key) {
return _times[key] ?? const TimeOfDay(hour: 12, minute: 0); return _times[key] ?? const TimeOfDay(hour: 12, minute: 0);
} }
@ -36,17 +44,24 @@ class InputProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
void saveMood() { Future<int> saveMood() {
Mood mood = Mood mood =
Mood(_sliderValue.toInt(), _textController.text, DateTime.now()); Mood(_sliderValue.toInt(), _textController.text, DateTime.now());
globals.databaseService.addMood(mood);
_resetFields(); _resetFields();
return globals.databaseService.addMood(mood);
} }
void saveSleep(String wokeUpKey, String sleptKey) { Future<int> saveRelapse() {
Relapse relapse =
Relapse(_relapseCategory, _textController.text, DateTime.now());
_resetFields();
return globals.databaseService.addRelapse(relapse);
}
Future<int> saveSleep(String wokeUpKey, String 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));
globals.databaseService.addSleep(sleep);
_resetFields(); _resetFields();
return globals.databaseService.addSleep(sleep);
} }
} }

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:smoke_cess_app/services/settings_service.dart';
import '../models/settings.dart';
class SettingsProvider extends ChangeNotifier {
Settings? _settings;
bool _initialized = false;
Settings? get settings => _settings;
bool get initialized => _initialized;
SettingsProvider() {
initSettings();
}
void initSettings() async {
_settings = await loadSettings();
_initialized = _settings != null ? true : false;
notifyListeners();
}
}

View File

@ -3,6 +3,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/workout.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
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -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');
@ -54,6 +54,15 @@ class DatabaseService {
return sleepList; return sleepList;
} }
Future<List<Relapse>> getRelapseRecords() async {
Database db = await instance.database;
var relapseRecords = await db.query('relapse');
List<Relapse> relapseList = relapseRecords.isNotEmpty
? relapseRecords.map((e) => Relapse.fromDatabase(e)).toList()
: [];
return relapseList;
}
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());
@ -68,9 +77,13 @@ class DatabaseService {
Database db = await instance.database; Database db = await instance.database;
return await db.insert('workout', workout.toMap()); return await db.insert('workout', workout.toMap());
} }
}
String _createMoodTable = ''' Future<int> addRelapse(Relapse relapse) async {
Database db = await instance.database;
return await db.insert('relapse', relapse.toMap());
}
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,
@ -79,7 +92,7 @@ String _createMoodTable = '''
) )
'''; ''';
String _createSleepTable = ''' 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,
@ -92,7 +105,7 @@ String _createSleepTable = '''
) )
'''; ''';
String _createRelapseTable = ''' 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,
@ -101,7 +114,7 @@ String _createRelapseTable = '''
) )
'''; ''';
String _createWorkoutTable = ''' 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,
@ -109,3 +122,4 @@ String _createWorkoutTable = '''
motivationAfter INTEGER, motivationAfter INTEGER,
) )
'''; ''';
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import '../pages/interval_page.dart';
import '../pages/mood_page.dart';
import '../pages/relapse_page.dart';
import '../pages/scanner_page.dart';
import '../pages/sleep_page.dart';
const pages = {
'Stimmung': {
'page': MoodPage(),
'icon': Icon(Icons.mood_outlined, color: Colors.black)
},
'Schlaf': {
'page': SleepPage(),
'icon': Icon(Icons.bedtime_outlined, color: Colors.black)
},
'Timer': {
'page': IntervalTimerPage(),
'icon': Icon(Icons.timer_outlined, color: Colors.black)
},
'Rückfall': {
'page': RelapsePage(),
'icon': Icon(Icons.smoke_free_outlined, color: Colors.black),
},
'Scanner': {
'page': ScannerPage(),
'icon': Icon(Icons.camera_alt_outlined, color: Colors.black)
},
};

View File

@ -54,14 +54,37 @@ Future<void> loadSettingsFromLocalJSON() async {
void saveSettings(Settings settings) { void saveSettings(Settings settings) {
_setIntSetting('group', settings.group); _setIntSetting('group', settings.group);
_setStringListSetting('relapse_categories', settings.relapseCategories!); _setStringListSetting('relapse_categories', settings.relapseCategories!);
_setStringListSetting('mood_query_days', settings.moodQuery.days!); _setStringListSetting('mood_query_days', settings.moodQuery!.days!);
_setIntSetting('mood_query_hours', settings.moodQuery.hours); _setIntSetting('mood_query_hours', settings.moodQuery!.hours!);
_setIntSetting('mood_query_minutes', settings.moodQuery.minutes); _setIntSetting('mood_query_minutes', settings.moodQuery!.minutes!);
_setStringListSetting('sleep_query_days', settings.sleepQuery.days!); _setStringListSetting('sleep_query_days', settings.sleepQuery!.days!);
_setIntSetting('sleep_query_hours', settings.sleepQuery.hours); _setIntSetting('sleep_query_hours', settings.sleepQuery!.hours!);
_setIntSetting('sleep_query_minutes', settings.sleepQuery.minutes); _setIntSetting('sleep_query_minutes', settings.sleepQuery!.minutes!);
if (settings.chessTime != null) { if (settings.chessTime != null) {
_setIntSetting('chess_hours', settings.chessTime!.hours); _setIntSetting('chess_hours', settings.chessTime!.hours!);
_setIntSetting('chess_minutes', settings.chessTime!.minutes); _setIntSetting('chess_minutes', settings.chessTime!.minutes!);
} }
} }
Future<Settings?> loadSettings() async {
int? group = await getGroup();
List<String>? relapseCategories = await getRelapseCategories();
int? moodHours = await getMoodQueryHours();
int? moodMinutes = await getMoodQueryMinutes();
List<String>? moodDays = await getMoodQueryDaysCategories();
int? sleepHours = await getSleepQueryHours();
int? sleepMinutes = await getSleepQueryMinutes();
List<String>? sleepDays = await getSleepQueryDaysCategories();
int? chessHours = await getChessHours();
int? chessMinutes = await getChessMinutes();
if (group != null) {
return Settings(
group,
relapseCategories,
QueryConfig(moodHours, moodMinutes, moodDays),
QueryConfig(sleepHours, sleepMinutes, sleepDays),
TimeConfig(chessHours, chessMinutes));
}
return null;
}

View File

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/input_provider.dart';
class DropDown extends StatelessWidget {
final List<String> _items;
const DropDown(this._items, {super.key});
@override
Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>();
return DropdownButtonFormField<String>(
value: _items.isEmpty ? null : _items[0],
icon: const Icon(Icons.arrow_downward),
elevation: 16,
onChanged: (String? value) {
inputModel.relapseCategory = value ?? '';
},
items: _items.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
}
}

View File

@ -30,9 +30,8 @@ class MoodForm extends StatelessWidget {
const SizedBox( const SizedBox(
height: 80, height: 80,
), ),
ElevatedButton( SubmitFormButton(
onPressed: () => inputModel.saveMood(), submitCallback: inputModel.saveMood,
child: const Text('Speichern'),
) )
], ],
); );

View File

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:provider/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 'elevated_card.dart';
class RelapseForm extends StatelessWidget {
const RelapseForm({super.key});
@override
Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>();
var settingsModel = context.watch<SettingsProvider>();
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedCard(
title: 'Rückfallkategorie',
child: DropDown(settingsModel.settings?.relapseCategories ?? []),
),
const ElevatedCard(
title: 'Beschreibe deinen Rückfall',
child: MyTextFormField('Beschreibe deinen Rückfall'),
),
const SizedBox(
height: 80,
),
SubmitFormButton(submitCallback: inputModel.saveRelapse)
],
);
}
}

View File

@ -0,0 +1,68 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:provider/provider.dart';
import 'package:smoke_cess_app/models/settings.dart';
import 'package:smoke_cess_app/services/json_service.dart';
import 'package:smoke_cess_app/services/settings_service.dart';
import '../providers/settings_provider.dart';
import '../services/notification_service.dart';
class MyScanner extends StatefulWidget {
const MyScanner({super.key});
@override
State<StatefulWidget> createState() => MyScannerState();
}
class MyScannerState extends State<MyScanner> {
bool scanning = false;
void handleSucces(String? rawValue) {
String qrText = rawValue!;
Map<String, dynamic> json = stringToJSON(qrText);
Settings settings = Settings.fromJson(json);
saveSettings(settings);
var settingsModel = context.read<SettingsProvider>();
settingsModel.initSettings();
NotificationService().setAllNotifications();
setState(() {
scanning = false;
AwesomeDialog(
context: context,
dialogType: DialogType.success,
title: 'Geschafft',
desc: 'Der Code wurde erfolgreich gescannt!',
).show();
});
}
void onDetect(capture) {
final List<Barcode> barcodes = capture.barcodes;
for (final barcode in barcodes) {
if (barcode.rawValue != null) {
return handleSucces(barcode.rawValue);
}
}
}
@override
Widget build(BuildContext context) {
return scanning
? Expanded(
child: MobileScanner(
fit: BoxFit.contain,
controller: MobileScannerController(
detectionTimeoutMs: 2000,
),
onDetect: onDetect))
: ElevatedButton(
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20)),
onPressed: () {
setState(() => scanning = true);
},
child: const Text('Scan QR Code'),
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/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/text_formfield.dart'; import 'package:smoke_cess_app/widgets/text_formfield.dart';
import 'package:smoke_cess_app/widgets/timepicker.dart'; import 'package:smoke_cess_app/widgets/timepicker.dart';
@ -41,9 +42,9 @@ class SleepForm extends StatelessWidget {
const SizedBox( const SizedBox(
height: 80, height: 80,
), ),
ElevatedButton( SubmitFormButton(
onPressed: () => inputModel.saveSleep(wokeUpKey, sleptKey), submitCallback: () => inputModel.saveSleep(wokeUpKey, sleptKey),
child: const Text('Speichern')) )
], ],
); );
} }

View File

@ -1,15 +1,31 @@
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SubmitFormButton extends StatelessWidget { class SubmitFormButton extends StatelessWidget {
final VoidCallback submitCallback; final Future<int> Function() submitCallback;
const SubmitFormButton(this.submitCallback, {super.key}); const SubmitFormButton({super.key, required this.submitCallback});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton( child: ElevatedButton(
onPressed: submitCallback, 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();
},
child: const Text('Speichern'), child: const Text('Speichern'),
), ),
); );

View File

@ -64,6 +64,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.3" 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: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -177,6 +184,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
graphs:
dependency: transitive
description:
name: graphs
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
http: http:
dependency: transitive dependency: transitive
description: description:
@ -324,6 +338,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.5" version: "6.0.5"
rive:
dependency: transitive
description:
name: rive
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -35,6 +35,7 @@ dependencies:
path: path:
path_provider: ^2.0.12 path_provider: ^2.0.12
provider: ^6.0.5 provider: ^6.0.5
awesome_dialog: ^3.0.2
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.