From 77ee2ef3c97aa16670457c84ea9220d17c150848 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Mon, 18 Jan 2021 08:38:19 +0100 Subject: [PATCH] feat: Implement app lock --- lib/config/setting_keys.dart | 1 + lib/l10n/intl_en.arb | 20 +++++++++++++++ lib/main.dart | 10 +++++++- lib/views/loading_view.dart | 6 +---- lib/views/lock_screen.dart | 35 ++++++++++++++++++++++++++ lib/views/settings.dart | 48 +++++++++++++++++++++++++++++++++++- pubspec.lock | 14 +++++++++++ pubspec.yaml | 2 ++ 8 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 lib/views/lock_screen.dart diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 474dda94..27abb602 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -11,4 +11,5 @@ abstract class SettingKeys { static const String showNoGoogle = 'chat.fluffy.show_no_google'; static const String showNoPid = 'chat.fluffy.show_no_pid'; static const String databasePassword = 'database-password'; + static const String appLockKey = 'chat.fluffy.app_lock'; } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index a99ed2a7..ae0691c0 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1604,6 +1604,26 @@ "type": "text", "placeholders": {} }, + "pleaseEnter4Digits": "Please enter 4 digits or leave empty to disable app lock.", + "@pleaseEnter4Digits": { + "type": "text", + "placeholders": {} + }, + "pleaseChooseAPasscode": "Please choose a pass code", + "@pleaseChooseAPasscode": { + "type": "text", + "placeholders": {} + }, + "appLock": "App lock", + "@appLock": { + "type": "text", + "placeholders": {} + }, + "security": "Security", + "@security": { + "type": "text", + "placeholders": {} + }, "sourceCode": "Source code", "@sourceCode": { "type": "text", diff --git a/lib/main.dart b/lib/main.dart index ed8398ee..8adeaf96 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,11 +5,14 @@ import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:adaptive_page_layout/adaptive_page_layout.dart'; import 'package:famedlysdk/famedlysdk.dart'; import 'package:fluffychat/config/routes.dart'; +import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/sentry_controller.dart'; +import 'package:fluffychat/views/lock_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_app_lock/flutter_app_lock.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:universal_html/prefer_universal/html.dart' as html; @@ -23,7 +26,12 @@ void main() async { FlutterError.onError = (FlutterErrorDetails details) => Zone.current.handleUncaughtError(details.exception, details.stack); runZonedGuarded( - () => runApp(App()), + () => runApp(PlatformInfos.isMobile + ? AppLock( + builder: (args) => App(), + lockScreen: LockScreen(), + ) + : App()), SentryController.captureException, ); } diff --git a/lib/views/loading_view.dart b/lib/views/loading_view.dart index eaf7d0ee..e222bca5 100644 --- a/lib/views/loading_view.dart +++ b/lib/views/loading_view.dart @@ -9,10 +9,6 @@ class LoadingView extends StatelessWidget { WidgetsBinding.instance.addPostFrameCallback((_) => AdaptivePageLayout.of(context).pushNamedAndRemoveAllOthers('/')); } - return Scaffold( - body: Center( - child: CircularProgressIndicator(), - ), - ); + return Scaffold(body: Center(child: CircularProgressIndicator())); } } diff --git a/lib/views/lock_screen.dart b/lib/views/lock_screen.dart new file mode 100644 index 00000000..49c41efa --- /dev/null +++ b/lib/views/lock_screen.dart @@ -0,0 +1,35 @@ +import 'package:fluffychat/config/setting_keys.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_app_lock/flutter_app_lock.dart'; +import 'package:flutter_screen_lock/lock_screen.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class LockScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: FlutterSecureStorage().read(key: SettingKeys.appLockKey), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Scaffold(body: Center(child: Text(snapshot.error.toString()))); + } + if (snapshot.connectionState == ConnectionState.done) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (snapshot.data?.isNotEmpty ?? false) { + showLockScreen( + context: context, + correctString: snapshot.data, + onUnlocked: () => AppLock.of(context).didUnlock(), + canBiometric: true, + canCancel: false, + ); + } else { + AppLock.of(context).didUnlock(); + } + }); + } + return Scaffold(body: Center(child: CircularProgressIndicator())); + }, + ); + } +} diff --git a/lib/views/settings.dart b/lib/views/settings.dart index 36ab7d91..ca8b4ab9 100644 --- a/lib/views/settings.dart +++ b/lib/views/settings.dart @@ -12,6 +12,8 @@ import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/sentry_controller.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:flutter_screen_lock/lock_screen.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:image_picker/image_picker.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -254,6 +256,44 @@ class _SettingsState extends State { } } + void _setAppLockAction(BuildContext context) async { + final currentLock = + await FlutterSecureStorage().read(key: SettingKeys.appLockKey); + if (currentLock?.isNotEmpty ?? false) { + var unlocked = false; + await showLockScreen( + context: context, + correctString: currentLock, + onUnlocked: () => unlocked = true, + canBiometric: true, + ); + if (unlocked != true) return; + } + final newLock = await showTextInputDialog( + context: context, + title: L10n.of(context).pleaseChooseAPasscode, + message: L10n.of(context).pleaseEnter4Digits, + textFields: [ + DialogTextField( + validator: (text) { + if (text.length != 4 && text.isNotEmpty) { + return L10n.of(context).pleaseEnter4Digits; + } + return null; + }, + keyboardType: TextInputType.number, + obscureText: true, + maxLines: 1, + minLines: 1, + ) + ], + ); + if (newLock != null) { + await FlutterSecureStorage() + .write(key: SettingKeys.appLockKey, value: newLock.first); + } + } + @override Widget build(BuildContext context) { final client = Matrix.of(context).client; @@ -439,13 +479,19 @@ class _SettingsState extends State { Divider(thickness: 1), ListTile( title: Text( - L10n.of(context).encryption, + L10n.of(context).security, style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), ), + if (PlatformInfos.isMobile) + ListTile( + trailing: Icon(Icons.lock_outlined), + title: Text(L10n.of(context).appLock), + onTap: () => _setAppLockAction(context), + ), ListTile( trailing: Icon(Icons.compare_arrows_outlined), title: Text(client.encryption.crossSigning.enabled diff --git a/pubspec.lock b/pubspec.lock index 65a7c103..eae18350 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -309,6 +309,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_app_lock: + dependency: "direct main" + description: + name: flutter_app_lock + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0+1" flutter_blurhash: dependency: "direct main" description: @@ -398,6 +405,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.11" + flutter_screen_lock: + dependency: "direct main" + description: + name: flutter_screen_lock + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.6" flutter_secure_storage: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 470725dc..6bfa0a82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -69,6 +69,8 @@ dependencies: emoji_picker: ^0.1.0 future_loading_dialog: ^0.1.2 package_info: ^0.4.3+2 + flutter_app_lock: ^1.4.0+1 + flutter_screen_lock: ^1.2.6 dev_dependencies: flutter_test: