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