Merge branch 'scanner-page' into 'main'

Get Settings input from Scanner Page

See merge request Crondung/hsma_cpd!4
main
Parricc35 2023-02-20 20:23:18 +00:00
commit 7a80851e05
16 changed files with 334 additions and 86 deletions

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdkVersion 33
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -47,7 +47,7 @@ android {
applicationId "com.example.smoke_cess_app" applicationId "com.example.smoke_cess_app"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion 21
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

15
assets/group1.json 100644
View File

@ -0,0 +1,15 @@
{
"group": 1,
"HITT_time": 35,
"relapse_categories": ["App stresst mich", "langeweile", "lunge braucht es"],
"mood_query": {
"days": ["Montag", "Freitag"],
"hours": 8,
"minutes": 30
},
"sleep_query": {
"days": ["Dienstag", "Samstag"],
"hours": 9,
"minutes": 30
}
}

19
assets/group3.json 100644
View File

@ -0,0 +1,19 @@
{
"group": 3,
"HITT_time": 35,
"chess_time": {
"hours": 8,
"minutes": 30
},
"relapse_categories": ["App stresst mich", "langeweile", "lunge braucht es"],
"mood_query": {
"days": ["Montag", "Freitag"],
"hours": 10,
"minutes": 30
},
"sleep_query": {
"days": ["Dienstag", "Samstag"],
"hours": 11,
"minutes": 30
}
}

View File

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>NSCameraUsageDescription</key>
<string>This app needs camera access to scan QR codes</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>

View File

@ -10,6 +10,6 @@ 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: MyHomePage());
} }
} }

View File

@ -0,0 +1,45 @@
import 'package:smoke_cess_app/service/json_service.dart';
class Settings {
final int group;
final List<String>? relapseCategories;
final QueryConfig moodQuery;
final QueryConfig sleepQuery;
final TimeConfig? chessTime;
Settings(this.group, this.relapseCategories, this.moodQuery, this.sleepQuery,
this.chessTime);
Settings.fromJson(Map<String, dynamic> json)
: group = json['group'] as int,
relapseCategories = jsonPropertyAsList(json['relapse_categories']),
moodQuery = QueryConfig.fromJson(json['mood_query']),
sleepQuery = QueryConfig.fromJson(json['sleep_query']),
chessTime = json['chess_time'] != null
? TimeConfig.fromJson(json['chess_time'])
: null;
}
class QueryConfig {
final int hours;
final int minutes;
final List<String>? days;
QueryConfig(this.hours, this.minutes, this.days);
QueryConfig.fromJson(Map<String, dynamic> json)
: hours = json['hours'] as int,
minutes = json['minutes'] as int,
days = jsonPropertyAsList(json['days']);
}
class TimeConfig {
final int hours;
final int minutes;
TimeConfig(this.hours, this.minutes);
TimeConfig.fromJson(Map<String, dynamic> json)
: hours = json['hours'] as int,
minutes = json['minutes'] as int;
}

View File

