From 660b6b5c53021887f204791b2a1de27eb71ff16f Mon Sep 17 00:00:00 2001 From: dreautall <109872040+dreautall@users.noreply.github.com> Date: Mon, 29 May 2023 17:24:05 +0000 Subject: [PATCH 1/6] settings: lockscreen --- lib/settings.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/settings.dart b/lib/settings.dart index 17a6b616..bd000fbc 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -34,6 +34,7 @@ class NotificationAppSettings { class SettingsProvider with ChangeNotifier { static const String settingDebug = "DEBUG"; static const String settingLocale = "LOCALE"; + static const String settingLock = "LOCK"; static const String settingNLKnownApps = "NL_KNOWNAPPS"; static const String settingNLUsedApps = "NL_USEDAPPS"; static const String settingNLAppPrefix = "NL_APP_"; @@ -52,6 +53,9 @@ class SettingsProvider with ChangeNotifier { bool get debug => _debug; StreamSubscription? _debugLogger; + bool _lock = false; + bool get lock => _lock; + bool _loaded = false; bool get loaded => _loaded; @@ -95,6 +99,9 @@ class SettingsProvider with ChangeNotifier { Logger.root.level = kDebugMode ? Level.ALL : Level.INFO; } + _lock = prefs.getBool(settingLock) ?? false; + log.config("read lock $lock"); + _notificationApps = prefs.getStringList(settingNLUsedApps) ?? []; _loaded = true; @@ -156,6 +163,19 @@ class SettingsProvider with ChangeNotifier { notifyListeners(); } + Future setLock(bool lock) async { + if (lock == _lock) { + return; + } + + SharedPreferences prefs = await SharedPreferences.getInstance(); + _lock = lock; + await prefs.setBool(settingLock, lock); + + log.finest(() => "notify SettingsProvider->setLock()"); + notifyListeners(); + } + Future notificationAddKnownApp(String packageName) async { SharedPreferences prefs = await SharedPreferences.getInstance(); final List apps = From 80e192c6586e4454493bb693fc637fd7cd19e498 Mon Sep 17 00:00:00 2001 From: dreautall <109872040+dreautall@users.noreply.github.com> Date: Mon, 29 May 2023 17:55:20 +0000 Subject: [PATCH 2/6] settings ui: lockscreen basics --- lib/pages/settings.dart | 17 +++++++++++++++++ lib/pages/settings/debug.dart | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 0342c580..2da6e65e 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -79,6 +79,23 @@ class SettingsPageState extends State }, ), const Divider(), + SwitchListTile( + title: Text("Lockscreen"), + value: context.select((SettingsProvider s) => s.lock), + subtitle: Text( + context.select((SettingsProvider s) => s.lock).toString(), + ), + secondary: CircleAvatar( + child: Icon(context.select((SettingsProvider s) => s.lock) + ? Icons.lock + : Icons.lock_outline), + ), + onChanged: (bool value) async { + // :TODO: init lockscreen stuff + await settings.setLock(value); + }, + ), + const Divider(), FutureBuilder( future: nlStatus(), builder: (BuildContext context, diff --git a/lib/pages/settings/debug.dart b/lib/pages/settings/debug.dart index 7ced0347..200a0334 100644 --- a/lib/pages/settings/debug.dart +++ b/lib/pages/settings/debug.dart @@ -25,14 +25,14 @@ class DebugDialog extends StatelessWidget { child: Text(S.of(context).settingsDialogDebugInfo), ), SwitchListTile( - value: context.watch().debug, + value: context.select((SettingsProvider s) => s.debug), onChanged: (bool value) => context.read().setDebug(value), title: Text(S.of(context).settingsDialogDebugTitle), secondary: const Icon(Icons.bug_report), ), ListTile( - enabled: context.watch().debug, + enabled: context.select((SettingsProvider s) => s.debug), isThreeLine: false, leading: const Icon(Icons.send), title: Text(S.of(context).settingsDialogDebugSendButton), From 81b265320517ca760550b1d5f475fa4c9778a043 Mon Sep 17 00:00:00 2001 From: dreautall <109872040+dreautall@users.noreply.github.com> Date: Mon, 29 May 2023 18:12:02 +0000 Subject: [PATCH 3/6] settings: check if any authentication mode is supported --- android/app/src/main/AndroidManifest.xml | 1 + lib/pages/settings.dart | 10 ++++++ pubspec.lock | 40 ++++++++++++++++++++++++ pubspec.yaml | 1 + 4 files changed, 52 insertions(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 93e0945c..453c52b3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 2da6e65e..b2cbc78d 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; +import 'package:local_auth/local_auth.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:waterflyiii/notificationlistener.dart'; @@ -92,6 +93,15 @@ class SettingsPageState extends State ), onChanged: (bool value) async { // :TODO: init lockscreen stuff + if (value == true) { + final LocalAuthentication auth = LocalAuthentication(); + final bool canAuth = await auth.isDeviceSupported() || + await auth.canCheckBiometrics; + if (!canAuth) { + log.warning("no auth method supported"); + return; + } + } await settings.setLock(value); }, ), diff --git a/pubspec.lock b/pubspec.lock index e28185f8..a171556f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -544,6 +544,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "0cf238be2bfa51a6c9e7e9cfc11c05ea39f2a3a4d3e5bb255d0ebc917da24401" + url: "https://pub.dev" + source: hosted + version: "2.1.6" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: c5e48c4a67fc0e5dd9b5725cc8766b67e2da9a54155c82c6e2ea4a0d1cf9ef93 + url: "https://pub.dev" + source: hosted + version: "1.0.28" + local_auth_ios: + dependency: transitive + description: + name: local_auth_ios + sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "9e160d59ef0743e35f1b50f4fb84dc64f55676b1b8071e319ef35e7f3bc13367" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: "19323b75ab781d5362dbb15dcb7e0916d2431c7a6dbdda016ec9708689877f73" + url: "https://pub.dev" + source: hosted + version: "1.0.8" logging: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ffc4742c..24948350 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: package_info_plus: ^4.0.0 logging: ^1.1.1 flutter_email_sender: ^5.2.0 + local_auth: ^2.1.6 dev_dependencies: flutter_lints: ^2.0.0 From 5b7bba3b165d9eb18675541a1a6fd57ac7c5142f Mon Sep 17 00:00:00 2001 From: dreautall <109872040+dreautall@users.noreply.github.com> Date: Mon, 29 May 2023 18:33:01 +0000 Subject: [PATCH 4/6] show lock screen on startup --- .../com/dreautall/waterflyiii/MainActivity.kt | 4 +-- lib/app.dart | 33 +++++++++++++++++-- lib/auth.dart | 3 +- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/kotlin/com/dreautall/waterflyiii/MainActivity.kt b/android/app/src/main/kotlin/com/dreautall/waterflyiii/MainActivity.kt index 711c01bc..23ef93dc 100644 --- a/android/app/src/main/kotlin/com/dreautall/waterflyiii/MainActivity.kt +++ b/android/app/src/main/kotlin/com/dreautall/waterflyiii/MainActivity.kt @@ -1,6 +1,6 @@ package com.dreautall.waterflyiii -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { } diff --git a/lib/app.dart b/lib/app.dart index 4537413f..a4f7e50d 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,12 +1,14 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show SystemChannels; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:local_auth/local_auth.dart'; import 'package:waterflyiii/auth.dart'; import 'package:waterflyiii/notificationlistener.dart'; @@ -84,11 +86,36 @@ class _WaterflyAppState extends State { } else { log.finer(() => "Load Step 2: Signin In"); context.read().signInFromStorage().then( - (_) => setState(() { + (bool success) async { + if (!success || !context.read().lock) { + setState(() { log.finest(() => "set _startup = false"); _startup = false; - }), - ); + }); + } + + // Authentication required + log.fine("awaiting authentication"); + final LocalAuthentication auth = LocalAuthentication(); + final bool authed = await auth.authenticate( + localizedReason: "Please authenticate", + options: const AuthenticationOptions( + useErrorDialogs: false, + stickyAuth: true, + )); + log.finest("done authing, $authed"); + if (authed) { + setState(() { + log.finest(() => "authentication succeeded"); + _startup = false; + }); + } else { + log.shout(() => "authentication failed"); + // close app + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } + }, + ); } } else { signedIn = context.select((FireflyService f) => f.signedIn); diff --git a/lib/auth.dart b/lib/auth.dart index 3e28ee37..4eebdb01 100644 --- a/lib/auth.dart +++ b/lib/auth.dart @@ -195,8 +195,7 @@ class FireflyService with ChangeNotifier { } try { - final bool success = await signIn(apiHost, apiKey, cert); - return success; + return await signIn(apiHost, apiKey, cert); } catch (e) { _storageSignInException = e; log.finest(() => "notify FireflyService->signInFromStorage"); From 09eebd1ab8db3f7827f8171d6868b654a9da3ec8 Mon Sep 17 00:00:00 2001 From: dreautall <109872040+dreautall@users.noreply.github.com> Date: Mon, 29 May 2023 18:51:15 +0000 Subject: [PATCH 5/6] settings: test authentication when enabling lockscreen + l10n --- lib/l10n/app_de.arb | 3 +++ lib/l10n/app_en.arb | 3 +++ lib/pages/settings.dart | 27 ++++++++++++++++++--------- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b62f1822..9613b625 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -93,6 +93,9 @@ "settingsDialogLanguageTitle": "Sprache auswählen", "settingsDialogThemeTitle": "Erscheinungsbild auswählen", "settingsLanguage": "Sprache", + "settingsLockscreen": "App-Sperre", + "settingsLockscreenHelp": "Authentifizierung beim Start der App erwzingen.", + "settingsLockscreenInitial": "Bitte authentifiziere dich, um die App-Sperre zu aktivieren.", "settingsNLAppAccount": "Standard-Konto", "settingsNLAppAccountDynamic": "", "settingsNLAppAdd": "App hinzufügen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2cc5c9c0..24625d49 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -489,6 +489,9 @@ "@settingsLanguage": { "description": "Currently selected language" }, + "settingsLockscreen": "Lockscreen", + "settingsLockscreenHelp": "Require authenticiation on app startup", + "settingsLockscreenInitial": "Please authenticate to enable the lock screen.", "settingsNLAppAccount": "Default Account", "@settingsNLAppAccount": { "description": "Default account which will be used for the transaction." diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index b2cbc78d..6741ab7b 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -81,18 +81,18 @@ class SettingsPageState extends State ), const Divider(), SwitchListTile( - title: Text("Lockscreen"), + title: Text(S.of(context).settingsLockscreen), + subtitle: Text(S.of(context).settingsLockscreenHelp), value: context.select((SettingsProvider s) => s.lock), - subtitle: Text( - context.select((SettingsProvider s) => s.lock).toString(), - ), secondary: CircleAvatar( - child: Icon(context.select((SettingsProvider s) => s.lock) - ? Icons.lock - : Icons.lock_outline), + child: Icon( + context.select((SettingsProvider s) => s.lock) + ? Icons.lock + : Icons.lock_outline, + ), ), onChanged: (bool value) async { - // :TODO: init lockscreen stuff + final S l10n = S.of(context); if (value == true) { final LocalAuthentication auth = LocalAuthentication(); final bool canAuth = await auth.isDeviceSupported() || @@ -101,8 +101,17 @@ class SettingsPageState extends State log.warning("no auth method supported"); return; } + log.finest("trying authentication"); + final bool authed = await auth.authenticate( + localizedReason: + l10n.settingsLockscreenInitial, // :TODO: translate + ); + if (!authed) { + log.warning("authentication was cancelled"); + return; + } } - await settings.setLock(value); + settings.setLock(value); }, ), const Divider(), From 61a558e787b103ae5545d9e3dd76f9286f77947e Mon Sep 17 00:00:00 2001 From: dreautall <109872040+dreautall@users.noreply.github.com> Date: Mon, 29 May 2023 18:51:26 +0000 Subject: [PATCH 6/6] fixes for lockscreen on startup --- lib/app.dart | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/app.dart b/lib/app.dart index a4f7e50d..84bbc1ec 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -92,27 +92,28 @@ class _WaterflyAppState extends State { log.finest(() => "set _startup = false"); _startup = false; }); - } - - // Authentication required - log.fine("awaiting authentication"); - final LocalAuthentication auth = LocalAuthentication(); - final bool authed = await auth.authenticate( - localizedReason: "Please authenticate", + } else { + // Authentication required + log.fine("awaiting authentication"); + final LocalAuthentication auth = LocalAuthentication(); + final bool authed = await auth.authenticate( + localizedReason: "Waterfly III", options: const AuthenticationOptions( useErrorDialogs: false, stickyAuth: true, - )); - log.finest("done authing, $authed"); - if (authed) { - setState(() { - log.finest(() => "authentication succeeded"); - _startup = false; - }); - } else { - log.shout(() => "authentication failed"); - // close app - SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + ), + ); + log.finest("done authing, $authed"); + if (authed) { + setState(() { + log.finest(() => "authentication succeeded"); + _startup = false; + }); + } else { + log.shout(() => "authentication failed"); + // close app + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } } }, );