// 👥 백품타 그룹 스터디 목록 화면 (UI 전용). "실시간 출석 현황"과 같은 패밀리룩을 따른다. // 서버 통신/상태는 lib/function/group_controller.dart가 담당한다. import 'package:flutter/material.dart'; import '../function/group_controller.dart'; import '../theme/app_palette.dart'; import 'app_notice.dart'; import 'group_detail_screen.dart'; import 'launchpad_transition.dart'; import 'title_pill.dart'; class GroupListScreen extends StatefulWidget { final String studentId; final String studentName; const GroupListScreen({ super.key, required this.studentId, required this.studentName, }); @override State createState() => _GroupListScreenState(); } class _GroupListScreenState extends State { late final GroupController _controller; @override void initState() { super.initState(); _controller = GroupController( studentId: widget.studentId, studentName: widget.studentName, ); _controller.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } Future _showCreateGroupDialog() async { final nameController = TextEditingController(); final result = await showDialog( context: context, builder: (dialogContext) => AlertDialog( title: const Text('그룹 만들기'), content: TextField( controller: nameController, maxLength: 20, decoration: const InputDecoration( labelText: '그룹 이름', border: OutlineInputBorder(), ), ), actions: [ TextButton( onPressed: () => Navigator.pop(dialogContext, false), child: const Text('취소', style: TextStyle(color: Colors.grey)), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, ), onPressed: () => Navigator.pop(dialogContext, true), child: const Text('만들기'), ), ], ), ); if (result != true || nameController.text.trim().isEmpty) return; final (success, message) = await _controller.createGroup( nameController.text.trim(), ); if (!mounted) return; AppNotice.show( context, message, icon: success ? Icons.check_circle_rounded : Icons.error_outline_rounded, ); } Future _respondInvite(int inviteId, bool accept) async { final (_, message) = await _controller.respondInvite(inviteId, accept); if (!mounted) return; AppNotice.show(context, message); } Future _openGroup(dynamic group) async { await pushLaunchpad( context, (context) => GroupDetailScreen( groupId: group['groupId'] as int, groupName: group['name'] as String, studentId: widget.studentId, ), ); _controller.refresh(); } Widget _sectionHeader(String title, {int? count}) { return Row( children: [ Container( width: 4, height: 16, decoration: BoxDecoration( color: AppPalette.ink, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppPalette.ink, ), ), if (count != null) ...[ const SizedBox(width: 6), Text( '$count개', style: TextStyle(fontSize: 13, color: Colors.grey[500]), ), ], ], ); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _controller, builder: (context, _) { return Scaffold( backgroundColor: AppPalette.mist, appBar: AppBar( backgroundColor: Colors.transparent, foregroundColor: AppPalette.ink, elevation: 0, ), floatingActionButton: FloatingActionButton.extended( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, onPressed: _showCreateGroupDialog, icon: const Icon(Icons.add_rounded), label: const Text('그룹 만들기'), ), body: _controller.isLoading ? const Center(child: CircularProgressIndicator()) : RefreshIndicator( onRefresh: _controller.refresh, child: ListView( padding: const EdgeInsets.fromLTRB(16, 0, 16, 90), children: [ const Center(child: TitlePill('그룹 스터디')), const SizedBox(height: 20), if (_controller.invites.isNotEmpty) ...[ _sectionHeader( '받은 그룹 초대', count: _controller.invites.length, ), const SizedBox(height: 10), for (final inv in _controller.invites) 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: [ Expanded( child: Text( '${inv['groupName']}', style: const TextStyle( fontWeight: FontWeight.bold, ), ), ), TextButton( onPressed: () => _respondInvite( inv['inviteId'] as int, false, ), child: const Text( '거절', style: TextStyle(color: Colors.grey), ), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, ), onPressed: () => _respondInvite( inv['inviteId'] as int, true, ), child: const Text('참여'), ), ], ), ), const SizedBox(height: 20), ], _sectionHeader('내 그룹', count: _controller.groups.length), const SizedBox(height: 10), if (_controller.groups.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Text( '아직 그룹이 없어요. 새 그룹을 만들거나 초대를 기다려보세요.', style: TextStyle(color: Colors.grey), ), ) else for (final g in _controller.groups) GestureDetector( onTap: () => _openGroup(g), child: Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), decoration: BoxDecoration( color: AppPalette.paper, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppPalette.sage), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.03), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppPalette.ink.withValues( alpha: 0.06, ), shape: BoxShape.circle, ), child: const Icon( Icons.groups_rounded, color: AppPalette.ink, size: 18, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${g['name']}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15, ), ), Text( '멤버 ${g['memberCount']}명' '${g['isOwner'] == true ? ' · 그룹장' : ''}', style: TextStyle( fontSize: 12, color: Colors.grey[500], ), ), ], ), ), const Icon( Icons.chevron_right_rounded, color: Colors.grey, ), ], ), ), ), ], ), ), ); }, ); } }