@ -1,9 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:smoke_cess_app/pages/mood_page.dart'; import 'package:smoke_cess_app/pages/mood_page.dart';
import 'package:smoke_cess_app/pages/relapse_page.dart'; import 'package:smoke_cess_app/pages/relapse_page.dart';
import 'package:smoke_cess_app/pages/settings_page.dart'; import 'package:smoke_cess_app/pages/scanner_page.dart';
import 'package:smoke_cess_app/pages/sleep_page.dart'; import 'package:smoke_cess_app/pages/sleep_page.dart';
import 'package:smoke_cess_app/pages/timer_page.dart'; import 'package:smoke_cess_app/pages/timer_page.dart';
import 'package:smoke_cess_app/service/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});
@ -13,30 +15,47 @@ class MyHomePage extends StatefulWidget {
} }
class MyHomePageState extends State<MyHomePage> { class MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 2; int _selectedIndex = 4;
int? _gruppe;
final List<String> _titles = [ final List<String> _titles = [
'Stimmung', 'Stimmung',
'Schlaf', 'Schlaf',
'Timer', 'Timer',
'Rückfall', 'Rückfall',
'Einstellungen' 'Scanner'
]; ];
static const List<Widget> _widgetOptions = <Widget>[ static const List<Widget> _widgetOptions = <Widget>[
MoodPage(), MoodPage(),
SleepPage(), SleepPage(),
StopWatchTimerPage(), StopWatchTimerPage(),
RelapsePage(), RelapsePage(),
SettingsPage(), ScannerPage(),
]; ];
void _onItemTapped(int index) { Future<void> _onItemTapped(int index) async {
setState(() => _selectedIndex = index); _gruppe = await getGroup();
bool isConfigured = _gruppe != null;
setState(() {
isConfigured
? _selectedIndex = index
: showDialog(
context: context,
builder: (BuildContext context) {
return const MissingConfigPopup(
title: 'Fehlende Konfiguration',
text: 'Bitte QR Code Scannen!',
);
});
});
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(_titles[_selectedIndex])), appBar: AppBar(
title: Text(
'${_titles[_selectedIndex]} ${_gruppe != null ? "Gruppe $_gruppe" : ""}')),
body: _widgetOptions.elementAt(_selectedIndex), body: _widgetOptions.elementAt(_selectedIndex),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
onDestinationSelected: _onItemTapped, onDestinationSelected: _onItemTapped,
@ -58,7 +77,7 @@ class MyHomePageState extends State<MyHomePage> {
icon: Icon(Icons.smoke_free_outlined, color: Colors.black), icon: Icon(Icons.smoke_free_outlined, color: Colors.black),
label: 'Rückfall'), label: 'Rückfall'),
NavigationDestination( NavigationDestination(
icon: Icon(Icons.settings, color: Colors.black), icon: Icon(Icons.camera_alt_outlined, color: Colors.black),
label: 'Settings'), label: 'Settings'),
], ],

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:smoke_cess_app/models/settings.dart';
import 'package:smoke_cess_app/service/json_service.dart';
import 'package:smoke_cess_app/service/settings_service.dart';
import '../widgets/missing_config_popup.dart';
class ScannerPage extends StatefulWidget {
const ScannerPage({super.key});
@override
State<StatefulWidget> createState() => ScannerPageState();
}
class ScannerPageState extends State<ScannerPage> {
bool scanning = false;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
scanning
? 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),
ElevatedButton(
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 20)),
onPressed: () {
loadSettingsFromLocalJSON();
},
child: const Text('Read JSON'),
)
],
));
}
}

View File

@ -1,10 +0,0 @@
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return const Center(child: Text('Hier können Settings eingestellt werden'));
}
}

View File

@ -11,7 +11,6 @@ class StopWatchTimerPage extends StatefulWidget {
} }
class StopWatchTimerPageState extends State<StopWatchTimerPage> { class StopWatchTimerPageState extends State<StopWatchTimerPage> {
SettingsService settings = SettingsService();
Duration duration = const Duration(minutes: 1); Duration duration = const Duration(minutes: 1);
Timer? timer; Timer? timer;
@ -19,21 +18,11 @@ class StopWatchTimerPageState extends State<StopWatchTimerPage> {
@override @override
void initState() { void initState() {
setDurationWithSetting();
super.initState(); super.initState();
} }
void setDurationWithSetting() {
settings.getIntSetting('workout_duration_minutes').then((workoutMinutes) =>
{setState(() => duration = Duration(minutes: workoutMinutes ?? 10))});
}
void reset() { void reset() {
if (countDown) { setState(() => duration = const Duration());
setDurationWithSetting();
} else {
setState(() => duration = const Duration());
}
} }
void startTimer() { void startTimer() {

View File

@ -0,0 +1,14 @@
import 'package:flutter/services.dart';
import 'dart:convert';
const String configJSONPath = 'assets/group3.json';
Future<Map<String, dynamic>> loadLocalConfigJSON() async {
String content = await rootBundle.loadString(configJSONPath);
return jsonDecode(content);
}
List<String>? jsonPropertyAsList(dynamic property) =>
property != null ? List.from(property) : null;
Map<String, dynamic> stringToJSON(String jsonString) => jsonDecode(jsonString);

View File

@ -1,25 +1,67 @@
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:smoke_cess_app/models/settings.dart';
import 'package:smoke_cess_app/service/json_service.dart';
class SettingsService { //access group setting which was saved in local storage
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance(); Future<int?> getGroup() => _getIntSetting('group');
SettingsService() { Future<List<String>?> getRelapseCategories() =>
setIntSetting('workout_duration_minutes', 5); _getStringListSetting('relapse_categories');
}
void setStringSetting(String settingKey, String settingValue) => Future<List<String>?> getSleepQueryDaysCategories() =>
_prefs.then((pref) => pref.setString(settingKey, settingValue)); _getStringListSetting('sleep_query_days');
Future<String?> getStringSetting(String settingKey) => Future<int?> getSleepQueryHours() => _getIntSetting('sleep_query_hours');
_prefs.then((pref) => pref.getString(settingKey)); Future<int?> getSleepQueryMinutes() => _getIntSetting('sleep_query_minutes');
void setIntSetting(String settingKey, int settingValue) => Future<List<String>?> getMoodQueryDaysCategories() =>
_prefs.then((pref) => pref.setInt(settingKey, settingValue)); _getStringListSetting('mood_query_days');
Future<int?> getIntSetting(String settingKey) => Future<int?> getMoodQueryHours() => _getIntSetting('mood_query_hours');
_prefs.then((pref) => pref.getInt(settingKey)); Future<int?> getMoodQueryMinutes() => _getIntSetting('mood_query_minutes');
//Add other setters and getters if needed Future<int?> getChessHours() => _getIntSetting('chess_hours');
//other possible SharedPreferences Types: Int, Bool, Double, StringList Future<int?> getChessMinutes() => _getIntSetting('chess_minutes');
//see https://pub.dev/packages/shared_preferences
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) =>
SharedPreferences.getInstance()
.then((pref) => pref.setInt(settingKey, settingValue));
Future<int?> _getIntSetting(String settingKey) =>
SharedPreferences.getInstance().then((pref) => pref.getInt(settingKey));
void _setStringListSetting(String settingKey, List<String> list) =>
SharedPreferences.getInstance()
.then((pref) => pref.setStringList(settingKey, list));
Future<List<String>?> _getStringListSetting(String settingKey) =>
SharedPreferences.getInstance()
.then((pref) => pref.getStringList(settingKey));
Future<void> loadSettingsFromLocalJSON() async {
Map<String, dynamic> configJSON = await loadLocalConfigJSON();
Settings settings = Settings.fromJson(configJSON);
saveSettings(settings);
}
void saveSettings(Settings settings) {
_setIntSetting('group', settings.group);
_setStringListSetting('relapse_categories', settings.relapseCategories!);
_setStringListSetting('mood_query_days', settings.moodQuery.days!);
_setIntSetting('mood_query_hours', settings.moodQuery.hours);
_setIntSetting('mood_query_minutes', settings.moodQuery.minutes);
_setStringListSetting('sleep_query_days', settings.sleepQuery.days!);
_setIntSetting('sleep_query_hours', settings.sleepQuery.hours);
_setIntSetting('sleep_query_minutes', settings.sleepQuery.minutes);
if (settings.chessTime != null) {
_setIntSetting('chess_hours', settings.chessTime!.hours);
_setIntSetting('chess_minutes', settings.chessTime!.minutes);
}
} }

View File

@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
class MissingConfigPopup extends StatelessWidget {
final String title;
final String text;
const MissingConfigPopup(
{super.key, required this.title, required this.text});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(title),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(text),
],
),
);
}
}

