ente/mobile/lib/ui/tools/app_lock.dart
2024-03-08 09:36:03 +05:30

208 lines
6.4 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import "package:photos/generated/l10n.dart";
import "package:photos/l10n/l10n.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.
///
// ignore_for_file: unnecessary_this, library_private_types_in_public_api
class AppLock extends StatefulWidget {
final Widget Function(Object?) builder;
final Widget lockScreen;
final bool enabled;
final Duration backgroundLockLatency;
final ThemeData? darkTheme;
final ThemeData? lightTheme;
final ThemeMode savedThemeMode;
final Locale locale;
const AppLock({
Key? key,
required this.builder,
required this.lockScreen,
required this.savedThemeMode,
this.enabled = true,
this.locale = const Locale("en", "US"),
this.backgroundLockLatency = const Duration(seconds: 0),
this.darkTheme,
this.lightTheme,
}) : super(key: key);
static _AppLockState? of(BuildContext context) =>
context.findAncestorStateOfType<_AppLockState>();
@override
State<AppLock> createState() => _AppLockState();
}
class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
static final GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
late bool _didUnlockForAppLaunch;
late bool _isLocked;
late 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,
themeMode: widget.savedThemeMode,
theme: widget.lightTheme,
darkTheme: widget.darkTheme,
locale: widget.locale,
debugShowCheckedModeBanner: false,
supportedLocales: appSupportedLocales,
localeListResolutionCallback: localResolutionCallBack,
localizationsDelegates: const [
...AppLocalizations.localizationsDelegates,
S.delegate,
],
onGenerateRoute: (settings) {
switch (settings.name) {
case '/lock-screen':
return PageRouteBuilder(
pageBuilder: (_, __, ___) => this._lockScreen,
);
case '/unlocked':
return PageRouteBuilder(
pageBuilder: (_, __, ___) =>
this.widget.builder(settings.arguments),
);
}
return PageRouteBuilder(pageBuilder: (_, __, ___) => this._lockScreen);
},
);
}
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();
}
}