From 5e25126939bad8ef8cdd25a55bcefb4803f37fe8 Mon Sep 17 00:00:00 2001 From: TachibanaLolo Date: Sun, 25 Jan 2026 03:45:16 +0800 Subject: [PATCH 1/6] feat(kiosk): replace admin pin dialog with custom numeric keypad --- Kiosk/lib/screens/main_screen.dart | 37 +---- Kiosk/lib/screens/pin_input_dialog.dart | 177 ++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 33 deletions(-) create mode 100644 Kiosk/lib/screens/pin_input_dialog.dart diff --git a/Kiosk/lib/screens/main_screen.dart b/Kiosk/lib/screens/main_screen.dart index 01c5adc..691665d 100644 --- a/Kiosk/lib/screens/main_screen.dart +++ b/Kiosk/lib/screens/main_screen.dart @@ -9,6 +9,7 @@ import 'package:kiosk/models/product.dart'; import 'package:kiosk/models/order.dart' as model; // Alias to avoid conflict if needed import 'package:kiosk/screens/payment_screen.dart'; import 'package:kiosk/screens/manual_barcode_dialog.dart'; +import 'package:kiosk/screens/pin_input_dialog.dart'; import 'package:kiosk/screens/no_barcode_products_dialog.dart'; import 'package:kiosk/screens/settings_screen.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; @@ -182,42 +183,12 @@ class _MainScreenState extends State { final expected = _settingsService.getPin(); if (expected == null || expected.isEmpty) return true; - final l10n = AppLocalizations.of(context)!; - final controller = TextEditingController(); - final ok = await showDialog( + final result = await showDialog( context: context, barrierDismissible: false, - builder: (dialogContext) { - return AlertDialog( - title: Text(l10n.adminConfirm), - content: TextField( - controller: controller, - obscureText: true, - keyboardType: TextInputType.number, - decoration: InputDecoration(labelText: l10n.enterPin), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(dialogContext, false), - child: Text(l10n.cancel), - ), - TextButton( - onPressed: () { - if (controller.text.trim() == expected) { - Navigator.pop(dialogContext, true); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.invalidPin)), - ); - } - }, - child: Text(l10n.confirm), - ), - ], - ); - }, + builder: (dialogContext) => PinInputDialog(expectedPin: expected), ); - return ok ?? false; + return result ?? false; } Future _resumePendingPaymentIfAny() async { diff --git a/Kiosk/lib/screens/pin_input_dialog.dart b/Kiosk/lib/screens/pin_input_dialog.dart new file mode 100644 index 0000000..862882e --- /dev/null +++ b/Kiosk/lib/screens/pin_input_dialog.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:kiosk/l10n/app_localizations.dart'; + +class PinInputDialog extends StatefulWidget { + final String? expectedPin; // If provided, validates internally + + const PinInputDialog({ + super.key, + this.expectedPin, + }); + + @override + State createState() => _PinInputDialogState(); +} + +class _PinInputDialogState extends State { + String _input = ''; + + void _onKeyTap(String key) { + if (_input.length >= 6) return; // Limit PIN length + setState(() { + _input += key; + }); + } + + void _onBackspace() { + if (_input.isNotEmpty) { + setState(() { + _input = _input.substring(0, _input.length - 1); + }); + } + } + + void _onClear() { + setState(() { + _input = ''; + }); + } + + void _onConfirm() { + if (widget.expectedPin != null) { + if (_input == widget.expectedPin) { + Navigator.pop(context, true); + } else { + final l10n = AppLocalizations.of(context)!; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.invalidPin)), + ); + _onClear(); + } + } else { + Navigator.pop(context, _input); + } + } + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + + return Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + width: 400, // Narrower than barcode dialog + height: 550, + padding: const EdgeInsets.all(24), + child: Column( + children: [ + // Header + Text( + l10n.adminConfirm, + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold) + ), + const SizedBox(height: 8), + Text(l10n.enterPin, style: const TextStyle(color: Colors.grey)), + const Divider(height: 32), + + // Input Display (Masked) + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _input.isEmpty ? '----' : '*' * _input.length, + style: TextStyle( + fontSize: 32, + color: _input.isEmpty ? Colors.grey : Colors.black, + letterSpacing: 8, + ), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 32), + + // Numeric Keypad + Expanded( + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 1.5, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), + children: [ + for (var i = 1; i <= 9; i++) + _buildKey(i.toString()), + _buildActionButton(l10n.clear, Colors.orange, _onClear), + _buildKey('0'), + _buildBackspace(), + ], + ), + ), + const SizedBox(height: 16), + + // Actions + Row( + children: [ + Expanded(child: _buildActionButton(l10n.cancel, Colors.grey, () => Navigator.pop(context))), + const SizedBox(width: 16), + Expanded(child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm)), + ], + ), + ], + ), + ), + ); + } + + Widget _buildKey(String label, {VoidCallback? onTap}) { + return InkWell( + onTap: onTap ?? () => _onKeyTap(label), + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[200], + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 2, offset: const Offset(1, 1)), + ], + ), + alignment: Alignment.center, + child: Text(label, style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), + ), + ); + } + + Widget _buildBackspace() { + return InkWell( + onTap: _onBackspace, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: const Icon(Icons.backspace, size: 28), + ), + ); + } + + Widget _buildActionButton(String label, Color color, VoidCallback onTap) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: Text(label, style: const TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold)), + ), + ); + } +} From 9eb292a9676e6092f9702972592c7b897e5ad03d Mon Sep 17 00:00:00 2001 From: TachibanaLolo Date: Sun, 25 Jan 2026 03:55:26 +0800 Subject: [PATCH 2/6] feat(kiosk): numeric keypad for pin setup and settings --- Kiosk/lib/screens/pin_input_dialog.dart | 30 +-- Kiosk/lib/screens/pin_setup_screen.dart | 308 ++++++++++++++++++------ Kiosk/lib/screens/settings_screen.dart | 34 +-- 3 files changed, 253 insertions(+), 119 deletions(-) diff --git a/Kiosk/lib/screens/pin_input_dialog.dart b/Kiosk/lib/screens/pin_input_dialog.dart index 862882e..99f76ba 100644 --- a/Kiosk/lib/screens/pin_input_dialog.dart +++ b/Kiosk/lib/screens/pin_input_dialog.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:kiosk/l10n/app_localizations.dart'; class PinInputDialog extends StatefulWidget { - final String? expectedPin; // If provided, validates internally + final String? expectedPin; const PinInputDialog({ super.key, @@ -17,7 +17,7 @@ class _PinInputDialogState extends State { String _input = ''; void _onKeyTap(String key) { - if (_input.length >= 6) return; // Limit PIN length + if (_input.length >= 6) return; setState(() { _input += key; }); @@ -60,21 +60,19 @@ class _PinInputDialogState extends State { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( - width: 400, // Narrower than barcode dialog + width: 420, height: 550, padding: const EdgeInsets.all(24), child: Column( children: [ - // Header Text( - l10n.adminConfirm, - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold) + l10n.adminConfirm, + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text(l10n.enterPin, style: const TextStyle(color: Colors.grey)), const Divider(height: 32), - // Input Display (Masked) Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), @@ -94,26 +92,24 @@ class _PinInputDialogState extends State { ), const SizedBox(height: 32), - // Numeric Keypad - Expanded( + SizedBox( + height: 260, child: GridView.count( crossAxisCount: 3, - childAspectRatio: 1.5, + childAspectRatio: 1.55, mainAxisSpacing: 10, crossAxisSpacing: 10, physics: const NeverScrollableScrollPhysics(), children: [ for (var i = 1; i <= 9; i++) _buildKey(i.toString()), - _buildActionButton(l10n.clear, Colors.orange, _onClear), - _buildKey('0'), _buildBackspace(), + _buildKey('0'), + _buildActionButton(l10n.clear, Colors.orange, _onClear), ], ), ), const SizedBox(height: 16), - - // Actions Row( children: [ Expanded(child: _buildActionButton(l10n.cancel, Colors.grey, () => Navigator.pop(context))), @@ -136,7 +132,11 @@ class _PinInputDialogState extends State { color: Colors.grey[200], borderRadius: BorderRadius.circular(8), boxShadow: [ - BoxShadow(color: Colors.black.withValues(alpha: 0.1), blurRadius: 2, offset: const Offset(1, 1)), + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 2, + offset: const Offset(1, 1), + ), ], ), alignment: Alignment.center, diff --git a/Kiosk/lib/screens/pin_setup_screen.dart b/Kiosk/lib/screens/pin_setup_screen.dart index 7b445cd..a746695 100644 --- a/Kiosk/lib/screens/pin_setup_screen.dart +++ b/Kiosk/lib/screens/pin_setup_screen.dart @@ -11,20 +11,73 @@ class PinSetupScreen extends StatefulWidget { } class _PinSetupScreenState extends State { - final TextEditingController _pinController = TextEditingController(); - final TextEditingController _confirmPinController = TextEditingController(); final SettingsService _settingsService = SettingsService(); - final _formKey = GlobalKey(); - - void _savePin() async { - if (_formKey.currentState!.validate()) { - await _settingsService.setPin(_pinController.text); - if (mounted) { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (_) => const MainScreen()), - ); + String _pin = ''; + String _confirmPin = ''; + bool _editingConfirm = false; + + void _onKeyTap(String key) { + final value = _editingConfirm ? _confirmPin : _pin; + if (value.length >= 6) return; + setState(() { + if (_editingConfirm) { + _confirmPin += key; + } else { + _pin += key; + } + }); + } + + void _onBackspace() { + setState(() { + if (_editingConfirm) { + if (_confirmPin.isNotEmpty) { + _confirmPin = _confirmPin.substring(0, _confirmPin.length - 1); + } + } else { + if (_pin.isNotEmpty) { + _pin = _pin.substring(0, _pin.length - 1); + } } + }); + } + + void _onClear() { + setState(() { + if (_editingConfirm) { + _confirmPin = ''; + } else { + _pin = ''; + } + }); + } + + Future _savePin() async { + final l10n = AppLocalizations.of(context)!; + if (_pin.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.pinRequired)), + ); + return; + } + if (_pin.length < 4) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.pinLength)), + ); + return; } + if (_confirmPin != _pin) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.pinMismatch)), + ); + return; + } + + await _settingsService.setPin(_pin); + if (!mounted) return; + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (_) => const MainScreen()), + ); } @override @@ -41,75 +94,184 @@ class _PinSetupScreenState extends State { child: Container( constraints: const BoxConstraints(maxWidth: 400), padding: const EdgeInsets.all(24), - child: Form( - key: _formKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - l10n.setAdminPin, - style: theme.textTheme.headlineMedium, - textAlign: TextAlign.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + l10n.setAdminPin, + style: theme.textTheme.headlineMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + Text( + l10n.pinDescription, + style: theme.textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + _PinDisplay( + label: l10n.enterPin, + value: _pin, + selected: !_editingConfirm, + icon: Icons.lock, + onTap: () => setState(() => _editingConfirm = false), + ), + const SizedBox(height: 12), + _PinDisplay( + label: l10n.confirmPin, + value: _confirmPin, + selected: _editingConfirm, + icon: Icons.lock_outline, + onTap: () => setState(() => _editingConfirm = true), + ), + const SizedBox(height: 20), + SizedBox( + height: 280, + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 1.55, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), + children: [ + for (var i = 1; i <= 9; i++) + _PinKey( + label: i.toString(), + onTap: () => _onKeyTap(i.toString()), + ), + _PinKey( + onTap: _onBackspace, + child: const Icon(Icons.backspace, size: 28), + ), + _PinKey(label: '0', onTap: () => _onKeyTap('0')), + _PinKey( + label: l10n.clear, + color: Colors.orange, + textStyle: const TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + onTap: _onClear, + ), + ], ), - const SizedBox(height: 16), - Text( - l10n.pinDescription, - style: theme.textTheme.bodyMedium, - textAlign: TextAlign.center, + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: _savePin, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), ), - const SizedBox(height: 32), - TextFormField( - controller: _pinController, - decoration: InputDecoration( - labelText: l10n.enterPin, - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.lock), - ), - keyboardType: TextInputType.number, - obscureText: true, - validator: (value) { - if (value == null || value.isEmpty) { - return l10n.pinRequired; - } - if (value.length < 4) { - return l10n.pinLength; - } - return null; - }, - ), - const SizedBox(height: 16), - TextFormField( - controller: _confirmPinController, - decoration: InputDecoration( - labelText: l10n.confirmPin, - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.lock_outline), - ), - keyboardType: TextInputType.number, - obscureText: true, - validator: (value) { - if (value != _pinController.text) { - return l10n.pinMismatch; - } - return null; - }, + child: Text( + l10n.saveAndContinue, + style: const TextStyle(fontSize: 18), ), - const SizedBox(height: 32), - ElevatedButton( - onPressed: _savePin, - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + ), + ], + ), + ), + ), + ); + } +} + +class _PinDisplay extends StatelessWidget { + final String label; + final String value; + final bool selected; + final IconData icon; + final VoidCallback onTap; + + const _PinDisplay({ + required this.label, + required this.value, + required this.selected, + required this.icon, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final borderColor = selected ? theme.colorScheme.primary : Colors.grey; + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14), + decoration: BoxDecoration( + border: Border.all(color: borderColor), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon(icon, color: selected ? theme.colorScheme.primary : Colors.grey), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: theme.textTheme.labelMedium?.copyWith(color: Colors.grey), ), - child: Text( - l10n.saveAndContinue, - style: const TextStyle(fontSize: 18), + const SizedBox(height: 6), + Text( + value.isEmpty ? '----' : '*' * value.length, + style: const TextStyle(fontSize: 22, letterSpacing: 4), ), - ), - ], + ], + ), ), - ), + ], + ), + ), + ); + } +} + +class _PinKey extends StatelessWidget { + final String? label; + final Widget? child; + final VoidCallback onTap; + final Color? color; + final TextStyle? textStyle; + + const _PinKey({ + this.label, + this.child, + required this.onTap, + this.color, + this.textStyle, + }) : assert(label != null || child != null); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: color ?? Colors.grey[200], + borderRadius: BorderRadius.circular(8), + boxShadow: color == null + ? [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 2, + offset: const Offset(1, 1), + ), + ] + : null, ), + alignment: Alignment.center, + child: child ?? + Text( + label!, + style: textStyle ?? const TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + ), ), ); } diff --git a/Kiosk/lib/screens/settings_screen.dart b/Kiosk/lib/screens/settings_screen.dart index a051813..165a758 100644 --- a/Kiosk/lib/screens/settings_screen.dart +++ b/Kiosk/lib/screens/settings_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:kiosk/services/server/kiosk_server.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:kiosk/l10n/app_localizations.dart'; +import 'package:kiosk/screens/pin_input_dialog.dart'; import 'package:kiosk/services/android_launcher_service.dart'; import 'package:kiosk/services/settings_service.dart'; @@ -52,40 +53,11 @@ class _SettingsScreenState extends State { } Future _confirmPin() async { - final l10n = AppLocalizations.of(context)!; final expected = _settingsService.getPin() ?? _pinController.text.trim(); - final controller = TextEditingController(); final ok = await showDialog( context: context, - builder: (context) { - return AlertDialog( - title: Text(l10n.adminConfirm), - content: TextField( - controller: controller, - obscureText: true, - keyboardType: TextInputType.number, - decoration: InputDecoration(labelText: l10n.enterPin), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: Text(l10n.cancel), - ), - TextButton( - onPressed: () { - if (controller.text.trim() == expected) { - Navigator.pop(context, true); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.invalidPin)), - ); - } - }, - child: Text(l10n.confirm), - ), - ], - ); - }, + barrierDismissible: false, + builder: (context) => PinInputDialog(expectedPin: expected), ); return ok ?? false; } From 2360b72adb4fdd1f61c62586928b9404bd04a992 Mon Sep 17 00:00:00 2001 From: TachibanaLolo Date: Sun, 25 Jan 2026 04:05:16 +0800 Subject: [PATCH 3/6] fix(kiosk): pin keypad layout and actions --- Kiosk/lib/screens/pin_input_dialog.dart | 177 ++++++++++++++---------- Kiosk/lib/screens/pin_setup_screen.dart | 84 ++++++++--- 2 files changed, 168 insertions(+), 93 deletions(-) diff --git a/Kiosk/lib/screens/pin_input_dialog.dart b/Kiosk/lib/screens/pin_input_dialog.dart index 99f76ba..7d3294f 100644 --- a/Kiosk/lib/screens/pin_input_dialog.dart +++ b/Kiosk/lib/screens/pin_input_dialog.dart @@ -59,70 +59,122 @@ class _PinInputDialogState extends State { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Container( - width: 420, - height: 550, - padding: const EdgeInsets.all(24), - child: Column( - children: [ - Text( - l10n.adminConfirm, - style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text(l10n.enterPin, style: const TextStyle(color: Colors.grey)), - const Divider(height: 32), - - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20), - decoration: BoxDecoration( - border: Border.all(color: Colors.grey), - borderRadius: BorderRadius.circular(8), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: 420, + maxHeight: MediaQuery.sizeOf(context).height * 0.85, + ), + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Text( + l10n.adminConfirm, + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), - child: Text( - _input.isEmpty ? '----' : '*' * _input.length, - style: TextStyle( - fontSize: 32, - color: _input.isEmpty ? Colors.grey : Colors.black, - letterSpacing: 8, + const SizedBox(height: 8), + Text(l10n.enterPin, style: const TextStyle(color: Colors.grey)), + const Divider(height: 24), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + _input.isEmpty ? '----' : '*' * _input.length, + style: TextStyle( + fontSize: 32, + color: _input.isEmpty ? Colors.grey : Colors.black, + letterSpacing: 8, + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, ), - ), - const SizedBox(height: 32), - - SizedBox( - height: 260, - child: GridView.count( - crossAxisCount: 3, - childAspectRatio: 1.55, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - physics: const NeverScrollableScrollPhysics(), - children: [ - for (var i = 1; i <= 9; i++) - _buildKey(i.toString()), - _buildBackspace(), - _buildKey('0'), - _buildActionButton(l10n.clear, Colors.orange, _onClear), - ], + const SizedBox(height: 16), + Expanded( + child: Column( + children: [ + Expanded( + child: Column( + children: [ + _buildRow(['1', '2', '3']), + const SizedBox(height: 10), + _buildRow(['4', '5', '6']), + const SizedBox(height: 10), + _buildRow(['7', '8', '9']), + const SizedBox(height: 10), + _buildBottomRow(l10n), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: _buildActionButton( + l10n.cancel, + Colors.grey, + () => Navigator.pop(context, false), + ), + ), + const SizedBox(width: 16), + Expanded(child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm)), + ], + ), + ], + ), ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded(child: _buildActionButton(l10n.cancel, Colors.grey, () => Navigator.pop(context))), - const SizedBox(width: 16), - Expanded(child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm)), - ], - ), - ], + ], + ), ), ), ); } + Widget _buildRow(List labels) { + return Expanded( + child: Row( + children: [ + Expanded(child: _buildKey(labels[0])), + const SizedBox(width: 10), + Expanded(child: _buildKey(labels[1])), + const SizedBox(width: 10), + Expanded(child: _buildKey(labels[2])), + ], + ), + ); + } + + Widget _buildBottomRow(AppLocalizations l10n) { + return Expanded( + child: Row( + children: [ + Expanded( + child: InkWell( + onTap: _onBackspace, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: const Icon(Icons.backspace, size: 28), + ), + ), + ), + const SizedBox(width: 10), + Expanded(child: _buildKey('0')), + const SizedBox(width: 10), + Expanded(child: _buildActionButton(l10n.clear, Colors.orange, _onClear)), + ], + ), + ); + } + Widget _buildKey(String label, {VoidCallback? onTap}) { return InkWell( onTap: onTap ?? () => _onKeyTap(label), @@ -144,21 +196,6 @@ class _PinInputDialogState extends State { ), ); } - - Widget _buildBackspace() { - return InkWell( - onTap: _onBackspace, - borderRadius: BorderRadius.circular(8), - child: Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(8), - ), - alignment: Alignment.center, - child: const Icon(Icons.backspace, size: 28), - ), - ); - } Widget _buildActionButton(String label, Color color, VoidCallback onTap) { return InkWell( diff --git a/Kiosk/lib/screens/pin_setup_screen.dart b/Kiosk/lib/screens/pin_setup_screen.dart index a746695..606f047 100644 --- a/Kiosk/lib/screens/pin_setup_screen.dart +++ b/Kiosk/lib/screens/pin_setup_screen.dart @@ -127,33 +127,71 @@ class _PinSetupScreenState extends State { ), const SizedBox(height: 20), SizedBox( - height: 280, - child: GridView.count( - crossAxisCount: 3, - childAspectRatio: 1.55, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - physics: const NeverScrollableScrollPhysics(), + height: 320, + child: Column( children: [ - for (var i = 1; i <= 9; i++) - _PinKey( - label: i.toString(), - onTap: () => _onKeyTap(i.toString()), + Expanded( + child: Row( + children: [ + Expanded(child: _PinKey(label: '1', onTap: () => _onKeyTap('1'))), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '2', onTap: () => _onKeyTap('2'))), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '3', onTap: () => _onKeyTap('3'))), + ], ), - _PinKey( - onTap: _onBackspace, - child: const Icon(Icons.backspace, size: 28), ), - _PinKey(label: '0', onTap: () => _onKeyTap('0')), - _PinKey( - label: l10n.clear, - color: Colors.orange, - textStyle: const TextStyle( - fontSize: 18, - color: Colors.white, - fontWeight: FontWeight.bold, + const SizedBox(height: 10), + Expanded( + child: Row( + children: [ + Expanded(child: _PinKey(label: '4', onTap: () => _onKeyTap('4'))), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '5', onTap: () => _onKeyTap('5'))), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '6', onTap: () => _onKeyTap('6'))), + ], + ), + ), + const SizedBox(height: 10), + Expanded( + child: Row( + children: [ + Expanded(child: _PinKey(label: '7', onTap: () => _onKeyTap('7'))), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '8', onTap: () => _onKeyTap('8'))), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '9', onTap: () => _onKeyTap('9'))), + ], + ), + ), + const SizedBox(height: 10), + Expanded( + child: Row( + children: [ + Expanded( + child: _PinKey( + onTap: _onBackspace, + child: const Icon(Icons.backspace, size: 28), + ), + ), + const SizedBox(width: 10), + Expanded(child: _PinKey(label: '0', onTap: () => _onKeyTap('0'))), + const SizedBox(width: 10), + Expanded( + child: _PinKey( + label: l10n.clear, + color: Colors.orange, + textStyle: const TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + onTap: _onClear, + ), + ), + ], ), - onTap: _onClear, ), ], ), From c386afdfaf57e69f3e4aeed7815b03e23533f3f5 Mon Sep 17 00:00:00 2001 From: TachibanaLolo Date: Sun, 25 Jan 2026 04:10:36 +0800 Subject: [PATCH 4/6] ui(kiosk): shrink pin keypad --- Kiosk/lib/screens/pin_input_dialog.dart | 12 ++++++------ Kiosk/lib/screens/pin_setup_screen.dart | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Kiosk/lib/screens/pin_input_dialog.dart b/Kiosk/lib/screens/pin_input_dialog.dart index 7d3294f..d640a54 100644 --- a/Kiosk/lib/screens/pin_input_dialog.dart +++ b/Kiosk/lib/screens/pin_input_dialog.dart @@ -101,11 +101,11 @@ class _PinInputDialogState extends State { child: Column( children: [ _buildRow(['1', '2', '3']), - const SizedBox(height: 10), + const SizedBox(height: 8), _buildRow(['4', '5', '6']), - const SizedBox(height: 10), + const SizedBox(height: 8), _buildRow(['7', '8', '9']), - const SizedBox(height: 10), + const SizedBox(height: 8), _buildBottomRow(l10n), ], ), @@ -162,7 +162,7 @@ class _PinInputDialogState extends State { borderRadius: BorderRadius.circular(8), ), alignment: Alignment.center, - child: const Icon(Icons.backspace, size: 28), + child: const Icon(Icons.backspace, size: 24), ), ), ), @@ -192,7 +192,7 @@ class _PinInputDialogState extends State { ], ), alignment: Alignment.center, - child: Text(label, style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), + child: Text(label, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), ), ); } @@ -207,7 +207,7 @@ class _PinInputDialogState extends State { borderRadius: BorderRadius.circular(8), ), alignment: Alignment.center, - child: Text(label, style: const TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold)), + child: Text(label, style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold)), ), ); } diff --git a/Kiosk/lib/screens/pin_setup_screen.dart b/Kiosk/lib/screens/pin_setup_screen.dart index 606f047..2074da6 100644 --- a/Kiosk/lib/screens/pin_setup_screen.dart +++ b/Kiosk/lib/screens/pin_setup_screen.dart @@ -172,7 +172,7 @@ class _PinSetupScreenState extends State { Expanded( child: _PinKey( onTap: _onBackspace, - child: const Icon(Icons.backspace, size: 28), + child: const Icon(Icons.backspace, size: 24), ), ), const SizedBox(width: 10), @@ -183,7 +183,7 @@ class _PinSetupScreenState extends State { label: l10n.clear, color: Colors.orange, textStyle: const TextStyle( - fontSize: 18, + fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold, ), @@ -308,7 +308,7 @@ class _PinKey extends StatelessWidget { child: child ?? Text( label!, - style: textStyle ?? const TextStyle(fontSize: 28, fontWeight: FontWeight.bold), + style: textStyle ?? const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), ), ); From 7da9639ce6f5fad66cfe2c17d1a45b3553e1e9ce Mon Sep 17 00:00:00 2001 From: TachibanaLolo Date: Sun, 25 Jan 2026 04:16:00 +0800 Subject: [PATCH 5/6] ui(kiosk): shorten pin keypad and set number font 28 --- Kiosk/lib/screens/pin_input_dialog.dart | 111 +++++++++--------------- Kiosk/lib/screens/pin_setup_screen.dart | 86 +++++------------- 2 files changed, 65 insertions(+), 132 deletions(-) diff --git a/Kiosk/lib/screens/pin_input_dialog.dart b/Kiosk/lib/screens/pin_input_dialog.dart index d640a54..4f2dc0e 100644 --- a/Kiosk/lib/screens/pin_input_dialog.dart +++ b/Kiosk/lib/screens/pin_input_dialog.dart @@ -75,10 +75,10 @@ class _PinInputDialogState extends State { ), const SizedBox(height: 8), Text(l10n.enterPin, style: const TextStyle(color: Colors.grey)), - const Divider(height: 24), + const Divider(height: 20), Container( width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), decoration: BoxDecoration( border: Border.all(color: Colors.grey), borderRadius: BorderRadius.circular(8), @@ -93,40 +93,37 @@ class _PinInputDialogState extends State { textAlign: TextAlign.center, ), ), - const SizedBox(height: 16), - Expanded( - child: Column( + const SizedBox(height: 12), + SizedBox( + height: 240, + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 2.35, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), children: [ - Expanded( - child: Column( - children: [ - _buildRow(['1', '2', '3']), - const SizedBox(height: 8), - _buildRow(['4', '5', '6']), - const SizedBox(height: 8), - _buildRow(['7', '8', '9']), - const SizedBox(height: 8), - _buildBottomRow(l10n), - ], - ), - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: _buildActionButton( - l10n.cancel, - Colors.grey, - () => Navigator.pop(context, false), - ), - ), - const SizedBox(width: 16), - Expanded(child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm)), - ], - ), + for (var i = 1; i <= 9; i++) _buildKey(i.toString()), + _buildBackspaceKey(), + _buildKey('0'), + _buildActionButton(l10n.clear, Colors.orange, _onClear), ], ), ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildActionButton( + l10n.cancel, + Colors.grey, + () => Navigator.pop(context, false), + ), + ), + const SizedBox(width: 16), + Expanded(child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm)), + ], + ), ], ), ), @@ -134,43 +131,17 @@ class _PinInputDialogState extends State { ); } - Widget _buildRow(List labels) { - return Expanded( - child: Row( - children: [ - Expanded(child: _buildKey(labels[0])), - const SizedBox(width: 10), - Expanded(child: _buildKey(labels[1])), - const SizedBox(width: 10), - Expanded(child: _buildKey(labels[2])), - ], - ), - ); - } - - Widget _buildBottomRow(AppLocalizations l10n) { - return Expanded( - child: Row( - children: [ - Expanded( - child: InkWell( - onTap: _onBackspace, - borderRadius: BorderRadius.circular(8), - child: Container( - decoration: BoxDecoration( - color: Colors.grey[300], - borderRadius: BorderRadius.circular(8), - ), - alignment: Alignment.center, - child: const Icon(Icons.backspace, size: 24), - ), - ), - ), - const SizedBox(width: 10), - Expanded(child: _buildKey('0')), - const SizedBox(width: 10), - Expanded(child: _buildActionButton(l10n.clear, Colors.orange, _onClear)), - ], + Widget _buildBackspaceKey() { + return InkWell( + onTap: _onBackspace, + borderRadius: BorderRadius.circular(8), + child: Container( + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + child: const Icon(Icons.backspace, size: 24), ), ); } @@ -192,7 +163,7 @@ class _PinInputDialogState extends State { ], ), alignment: Alignment.center, - child: Text(label, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + child: Text(label, style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold)), ), ); } diff --git a/Kiosk/lib/screens/pin_setup_screen.dart b/Kiosk/lib/screens/pin_setup_screen.dart index 2074da6..328ef64 100644 --- a/Kiosk/lib/screens/pin_setup_screen.dart +++ b/Kiosk/lib/screens/pin_setup_screen.dart @@ -127,71 +127,33 @@ class _PinSetupScreenState extends State { ), const SizedBox(height: 20), SizedBox( - height: 320, - child: Column( + height: 260, + child: GridView.count( + crossAxisCount: 3, + childAspectRatio: 2.35, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + physics: const NeverScrollableScrollPhysics(), children: [ - Expanded( - child: Row( - children: [ - Expanded(child: _PinKey(label: '1', onTap: () => _onKeyTap('1'))), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '2', onTap: () => _onKeyTap('2'))), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '3', onTap: () => _onKeyTap('3'))), - ], + for (var i = 1; i <= 9; i++) + _PinKey( + label: i.toString(), + onTap: () => _onKeyTap(i.toString()), ), + _PinKey( + onTap: _onBackspace, + child: const Icon(Icons.backspace, size: 24), ), - const SizedBox(height: 10), - Expanded( - child: Row( - children: [ - Expanded(child: _PinKey(label: '4', onTap: () => _onKeyTap('4'))), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '5', onTap: () => _onKeyTap('5'))), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '6', onTap: () => _onKeyTap('6'))), - ], - ), - ), - const SizedBox(height: 10), - Expanded( - child: Row( - children: [ - Expanded(child: _PinKey(label: '7', onTap: () => _onKeyTap('7'))), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '8', onTap: () => _onKeyTap('8'))), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '9', onTap: () => _onKeyTap('9'))), - ], - ), - ), - const SizedBox(height: 10), - Expanded( - child: Row( - children: [ - Expanded( - child: _PinKey( - onTap: _onBackspace, - child: const Icon(Icons.backspace, size: 24), - ), - ), - const SizedBox(width: 10), - Expanded(child: _PinKey(label: '0', onTap: () => _onKeyTap('0'))), - const SizedBox(width: 10), - Expanded( - child: _PinKey( - label: l10n.clear, - color: Colors.orange, - textStyle: const TextStyle( - fontSize: 16, - color: Colors.white, - fontWeight: FontWeight.bold, - ), - onTap: _onClear, - ), - ), - ], + _PinKey(label: '0', onTap: () => _onKeyTap('0')), + _PinKey( + label: l10n.clear, + color: Colors.orange, + textStyle: const TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.bold, ), + onTap: _onClear, ), ], ), @@ -308,7 +270,7 @@ class _PinKey extends StatelessWidget { child: child ?? Text( label!, - style: textStyle ?? const TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + style: textStyle ?? const TextStyle(fontSize: 28, fontWeight: FontWeight.bold), ), ), ); From be648e8cf10a83e086d864e5002d1de8d889c180 Mon Sep 17 00:00:00 2001 From: TachibanaLolo Date: Sun, 25 Jan 2026 04:23:11 +0800 Subject: [PATCH 6/6] ui(kiosk): fix pinpad height and enlarge action buttons --- Kiosk/lib/screens/pin_input_dialog.dart | 23 +++++++++++++++-------- Kiosk/lib/screens/pin_setup_screen.dart | 6 +++--- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Kiosk/lib/screens/pin_input_dialog.dart b/Kiosk/lib/screens/pin_input_dialog.dart index 4f2dc0e..700bc0f 100644 --- a/Kiosk/lib/screens/pin_input_dialog.dart +++ b/Kiosk/lib/screens/pin_input_dialog.dart @@ -62,12 +62,11 @@ class _PinInputDialogState extends State { child: ConstrainedBox( constraints: BoxConstraints( maxWidth: 420, - maxHeight: MediaQuery.sizeOf(context).height * 0.85, ), child: Padding( padding: const EdgeInsets.all(24), child: Column( - mainAxisSize: MainAxisSize.max, + mainAxisSize: MainAxisSize.min, children: [ Text( l10n.adminConfirm, @@ -114,14 +113,22 @@ class _PinInputDialogState extends State { Row( children: [ Expanded( - child: _buildActionButton( - l10n.cancel, - Colors.grey, - () => Navigator.pop(context, false), + child: SizedBox( + height: 56, + child: _buildActionButton( + l10n.cancel, + Colors.grey, + () => Navigator.pop(context, false), + ), ), ), const SizedBox(width: 16), - Expanded(child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm)), + Expanded( + child: SizedBox( + height: 56, + child: _buildActionButton(l10n.confirm, Colors.blue, _onConfirm), + ), + ), ], ), ], @@ -178,7 +185,7 @@ class _PinInputDialogState extends State { borderRadius: BorderRadius.circular(8), ), alignment: Alignment.center, - child: Text(label, style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold)), + child: Text(label, style: const TextStyle(fontSize: 18, color: Colors.white, fontWeight: FontWeight.bold)), ), ); } diff --git a/Kiosk/lib/screens/pin_setup_screen.dart b/Kiosk/lib/screens/pin_setup_screen.dart index 328ef64..50f114b 100644 --- a/Kiosk/lib/screens/pin_setup_screen.dart +++ b/Kiosk/lib/screens/pin_setup_screen.dart @@ -127,7 +127,7 @@ class _PinSetupScreenState extends State { ), const SizedBox(height: 20), SizedBox( - height: 260, + height: 240, child: GridView.count( crossAxisCount: 3, childAspectRatio: 2.35, @@ -162,11 +162,11 @@ class _PinSetupScreenState extends State { ElevatedButton( onPressed: _savePin, style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), + minimumSize: const Size.fromHeight(56), ), child: Text( l10n.saveAndContinue, - style: const TextStyle(fontSize: 18), + style: const TextStyle(fontSize: 20), ), ), ],