View File

@ -121,6 +121,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:

View File

@ -36,6 +36,7 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
shared_preferences: ^2.0.17 shared_preferences: ^2.0.17
mobile_scanner: ^3.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -62,6 +63,9 @@ flutter:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg # - images/a_dot_ham.jpeg
assets:
- group1.json
- group3.json
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware

View File

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- <!--
If you are serving your web app in a path other than the root, change the If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from. href value below to reflect the base path you are serving from.
@ -14,45 +14,49 @@
This is a placeholder for base href that will be replaced by the value of This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`. the `--base-href` argument provided to `flutter build`.
--> -->
<base href="$FLUTTER_BASE_HREF"> <base href="$FLUTTER_BASE_HREF" />
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A new Flutter project."> <meta name="description" content="A new Flutter project." />
<!-- iOS meta tags & icons --> <!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="smoke_cess_app"> <meta name="apple-mobile-web-app-title" content="smoke_cess_app" />
<link rel="apple-touch-icon" href="icons/Icon-192.png"> <link rel="apple-touch-icon" href="icons/Icon-192.png" />
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/> <link rel="icon" type="image/png" href="favicon.png" />
<title>smoke_cess_app</title> <title>smoke_cess_app</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json" />
<script> <script>
// The value below is injected by flutter build, do not touch. // The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null; var serviceWorkerVersion = null;
</script> </script>
<!-- This script adds the flutter initialization JS code --> <!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script> <script src="flutter.js" defer></script>
</head> </head>
<body> <body>
<script> <script>
window.addEventListener('load', function(ev) { window.addEventListener('load', function (ev) {
// Download main.dart.js // Download main.dart.js
_flutter.loader.loadEntrypoint({ _flutter.loader
serviceWorker: { .loadEntrypoint({
serviceWorkerVersion: serviceWorkerVersion, serviceWorker: {
} serviceWorkerVersion: serviceWorkerVersion,
}).then(function(engineInitializer) { },
return engineInitializer.initializeEngine(); })
}).then(function(appRunner) { .then(function (engineInitializer) {
return appRunner.runApp(); return engineInitializer.initializeEngine();
})
.then(function (appRunner) {
return appRunner.runApp();
});
}); });
}); </script>
</script> <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
</body> </body>
</html> </html>