diff --git a/lib/screens/change_password_screen.dart b/lib/screens/change_password_screen.dart index b0d1b66..0837986 100644 --- a/lib/screens/change_password_screen.dart +++ b/lib/screens/change_password_screen.dart @@ -5,6 +5,7 @@ import 'package:http/http.dart' as http; import '../config.dart'; import '../theme/app_palette.dart'; import '../ui/app_notice.dart'; +import '../ui/hold_to_reveal_password_field.dart'; import '../ui/login_screen.dart'; // ๐Ÿ’ก main.dart ํŒŒ์ผ์˜ ์ตœํ•˜๋‹จ(๋‹ค๋ฅธ ํด๋ž˜์Šค ์ค‘๊ด„ํ˜ธ ๋ฐ–)์— ๋ถ™์—ฌ๋„ฃ์œผ์„ธ์š”. @@ -75,13 +76,12 @@ class _ChangePasswordScreenState extends State { style: TextStyle(color: Colors.grey), ), const SizedBox(height: 32), - TextField( + HoldToRevealPasswordField( controller: _pwController, - obscureText: true, - decoration: const InputDecoration( - labelText: '์ƒˆ๋กœ์šด ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ', - border: OutlineInputBorder(), - ), + autofocus: true, + labelText: '์ƒˆ๋กœ์šด ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ', + textInputAction: TextInputAction.done, + onSubmitted: (_) => _updatePassword(), ), const SizedBox(height: 16), SizedBox( diff --git a/lib/ui/admin_dashboard.dart b/lib/ui/admin_dashboard.dart index 4397c05..b307841 100644 --- a/lib/ui/admin_dashboard.dart +++ b/lib/ui/admin_dashboard.dart @@ -53,6 +53,7 @@ class _AdminDashboardState extends State { child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( + autofocus: true, style: ElevatedButton.styleFrom( backgroundColor: AppPalette.danger, ), diff --git a/lib/ui/board_post_detail_screen.dart b/lib/ui/board_post_detail_screen.dart index efc2bd0..4af2953 100644 --- a/lib/ui/board_post_detail_screen.dart +++ b/lib/ui/board_post_detail_screen.dart @@ -139,6 +139,7 @@ class _BoardPostDetailScreenState extends State { child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), TextButton( + autofocus: true, onPressed: () => Navigator.pop(dialogContext, true), child: const Text('์‚ญ์ œ', style: TextStyle(color: AppPalette.danger)), ), diff --git a/lib/ui/device_checkout_ledger_page.dart b/lib/ui/device_checkout_ledger_page.dart index 2453ae3..b762e85 100644 --- a/lib/ui/device_checkout_ledger_page.dart +++ b/lib/ui/device_checkout_ledger_page.dart @@ -77,6 +77,7 @@ class _DeviceCheckoutLedgerPageState extends State { child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( + autofocus: true, style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, @@ -208,6 +209,7 @@ class _DeviceCheckoutLedgerPageState extends State { ), actions: [ TextButton( + autofocus: true, onPressed: () => Navigator.pop(dialogContext), child: const Text('๋‹ซ๊ธฐ'), ), diff --git a/lib/ui/friends_screen.dart b/lib/ui/friends_screen.dart index 97878dd..4850169 100644 --- a/lib/ui/friends_screen.dart +++ b/lib/ui/friends_screen.dart @@ -47,7 +47,10 @@ class _FriendsScreenState extends State { title: const Text('์นœ๊ตฌ ์ถ”๊ฐ€'), content: TextField( controller: idController, + autofocus: true, keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + onSubmitted: (_) => Navigator.pop(dialogContext, true), decoration: const InputDecoration( labelText: '์นœ๊ตฌ ํ•™๋ฒˆ', border: OutlineInputBorder(), @@ -99,6 +102,7 @@ class _FriendsScreenState extends State { child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), TextButton( + autofocus: true, onPressed: () => Navigator.pop(dialogContext, true), child: const Text('์‚ญ์ œ', style: TextStyle(color: AppPalette.danger)), ), diff --git a/lib/ui/group_detail_screen.dart b/lib/ui/group_detail_screen.dart index 3fe1bb9..7af9daa 100644 --- a/lib/ui/group_detail_screen.dart +++ b/lib/ui/group_detail_screen.dart @@ -158,6 +158,7 @@ class _GroupDetailScreenState extends State { child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( + autofocus: true, style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, @@ -195,6 +196,7 @@ class _GroupDetailScreenState extends State { child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), TextButton( + autofocus: true, onPressed: () => Navigator.pop(dialogContext, true), child: const Text( '๋‚˜๊ฐ€๊ธฐ', diff --git a/lib/ui/group_list_screen.dart b/lib/ui/group_list_screen.dart index d4b2959..ea46fb7 100644 --- a/lib/ui/group_list_screen.dart +++ b/lib/ui/group_list_screen.dart @@ -57,7 +57,10 @@ class _GroupListScreenState extends State { children: [ TextField( controller: nameController, + autofocus: true, maxLength: 20, + textInputAction: TextInputAction.done, + onSubmitted: (_) => Navigator.pop(dialogContext, true), decoration: const InputDecoration( labelText: '๊ทธ๋ฃน ์ด๋ฆ„', border: OutlineInputBorder(), diff --git a/lib/ui/hold_to_reveal_password_field.dart b/lib/ui/hold_to_reveal_password_field.dart new file mode 100644 index 0000000..15b2e4a --- /dev/null +++ b/lib/ui/hold_to_reveal_password_field.dart @@ -0,0 +1,67 @@ +// ๐Ÿ‘๏ธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ์ฐฝ - ์˜ค๋ฅธ์ชฝ ๋ˆˆ ์•„์ด์ฝ˜์„ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ๋งŒ ํ‰๋ฌธ์œผ๋กœ ๋ณด์—ฌ์ค€๋‹ค. +// ํ† ๊ธ€ํ˜•(๋ˆŒ๋Ÿฌ์„œ ๊ณ„์† ๋ณด๊ธฐ) ๋Œ€์‹  "ํ™€๋“œ"๋กœ ๋งŒ๋“  ์ด์œ : ์†์„ ๋–ผ๋Š” ์ˆœ๊ฐ„ ๋ฐ”๋กœ ๋‹ค์‹œ ๊ฐ€๋ ค์ ธ์„œ +// ๋ˆ„๊ฐ€ ์˜†์—์„œ ๋ณด๋”๋ผ๋„ ์งง๊ฒŒ ํ™•์ธ๋งŒ ํ•˜๊ณ  ๋๋‚ผ ์ˆ˜ ์žˆ๋‹ค. +import 'package:flutter/material.dart'; + +class HoldToRevealPasswordField extends StatefulWidget { + final TextEditingController controller; + final String labelText; + final String? hintText; + final IconData prefixIcon; + final bool autofocus; + final TextInputType? keyboardType; + final TextInputAction? textInputAction; + final ValueChanged? onSubmitted; + + const HoldToRevealPasswordField({ + super.key, + required this.controller, + required this.labelText, + this.hintText, + this.prefixIcon = Icons.lock, + this.autofocus = false, + this.keyboardType, + this.textInputAction, + this.onSubmitted, + }); + + @override + State createState() => + _HoldToRevealPasswordFieldState(); +} + +class _HoldToRevealPasswordFieldState extends State { + bool _revealed = false; + + void _setRevealed(bool value) { + if (_revealed != value) setState(() => _revealed = value); + } + + @override + Widget build(BuildContext context) { + return TextField( + controller: widget.controller, + obscureText: !_revealed, + autofocus: widget.autofocus, + keyboardType: widget.keyboardType, + textInputAction: widget.textInputAction, + onSubmitted: widget.onSubmitted, + decoration: InputDecoration( + labelText: widget.labelText, + hintText: widget.hintText, + border: const OutlineInputBorder(), + prefixIcon: Icon(widget.prefixIcon), + suffixIcon: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (_) => _setRevealed(true), + onTapUp: (_) => _setRevealed(false), + onTapCancel: () => _setRevealed(false), + child: Icon( + _revealed ? Icons.visibility_rounded : Icons.visibility_off_rounded, + color: Colors.grey, + ), + ), + ), + ); + } +} diff --git a/lib/ui/login_screen.dart b/lib/ui/login_screen.dart index 7075f5f..4d14746 100644 --- a/lib/ui/login_screen.dart +++ b/lib/ui/login_screen.dart @@ -6,6 +6,7 @@ import '../function/login_controller.dart'; import '../function/session_store.dart'; import '../theme/app_palette.dart'; import 'app_notice.dart'; +import 'hold_to_reveal_password_field.dart'; import 'main_dashboard.dart'; import 'teacher_register_screen.dart'; @@ -112,6 +113,36 @@ class _LoginScreenState extends State { return StatefulBuilder( builder: (context, setDialogState) { + Future submit() async { + String newPassword = newPwController.text.trim(); + if (newPassword.isEmpty) { + AppNotice.show(context, '์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'); + return; + } + + setDialogState(() => isUpdating = true); + + final (success, message) = await _controller.changePassword( + studentId: studentId, + newPassword: newPassword, + ); + setDialogState(() => isUpdating = false); + + if (success) { + Navigator.pop(dialogContext); + AppNotice.show(context, message); + _navigateBasedOnRole( + role, + studentId, + name, + isDeviceMatched, + grade, + ); + } else { + AppNotice.show(context, message); + } + } + return AlertDialog( title: const Text( '์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ', @@ -125,14 +156,13 @@ class _LoginScreenState extends State { style: TextStyle(color: AppPalette.danger), ), const SizedBox(height: 16), - TextField( + HoldToRevealPasswordField( controller: newPwController, - obscureText: true, - decoration: const InputDecoration( - labelText: '์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.lock_reset), - ), + autofocus: true, + labelText: '์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ', + prefixIcon: Icons.lock_reset, + textInputAction: TextInputAction.done, + onSubmitted: (_) => submit(), ), ], ), @@ -147,36 +177,7 @@ class _LoginScreenState extends State { backgroundColor: AppPalette.ink, foregroundColor: Colors.white, ), - onPressed: () async { - String newPassword = newPwController.text.trim(); - if (newPassword.isEmpty) { - AppNotice.show(context, '์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'); - return; - } - - setDialogState(() => isUpdating = true); - - final (success, message) = await _controller - .changePassword( - studentId: studentId, - newPassword: newPassword, - ); - setDialogState(() => isUpdating = false); - - if (success) { - Navigator.pop(dialogContext); - AppNotice.show(context, message); - _navigateBasedOnRole( - role, - studentId, - name, - isDeviceMatched, - grade, - ); - } else { - AppNotice.show(context, message); - } - }, + onPressed: submit, child: const Text('๋ณ€๊ฒฝํ•˜๊ณ  ์‹œ์ž‘ํ•˜๊ธฐ'), ), ], @@ -234,6 +235,7 @@ class _LoginScreenState extends State { content: Text(message), actions: [ TextButton( + autofocus: true, onPressed: () => Navigator.pop(ctx), child: const Text('ํ™•์ธ'), ), @@ -254,9 +256,11 @@ class _LoginScreenState extends State { builder: (BuildContext dialogContext) { return AlertDialog( title: Text('$title ๊ถŒํ•œ ์ธ์ฆ (๊ธฐ์กด ๋ฐฉ์‹)'), - content: TextField( + content: HoldToRevealPasswordField( controller: passwordController, - obscureText: true, + autofocus: true, + labelText: '๋น„๋ฐ€๋ฒˆํ˜ธ', + hintText: '๋น„๋ฐ€๋ฒˆํ˜ธ 4์ž๋ฆฌ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', keyboardType: TextInputType.number, textInputAction: TextInputAction.done, onSubmitted: (value) { @@ -270,10 +274,6 @@ class _LoginScreenState extends State { AppNotice.show(context, '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.'); } }, - decoration: const InputDecoration( - hintText: '๋น„๋ฐ€๋ฒˆํ˜ธ 4์ž๋ฆฌ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”', - border: OutlineInputBorder(), - ), ), actions: [ TextButton( @@ -341,16 +341,11 @@ class _LoginScreenState extends State { ), ), const SizedBox(height: 16), - TextField( + HoldToRevealPasswordField( controller: _pwController, - obscureText: true, + labelText: '๋น„๋ฐ€๋ฒˆํ˜ธ', textInputAction: TextInputAction.done, onSubmitted: (_) => _login(), - decoration: const InputDecoration( - labelText: '๋น„๋ฐ€๋ฒˆํ˜ธ', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.lock), - ), ), const SizedBox(height: 8), InkWell( diff --git a/lib/ui/main_dashboard.dart b/lib/ui/main_dashboard.dart index 7f940a2..8a2aa3c 100644 --- a/lib/ui/main_dashboard.dart +++ b/lib/ui/main_dashboard.dart @@ -157,6 +157,15 @@ class _MainDashboardState extends State { const SizedBox(height: 16), TextField( controller: idController, + autofocus: true, + textInputAction: TextInputAction.done, + onSubmitted: (value) { + final inputId = value.trim(); + if (inputId.isNotEmpty) { + Navigator.pop(context); + _deleteUser(inputId); + } + }, decoration: InputDecoration( labelText: 'ํ•™๋ฒˆ ๋˜๋Š” ๊ต์ง์› ๋ฒˆํ˜ธ', hintText: '์˜ˆ: 201101 ๋˜๋Š” T1001', diff --git a/lib/ui/nfc_poccket_checkin_screen.dart b/lib/ui/nfc_poccket_checkin_screen.dart index ce57136..55ae3d4 100644 --- a/lib/ui/nfc_poccket_checkin_screen.dart +++ b/lib/ui/nfc_poccket_checkin_screen.dart @@ -86,6 +86,7 @@ class _NfcPocketCheckInScreenState extends State { ), actions: [ TextButton( + autofocus: true, onPressed: () => Navigator.pop(context), child: const Text("ํ™•์ธ"), ), @@ -114,6 +115,7 @@ class _NfcPocketCheckInScreenState extends State { ), actions: [ TextButton( + autofocus: true, onPressed: () { Navigator.pop(context); _controller.beginIosProximityWatch(pocketNumber); @@ -139,6 +141,7 @@ class _NfcPocketCheckInScreenState extends State { ), actions: [ TextButton( + autofocus: true, onPressed: () => Navigator.pop(context), child: const Text("ํ™•์ธ"), ), diff --git a/lib/ui/profile_customize_screen.dart b/lib/ui/profile_customize_screen.dart index 9105a92..3e36322 100644 --- a/lib/ui/profile_customize_screen.dart +++ b/lib/ui/profile_customize_screen.dart @@ -295,7 +295,10 @@ class _CategoryTile extends StatelessWidget { ) : null, border: Border.all( - color: selected ? AppPalette.ink : AppPalette.sage, + color: hasPreview + ? previewSecondary! + : (selected ? AppPalette.ink : AppPalette.sage), + width: hasPreview ? 2 : 1, ), ), child: Column( @@ -517,23 +520,38 @@ class _TemplateTile extends StatelessWidget { style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), const SizedBox(height: 8), - SizedBox( - width: double.infinity, - child: template.equipped - ? OutlinedButton( - onPressed: null, - style: OutlinedButton.styleFrom( - foregroundColor: AppPalette.approved, - side: const BorderSide(color: AppPalette.approved), - ), - child: const Text('์žฅ์ฐฉ์ค‘'), - ) - : template.owned - ? OutlinedButton( - onPressed: onEquip, - child: const Text('์žฅ์ฐฉํ•˜๊ธฐ'), - ) - : ElevatedButton( + if (template.equipped) + SizedBox( + width: double.infinity, + child: OutlinedButton( + onPressed: null, + style: OutlinedButton.styleFrom( + foregroundColor: AppPalette.approved, + side: const BorderSide(color: AppPalette.approved), + ), + child: const Text('์žฅ์ฐฉ์ค‘'), + ), + ) + else if (template.owned) + SizedBox( + width: double.infinity, + child: OutlinedButton( + onPressed: onEquip, + child: const Text('์žฅ์ฐฉํ•˜๊ธฐ'), + ), + ) + else + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: onPreview, + child: const Text('์ฒดํ—˜ํ•˜๊ธฐ'), + ), + ), + const SizedBox(width: 8), + Expanded( + child: ElevatedButton( onPressed: canAfford ? onPurchase : null, style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, @@ -542,7 +560,9 @@ class _TemplateTile extends StatelessWidget { ), child: const Text('๊ตฌ๋งคํ•˜๊ธฐ'), ), - ), + ), + ], + ), ], ), ), diff --git a/lib/ui/study_calendar_screen.dart b/lib/ui/study_calendar_screen.dart index 1ad57f2..4fb54df 100644 --- a/lib/ui/study_calendar_screen.dart +++ b/lib/ui/study_calendar_screen.dart @@ -58,6 +58,7 @@ class _StudyCalendarScreenState extends State { ), actions: [ TextButton( + autofocus: true, onPressed: () => Navigator.pop(dialogContext), child: const Text('๋‹ซ๊ธฐ'), ), diff --git a/lib/ui/teacher_attendance_page.dart b/lib/ui/teacher_attendance_page.dart index 444964a..c8311bf 100644 --- a/lib/ui/teacher_attendance_page.dart +++ b/lib/ui/teacher_attendance_page.dart @@ -75,6 +75,7 @@ class _TeacherAttendancePageState extends State { child: const Text('์ทจ์†Œ'), ), ElevatedButton( + autofocus: true, onPressed: () { Navigator.pop(context); _runAction(() => _controller.resetTestPermissions()); @@ -98,7 +99,14 @@ class _TeacherAttendancePageState extends State { title: Text('$studentName ํ•™์ƒ ๋ฐ˜์ถœ ํ—ˆ์šฉ'), content: TextField( controller: controller, + autofocus: true, keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + onSubmitted: (value) { + final minutes = int.tryParse(value.trim()) ?? 5; + Navigator.pop(context); + _runAction(() => _controller.allowRemoval(studentId, minutes)); + }, decoration: const InputDecoration( labelText: 'ํ—ˆ์šฉ ์‹œ๊ฐ„ (๋ถ„)', border: OutlineInputBorder(), @@ -139,7 +147,14 @@ class _TeacherAttendancePageState extends State { const SizedBox(height: 16), TextField( controller: controller, + autofocus: true, keyboardType: TextInputType.number, + textInputAction: TextInputAction.done, + onSubmitted: (value) { + final minutes = int.tryParse(value.trim()) ?? 10; + Navigator.pop(context); + _runAction(() => _controller.setPermissionWindow(minutes)); + }, decoration: const InputDecoration( labelText: '์ง€๊ธˆ๋ถ€ํ„ฐ ํ—ˆ์šฉ ์‹œ๊ฐ„ (๋ถ„)', border: OutlineInputBorder(), @@ -180,6 +195,7 @@ class _TeacherAttendancePageState extends State { child: const Text('์ทจ์†Œ'), ), ElevatedButton( + autofocus: true, onPressed: () { Navigator.pop(context); _runAction(() => _controller.dismissAll()); diff --git a/lib/ui/teacher_student_management_page.dart b/lib/ui/teacher_student_management_page.dart index 520e20b..0913eed 100644 --- a/lib/ui/teacher_student_management_page.dart +++ b/lib/ui/teacher_student_management_page.dart @@ -150,6 +150,7 @@ class _TeacherStudentManagementPageState child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( + autofocus: true, onPressed: () { Navigator.pop(context); _runBulkImport(rows); @@ -236,6 +237,7 @@ class _TeacherStudentManagementPageState ), actions: [ TextButton( + autofocus: true, onPressed: () => Navigator.pop(context), child: const Text('ํ™•์ธ'), ), @@ -296,6 +298,7 @@ class _TeacherStudentManagementPageState child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( + autofocus: true, style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, @@ -341,6 +344,7 @@ class _TeacherStudentManagementPageState child: const Text('์ทจ์†Œ', style: TextStyle(color: Colors.grey)), ), ElevatedButton( + autofocus: true, style: ElevatedButton.styleFrom( backgroundColor: AppPalette.danger, foregroundColor: Colors.white,