Show lockscreen onStart and onResume
This commit is contained in:
parent
80d62620a1
commit
bf09a87997
7 changed files with 296 additions and 77 deletions
|
@ -44,6 +44,8 @@ PODS:
|
|||
- libwebp/mux (1.1.0):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.1.0)
|
||||
- local_auth (0.0.1):
|
||||
- Flutter
|
||||
- Mantle (2.1.1):
|
||||
- Mantle/extobjc (= 2.1.1)
|
||||
- Mantle/extobjc (2.1.1)
|
||||
|
@ -91,6 +93,7 @@ DEPENDENCIES:
|
|||
- fluttercontactpicker (from `.symlinks/plugins/fluttercontactpicker/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- in_app_purchase (from `.symlinks/plugins/in_app_purchase/ios`)
|
||||
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
|
@ -143,6 +146,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
in_app_purchase:
|
||||
:path: ".symlinks/plugins/in_app_purchase/ios"
|
||||
local_auth:
|
||||
:path: ".symlinks/plugins/local_auth/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider:
|
||||
|
@ -182,6 +187,7 @@ SPEC CHECKSUMS:
|
|||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
in_app_purchase: 3e2155afa9d03d4fa32d9e62d567885080ce97d6
|
||||
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
||||
local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd
|
||||
Mantle: 35238ae6f2e2b2d474fa7b67fee82a59fea71915
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||
|
|
|
@ -273,6 +273,7 @@
|
|||
"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/in_app_purchase/in_app_purchase.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
|
||||
|
@ -307,6 +308,7 @@
|
|||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
|
||||
|
|
|
@ -11,7 +11,9 @@ import 'package:photos/services/billing_service.dart';
|
|||
import 'package:photos/services/collections_service.dart';
|
||||
import 'package:photos/services/memories_service.dart';
|
||||
import 'package:photos/services/sync_service.dart';
|
||||
import 'package:photos/ui/app_lock.dart';
|
||||
import 'package:photos/ui/home_widget.dart';
|
||||
import 'package:photos/ui/lock_screen.dart';
|
||||
import 'package:photos/utils/crypto_util.dart';
|
||||
import 'package:photos/utils/file_uploader.dart';
|
||||
import 'package:super_logging/super_logging.dart';
|
||||
|
@ -21,6 +23,27 @@ final _logger = Logger("main");
|
|||
|
||||
Completer<void> _initializationStatus;
|
||||
|
||||
final themeData = ThemeData(
|
||||
fontFamily: 'Ubuntu',
|
||||
brightness: Brightness.dark,
|
||||
hintColor: Colors.grey,
|
||||
accentColor: Color.fromRGBO(45, 194, 98, 1.0),
|
||||
buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
|
||||
buttonTheme: ButtonThemeData().copyWith(
|
||||
buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
|
||||
),
|
||||
toggleableActiveColor: Colors.green[400],
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
backgroundColor: Colors.black,
|
||||
appBarTheme: AppBarTheme().copyWith(
|
||||
color: Color.fromRGBO(10, 20, 20, 1.0),
|
||||
),
|
||||
cardColor: Color.fromRGBO(25, 25, 25, 1.0),
|
||||
dialogTheme: DialogTheme().copyWith(
|
||||
backgroundColor: Color.fromRGBO(20, 20, 20, 1.0),
|
||||
),
|
||||
);
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await _runInForeground();
|
||||
|
@ -32,7 +55,12 @@ Future<void> _runInForeground() async {
|
|||
_logger.info("Starting app in foreground");
|
||||
await _init(false);
|
||||
_sync();
|
||||
runApp(MyApp());
|
||||
runApp(AppLock(
|
||||
builder: (args) => EnteApp(),
|
||||
lockScreen: LockScreen(),
|
||||
enabled: Configuration.instance.shouldShowLockScreen(),
|
||||
themeData: themeData,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -104,13 +132,30 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
|||
));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
||||
final _title = 'ente';
|
||||
class EnteApp extends StatelessWidget with WidgetsBindingObserver {
|
||||
static const _homeWidget = const HomeWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_configureBackgroundFetch();
|
||||
return MaterialApp(
|
||||
title: "ente",
|
||||
theme: themeData,
|
||||
home: _homeWidget,
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
}
|
||||
|
||||
// Configure BackgroundFetch.
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_logger.info("App resumed");
|
||||
_sync();
|
||||
}
|
||||
}
|
||||
|
||||
void _configureBackgroundFetch() {
|
||||
BackgroundFetch.configure(
|
||||
BackgroundFetchConfig(
|
||||
minimumFetchInterval: 15,
|
||||
|
@ -130,39 +175,5 @@ class MyApp extends StatelessWidget with WidgetsBindingObserver {
|
|||
}).catchError((e) {
|
||||
_logger.info('[BackgroundFetch] configure ERROR: $e');
|
||||
});
|
||||
|
||||
return MaterialApp(
|
||||
title: _title,
|
||||
theme: ThemeData(
|
||||
fontFamily: 'Ubuntu',
|
||||
brightness: Brightness.dark,
|
||||
hintColor: Colors.grey,
|
||||
accentColor: Color.fromRGBO(45, 194, 98, 1.0),
|
||||
buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
|
||||
buttonTheme: ButtonThemeData().copyWith(
|
||||
buttonColor: Color.fromRGBO(45, 194, 98, 1.0),
|
||||
),
|
||||
toggleableActiveColor: Colors.green[400],
|
||||
scaffoldBackgroundColor: Colors.black,
|
||||
backgroundColor: Colors.black,
|
||||
appBarTheme: AppBarTheme().copyWith(
|
||||
color: Color.fromRGBO(10, 20, 20, 1.0),
|
||||
),
|
||||
cardColor: Color.fromRGBO(25, 25, 25, 1.0),
|
||||
dialogTheme: DialogTheme().copyWith(
|
||||
backgroundColor: Color.fromRGBO(20, 20, 20, 1.0),
|
||||
),
|
||||
),
|
||||
home: HomeWidget(_title),
|
||||
debugShowCheckedModeBanner: false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
_logger.info("App resumed");
|
||||
_sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
176
lib/ui/app_lock.dart
Normal file
176
lib/ui/app_lock.dart
Normal file
|
@ -0,0 +1,176 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A widget which handles app lifecycle events for showing and hiding a lock screen.
|
||||
/// This should wrap around a `MyApp` widget (or equivalent).
|
||||
///
|
||||
/// [lockScreen] is a [Widget] which should be a screen for handling login logic and
|
||||
/// calling `AppLock.of(context).didUnlock();` upon a successful login.
|
||||
///
|
||||
/// [builder] is a [Function] taking an [Object] as its argument and should return a
|
||||
/// [Widget]. The [Object] argument is provided by the [lockScreen] calling
|
||||
/// `AppLock.of(context).didUnlock();` with an argument. [Object] can then be injected
|
||||
/// in to your `MyApp` widget (or equivalent).
|
||||
///
|
||||
/// [enabled] determines wether or not the [lockScreen] should be shown on app launch
|
||||
/// and subsequent app pauses. This can be changed later on using `AppLock.of(context).enable();`,
|
||||
/// `AppLock.of(context).disable();` or the convenience method `AppLock.of(context).setEnabled(enabled);`
|
||||
/// using a bool argument.
|
||||
///
|
||||
/// [backgroundLockLatency] determines how much time is allowed to pass when
|
||||
/// the app is in the background state before the [lockScreen] widget should be
|
||||
/// shown upon returning. It defaults to instantly.
|
||||
class AppLock extends StatefulWidget {
|
||||
final Widget Function(Object) builder;
|
||||
final Widget lockScreen;
|
||||
final bool enabled;
|
||||
final Duration backgroundLockLatency;
|
||||
final ThemeData themeData;
|
||||
|
||||
const AppLock({
|
||||
Key key,
|
||||
@required this.builder,
|
||||
@required this.lockScreen,
|
||||
this.enabled = true,
|
||||
this.backgroundLockLatency = const Duration(seconds: 0),
|
||||
this.themeData,
|
||||
}) : super(key: key);
|
||||
|
||||
static _AppLockState of(BuildContext context) =>
|
||||
context.findAncestorStateOfType<_AppLockState>();
|
||||
|
||||
@override
|
||||
_AppLockState createState() => _AppLockState();
|
||||
}
|
||||
|
||||
class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
|
||||
static final GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
|
||||
|
||||
bool _didUnlockForAppLaunch;
|
||||
bool _isLocked;
|
||||
bool _enabled;
|
||||
|
||||
Timer _backgroundLockLatencyTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
this._didUnlockForAppLaunch = !this.widget.enabled;
|
||||
this._isLocked = false;
|
||||
this._enabled = this.widget.enabled;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == AppLifecycleState.paused &&
|
||||
(!this._isLocked && this._didUnlockForAppLaunch)) {
|
||||
this._backgroundLockLatencyTimer =
|
||||
Timer(this.widget.backgroundLockLatency, () => this.showLockScreen());
|
||||
}
|
||||
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
this._backgroundLockLatencyTimer?.cancel();
|
||||
}
|
||||
|
||||
super.didChangeAppLifecycleState(state);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
|
||||
this._backgroundLockLatencyTimer?.cancel();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: this.widget.enabled ? this._lockScreen : this.widget.builder(null),
|
||||
navigatorKey: _navigatorKey,
|
||||
theme: widget.themeData,
|
||||
routes: {
|
||||
'/lock-screen': (context) => this._lockScreen,
|
||||
'/unlocked': (context) =>
|
||||
this.widget.builder(ModalRoute.of(context).settings.arguments)
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _lockScreen {
|
||||
return WillPopScope(
|
||||
child: this.widget.lockScreen,
|
||||
onWillPop: () => Future.value(false),
|
||||
);
|
||||
}
|
||||
|
||||
/// Causes `AppLock` to either pop the [lockScreen] if the app is already running
|
||||
/// or instantiates widget returned from the [builder] method if the app is cold
|
||||
/// launched.
|
||||
///
|
||||
/// [args] is an optional argument which will get passed to the [builder] method
|
||||
/// when built. Use this when you want to inject objects created from the
|
||||
/// [lockScreen] in to the rest of your app so you can better guarantee that some
|
||||
/// objects, services or databases are already instantiated before using them.
|
||||
void didUnlock([Object args]) {
|
||||
if (this._didUnlockForAppLaunch) {
|
||||
this._didUnlockOnAppPaused();
|
||||
} else {
|
||||
this._didUnlockOnAppLaunch(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes sure that [AppLock] shows the [lockScreen] on subsequent app pauses if
|
||||
/// [enabled] is true of makes sure it isn't shown on subsequent app pauses if
|
||||
/// [enabled] is false.
|
||||
///
|
||||
/// This is a convenience method for calling the [enable] or [disable] method based
|
||||
/// on [enabled].
|
||||
void setEnabled(bool enabled) {
|
||||
if (enabled) {
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes sure that [AppLock] shows the [lockScreen] on subsequent app pauses.
|
||||
void enable() {
|
||||
setState(() {
|
||||
this._enabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
/// Makes sure that [AppLock] doesn't show the [lockScreen] on subsequent app pauses.
|
||||
void disable() {
|
||||
setState(() {
|
||||
this._enabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
/// Manually show the [lockScreen].
|
||||
Future<void> showLockScreen() {
|
||||
this._isLocked = true;
|
||||
return _navigatorKey.currentState.pushNamed('/lock-screen');
|
||||
}
|
||||
|
||||
void _didUnlockOnAppLaunch(Object args) {
|
||||
this._didUnlockForAppLaunch = true;
|
||||
_navigatorKey.currentState
|
||||
.pushReplacementNamed('/unlocked', arguments: args);
|
||||
}
|
||||
|
||||
void _didUnlockOnAppPaused() {
|
||||
this._isLocked = false;
|
||||
_navigatorKey.currentState.pop();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:photos/ui/home_widget.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:photos/ui/app_lock.dart';
|
||||
import 'package:photos/ui/loading_widget.dart';
|
||||
import 'package:photos/utils/auth_util.dart';
|
||||
|
||||
|
@ -12,52 +13,68 @@ class LockScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _LockScreenState extends State<LockScreen> {
|
||||
bool _isUnlocking = false;
|
||||
final _logger = Logger("LockScreen");
|
||||
bool _isUnlocking = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_showLockScreen();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: 64,
|
||||
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
|
||||
child: _isUnlocking
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: loadWidget,
|
||||
)
|
||||
: RaisedButton(
|
||||
child: Text(
|
||||
"unlock",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
letterSpacing: 1.0,
|
||||
width: double.infinity,
|
||||
height: 64,
|
||||
padding: const EdgeInsets.fromLTRB(80, 0, 80, 0),
|
||||
child: _isUnlocking
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: loadWidget,
|
||||
)
|
||||
: RaisedButton(
|
||||
child: Text(
|
||||
"unlock",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_isUnlocking = true;
|
||||
});
|
||||
final result = await requestAuthentication();
|
||||
if (result) {
|
||||
Navigator.pushReplacement(context,
|
||||
MaterialPageRoute(builder: (_) {
|
||||
return HomeWidget();
|
||||
}));
|
||||
} else {
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_isUnlocking = false;
|
||||
_isUnlocking = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
_showLockScreen();
|
||||
},
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showLockScreen() async {
|
||||
_logger.info("Showing lockscreen");
|
||||
try {
|
||||
final result = await requestAuthentication();
|
||||
if (result) {
|
||||
AppLock.of(context).didUnlock();
|
||||
} else {
|
||||
setState(() {
|
||||
_isUnlocking = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
setState(() {
|
||||
_isUnlocking = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_email_sender/flutter_email_sender.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:photos/ui/app_lock.dart';
|
||||
import 'package:photos/utils/auth_util.dart';
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import 'package:photos/db/files_db.dart';
|
||||
|
@ -284,10 +284,15 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
Switch(
|
||||
value: Configuration.instance.shouldShowLockScreen(),
|
||||
onChanged: (value) async {
|
||||
AppLock.of(context).disable();
|
||||
final result = await requestAuthentication();
|
||||
if (result) {
|
||||
AppLock.of(context).setEnabled(value);
|
||||
Configuration.instance.setShouldShowLockScreen(value);
|
||||
setState(() {});
|
||||
} else {
|
||||
AppLock.of(context).setEnabled(
|
||||
Configuration.instance.shouldShowLockScreen());
|
||||
}
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:local_auth/auth_strings.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
Future<bool> requestAuthentication() async {
|
||||
Logger("AuthUtil").info("Requesting authentication");
|
||||
return await LocalAuthentication().authenticate(
|
||||
localizedReason: "please authenticate to view your memories",
|
||||
androidAuthStrings: AndroidAuthMessages(
|
||||
|
|
Loading…
Reference in a new issue