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, ), ), ),