199 lines
6.4 KiB
Dart
199 lines
6.4 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 'dart:async';
|
|
|
|
import 'package:tatchat/l10n/l10n.dart';
|
|
import 'package:tatchat/widgets/app_lock.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:local_auth/local_auth.dart';
|
|
import 'package:material_ui/material_ui.dart';
|
|
|
|
class LockScreen extends StatefulWidget {
|
|
const LockScreen({super.key});
|
|
|
|
@override
|
|
State<LockScreen> createState() => _LockScreenState();
|
|
}
|
|
|
|
class _LockScreenState extends State<LockScreen> {
|
|
String? _errorText;
|
|
int _coolDownSeconds = 5;
|
|
bool _inputBlocked = false;
|
|
final TextEditingController _textEditingController = TextEditingController();
|
|
|
|
Future<void> tryUnlockWithBiometrics() async {
|
|
setState(() {
|
|
_errorText = null;
|
|
});
|
|
|
|
final success = await AppLock.of(context).unlockWithBiometrics();
|
|
|
|
if (success) {
|
|
setState(() {
|
|
_inputBlocked = false;
|
|
_errorText = null;
|
|
});
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_errorText = L10n.of(context).wrongPinEntered(_coolDownSeconds);
|
|
_inputBlocked = true;
|
|
});
|
|
Future.delayed(Duration(seconds: _coolDownSeconds)).then((_) {
|
|
setState(() {
|
|
_inputBlocked = false;
|
|
_coolDownSeconds *= 2;
|
|
_errorText = null;
|
|
});
|
|
});
|
|
_textEditingController.clear();
|
|
}
|
|
|
|
Future<void> tryUnlock(String text) async {
|
|
text = text.trim();
|
|
setState(() {
|
|
_errorText = null;
|
|
});
|
|
|
|
final enteredPin = int.tryParse(text);
|
|
if (enteredPin == null) {
|
|
setState(() {
|
|
_errorText = L10n.of(context).invalidInput;
|
|
});
|
|
_textEditingController.clear();
|
|
return;
|
|
}
|
|
|
|
if (AppLock.of(context).unlock(text)) {
|
|
setState(() {
|
|
_inputBlocked = false;
|
|
_errorText = null;
|
|
});
|
|
_textEditingController.clear();
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_errorText = L10n.of(context).wrongPinEntered(_coolDownSeconds);
|
|
_inputBlocked = true;
|
|
});
|
|
Future.delayed(Duration(seconds: _coolDownSeconds)).then((_) {
|
|
setState(() {
|
|
_inputBlocked = false;
|
|
_coolDownSeconds *= 2;
|
|
_errorText = null;
|
|
});
|
|
});
|
|
_textEditingController.clear();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Overlay(
|
|
initialEntries: [
|
|
OverlayEntry(
|
|
builder: (context) => Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(L10n.of(context).pleaseEnterYourPin),
|
|
centerTitle: true,
|
|
),
|
|
body: ListView(
|
|
shrinkWrap: true,
|
|
padding: const EdgeInsets.all(16.0),
|
|
children: [
|
|
Center(
|
|
child: Image.asset(
|
|
'assets/logo/mini/logo_mono_mini.png',
|
|
width: 128,
|
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Center(
|
|
child: TextField(
|
|
controller: _textEditingController,
|
|
textInputAction: TextInputAction.go,
|
|
keyboardType: TextInputType.number,
|
|
obscureText: true,
|
|
autofocus: true,
|
|
textAlign: TextAlign.center,
|
|
readOnly: _inputBlocked,
|
|
onChanged: (text) {
|
|
if (text.length >= 6) tryUnlock(text);
|
|
},
|
|
onSubmitted: tryUnlock,
|
|
style: const TextStyle(fontSize: 40),
|
|
inputFormatters: [LengthLimitingTextInputFormatter(6)],
|
|
decoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(40),
|
|
),
|
|
errorText: _errorText,
|
|
hintText: '✱✱✱✱',
|
|
hintStyle: TextStyle(
|
|
color: Theme.of(
|
|
context,
|
|
).colorScheme.surfaceContainerHighest,
|
|
),
|
|
prefix: AppLock.of(context).useBiometrics
|
|
? IconButton(
|
|
tooltip: L10n.of(context).unlockWithBiometrics,
|
|
icon: FutureBuilder(
|
|
future: LocalAuthentication()
|
|
.getAvailableBiometrics(),
|
|
builder: (context, snapshot) {
|
|
final availableBiometrics =
|
|
snapshot.data ?? [];
|
|
if (availableBiometrics.contains(
|
|
BiometricType.face,
|
|
)) {
|
|
return Icon(Icons.face_unlock_outlined);
|
|
}
|
|
return Icon(Icons.fingerprint_outlined);
|
|
},
|
|
),
|
|
onPressed: _inputBlocked
|
|
? null
|
|
: tryUnlockWithBiometrics,
|
|
)
|
|
: IconButton(
|
|
tooltip: L10n.of(context).reset,
|
|
icon: Icon(Icons.cancel_outlined),
|
|
onPressed: _inputBlocked
|
|
? null
|
|
: _textEditingController.clear,
|
|
),
|
|
suffix: IconButton(
|
|
tooltip: L10n.of(context).unlock,
|
|
icon: Icon(Icons.send_outlined),
|
|
onPressed: _inputBlocked
|
|
? null
|
|
: () => tryUnlock(_textEditingController.text),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (_inputBlocked)
|
|
const Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: CircularProgressIndicator.adaptive(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_textEditingController.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|