fix: pincode functionality

This commit is contained in:
2026-07-29 22:43:38 +02:00
parent 3cf4787e8c
commit a732e2d07f
8 changed files with 112 additions and 56 deletions
+6 -4
View File
@@ -21,10 +21,12 @@ GoRouter createAppRouter(PinLockViewModel pinLockViewModel) {
redirect: (context, state) {
if (!pinLockViewModel.hasLoaded) return null;
final needsSetup = !pinLockViewModel.isPinSet;
final isLocked =
pinLockViewModel.isPinSet && !pinLockViewModel.isUnlocked;
final mustBeOnLock = needsSetup || isLocked;
final needsPinSetup =
!pinLockViewModel.isPinSet && pinLockViewModel.pinRequired;
final isLocked = pinLockViewModel.isPinSet &&
!pinLockViewModel.isUnlocked &&
pinLockViewModel.pinRequired;
final mustBeOnLock = needsPinSetup || isLocked;
final goingToLock = state.matchedLocation == '/lock';
+1
View File
@@ -2,6 +2,7 @@ import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:path_provider/path_provider.dart';
part 'app_database.g.dart';
@DataClassName('ProductRow')
+7 -5
View File
@@ -31,7 +31,12 @@ Future<void> main() async {
DeviceOrientation.landscapeRight,
]);
final pinLockViewModel = PinLockViewModel(pinLockService: PinLockService());
final database = AppDatabase();
final pinLockViewModel = PinLockViewModel(
pinLockService: PinLockService(),
settingsService: DriftSettingsService(database: database),
);
await pinLockViewModel.ensureLoaded();
final appRouter = createAppRouter(pinLockViewModel);
@@ -39,10 +44,7 @@ Future<void> main() async {
runApp(
MultiProvider(
providers: [
Provider<AppDatabase>(
create: (_) => AppDatabase(),
dispose: (_, database) => database.close(),
),
Provider<AppDatabase>.value(value: database),
Provider<ProductService>(
create: (context) =>
DriftProductService(database: context.read<AppDatabase>()),
+3
View File
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:kooltab2/utils/app_update_util.dart';
import 'package:ota_update/ota_update.dart';
@@ -8,6 +10,7 @@ class UpdateChecker {
static bool _hasChecked = false;
static Future<void> check(BuildContext context) async {
if (!Platform.isAndroid) return;
if (_hasChecked) {
debugPrint("Update already checked this boot");
return;
+34 -1
View File
@@ -1,16 +1,19 @@
import 'package:flutter/foundation.dart';
import '../services/pin_lock_service.dart';
import '../services/settings_service.dart';
class PinLockViewModel extends ChangeNotifier {
final PinLockService pinLockService;
final SettingsService? settingsService;
PinLockViewModel({required this.pinLockService});
PinLockViewModel({required this.pinLockService, this.settingsService});
bool _isPinSet = false;
bool _isUnlocked = false;
bool _isLoading = false;
bool _hasLoaded = false;
bool _pinRequired = true;
String? _errorMessage;
bool get isPinSet => _isPinSet;
@@ -21,8 +24,16 @@ class PinLockViewModel extends ChangeNotifier {
bool get hasLoaded => _hasLoaded;
bool get pinRequired => _pinRequired;
String? get errorMessage => _errorMessage;
void setPinRequired(bool value) {
if (_pinRequired == value) return;
_pinRequired = value;
notifyListeners();
}
Future<void> ensureLoaded() async {
if (_hasLoaded || _isLoading) return;
@@ -39,6 +50,12 @@ class PinLockViewModel extends ChangeNotifier {
try {
_isPinSet = await pinLockService.hasPin();
final svc = settingsService;
if (svc != null) {
final settings = await svc.getSettings();
_pinRequired = settings.pinRequired;
}
} catch (_) {
_errorMessage = 'Could not check PIN status.';
} finally {
@@ -58,6 +75,14 @@ class PinLockViewModel extends ChangeNotifier {
await pinLockService.setPin(pin);
_isPinSet = true;
_isUnlocked = true;
_pinRequired = true;
final svc = settingsService;
if (svc != null) {
final current = await svc.getSettings();
await svc.saveSettings(current.copyWith(pinRequired: true));
}
notifyListeners();
return true;
} catch (_) {
@@ -118,6 +143,14 @@ class PinLockViewModel extends ChangeNotifier {
await pinLockService.clearPin();
_isPinSet = false;
_pinRequired = false;
final svc = settingsService;
if (svc != null) {
final current = await svc.getSettings();
await svc.saveSettings(current.copyWith(pinRequired: false));
}
notifyListeners();
return true;
}
+10 -8
View File
@@ -62,14 +62,16 @@ class _BarScreenViewState extends State<BarScreenView> {
onPressed: () => context.go('/settings'),
icon: const Icon(Icons.settings),
),
const SizedBox(width: 6),
IconButton(
tooltip: 'logout',
onPressed: () {
Provider.of<PinLockViewModel>(context, listen: false).lock();
},
icon: const Icon(Icons.logout),
),
if (context.watch<PinLockViewModel>().isPinSet) ...[
const SizedBox(width: 6),
IconButton(
tooltip: 'Logout',
onPressed: () {
Provider.of<PinLockViewModel>(context, listen: false).lock();
},
icon: const Icon(Icons.logout),
),
],
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
),
+10 -8
View File
@@ -242,30 +242,32 @@ class _PinKeypad extends StatelessWidget {
children: [
for (final row in _rows)
Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
for (final digit in row)
for (final digit in row) ...[
_KeypadButton(
label: digit,
onTap: enabled ? () => onDigit(digit) : null,
),
const SizedBox(width: 20),
],
],
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
spacing: 16,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const _KeypadButton(isSpacer: true, onTap: null),
const SizedBox(width: 20),
_KeypadButton(
label: '0',
onTap: enabled ? () => onDigit('0') : null,
),
const SizedBox(width: 20),
_KeypadButton(
icon: Icons.backspace_outlined,
onTap: enabled ? onBackspace : null,
@@ -294,14 +296,14 @@ class _KeypadButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (isSpacer) {
return const SizedBox(width: 72, height: 72);
return const SizedBox(width: 80, height: 80);
}
final scheme = Theme.of(context).colorScheme;
return SizedBox(
width: 72,
height: 72,
width: 80,
height: 80,
child: Material(
color: scheme.onSurface.withValues(alpha: 0.04),
shape: const CircleBorder(),
+40 -29
View File
@@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:package_info_plus/package_info_plus.dart';
@@ -55,32 +57,37 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
});
}
Future<String?> _promptPin(String title, {String hint = 'Enter PIN'}) {
Future<String?> _promptPin(String title, {String hint = 'Enter PIN (4 digits)'}) {
final controller = TextEditingController();
return showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: Text(title),
content: TextField(
controller: controller,
autofocus: true,
obscureText: true,
keyboardType: TextInputType.number,
maxLength: 6,
decoration: InputDecoration(hintText: hint),
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 0),
content: SizedBox(
width: 320,
child: TextField(
controller: controller,
autofocus: true,
obscureText: true,
keyboardType: TextInputType.number,
maxLength: 4,
decoration: InputDecoration(hintText: hint),
),
),
actionsPadding: const EdgeInsets.fromLTRB(16, 8, 16, 12),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
const SizedBox(width: 12),
FilledButton(
onPressed: () => Navigator.pop(context, controller.text),
child: const Text('Confirm'),
),
],
actionsPadding: const EdgeInsets.symmetric(horizontal: 8),
),
);
}
@@ -93,11 +100,11 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
}
Future<void> _enablePin() async {
final pin = await _promptPin('Set PIN Code', hint: '46 digits');
final pin = await _promptPin('Set PIN Code', hint: '4 digits');
if (pin == null) return;
if (pin.length < 4) {
_showError('PIN must be at least 4 digits');
if (pin.length != 4) {
_showError('PIN must be exactly 4 digits');
return;
}
@@ -109,6 +116,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
return;
}
pinLockViewModel.setPinRequired(true);
await context.read<SettingsViewModel>().updatePinRequired(true);
}
@@ -116,11 +124,11 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
final current = await _promptPin('Enter Current PIN');
if (current == null) return;
final newPin = await _promptPin('Enter New PIN', hint: '46 digits');
final newPin = await _promptPin('Enter New PIN', hint: '4 digits');
if (newPin == null) return;
if (newPin.length < 4) {
_showError('PIN must be at least 4 digits');
if (newPin.length != 4) {
_showError('PIN must be exactly 4 digits');
return;
}
@@ -147,6 +155,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
return;
}
pinLockViewModel.setPinRequired(false);
await context.read<SettingsViewModel>().updatePinRequired(false);
}
@@ -252,20 +261,21 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
),
],
),
_SettingsSection(
title: 'Updates',
children: [
_SettingsTile(
icon: Icons.system_update_alt_rounded,
title: settingsViewModel.checkingForUpdates
? 'Checking for updates...'
: 'Check for updates',
onTap: settingsViewModel.checkingForUpdates
? null
: _checkForUpdates,
),
],
),
if (Platform.isAndroid)
_SettingsSection(
title: 'Updates',
children: [
_SettingsTile(
icon: Icons.system_update_alt_rounded,
title: settingsViewModel.checkingForUpdates
? 'Checking for updates...'
: 'Check for updates',
onTap: settingsViewModel.checkingForUpdates
? null
: _checkForUpdates,
),
],
),
const SizedBox(height: 40),
GestureDetector(
onTap: _onVersionTap,
@@ -289,6 +299,7 @@ class _SettingsScreenViewState extends State<SettingsScreenView> {
}
Future<void> _checkForUpdates() async {
if (!Platform.isAndroid) return;
final vm = context.read<SettingsViewModel>();
try {