diff --git a/lib/screens/change_password_screen.dart b/lib/screens/change_password_screen.dart index 9f060bf..b0d1b66 100644 --- a/lib/screens/change_password_screen.dart +++ b/lib/screens/change_password_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import '../config.dart'; import '../theme/app_palette.dart'; +import '../ui/app_notice.dart'; import '../ui/login_screen.dart'; // ๐Ÿ’ก main.dart ํŒŒ์ผ์˜ ์ตœํ•˜๋‹จ(๋‹ค๋ฅธ ํด๋ž˜์Šค ์ค‘๊ด„ํ˜ธ ๋ฐ–)์— ๋ถ™์—ฌ๋„ฃ์œผ์„ธ์š”. @@ -22,9 +23,7 @@ class _ChangePasswordScreenState extends State { Future _updatePassword() async { String newPw = _pwController.text.trim(); if (newPw.isEmpty || newPw == "1234") { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋‹ค๋ฅธ ์•ˆ์ „ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.')), - ); + AppNotice.show(context, '์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๋‹ค๋ฅธ ์•ˆ์ „ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”.'); return; } @@ -38,9 +37,7 @@ class _ChangePasswordScreenState extends State { final res = jsonDecode(response.body); if (response.statusCode == 200 && res['status'] == 'success') { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์™„๋ฃŒ! ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด ์ฃผ์„ธ์š”.')), - ); + AppNotice.show(context, '๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์™„๋ฃŒ! ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด ์ฃผ์„ธ์š”.'); // ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ”๊ฟจ์œผ๋‹ˆ ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ๊ฐ•์ œ ์ด๋™ Navigator.pushReplacement( context, @@ -48,9 +45,7 @@ class _ChangePasswordScreenState extends State { ); } } catch (e) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('ํ†ต์‹  ์‹คํŒจ'))); + AppNotice.show(context, 'ํ†ต์‹  ์‹คํŒจ'); } finally { setState(() => _isLoading = false); } diff --git a/lib/ui/admin_dashboard.dart b/lib/ui/admin_dashboard.dart index 2eba821..183d4e1 100644 --- a/lib/ui/admin_dashboard.dart +++ b/lib/ui/admin_dashboard.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../function/admin_controller.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; import 'login_screen.dart'; // ========================================== @@ -27,9 +28,7 @@ class _AdminDashboardState extends State { Future _resetDatabase() async { final message = await _controller.resetDatabase(); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } void _showResetConfirmDialog() { diff --git a/lib/ui/app_notice.dart b/lib/ui/app_notice.dart index cb5c138..f51a4e8 100644 --- a/lib/ui/app_notice.dart +++ b/lib/ui/app_notice.dart @@ -9,8 +9,13 @@ class AppNotice { static final List<_QueuedNotice> _queue = []; static bool _showing = false; - static void show(BuildContext context, String message, {IconData? icon}) { - _queue.add(_QueuedNotice(context, message, icon)); + static void show( + BuildContext context, + String message, { + IconData? icon, + Color? color, + }) { + _queue.add(_QueuedNotice(context, message, icon, color)); _tryShowNext(); } @@ -28,6 +33,7 @@ class AppNotice { builder: (context) => _NoticeBanner( message: next.message, icon: next.icon, + color: next.color, onDone: () { entry.remove(); _showing = false; @@ -43,17 +49,20 @@ class _QueuedNotice { final BuildContext context; final String message; final IconData? icon; - _QueuedNotice(this.context, this.message, this.icon); + final Color? color; + _QueuedNotice(this.context, this.message, this.icon, this.color); } class _NoticeBanner extends StatefulWidget { final String message; final IconData? icon; + final Color? color; final VoidCallback onDone; const _NoticeBanner({ required this.message, required this.icon, + required this.color, required this.onDone, }); @@ -102,7 +111,7 @@ class _NoticeBannerState extends State<_NoticeBanner> constraints: const BoxConstraints(maxWidth: 360), padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), decoration: BoxDecoration( - color: AppPalette.ink, + color: widget.color ?? AppPalette.ink, borderRadius: BorderRadius.circular(999), boxShadow: [ BoxShadow( diff --git a/lib/ui/device_checkout_ledger_page.dart b/lib/ui/device_checkout_ledger_page.dart index 0c4a65e..47eaae2 100644 --- a/lib/ui/device_checkout_ledger_page.dart +++ b/lib/ui/device_checkout_ledger_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../function/device_checkout_ledger_controller.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; class DeviceCheckoutLedgerPage extends StatefulWidget { final String? teacherId; @@ -38,26 +39,20 @@ class _DeviceCheckoutLedgerPageState extends State { teacherName: widget.teacherName ?? '๊ฐ„ํŽธ์ธ์ฆ ์„ ์ƒ', ); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } Future _reject(int id) async { final (_, message) = await _controller.reject(id); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } // ๐Ÿ–๏ธ [ํ•˜๋“œ์›จ์–ด ์ž๋™ ๊ฐ์ง€ ์ „๊นŒ์ง€ ์ž„์‹œ] ๊ธฐ๊ธฐ๋ฅผ ์‹ค์ œ๋กœ ๋Œ๋ ค๋ฐ›์•˜์„ ๋•Œ ๋ˆ„๋ฅด๋Š” ๋ฒ„ํŠผ. Future _confirmReturn(int id) async { final (_, message) = await _controller.confirmReturn(id); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } ({Color color, String label}) _statusInfo(String status) { diff --git a/lib/ui/device_checkout_request_screen.dart b/lib/ui/device_checkout_request_screen.dart index 734be69..c588b17 100644 --- a/lib/ui/device_checkout_request_screen.dart +++ b/lib/ui/device_checkout_request_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../function/device_checkout_controller.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; class DeviceCheckoutRequestScreen extends StatefulWidget { final String studentId; @@ -57,9 +58,7 @@ class _DeviceCheckoutRequestScreenState Future _submit() async { final purpose = _purposeController.text.trim(); if (_startTime == null || _endTime == null || purpose.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('์‹œ์ž‘/์ข…๋ฃŒ ์‹œ๊ฐ๊ณผ ์‚ฌ์šฉ ๋ชฉ์ ์„ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.')), - ); + AppNotice.show(context, '์‹œ์ž‘/์ข…๋ฃŒ ์‹œ๊ฐ๊ณผ ์‚ฌ์šฉ ๋ชฉ์ ์„ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'); return; } @@ -71,9 +70,7 @@ class _DeviceCheckoutRequestScreenState endTime: _formatTime(_endTime!), ); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); if (success) Navigator.pop(context); } diff --git a/lib/ui/login_screen.dart b/lib/ui/login_screen.dart index 6ac3348..9395d1d 100644 --- a/lib/ui/login_screen.dart +++ b/lib/ui/login_screen.dart @@ -5,6 +5,7 @@ import '../config.dart' show schoolName; import '../function/login_controller.dart'; import '../function/session_store.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; import 'main_dashboard.dart'; import 'teacher_register_screen.dart'; @@ -58,9 +59,7 @@ class _LoginScreenState extends State { await SessionStore.clear(); } if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('๊ฐœ๋ฐœ์ž ์ตœ๊ณ  ๊ถŒํ•œ์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'))); + AppNotice.show(context, '๊ฐœ๋ฐœ์ž ์ตœ๊ณ  ๊ถŒํ•œ์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); Navigator.pushReplacement( context, MaterialPageRoute( @@ -82,9 +81,7 @@ class _LoginScreenState extends State { ); break; case LoginOutcome.success: - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('${result.name}๋‹˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!'))); + AppNotice.show(context, '${result.name}๋‹˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค!'); _navigateBasedOnRole( result.role!, result.studentId!, @@ -150,9 +147,7 @@ class _LoginScreenState extends State { onPressed: () async { String newPassword = newPwController.text.trim(); if (newPassword.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.')), - ); + AppNotice.show(context, '์ƒˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'); return; } @@ -167,9 +162,7 @@ class _LoginScreenState extends State { if (success) { Navigator.pop(dialogContext); - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); _navigateBasedOnRole( role, studentId, @@ -177,9 +170,7 @@ class _LoginScreenState extends State { isDeviceMatched, ); } else { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } }, child: const Text('๋ณ€๊ฒฝํ•˜๊ณ  ์‹œ์ž‘ํ•˜๊ธฐ'), @@ -269,9 +260,7 @@ class _LoginScreenState extends State { MaterialPageRoute(builder: (context) => nextPage), ); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.')), - ); + AppNotice.show(context, '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.'); } }, decoration: const InputDecoration( @@ -293,9 +282,7 @@ class _LoginScreenState extends State { MaterialPageRoute(builder: (context) => nextPage), ); } else { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.')), - ); + AppNotice.show(context, '๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.'); } }, child: const Text('์ธ์ฆํ•˜๊ธฐ'), diff --git a/lib/ui/nfc_poccket_checkin_screen.dart b/lib/ui/nfc_poccket_checkin_screen.dart index d541f1c..c803b33 100644 --- a/lib/ui/nfc_poccket_checkin_screen.dart +++ b/lib/ui/nfc_poccket_checkin_screen.dart @@ -5,6 +5,7 @@ import 'dart:io' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import '../function/nfc_pocket_checkin_controller.dart'; +import 'app_notice.dart'; class NfcPocketCheckInScreen extends StatefulWidget { final String studentId; // ๋กœ๊ทธ์ธ๋œ ํ•™์ƒ ํ•™๋ฒˆ (์˜ˆ: "2061") @@ -143,9 +144,7 @@ class _NfcPocketCheckInScreenState extends State { void _showSnackBar(String text, Color color) { if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(text), backgroundColor: color)); + AppNotice.show(context, text, color: color); } @override diff --git a/lib/ui/nfc_tag_writer_screen.dart b/lib/ui/nfc_tag_writer_screen.dart index b435e4d..02db293 100644 --- a/lib/ui/nfc_tag_writer_screen.dart +++ b/lib/ui/nfc_tag_writer_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../function/nfc_tag_writer_controller.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; class NfcTagWriterScreen extends StatefulWidget { const NfcTagWriterScreen({super.key}); @@ -35,11 +36,11 @@ class _NfcTagWriterScreenState extends State { void _showSnackBar(String text, bool isError) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(text), - backgroundColor: isError ? Colors.red : Colors.green, - ), + AppNotice.show( + context, + text, + color: isError ? Colors.red[700] : Colors.green[700], + icon: isError ? Icons.error_outline_rounded : Icons.check_circle_rounded, ); } diff --git a/lib/ui/teacher_register_screen.dart b/lib/ui/teacher_register_screen.dart index 5e6842a..87ee51f 100644 --- a/lib/ui/teacher_register_screen.dart +++ b/lib/ui/teacher_register_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../function/teacher_register_controller.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; class TeacherRegisterScreen extends StatefulWidget { const TeacherRegisterScreen({super.key}); @@ -35,9 +36,7 @@ class _TeacherRegisterScreenState extends State { String secret = _secretController.text.trim(); if (id.isEmpty || pw.isEmpty || name.isEmpty || secret.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('๋ชจ๋“  ๋นˆ์นธ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'))); + AppNotice.show(context, '๋ชจ๋“  ๋นˆ์นธ์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.'); return; } @@ -48,9 +47,7 @@ class _TeacherRegisterScreenState extends State { secretCode: secret, ); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); if (success) { Navigator.pop(context); // ๊ฐ€์ž… ์„ฑ๊ณต ์‹œ ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ๋ณต๊ท€ } diff --git a/lib/ui/teacher_student_management_page.dart b/lib/ui/teacher_student_management_page.dart index 784f74f..0a2673d 100644 --- a/lib/ui/teacher_student_management_page.dart +++ b/lib/ui/teacher_student_management_page.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import '../function/excel_file_picker.dart'; import '../function/teacher_student_management_controller.dart'; import '../theme/app_palette.dart'; +import 'app_notice.dart'; // ๐Ÿ“ "์‹ค์‹œ๊ฐ„ ์ถœ์„ ํ™•์ธ" ๋ชฉ๋ก(teacher_attendance_page.dart)์˜ ํƒ€์ผ ๊ทœ๊ฒฉ๊ณผ ๋™์ผํ•˜๊ฒŒ ๋งž์ถ˜๋‹ค. const double kAttendanceTileWidth = 220; @@ -51,9 +52,7 @@ class _TeacherStudentManagementPageState final sName = _addNameController.text.trim(); if (sId.isEmpty || sName.isEmpty) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('๋ชจ๋“  ์ž…๋ ฅ๋ž€์„ ์ฑ„์›Œ์ฃผ์„ธ์š”.'))); + AppNotice.show(context, '๋ชจ๋“  ์ž…๋ ฅ๋ž€์„ ์ฑ„์›Œ์ฃผ์„ธ์š”.'); return; } @@ -69,9 +68,7 @@ class _TeacherStudentManagementPageState _addNameController.clear(); setState(() => _selectedGrade = null); } - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } // ๐Ÿ“„ [ํŒŒ์ผ ์„ ํƒ ๋ฒ„ํŠผ ๋™์ž‘] "ํŒŒ์ผ ์„ ํƒ"์œผ๋กœ ์—‘์…€ ๊ณ ๋ฅด๊ธฐ @@ -89,16 +86,12 @@ class _TeacherStudentManagementPageState rows = _controller.parseExcelBytes(bytes); } catch (e) { if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('์—‘์…€ ํŒŒ์ผ์„ ์ฝ๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: $e'))); + AppNotice.show(context, '์—‘์…€ ํŒŒ์ผ์„ ์ฝ๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค: $e'); return; } if (rows.isEmpty) { if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('์œ ํšจํ•œ ํ•™์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. (1ํ–‰์€ ๋จธ๋ฆฌ๊ธ€๋กœ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค)')), - ); + AppNotice.show(context, '์œ ํšจํ•œ ํ•™์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. (1ํ–‰์€ ๋จธ๋ฆฌ๊ธ€๋กœ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค)'); return; } _showBulkPreviewDialog(rows); @@ -280,9 +273,7 @@ class _TeacherStudentManagementPageState grade: selected, ); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); } // ๐Ÿ”“ [๋ชฉ๋ก ํ–‰์˜ "๊ธฐ๊ธฐ ๋ฆฌ์…‹" ๋ฒ„ํŠผ] ํ•™์ƒ์ด ํฐ์„ ๋ฐ”๊ฟจ์„ ๋•Œ ๋“ฑ๋ก ์ดˆ๊ธฐํ™”. @@ -312,9 +303,7 @@ class _TeacherStudentManagementPageState studentName, ); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); }, child: const Text('์ดˆ๊ธฐํ™” ์Šน์ธ'), ), @@ -358,9 +347,7 @@ class _TeacherStudentManagementPageState studentId, ); if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(message))); + AppNotice.show(context, message); }, child: const Text('์˜๊ตฌ ์‚ญ์ œ'), ),