Files
tatuchat/lib/widgets/app_lock.dart
Welton Moura 0037b0b9cf
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
Initial commit
2026-09-11 15:59:56 -03:00

147 lines
3.6 KiB
Dart

// SPDX-FileCopyrightText: 2019-Present Christian Kußowski
// SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
//
// SPDX-License-Identifier: AGPL-3.0-or-later
import 'package:tatchat/widgets/lock_screen.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:local_auth/local_auth.dart';
import 'package:material_ui/material_ui.dart';
import 'package:provider/provider.dart';
class AppLockWidget extends StatefulWidget {
const AppLockWidget({
required this.child,
required this.pincode,
required this.useBiometrics,
required this.isLoggedIn,
super.key,
});
final bool isLoggedIn, useBiometrics;
final String? pincode;
final Widget child;
@override
State<AppLockWidget> createState() => AppLock();
}
class AppLock extends State<AppLockWidget> with WidgetsBindingObserver {
String? _pincode;
bool _isLocked = false;
bool _useBiometrics = false;
bool _triedAutoBiometrics = false;
bool _paused = false;
bool get isActive =>
_pincode != null &&
int.tryParse(_pincode!) != null &&
_pincode!.length >= 4 &&
!_paused;
bool get useBiometrics => _useBiometrics;
@override
void initState() {
_useBiometrics = widget.useBiometrics;
_pincode = widget.pincode;
_isLocked = isActive;
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback(_checkLoggedIn);
if (isActive && useBiometrics) unlockWithBiometrics();
}
Future<void> _checkLoggedIn(_) async {
if (widget.isLoggedIn) return;
await changePincode(null);
setState(() {
_isLocked = false;
});
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (isActive && state == AppLifecycleState.hidden && !_isLocked) {
showLockScreen();
}
if (_isLocked &&
state == AppLifecycleState.resumed &&
useBiometrics &&
!_triedAutoBiometrics) {
unlockWithBiometrics();
}
}
bool get isLocked => _isLocked;
Future<void> changeUseBiometrics(bool useBiometrics) async {
await const FlutterSecureStorage().write(
key: 'chat.fluffy.use_biometrics',
value: useBiometrics.toString(),
);
_useBiometrics = useBiometrics;
return;
}
Future<void> changePincode(String? pincode) async {
await const FlutterSecureStorage().write(
key: 'chat.fluffy.app_lock',
value: pincode,
);
_pincode = pincode;
return;
}
Future<bool> unlockWithBiometrics() async {
_triedAutoBiometrics = true;
final localAuth = LocalAuthentication();
final unlocked = await localAuth.authenticate(
localizedReason: 'Please authenticate to unlock the app.',
persistAcrossBackgrounding: true,
biometricOnly: true,
);
if (unlocked) {
setState(() {
_isLocked = false;
_triedAutoBiometrics = false;
});
}
return unlocked;
}
bool unlock(String pincode) {
final isCorrect = pincode == _pincode;
if (isCorrect) {
setState(() {
_isLocked = false;
});
}
return isCorrect;
}
void showLockScreen() => setState(() {
_isLocked = true;
});
Future<T> pauseWhile<T>(Future<T> future) async {
_paused = true;
try {
return await future;
} finally {
_paused = false;
}
}
static AppLock of(BuildContext context) =>
Provider.of<AppLock>(context, listen: false);
@override
Widget build(BuildContext context) => Provider<AppLock>(
create: (_) => this,
child: Stack(
fit: StackFit.expand,
children: [widget.child, if (isLocked) const LockScreen()],
),
);
}