Skip to content

Instantly share code, notes, and snippets.

@urusaich
Last active June 21, 2024 09:47
Show Gist options
  • Save urusaich/290d96eac64a3d7047b8187805d18107 to your computer and use it in GitHub Desktop.
Save urusaich/290d96eac64a3d7047b8187805d18107 to your computer and use it in GitHub Desktop.
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'resume_lock_action.dart';
import 'resume_lock_delegate.dart';
export 'resume_lock_action.dart';
export 'resume_lock_delegate.dart';
typedef LockerRouteBuilder<T> = Route<T> Function(Widget child);
abstract base class ResumeLock extends StatefulWidget {
const ResumeLock({
super.key,
required this.delegate,
required this.navigatorKey,
this.locked = false,
required this.child,
});
final ResumeLockDelegate delegate;
final GlobalKey<NavigatorState> navigatorKey;
final bool locked;
final Widget child;
@override
ResumeLockState createState() => ResumeLockState();
Widget buildLocker(BuildContext context);
Route<dynamic> buildRoute(Widget child);
static ResumeLockState? maybeOf(BuildContext context) =>
context.findAncestorStateOfType<ResumeLockState>();
static ResumeLockState of(BuildContext context) => maybeOf(context)!;
}
class ResumeLockState extends State<ResumeLock> with WidgetsBindingObserver {
late bool _locked;
NavigatorState? get navigator => widget.navigatorKey.currentState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_locked = widget.locked;
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.paused:
widget.delegate.onPaused();
case AppLifecycleState.resumed:
switch (await widget.delegate.onResumed(context)) {
case ResumeLockActionNone():
return;
case ResumeLockActionLock():
lock();
}
case _:
}
}
Widget buildChild(Widget child) => PopScope(canPop: false, child: child);
void lock() {
if (_locked || navigator == null) {
return;
}
_locked = true;
unawaited(
navigator!.push(
widget.buildRoute(buildChild(widget.buildLocker(context))),
),
);
widget.delegate.onLocked();
}
void unlock() {
if (!_locked || navigator == null) {
return;
}
_locked = false;
navigator!.pop();
widget.delegate.onUnlocked();
}
@override
Widget build(BuildContext context) => widget.child;
}
sealed class ResumeLockAction {
const ResumeLockAction._();
const factory ResumeLockAction.none() = ResumeLockActionNone._;
const factory ResumeLockAction.lock() = ResumeLockActionLock._;
}
class ResumeLockActionNone extends ResumeLockAction {
const ResumeLockActionNone._() : super._();
}
class ResumeLockActionLock extends ResumeLockAction {
const ResumeLockActionLock._() : super._();
}
import 'package:flutter/widgets.dart';
import 'resume_lock_action.dart';
abstract class ResumeLockDelegate {
const ResumeLockDelegate();
Future<ResumeLockAction> onResumed(BuildContext context);
void onPaused() {}
void onLocked() {}
void onUnlocked() {}
@protected
ResumeLockAction doNone() => const ResumeLockAction.none();
@protected
ResumeLockAction doLock() => const ResumeLockAction.lock();
}
import 'resume_lock_delegate.dart';
abstract class TimeResumeLockDelegate extends ResumeLockDelegate {
TimeResumeLockDelegate({required this.lockTimeout});
final Duration lockTimeout;
DateTime? _pauseTime;
bool shouldLock() {
if (_pauseTime == null) {
return false;
}
final shouldLockAt = _pauseTime!.add(lockTimeout);
final should = DateTime.now().isAfter(shouldLockAt);
if (should) {
return true;
}
_pauseTime = null;
return false;
}
@override
void onPaused() => _pauseTime = DateTime.now();
@override
void onUnlocked() => _pauseTime = null;
}
class MyResumeLockDelegate extends TimeResumeLockDelegate {
MyResumeLockDelegate({required super.lockTimeout});
@override
Future<ResumeLockAction> onResumed(BuildContext context) async {
// return doNone() or doLock()
// unauth and route user to auth page here and return doNone() after
}
}
base class MyResumeLock extends ResumeLock {
const MyResumeLock({
super.key,
required super.delegate,
required super.navigatorKey,
required super.child,
});
@override
Widget buildLocker(BuildContext context) => const ResumeView();
@override
Route<dynamic> buildRoute(Widget child) =>
MaterialPageRoute(builder: (context) => child)
}
class ResumeView extends StatelessWidget {
Widget build(BuildContext context) {
// ResumeLock.of(context) - ResumeLockState.lock() or ResumeLockState.unlock() available here
}
}
MaterialApp.router(
builder: (context, child) => MyResumeLock(navigatorKey: navigatorKey, delegate: MyResumeLockDelegate(lockTimeout: const Duration(seconds: 10)), child: child)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment