mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-25 00:14:42 +01:00
Require auth again after suspending for some time
This commit is contained in:
parent
38fe935764
commit
24e5130535
6 changed files with 173 additions and 6 deletions
|
@ -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/android/android_info.dart';
|
||||||
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
||||||
import 'package:nc_photos/platform/features.dart' as features;
|
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:nc_photos/touch_manager.dart';
|
||||||
import 'package:np_db/np_db.dart';
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_gps_map/np_gps_map.dart';
|
import 'package:np_gps_map/np_gps_map.dart';
|
||||||
|
@ -73,6 +74,8 @@ Future<void> init(InitIsolateType isolateType) async {
|
||||||
await _initDiContainer(isolateType);
|
await _initDiContainer(isolateType);
|
||||||
_initVisibilityDetector();
|
_initVisibilityDetector();
|
||||||
GpsMap.init();
|
GpsMap.init();
|
||||||
|
// init session storage
|
||||||
|
SessionStorage();
|
||||||
|
|
||||||
_hasInitedInThisIsolate = true;
|
_hasInitedInThisIsolate = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ extension ProtectedPageBuildContextExtension on NavigatorState {
|
||||||
U? result,
|
U? result,
|
||||||
Object? arguments,
|
Object? arguments,
|
||||||
}) async {
|
}) async {
|
||||||
if (await _auth()) {
|
if (await authProtectedPage()) {
|
||||||
return pushReplacementNamed(routeName,
|
return pushReplacementNamed(routeName,
|
||||||
arguments: arguments, result: result);
|
arguments: arguments, result: result);
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,7 +46,7 @@ extension ProtectedPageBuildContextExtension on NavigatorState {
|
||||||
String routeName, {
|
String routeName, {
|
||||||
Object? arguments,
|
Object? arguments,
|
||||||
}) async {
|
}) async {
|
||||||
if (await _auth()) {
|
if (await authProtectedPage()) {
|
||||||
return pushNamed(routeName, arguments: arguments);
|
return pushNamed(routeName, arguments: arguments);
|
||||||
} else {
|
} else {
|
||||||
throw const ProtectedPageAuthException();
|
throw const ProtectedPageAuthException();
|
||||||
|
@ -54,14 +54,14 @@ extension ProtectedPageBuildContextExtension on NavigatorState {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<T?> pushProtected<T extends Object?>(Route<T> route) async {
|
Future<T?> pushProtected<T extends Object?>(Route<T> route) async {
|
||||||
if (await _auth()) {
|
if (await authProtectedPage()) {
|
||||||
return push(route);
|
return push(route);
|
||||||
} else {
|
} else {
|
||||||
throw const ProtectedPageAuthException();
|
throw const ProtectedPageAuthException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _auth() async {
|
Future<bool> authProtectedPage() async {
|
||||||
final securePrefController = context.read<SecurePrefController>();
|
final securePrefController = context.read<SecurePrefController>();
|
||||||
switch (securePrefController.protectedPageAuthTypeValue) {
|
switch (securePrefController.protectedPageAuthTypeValue) {
|
||||||
case null:
|
case null:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
|
|
||||||
/// Hold non-persisted global variables
|
/// Hold non-persisted global variables
|
||||||
class SessionStorage {
|
class SessionStorage {
|
||||||
factory SessionStorage() {
|
factory SessionStorage() {
|
||||||
|
@ -15,5 +17,7 @@ class SessionStorage {
|
||||||
/// Whether the dynamic_color library is supported in this platform
|
/// Whether the dynamic_color library is supported in this platform
|
||||||
bool isSupportDynamicColor = false;
|
bool isSupportDynamicColor = false;
|
||||||
|
|
||||||
|
DateTime lastSuspendTime = clock.now();
|
||||||
|
|
||||||
static final _inst = SessionStorage._();
|
static final _inst = SessionStorage._();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:clock/clock.dart';
|
||||||
import 'package:copy_with/copy_with.dart';
|
import 'package:copy_with/copy_with.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/gestures.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/connect.dart' as legacy;
|
||||||
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
|
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
|
||||||
import 'package:nc_photos/navigation_manager.dart';
|
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/session_storage.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.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';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'my_app.g.dart';
|
part 'my_app.g.dart';
|
||||||
|
part 'my_app/app_lock.dart';
|
||||||
part 'my_app/bloc.dart';
|
part 'my_app/bloc.dart';
|
||||||
part 'my_app/state_event.dart';
|
part 'my_app/state_event.dart';
|
||||||
|
|
||||||
|
@ -171,9 +176,9 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
|
||||||
SnackBarManager().unregisterHandler(this);
|
SnackBarManager().unregisterHandler(this);
|
||||||
NavigationManager().unsetHandler(this);
|
NavigationManager().unsetHandler(this);
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -560,7 +565,7 @@ class _ThemedMyApp extends StatelessWidget {
|
||||||
systemNavigationBarColor: theme.colorScheme.secondaryContainer,
|
systemNavigationBarColor: theme.colorScheme.secondaryContainer,
|
||||||
systemNavigationBarIconBrightness: theme.brightness.invert(),
|
systemNavigationBarIconBrightness: theme.brightness.invert(),
|
||||||
),
|
),
|
||||||
child: child,
|
child: _AppLockMyApp(child: child),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,20 @@ extension _$_WrappedAppStateNpLog on _WrappedAppState {
|
||||||
static final log = Logger("widget.my_app._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 {
|
extension _$_BlocNpLog on _Bloc {
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
Logger get _log => log;
|
Logger get _log => log;
|
||||||
|
|
141
app/lib/widget/my_app/app_lock.dart
Normal file
141
app/lib/widget/my_app/app_lock.dart
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue