Require auth again after suspending for some time

This commit is contained in:
Ming Ming 2024-05-28 00:26:06 +08:00
parent 38fe935764
commit 24e5130535
6 changed files with 173 additions and 6 deletions

View file

@ -40,6 +40,7 @@ import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/mobile/android/android_info.dart';
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
import 'package:nc_photos/platform/features.dart' as features;
import 'package:nc_photos/session_storage.dart';
import 'package:nc_photos/touch_manager.dart';
import 'package:np_db/np_db.dart';
import 'package:np_gps_map/np_gps_map.dart';
@ -73,6 +74,8 @@ Future<void> init(InitIsolateType isolateType) async {
await _initDiContainer(isolateType);
_initVisibilityDetector();
GpsMap.init();
// init session storage
SessionStorage();
_hasInitedInThisIsolate = true;
}

View file

@ -34,7 +34,7 @@ extension ProtectedPageBuildContextExtension on NavigatorState {
U? result,
Object? arguments,
}) async {
if (await _auth()) {
if (await authProtectedPage()) {
return pushReplacementNamed(routeName,
arguments: arguments, result: result);
} else {
@ -46,7 +46,7 @@ extension ProtectedPageBuildContextExtension on NavigatorState {
String routeName, {
Object? arguments,
}) async {
if (await _auth()) {
if (await authProtectedPage()) {
return pushNamed(routeName, arguments: arguments);
} else {
throw const ProtectedPageAuthException();
@ -54,14 +54,14 @@ extension ProtectedPageBuildContextExtension on NavigatorState {
}
Future<T?> pushProtected<T extends Object?>(Route<T> route) async {
if (await _auth()) {
if (await authProtectedPage()) {
return push(route);
} else {
throw const ProtectedPageAuthException();
}
}
Future<bool> _auth() async {
Future<bool> authProtectedPage() async {
final securePrefController = context.read<SecurePrefController>();
switch (securePrefController.protectedPageAuthTypeValue) {
case null:

View file

@ -1,3 +1,5 @@
import 'package:clock/clock.dart';
/// Hold non-persisted global variables
class SessionStorage {
factory SessionStorage() {
@ -15,5 +17,7 @@ class SessionStorage {
/// Whether the dynamic_color library is supported in this platform
bool isSupportDynamicColor = false;
DateTime lastSuspendTime = clock.now();
static final _inst = SessionStorage._();
}

View file

@ -1,3 +1,6 @@
import 'dart:async';
import 'package:clock/clock.dart';
import 'package:copy_with/copy_with.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/gestures.dart';
@ -15,6 +18,7 @@ import 'package:nc_photos/language_util.dart' as language_util;
import 'package:nc_photos/legacy/connect.dart' as legacy;
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
import 'package:nc_photos/navigation_manager.dart';
import 'package:nc_photos/protected_page_handler.dart';
import 'package:nc_photos/session_storage.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
@ -54,6 +58,7 @@ import 'package:np_db/np_db.dart';
import 'package:to_string/to_string.dart';
part 'my_app.g.dart';
part 'my_app/app_lock.dart';
part 'my_app/bloc.dart';
part 'my_app/state_event.dart';
@ -171,9 +176,9 @@ class _WrappedAppState extends State<_WrappedApp>
@override
void dispose() {
super.dispose();
SnackBarManager().unregisterHandler(this);
NavigationManager().unsetHandler(this);
super.dispose();
}
@override
@ -560,7 +565,7 @@ class _ThemedMyApp extends StatelessWidget {
systemNavigationBarColor: theme.colorScheme.secondaryContainer,
systemNavigationBarIconBrightness: theme.brightness.invert(),
),
child: child,
child: _AppLockMyApp(child: child),
);
}

View file

@ -66,6 +66,20 @@ extension _$_WrappedAppStateNpLog on _WrappedAppState {
static final log = Logger("widget.my_app._WrappedAppState");
}
extension _$_AppLockMyAppStateNpLog on _AppLockMyAppState {
// ignore: unused_element
Logger get _log => log;
static final log = Logger("widget.my_app._AppLockMyAppState");
}
extension _$_AppLockOverlayPageStateNpLog on _AppLockOverlayPageState {
// ignore: unused_element
Logger get _log => log;
static final log = Logger("widget.my_app._AppLockOverlayPageState");
}
extension _$_BlocNpLog on _Bloc {
// ignore: unused_element
Logger get _log => log;

View file

@ -0,0 +1,141 @@
part of '../my_app.dart';
class _AppLockMyApp extends StatefulWidget {
const _AppLockMyApp({
required this.child,
});
@override
State<StatefulWidget> createState() => _AppLockMyAppState();
final Widget child;
}
@npLog
class _AppLockMyAppState extends State<_AppLockMyApp> {
@override
void initState() {
super.initState();
_lifecycleListener = AppLifecycleListener(
onHide: () {
SessionStorage().lastSuspendTime = clock.now();
},
onShow: () async {
final now = clock.now();
final diff = now.difference(SessionStorage().lastSuspendTime);
_log.info("Suspended for: $diff");
if (diff >= const Duration(seconds: 30) && !_shouldLock) {
_log.info("Suspended for too long, auth required");
setState(() {
_shouldLock = true;
});
late final OverlayEntry authOverlay;
authOverlay = OverlayEntry(
builder: (_) => _AppLockOverlay(
onAuthSuccess: () {
authOverlay.remove();
if (mounted) {
setState(() {
_shouldLock = false;
});
}
},
),
);
_key.currentState?.insert(authOverlay);
}
},
);
}
@override
void dispose() {
_lifecycleListener.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Overlay(
key: _key,
initialEntries: [
OverlayEntry(
maintainState: true,
builder: (_) => widget.child,
),
],
);
}
late final AppLifecycleListener _lifecycleListener;
final _key = GlobalKey<OverlayState>();
var _shouldLock = false;
}
class _AppLockOverlay extends StatelessWidget {
const _AppLockOverlay({
required this.onAuthSuccess,
});
@override
Widget build(BuildContext context) {
return HeroControllerScope.none(
child: Navigator(
onGenerateRoute: (_) => MaterialPageRoute(
builder: (context) =>
_AppLockOverlayPage(onAuthSuccess: onAuthSuccess),
),
),
);
}
final VoidCallback onAuthSuccess;
}
class _AppLockOverlayPage extends StatefulWidget {
const _AppLockOverlayPage({
required this.onAuthSuccess,
});
@override
State<StatefulWidget> createState() => _AppLockOverlayPageState();
final VoidCallback onAuthSuccess;
}
@npLog
class _AppLockOverlayPageState extends State<_AppLockOverlayPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_auth();
}
});
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
child: Container(
color: Colors.black,
child: const Align(
alignment: Alignment(0, .75),
child: Icon(Icons.lock_outlined, size: 64),
),
),
);
}
Future<void> _auth() async {
if (mounted && await Navigator.of(context).authProtectedPage()) {
widget.onAuthSuccess();
} else {
_log.warning("[_auth] Auth failed");
await Future.delayed(const Duration(seconds: 2));
unawaited(_auth());
}
}
}