feat: add admin page

This commit is contained in:
2026-08-07 03:09:59 +02:00
parent 2c734493cf
commit 1dd7e69aa8
19 changed files with 891 additions and 65 deletions
+48 -6
View File
@@ -15,8 +15,20 @@ const _pinLength = 4;
class PinEntryView extends StatefulWidget {
final PinEntryMode mode;
final VoidCallback? onSuccess;
final bool isAdmin;
final Future<bool> Function(String)? onVerify;
final Future<bool> Function(String)? onSet;
final String? Function()? errorMessage;
const PinEntryView({super.key, required this.mode, this.onSuccess});
const PinEntryView({
super.key,
required this.mode,
this.onSuccess,
this.isAdmin = false,
this.onVerify,
this.onSet,
this.errorMessage,
});
@override
State<PinEntryView> createState() => _PinEntryViewState();
@@ -33,12 +45,22 @@ class _PinEntryViewState extends State<PinEntryView> {
bool get _isCreateFlow => widget.mode == PinEntryMode.create;
String _title(AppLocalizations l10n) {
if (widget.isAdmin) {
if (!_isCreateFlow) return l10n.enterAdminPin;
return _isConfirmStep ? l10n.confirmAdminPin : l10n.createAdminPin;
}
if (!_isCreateFlow) return l10n.enterPin;
return _isConfirmStep ? l10n.confirmPin : l10n.createPin;
}
String? _subtitle(AppLocalizations l10n) {
if (!_isCreateFlow) return null;
if (widget.isAdmin) {
return _isConfirmStep ? l10n.confirmPinSubtitle : l10n.adminPinSubtitle;
}
return _isConfirmStep ? l10n.confirmPinSubtitle : l10n.enterPinSubtitle;
}
@@ -64,7 +86,6 @@ class _PinEntryViewState extends State<PinEntryView> {
}
Future<void> _handleComplete() async {
final viewModel = context.read<PinLockViewModel>();
final l10n = AppLocalizations.of(context);
if (_isCreateFlow && !_isConfirmStep) {
@@ -90,7 +111,7 @@ class _PinEntryViewState extends State<PinEntryView> {
}
setState(() => _isSubmitting = true);
final ok = await viewModel.setPin(_digits);
final ok = await _setPin(_digits);
if (!mounted) return;
setState(() => _isSubmitting = false);
@@ -101,24 +122,45 @@ class _PinEntryViewState extends State<PinEntryView> {
_firstEntry = null;
_isConfirmStep = false;
});
_fail(l10n.localizedError(viewModel.errorMessage));
_fail(l10n.localizedError(_errorMessage()));
}
return;
}
// Unlock flow.
setState(() => _isSubmitting = true);
final ok = await viewModel.verify(_digits);
final ok = await _verify(_digits);
if (!mounted) return;
setState(() => _isSubmitting = false);
if (ok) {
widget.onSuccess?.call();
} else {
_fail(l10n.localizedError(viewModel.errorMessage));
_fail(l10n.localizedError(_errorMessage()));
}
}
Future<bool> _setPin(String pin) {
final callback = widget.onSet;
if (callback != null) return callback(pin);
return context.read<PinLockViewModel>().setPin(pin);
}
Future<bool> _verify(String pin) {
final callback = widget.onVerify;
if (callback != null) return callback(pin);
return context.read<PinLockViewModel>().verify(pin);
}
String? _errorMessage() {
final callback = widget.errorMessage;
if (callback != null) return callback();
return context.read<PinLockViewModel>().errorMessage;
}
void _fail(String message) {
setState(() {
_localError = message;