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