From 5d37d3e3b16a5a71a48bbaafee9c0d76e3dce0cc Mon Sep 17 00:00:00 2001 From: sihoo Date: Tue, 8 Sep 2026 16:26:29 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=9C=EB=AA=A9=20=EC=95=8C=EC=95=BD=20?= =?UTF-8?q?=ED=8F=B0=ED=8A=B8=20=ED=86=B5=EC=9D=BC=20+=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EC=A1=B0=EC=A0=95,=20=EC=A2=81=EC=9D=80=20?= =?UTF-8?q?=ED=8F=BC=20=EB=84=88=EB=B9=84,=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=ED=99=95=EB=8C=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TitlePill을 String 기반으로 바꿔서 모든 화면(설정/교사 회원가입/관리자 시스템/NFC 태그 쓰기·체크인/학생 관리/스마트기기 반출 신청·대장/실시간 출석 현황)이 완전히 같은 폰트(굵게, 15px, 흰색)를 쓰게 통일 - 알약이 화면 맨 위에 딱 붙어 보이지 않도록 위쪽에 8px 여백 추가 - "스마트기기 반출 신청" 폼과 "관리자 시스템"의 "모든 출석 데이터 초기화" 버튼이 넓은 화면에서 양옆으로 과하게 늘어지던 문제 수정 - 최대 폭 420px로 제한하고 가운데 정렬 (기존 대비 약 1/3 크기) - 메인 대시보드 타일 목표 크기를 184 → 276(50% 확대)로 키우고, 아이콘/ 패딩/폰트 기준값도 함께 올려서 제목 10→12pt, 부제 8→10pt로 확대 Co-Authored-By: Claude Sonnet 5 --- lib/ui/admin_dashboard.dart | 88 +++++----- lib/ui/dashboard_settings_page.dart | 4 +- lib/ui/device_checkout_ledger_page.dart | 10 +- lib/ui/device_checkout_request_screen.dart | 177 ++++++++++---------- lib/ui/main_dashboard.dart | 19 ++- lib/ui/nfc_poccket_checkin_screen.dart | 10 +- lib/ui/nfc_tag_writer_screen.dart | 7 +- lib/ui/teacher_attendance_page.dart | 25 +-- lib/ui/teacher_register_screen.dart | 4 +- lib/ui/teacher_student_management_page.dart | 10 +- lib/ui/title_pill.dart | 30 ++-- 11 files changed, 173 insertions(+), 211 deletions(-) diff --git a/lib/ui/admin_dashboard.dart b/lib/ui/admin_dashboard.dart index ff4a509..83540f2 100644 --- a/lib/ui/admin_dashboard.dart +++ b/lib/ui/admin_dashboard.dart @@ -78,9 +78,7 @@ class _AdminDashboardState extends State { foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text('관리자 시스템', style: TextStyle(color: Colors.white)), - ), + title: const TitlePill('관리자 시스템'), actions: [ IconButton( icon: const Icon(Icons.logout), @@ -94,50 +92,56 @@ class _AdminDashboardState extends State { body: Center( child: Padding( padding: const EdgeInsets.all(24.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.admin_panel_settings, - size: 100, - color: AppPalette.ink, - ), - const SizedBox(height: 20), - const Text( - '데이터베이스 관리', - style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 40), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.admin_panel_settings, + size: 100, + color: AppPalette.ink, + ), + const SizedBox(height: 20), + const Text( + '데이터베이스 관리', + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 40), - _controller.isLoading - ? const CircularProgressIndicator(color: Colors.red) - : SizedBox( - width: double.infinity, - height: 60, - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red[50], - foregroundColor: Colors.red, - side: const BorderSide( - color: Colors.red, - width: 2, + _controller.isLoading + ? const CircularProgressIndicator(color: Colors.red) + : SizedBox( + width: double.infinity, + height: 60, + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red[50], + foregroundColor: Colors.red, + side: const BorderSide( + color: Colors.red, + width: 2, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + icon: const Icon(Icons.delete_forever, size: 28), + label: const Text( + '모든 출석 데이터 초기화', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), + onPressed: _showResetConfirmDialog, ), - icon: const Icon(Icons.delete_forever, size: 28), - label: const Text( - '모든 출석 데이터 초기화', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - onPressed: _showResetConfirmDialog, ), - ), - ], + ], + ), ), ), ), diff --git a/lib/ui/dashboard_settings_page.dart b/lib/ui/dashboard_settings_page.dart index e1cf1c6..5dc7c7b 100644 --- a/lib/ui/dashboard_settings_page.dart +++ b/lib/ui/dashboard_settings_page.dart @@ -17,9 +17,7 @@ class DashboardSettingsPage extends StatelessWidget { foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text('설정', style: TextStyle(color: Colors.white)), - ), + title: const TitlePill('설정'), ), body: ListView( padding: const EdgeInsets.all(16), diff --git a/lib/ui/device_checkout_ledger_page.dart b/lib/ui/device_checkout_ledger_page.dart index bbef001..a3bed3d 100644 --- a/lib/ui/device_checkout_ledger_page.dart +++ b/lib/ui/device_checkout_ledger_page.dart @@ -94,15 +94,7 @@ class _DeviceCheckoutLedgerPageState extends State { foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text( - '스마트기기 반출 대장', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), + title: const TitlePill('스마트기기 반출 대장'), actions: [ IconButton( icon: const Icon(Icons.refresh_rounded), diff --git a/lib/ui/device_checkout_request_screen.dart b/lib/ui/device_checkout_request_screen.dart index bc116a8..ad0614e 100644 --- a/lib/ui/device_checkout_request_screen.dart +++ b/lib/ui/device_checkout_request_screen.dart @@ -87,108 +87,107 @@ class _DeviceCheckoutRequestScreenState foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text( - '스마트기기 반출 신청', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), + title: const TitlePill('스마트기기 반출 신청'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '패드는 NFC 태그가 안 되니, 사용 시간과 목적을 적어 신청하면\n' - '선생님이 확인하고 승인해줍니다. 승인되면 반납 바구니에서 꺼내 쓰세요.', - style: TextStyle( - color: Colors.black54, - fontSize: 13, - height: 1.4, - ), - ), - const SizedBox(height: 24), - Container( - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: AppPalette.paper, - borderRadius: BorderRadius.circular(24), - border: Border.all(color: AppPalette.sage), - ), - child: Column( - children: [ - Row( + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 420), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '패드는 NFC 태그가 안 되니, 사용 시간과 목적을 적어 신청하면\n' + '선생님이 확인하고 승인해줍니다. 승인되면 반납 바구니에서 꺼내 쓰세요.', + style: TextStyle( + color: Colors.black54, + fontSize: 13, + height: 1.4, + ), + ), + const SizedBox(height: 24), + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: AppPalette.paper, + borderRadius: BorderRadius.circular(24), + border: Border.all(color: AppPalette.sage), + ), + child: Column( children: [ - Expanded( - child: OutlinedButton.icon( - onPressed: _pickStartTime, - icon: const Icon(Icons.play_arrow_rounded), - label: Text( - _startTime == null - ? '시작 시각' - : _formatTime(_startTime!), + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: _pickStartTime, + icon: const Icon(Icons.play_arrow_rounded), + label: Text( + _startTime == null + ? '시작 시각' + : _formatTime(_startTime!), + ), + ), ), + const SizedBox(width: 12), + Expanded( + child: OutlinedButton.icon( + onPressed: _pickEndTime, + icon: const Icon(Icons.stop_rounded), + label: Text( + _endTime == null + ? '종료 시각' + : _formatTime(_endTime!), + ), + ), + ), + ], + ), + const SizedBox(height: 16), + TextField( + controller: _purposeController, + maxLines: 3, + decoration: const InputDecoration( + labelText: '사용 목적', + hintText: '예: 수행평가 자료조사', + alignLabelWithHint: true, + border: OutlineInputBorder(), ), ), - const SizedBox(width: 12), - Expanded( - child: OutlinedButton.icon( - onPressed: _pickEndTime, - icon: const Icon(Icons.stop_rounded), - label: Text( - _endTime == null - ? '종료 시각' - : _formatTime(_endTime!), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: _controller.isSubmitting + ? null + : _submit, + style: ElevatedButton.styleFrom( + backgroundColor: AppPalette.ink, + foregroundColor: AppPalette.paper, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), ), + child: _controller.isSubmitting + ? const CircularProgressIndicator( + color: Colors.white, + ) + : const Text( + '반출 신청하기', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), ), ), ], ), - const SizedBox(height: 16), - TextField( - controller: _purposeController, - maxLines: 3, - decoration: const InputDecoration( - labelText: '사용 목적', - hintText: '예: 수행평가 자료조사', - alignLabelWithHint: true, - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: _controller.isSubmitting ? null : _submit, - style: ElevatedButton.styleFrom( - backgroundColor: AppPalette.ink, - foregroundColor: AppPalette.paper, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: _controller.isSubmitting - ? const CircularProgressIndicator( - color: Colors.white, - ) - : const Text( - '반출 신청하기', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 15, - ), - ), - ), - ), - ], - ), + ), + ], ), - ], + ), ), ), ); diff --git a/lib/ui/main_dashboard.dart b/lib/ui/main_dashboard.dart index 3968239..c75dbb5 100644 --- a/lib/ui/main_dashboard.dart +++ b/lib/ui/main_dashboard.dart @@ -635,9 +635,10 @@ class _MainDashboardState extends State { : width < 700 ? 3 : 4; - // 📏 1칸(1x1) 크기가 대략 계정 프로필 카드만 해지도록 목표 크기를 잡고, - // 화면이 그보다 넓으면 가운데로 모아서 여백을 준다(좁으면 화면 폭에 맞춤). - const double targetCellSize = 184; + // 📏 1칸(1x1) 크기 목표. 계정 프로필 카드 크기(184)보다 50% 더 키워서 + // 타일 글자/아이콘이 더 잘 보이게 한다. 화면이 그보다 넓으면 가운데로 + // 모아서 여백을 주고, 좁으면(폰) 화면 폭에 맞춘다. + const double targetCellSize = 276; const double gap = 12; final double idealGridWidth = crossAxisCount * targetCellSize + (crossAxisCount - 1) * gap; @@ -787,13 +788,13 @@ class _MainDashboardState extends State { : constraints.maxHeight : constraints.maxWidth; final double scale = (referenceSize / 110).clamp(1.0, 3.4); - final double iconBoxPadding = 6 + 6 * (scale - 1); - final double iconSize = 16 + 10 * (scale - 1); - final double iconRadius = 10 + 6 * (scale - 1); - final double cardPadding = 8 + 8 * (scale - 1); + final double iconBoxPadding = 8 + 6 * (scale - 1); + final double iconSize = 22 + 10 * (scale - 1); + final double iconRadius = 12 + 6 * (scale - 1); + final double cardPadding = 10 + 8 * (scale - 1); final double cardRadius = 16 + 6 * (scale - 1); - final double titleFontSize = (10 + 3 * (scale - 1)).clamp(10, 18); - final double subtitleFontSize = (8 + 2 * (scale - 1)).clamp(8, 14); + final double titleFontSize = (12 + 3 * (scale - 1)).clamp(12, 20); + final double subtitleFontSize = (10 + 2 * (scale - 1)).clamp(10, 16); return InkWell( onTap: isLoading ? null : onTap, diff --git a/lib/ui/nfc_poccket_checkin_screen.dart b/lib/ui/nfc_poccket_checkin_screen.dart index c3982e1..beaa810 100644 --- a/lib/ui/nfc_poccket_checkin_screen.dart +++ b/lib/ui/nfc_poccket_checkin_screen.dart @@ -164,15 +164,7 @@ class _NfcPocketCheckInScreenState extends State { foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text( - "자습실 NFC 출석체크", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ), + title: const TitlePill("자습실 NFC 출석체크"), ), body: Center( child: Padding( diff --git a/lib/ui/nfc_tag_writer_screen.dart b/lib/ui/nfc_tag_writer_screen.dart index 5ae805f..21aa345 100644 --- a/lib/ui/nfc_tag_writer_screen.dart +++ b/lib/ui/nfc_tag_writer_screen.dart @@ -58,12 +58,7 @@ class _NfcTagWriterScreenState extends State { foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text( - "NFC 주머니 태그 쓰기", - style: TextStyle(color: Colors.white), - ), - ), + title: const TitlePill("NFC 주머니 태그 쓰기"), ), body: Padding( padding: const EdgeInsets.all(24.0), diff --git a/lib/ui/teacher_attendance_page.dart b/lib/ui/teacher_attendance_page.dart index a790b80..55e6523 100644 --- a/lib/ui/teacher_attendance_page.dart +++ b/lib/ui/teacher_attendance_page.dart @@ -5,6 +5,7 @@ import '../function/teacher_attendance_controller.dart'; import '../theme/app_palette.dart'; import 'app_notice.dart'; import 'launchpad_transition.dart'; +import 'title_pill.dart'; // ----------------------------------------------------------------------------- // 📅 [서브 화면 1] 실시간 출석 확인 란 (StudentDashboard 카드 스타일 리스트화) @@ -243,26 +244,6 @@ class _TeacherAttendancePageState extends State { ); } - // 🪶 "실시간 출석 현황" 제목 알약. 사이드바/모바일 요약 바로 위에 둔다. - Widget _buildTitlePill() { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: AppPalette.ink, - borderRadius: BorderRadius.circular(999), - ), - child: const Text( - '실시간 출석 현황', - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: 15, - ), - ), - ); - } - // ----------------------------------------------------------------------- // 📱 모바일 레이아웃 (기존 카드 리스트) // ----------------------------------------------------------------------- @@ -271,7 +252,7 @@ class _TeacherAttendancePageState extends State { return Column( children: [ const SizedBox(height: 12), - _buildTitlePill(), + const TitlePill('실시간 출석 현황'), const SizedBox(height: 12), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), @@ -554,7 +535,7 @@ class _TeacherAttendancePageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Center(child: _buildTitlePill()), + Center(child: const TitlePill('실시간 출석 현황')), const SizedBox(height: 16), _sidebarStatPill('전체 학생 수', total, 'ALL'), const SizedBox(height: 10), diff --git a/lib/ui/teacher_register_screen.dart b/lib/ui/teacher_register_screen.dart index 3cb51e4..33d7d3c 100644 --- a/lib/ui/teacher_register_screen.dart +++ b/lib/ui/teacher_register_screen.dart @@ -66,9 +66,7 @@ class _TeacherRegisterScreenState extends State { foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text('교사 회원가입', style: TextStyle(color: Colors.white)), - ), + title: const TitlePill('교사 회원가입'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(24.0), diff --git a/lib/ui/teacher_student_management_page.dart b/lib/ui/teacher_student_management_page.dart index 462fe19..fab2a0d 100644 --- a/lib/ui/teacher_student_management_page.dart +++ b/lib/ui/teacher_student_management_page.dart @@ -663,15 +663,7 @@ class _TeacherStudentManagementPageState foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, - title: const TitlePill( - child: Text( - '학생 통합 관리 센터', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), + title: const TitlePill('학생 통합 관리 센터'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(24.0), diff --git a/lib/ui/title_pill.dart b/lib/ui/title_pill.dart index a1ec1ff..9fbafff 100644 --- a/lib/ui/title_pill.dart +++ b/lib/ui/title_pill.dart @@ -1,22 +1,32 @@ -// 🪶 검정 AppBar 대신 쓰는 둥근 제목 알약. 실시간 출석 현황 화면과 톤을 맞추되, -// 크기는 줄이지 않고 각 화면이 원래 쓰던 폰트 스타일을 그대로 넘겨받는다. +// 🪶 검정 AppBar 대신 쓰는 둥근 제목 알약. 모든 화면이 똑같은 폰트/크기를 쓰고, +// 화면 맨 위에 딱 붙어 보이지 않도록 위쪽에 살짝 여백을 둔다. import 'package:flutter/material.dart'; import '../theme/app_palette.dart'; class TitlePill extends StatelessWidget { - final Widget child; + final String text; - const TitlePill({super.key, required this.child}); + const TitlePill(this.text, {super.key}); @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: AppPalette.ink, - borderRadius: BorderRadius.circular(999), + return Padding( + padding: const EdgeInsets.only(top: 8), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: BoxDecoration( + color: AppPalette.ink, + borderRadius: BorderRadius.circular(999), + ), + child: Text( + text, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), ), - child: child, ); } }