diff --git a/README.md b/README.md
index 274732f..ee7623a 100644
--- a/README.md
+++ b/README.md
@@ -14,3 +14,13 @@ Die App lässt sich als Android- und iOS App ausführen und zu Debugzwecken eben
## App bedienen
Um die App nutzen zu können, müssen Settings eingelesen werden. Dazu kann entweder ein in die App integrierter QR-Code Scanner (mit dem beigefügten [QR-Code](./gruppe1_QR.png)) benutzt werden, oder zu Debugzwecken mit dem dafür vorgesehenen Button auf der Scannerseite über eine lokale JSON-Datei.
+Es ist zu beachten, dass das verwendete QR-Code Scanner Package für Mobilgeräte optimiert ist. Deshalb kann es zu Abstürzen beim Verwenden des Scanners mit der Web-Version kommen.
+Beim Verwenden der Web-Version sollten die Settings mit der lokalen JSON-Datei eingelesen werden.
+Der Button zum Einlesen der lokalen JSON-Datei kann über die Variable bool useLocalConfig
in der Datei [globals.dart](./lib/globals.dart) ein- und ausgeblendet werden.
+
+## Authoren
+
+- Hinrik Ehrenfried 2012537
+- Patrick Meßmer 1911768
+- Kai Mannweiler 2012491
+- Julian Gegner 1922635
diff --git a/android/app/build.gradle b/android/app/build.gradle
index af69fb2..24b12bc 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -70,5 +70,7 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'androidx.window:window:1.0.0'
+ implementation 'androidx.window:window-java:1.0.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
}
diff --git a/lib/globals.dart b/lib/globals.dart
index 58229d9..3cdf823 100644
--- a/lib/globals.dart
+++ b/lib/globals.dart
@@ -3,5 +3,8 @@ library app.globals;
import 'package:smoke_cess_app/mock/db_mock.dart';
import 'package:smoke_cess_app/services/database_service.dart';
-DatabaseService databaseService = DatabaseMock();
+DatabaseService databaseService = DatabaseMock();
// DatabaseService databaseService = DatabaseService.instance;
+
+// set this to read settings from local json file instead of scanning a qr code
+bool useLocalConfig = false;
diff --git a/lib/pages/scanner_page.dart b/lib/pages/scanner_page.dart
index ebba252..eee7dd0 100644
--- a/lib/pages/scanner_page.dart
+++ b/lib/pages/scanner_page.dart
@@ -4,8 +4,10 @@ import 'package:provider/provider.dart';
import 'package:smoke_cess_app/services/export_service.dart';
import 'package:smoke_cess_app/services/settings_service.dart';
import 'package:smoke_cess_app/services/notification_service.dart';
+import 'package:smoke_cess_app/widgets/buttons/text_icon_button.dart';
import 'package:smoke_cess_app/widgets/scanner.dart';
import '../providers/settings_provider.dart';
+import '../globals.dart' as globals;
class ScannerPage extends StatelessWidget {
const ScannerPage({super.key});
@@ -16,7 +18,7 @@ class ScannerPage extends StatelessWidget {
}
void loadJSON(BuildContext context) async {
- var settingsModel = context.read();
+ SettingsProvider settingsModel = context.read();
await loadSettingsFromLocalJSON();
settingsModel.initSettings();
NotificationService().setAllNotifications();
@@ -32,25 +34,26 @@ class ScannerPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ SettingsProvider settingsProvider = context.watch();
+
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const MyScanner(),
const SizedBox(height: 30),
- ElevatedButton(
- style: ElevatedButton.styleFrom(
- textStyle: const TextStyle(fontSize: 20)),
- onPressed: () => loadJSON(context),
- child: const Text('Read JSON'),
- ),
- const SizedBox(height: 30),
- ElevatedButton(
- style: ElevatedButton.styleFrom(
- textStyle: const TextStyle(fontSize: 20)),
- onPressed: export,
- child: const Text('Export'),
- )
+ if (!settingsProvider.scanning)
+ TextIconButton(
+ text: 'Export',
+ onPressed: ExportService().exportData,
+ iconData: Icons.upload),
+ if (globals.useLocalConfig && !settingsProvider.scanning)
+ ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ textStyle: const TextStyle(fontSize: 20)),
+ onPressed: () => loadJSON(context),
+ child: const Text('Read JSON'),
+ ),
],
));
}
diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart
index f51c5e1..121ff0d 100644
--- a/lib/providers/settings_provider.dart
+++ b/lib/providers/settings_provider.dart
@@ -6,9 +6,16 @@ import '../models/settings.dart';
class SettingsProvider extends ChangeNotifier {
Settings? _settings;
bool _initialized = false;
+ bool _scanning = false;
Settings? get settings => _settings;
bool get initialized => _initialized;
+ bool get scanning => _scanning;
+
+ set scanning(bool value) {
+ _scanning = value;
+ notifyListeners();
+ }
SettingsProvider() {
initSettings();
diff --git a/lib/utils/timer_util.dart b/lib/utils/timer_util.dart
index 42a9180..57b9f9c 100644
--- a/lib/utils/timer_util.dart
+++ b/lib/utils/timer_util.dart
@@ -8,7 +8,9 @@ String formatTime(int seconds) {
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.inDays != 0) {
+ formattedTime += '$days Tag ${duration.inDays > 1 ? "e" : ""}, ';
+ }
if (duration.inHours != 0) formattedTime += '$hours:';
formattedTime += '$minutes:';
formattedTime += formattedSeconds;
diff --git a/lib/widgets/buttons/text_icon_button.dart b/lib/widgets/buttons/text_icon_button.dart
new file mode 100644
index 0000000..85534e2
--- /dev/null
+++ b/lib/widgets/buttons/text_icon_button.dart
@@ -0,0 +1,29 @@
+import 'package:flutter/material.dart';
+
+class TextIconButton extends StatelessWidget {
+ final String text;
+ final VoidCallback onPressed;
+ final IconData iconData;
+
+ const TextIconButton(
+ {super.key,
+ required this.text,
+ required this.onPressed,
+ required this.iconData});
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ width: MediaQuery.of(context).size.width * 0.4,
+ child: FloatingActionButton.extended(
+ label: Text(text),
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ icon: Icon(
+ iconData,
+ size: 24.0,
+ ),
+ onPressed: onPressed,
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/popup/popup_for_task_done.dart b/lib/widgets/popup/popup_for_task_done.dart
index cf7f7ed..4565aca 100644
--- a/lib/widgets/popup/popup_for_task_done.dart
+++ b/lib/widgets/popup/popup_for_task_done.dart
@@ -1,43 +1,45 @@
+import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
-import 'package:smoke_cess_app/providers/timer_provider.dart';
-import 'package:smoke_cess_app/widgets/timer_widget.dart';
import 'package:smoke_cess_app/services/date_service.dart';
import 'package:smoke_cess_app/services/pages_service.dart';
+import 'package:smoke_cess_app/widgets/timer_widget.dart';
+
+import '../../providers/timer_provider.dart';
void showTaskDonePopup(BuildContext context, Pages page) async {
Duration duration = await getTimeTill(page);
if (context.mounted) {
- await showDialog(
- context: context,
- builder: (BuildContext context) {
- return ChangeNotifierProvider(
- create: (context) => TimerProvider(),
- child: TaskDonePopup(
- duration: duration,
- ),
- );
- },
- );
- }
-}
-
-class TaskDonePopup extends StatelessWidget {
- final Duration duration;
- const TaskDonePopup({super.key, required this.duration});
-
- @override
- Widget build(BuildContext context) {
- TimerProvider timerProvider = context.read();
- timerProvider.startTimer(duration);
- return AlertDialog(
- title: const Text('Schon gemacht'),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Text('Nächstes mal wieder:'),
- TimerWidget(duration: duration)
- ]));
+ AwesomeDialog(
+ context: context,
+ dialogType: DialogType.info,
+ body: Column(
+ children: [
+ Text(
+ '${pages[page]?['title']} schon eingegeben',
+ style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
+ ),
+ const SizedBox(
+ height: 10,
+ ),
+ const Text(
+ 'Nächste Abfrage in',
+ ),
+ const SizedBox(
+ height: 8,
+ ),
+ ChangeNotifierProvider(
+ create: (context) => TimerProvider(),
+ builder: (context, child) {
+ TimerProvider timerProvider = context.read();
+ timerProvider.startTimer(duration);
+ return TimerWidget(duration: duration);
+ },
+ ),
+ const SizedBox(
+ height: 15,
+ ),
+ ],
+ )).show();
}
}
diff --git a/lib/widgets/scanner.dart b/lib/widgets/scanner.dart
index b635d67..c292bf4 100644
--- a/lib/widgets/scanner.dart
+++ b/lib/widgets/scanner.dart
@@ -5,80 +5,81 @@ 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 'package:smoke_cess_app/widgets/buttons/text_icon_button.dart';
import '../providers/settings_provider.dart';
import '../services/notification_service.dart';
-class MyScanner extends StatefulWidget {
+class MyScanner extends StatelessWidget {
const MyScanner({super.key});
@override
- State createState() => MyScannerState();
-}
+ Widget build(BuildContext context) {
+ SettingsProvider settingsProvider = context.watch();
-class MyScannerState extends State {
- bool scanning = false;
+ void handleSucces(String? rawValue) {
+ String qrText = rawValue!;
+ Map json = stringToJSON(qrText);
+ Settings settings = Settings.fromJson(json);
+ saveSettings(settings);
+ settingsProvider.initSettings();
+ NotificationService().setAllNotifications();
+ settingsProvider.scanning = false;
- void handleSucces(String? rawValue) {
- String qrText = rawValue!;
- Map json = stringToJSON(qrText);
- Settings settings = Settings.fromJson(json);
- saveSettings(settings);
- var settingsModel = context.read();
- settingsModel.initSettings();
- NotificationService().setAllNotifications();
- setState(() {
- scanning = false;
AwesomeDialog(
context: context,
dialogType: DialogType.success,
title: 'Geschafft',
desc: 'Der Code wurde erfolgreich gescannt!',
).show();
- });
- }
+ }
+
+ void handleError() {
+ settingsProvider.scanning = false;
- void handleError() {
- setState(() {
- scanning = false;
AwesomeDialog(
context: context,
dialogType: DialogType.error,
title: 'Fehler',
desc: 'Der QR-Code war fehlerhaft!',
).show();
- });
- }
-
- void onDetect(capture) {
- try {
- final List barcodes = capture.barcodes;
- for (final barcode in barcodes) {
- if (barcode.rawValue != null) {
- return handleSucces(barcode.rawValue);
- }
- }
- } catch (e) {
- handleError();
}
- }
- @override
- Widget build(BuildContext context) {
- return scanning
+ void onDetect(capture) {
+ try {
+ final List barcodes = capture.barcodes;
+ for (final barcode in barcodes) {
+ if (barcode.rawValue != null) {
+ return handleSucces(barcode.rawValue);
+ }
+ }
+ } catch (e) {
+ handleError();
+ }
+ }
+
+ return settingsProvider.scanning
? Expanded(
- child: MobileScanner(
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ 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'),
- );
+ onDetect: onDetect,
+ ),
+ ClipRRect(
+ borderRadius: BorderRadius.circular(20),
+ child: Container(
+ height: MediaQuery.of(context).size.height / 3,
+ width: MediaQuery.of(context).size.width * 0.8,
+ color: Colors.white.withOpacity(0.4))),
+ ],
+ ))
+ : TextIconButton(
+ text: "Scan",
+ onPressed: () => settingsProvider.scanning = true,
+ iconData: Icons.qr_code_scanner_outlined);
}
}