From f5aa832ad95acf7b1daffb3aae14de8ec728f5cc Mon Sep 17 00:00:00 2001 From: sihoo Date: Tue, 22 Sep 2026 03:58:21 +0000 Subject: [PATCH] =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EC=83=81=EC=84=B8:=20?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=EB=A5=BC=20=ED=83=80=EC=9D=BC=20=EA=B7=B8?= =?UTF-8?q?=EB=A6=AC=EB=93=9C=EB=A1=9C,=20=EA=B3=B5=EB=B6=80=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=EC=88=9C=20=EC=A0=95=EB=A0=AC,=20=EB=B0=B1=ED=92=88?= =?UTF-8?q?=ED=83=80=20=EB=B0=94=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가로로 길게 늘어지던 멤버 목록을 학생 계정관리 화면 같은 타일 그리드로 바꾸고, 오늘 공부 시간이 많은 순으로 정렬(백엔드). 화면 폭도 다른 화면들처럼 최대 560으로 제한. "백품타 바로가기" 버튼으로 그룹 화면에서 바로 공부 타이머로 이동 가능. Co-Authored-By: Claude Sonnet 5 --- lib/ui/group_detail_screen.dart | 159 ++++++++++++++++++++++++-------- lib/ui/group_list_screen.dart | 4 + lib/ui/main_dashboard.dart | 1 + 3 files changed, 124 insertions(+), 40 deletions(-) diff --git a/lib/ui/group_detail_screen.dart b/lib/ui/group_detail_screen.dart index 77e4577..a8d5148 100644 --- a/lib/ui/group_detail_screen.dart +++ b/lib/ui/group_detail_screen.dart @@ -5,18 +5,24 @@ import '../function/friend_controller.dart'; import '../function/group_detail_controller.dart'; import '../theme/app_palette.dart'; import 'app_notice.dart'; +import 'launchpad_transition.dart'; +import 'study_timer_screen.dart'; import 'title_pill.dart'; class GroupDetailScreen extends StatefulWidget { final int groupId; final String groupName; final String studentId; + final String studentName; + final int? grade; const GroupDetailScreen({ super.key, required this.groupId, required this.groupName, required this.studentId, + required this.studentName, + this.grade, }); @override @@ -208,6 +214,23 @@ class _GroupDetailScreenState extends State { return '${m ~/ 60}시간 ${m % 60}분'; } + void _openTimer() { + pushLaunchpad( + context, + (context) => StudyTimerScreen( + studentId: widget.studentId, + studentName: widget.studentName, + grade: widget.grade, + ), + ); + } + + /// 넓은 화면(웹 데스크톱)에서 내용이 양옆으로 늘어지지 않게 가운데 최대 560 폭으로 맞춘다. + double _sidePad(BuildContext context) { + final width = MediaQuery.of(context).size.width; + return ((width - 560) / 2).clamp(16.0, double.infinity); + } + @override Widget build(BuildContext context) { return ListenableBuilder( @@ -254,7 +277,12 @@ class _GroupDetailScreenState extends State { : RefreshIndicator( onRefresh: _controller.refresh, child: ListView( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 90), + padding: EdgeInsets.fromLTRB( + _sidePad(context), + 0, + _sidePad(context), + 90, + ), children: [ Center(child: TitlePill(widget.groupName)), const SizedBox(height: 16), @@ -265,6 +293,23 @@ class _GroupDetailScreenState extends State { Expanded(child: _statPill('오늘 공부함', attendedCount)), ], ), + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: _openTimer, + icon: const Icon(Icons.timer_rounded), + label: const Text('백품타 바로가기'), + style: OutlinedButton.styleFrom( + foregroundColor: AppPalette.ink, + side: const BorderSide(color: AppPalette.sage), + padding: const EdgeInsets.symmetric(vertical: 14), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + ), + ), if (_controller.isOwner && _controller.joinRequests.isNotEmpty) ...[ const SizedBox(height: 20), @@ -368,51 +413,85 @@ class _GroupDetailScreenState extends State { ], ), const SizedBox(height: 10), - for (final m in members) - Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - decoration: BoxDecoration( - color: AppPalette.paper, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: AppPalette.sage), - ), - child: Row( - children: [ - Icon( - m['attendedToday'] == true - ? Icons.check_circle_rounded - : Icons.radio_button_unchecked_rounded, - color: m['attendedToday'] == true + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + mainAxisExtent: 96, + ), + itemCount: members.length, + itemBuilder: (context, index) { + final m = members[index]; + final attended = m['attendedToday'] == true; + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: attended + ? AppPalette.ink + : AppPalette.paper, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: attended ? AppPalette.ink - : Colors.grey[350], - size: 20, + : AppPalette.sage, ), - const SizedBox(width: 10), - Expanded( - child: Text( - '${m['name']}' - '${m['role'] == 'owner' ? ' (그룹장)' : ''}', - style: const TextStyle( + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + attended + ? Icons.check_circle_rounded + : Icons.radio_button_unchecked_rounded, + color: attended + ? Colors.white + : Colors.grey[350], + size: 18, + ), + const Spacer(), + Text( + '${m['name']}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( fontWeight: FontWeight.bold, + fontSize: 13, + color: attended + ? Colors.white + : AppPalette.ink, ), ), - ), - Text( - _formatMinutes( - (m['todaySeconds'] as num?)?.toInt() ?? 0, + if (m['role'] == 'owner') + Text( + '그룹장', + style: TextStyle( + fontSize: 10, + color: attended + ? Colors.white70 + : Colors.grey[500], + ), + ), + const SizedBox(height: 2), + Text( + _formatMinutes( + (m['todaySeconds'] as num?)?.toInt() ?? 0, + ), + style: TextStyle( + fontSize: 11, + color: attended + ? Colors.white70 + : Colors.grey[500], + ), ), - style: TextStyle( - color: Colors.grey[500], - fontSize: 12, - ), - ), - ], - ), - ), + ], + ), + ); + }, + ), ], ), ), diff --git a/lib/ui/group_list_screen.dart b/lib/ui/group_list_screen.dart index 979a016..ad06f90 100644 --- a/lib/ui/group_list_screen.dart +++ b/lib/ui/group_list_screen.dart @@ -11,11 +11,13 @@ import 'title_pill.dart'; class GroupListScreen extends StatefulWidget { final String studentId; final String studentName; + final int? grade; const GroupListScreen({ super.key, required this.studentId, required this.studentName, + this.grade, }); @override @@ -127,6 +129,8 @@ class _GroupListScreenState extends State { groupId: group['groupId'] as int, groupName: group['name'] as String, studentId: widget.studentId, + studentName: widget.studentName, + grade: widget.grade, ), ); _controller.refresh(); diff --git a/lib/ui/main_dashboard.dart b/lib/ui/main_dashboard.dart index 1c86dc4..a1bab77 100644 --- a/lib/ui/main_dashboard.dart +++ b/lib/ui/main_dashboard.dart @@ -394,6 +394,7 @@ class _MainDashboardState extends State { (context) => GroupListScreen( studentId: _displayId, studentName: _displayName, + grade: widget.grade, ), ), ),