// ignore_for_file: avoid_print import 'package:flutter/material.dart'; import 'package:tugas_akhir_supabase/screens/community/components/group_chat_screen.dart'; import 'package:tugas_akhir_supabase/screens/community/components/simple_news_tab.dart'; import 'package:tugas_akhir_supabase/screens/community/components/farming_guide_tab.dart'; import 'package:tugas_akhir_supabase/screens/community/components/group_selector.dart'; import 'package:tugas_akhir_supabase/screens/community/models/group.dart'; import 'package:tugas_akhir_supabase/screens/community/services/group_service.dart'; import 'package:tugas_akhir_supabase/core/theme/app_colors.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tugas_akhir_supabase/screens/community/components/group_card.dart'; import 'package:uuid/uuid.dart'; import 'package:flutter/rendering.dart'; class EnhancedCommunityScreen extends StatefulWidget { const EnhancedCommunityScreen({super.key}); @override _EnhancedCommunityScreenState createState() => _EnhancedCommunityScreenState(); } class _EnhancedCommunityScreenState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { // View state late TabController _tabController; int _currentIndex = 0; bool _isLoadingGroups = true; String? _selectedGroupId; bool _showGroupSelector = false; // Group data List _userGroups = []; Set _userGroupIds = {}; String? _loadingGroupId; // Track grup yang sedang di-join/leave bool _isJoiningGroup = false; // Tambahkan deklarasi channel realtime RealtimeChannel? _groupRealtimeChannel; // Add a key for the GroupChatScreen final GlobalKey _groupChatKey = GlobalKey(); @override void initState() { super.initState(); _tabController = TabController( length: 3, vsync: this, initialIndex: 0, // Default to first tab ); _tabController.addListener(_handleTabChange); // Panggil langsung tanpa Future.delayed _safeLoadGroups(); _preloadDefaultGroupMessages(); Future.delayed(Duration(seconds: 2), () { if (mounted) { _forceRefreshAllMessages(); } }); _setupGroupRealtimeSubscription(); } @override void didChangeDependencies() { super.didChangeDependencies(); // Jangan reload groups di sini, karena bisa menyebabkan multiple loads // _loadUserGroups(); } Future _safeLoadGroups() async { try { // Set safety timer lebih awal Future.delayed(Duration(seconds: 3), () { if (mounted && _isLoadingGroups) { print('[WARNING] Safety timer triggered - forcing load completion'); setState(() { _isLoadingGroups = false; if (_userGroups.isEmpty) { _createFallbackGroup(); } }); } }); // Coba muat grup dengan timeout await _loadUserGroups().timeout( Duration(seconds: 3), onTimeout: () { print('[WARNING] _loadUserGroups timed out'); if (mounted && _isLoadingGroups) { setState(() { _isLoadingGroups = false; if (_userGroups.isEmpty) { _createFallbackGroup(); } }); } }, ); } catch (e) { print('[ERROR] Error in _safeLoadGroups: $e'); if (mounted && _isLoadingGroups) { setState(() { _isLoadingGroups = false; if (_userGroups.isEmpty) { _createFallbackGroup(); } }); } } } // Metode untuk membuat grup fallback void _createFallbackGroup() { // Gunakan UUID yang valid, bukan string timestamp final fallbackGroup = Group( id: '00000000-0000-0000-0000-000000000001', // UUID valid name: 'Grup Tani Umum', description: 'Grup diskusi umum untuk petani', createdBy: 'system', isDefault: true, isPublic: true, ); _userGroups = [fallbackGroup]; _userGroupIds = {fallbackGroup.id}; _selectedGroupId = fallbackGroup.id; } Future _loadUserGroups() async { // Perbaiki: hanya return jika sedang loading dan data sudah ada if (_isLoadingGroups && _userGroups.isNotEmpty) { print('[DEBUG] Preventing multiple loads - load already in progress'); return; } print('[DEBUG] Starting load process - setting loading state'); setState(() => _isLoadingGroups = true); // Store the current group ID before refresh to maintain selection final previouslySelectedGroupId = _selectedGroupId; print('[DEBUG] Saving current selected group: $previouslySelectedGroupId'); try { // Gunakan fungsi yang sudah dibuat di Supabase final supabase = Supabase.instance.client; // Verify user authentication dengan timeout User? currentUser; try { currentUser = supabase.auth.currentUser; if (currentUser == null) { print('[ERROR] User not authenticated'); throw Exception('User not authenticated'); } } catch (e) { print('[ERROR] Error getting current user: $e'); throw Exception('Error getting current user'); } print('[DEBUG] Using SQL functions from Supabase'); List allGroups = []; Set userGroupIds = {}; Group? defaultGroup; // Get all groups with member count dengan timeout try { print('[DEBUG] Calling get_all_groups_with_member_count() function'); final result = await supabase .rpc('get_all_groups_with_member_count') .timeout( Duration(seconds: 2), onTimeout: () { print('[WARNING] get_all_groups_with_member_count timed out'); return null; }, ); if (result != null) { for (final item in result) { try { final group = Group.fromMap(item); allGroups.add(group); // Mencatat grup default if (group.isDefault) { defaultGroup = group; print('[DEBUG] Found default group: ${group.name}'); } } catch (e) { print('[ERROR] Failed to parse group: $e'); } } } } catch (e) { print('[ERROR] Failed to load groups with count: $e'); } // Get user memberships dengan timeout try { print('[DEBUG] Getting user memberships with get_user_groups_simple()'); final userGroupsResult = await supabase .rpc('get_user_groups_simple') .timeout( Duration(seconds: 2), onTimeout: () { print('[WARNING] get_user_groups_simple timed out'); return null; }, ); if (userGroupsResult != null) { for (final item in userGroupsResult) { userGroupIds.add(item['id'] as String); } print('[DEBUG] User is member of ${userGroupIds.length} groups'); } } catch (e) { print('[ERROR] Failed to load user memberships: $e'); } // Fallback - always ensure at least one group if (allGroups.isEmpty) { print('[DEBUG] No groups loaded, creating fallback group'); final fallbackGroup = Group( id: '00000000-0000-0000-0000-000000000001', // UUID valid name: 'Grup Tani Umum', description: 'Grup diskusi umum untuk petani', createdBy: 'system', isDefault: true, isPublic: true, ); allGroups = [fallbackGroup]; defaultGroup = fallbackGroup; } // Update UI state if (mounted) { setState(() { _userGroups = allGroups; _userGroupIds = userGroupIds; _isLoadingGroups = false; // Check if the previously selected group still exists in the updated groups bool previousGroupStillExists = false; if (previouslySelectedGroupId != null) { previousGroupStillExists = _userGroups.any( (group) => group.id == previouslySelectedGroupId, ); } // Maintain the previously selected group if it still exists if (previousGroupStillExists) { _selectedGroupId = previouslySelectedGroupId; print( '[DEBUG] Maintained previously selected group: $_selectedGroupId', ); } else { // Otherwise select a default group if (defaultGroup != null) { _selectedGroupId = defaultGroup.id; print( '[DEBUG] Default group auto-selected: ${defaultGroup.name}', ); } else if (allGroups.isNotEmpty) { // Fallback: pilih grup yang user ikuti atau grup pertama final memberGroup = allGroups.firstWhere( (g) => userGroupIds.contains(g.id), orElse: () => allGroups.first, ); _selectedGroupId = memberGroup.id; print('[DEBUG] Fallback group selected: ${memberGroup.name}'); } } }); print('[DEBUG] State updated with ${allGroups.length} groups'); // Immediately force message load if a group is selected if (_selectedGroupId != null && _groupChatKey.currentState != null) { _triggerImmediateMessageLoad(); } } } catch (e) { print('[ERROR] Critical error in load process: $e'); // Ensure we complete loading with at least one fallback group if (mounted) { setState(() { _isLoadingGroups = false; if (_userGroups.isEmpty) { _createFallbackGroup(); } // Try to restore the previously selected group if possible if (previouslySelectedGroupId != null && _userGroups.any( (group) => group.id == previouslySelectedGroupId, )) { _selectedGroupId = previouslySelectedGroupId; print( '[DEBUG] Restored previously selected group after error: $_selectedGroupId', ); } }); } } } void _handleGroupSelected(Group group) { setState(() { _selectedGroupId = group.id; _showGroupSelector = false; }); } void _toggleGroupSelector() { setState(() { _showGroupSelector = !_showGroupSelector; }); } @override void dispose() { // Unsubscribe channel realtime _groupRealtimeChannel?.unsubscribe(); _tabController.removeListener(_handleTabChange); _tabController.dispose(); // Clean up any active subscriptions if (_selectedGroupId != null && _groupChatKey.currentState != null) { _groupChatKey.currentState!.disposeResources(); } super.dispose(); } void _handleTabChange() { if (_tabController.indexIsChanging) { // You can add analytics or other logic here when tabs change setState(() { _currentIndex = _tabController.index; // Hide group selector when switching tabs _showGroupSelector = false; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: AppColors.primary, foregroundColor: Colors.white, toolbarHeight: 56, elevation: 0, title: Text(_getTabTitle(_currentIndex)), bottom: PreferredSize( preferredSize: const Size.fromHeight(36), child: TabBar( controller: _tabController, indicatorColor: Colors.white, indicatorWeight: 3, labelColor: Colors.white, unselectedLabelColor: Colors.white70, labelStyle: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), labelPadding: const EdgeInsets.symmetric(vertical: 8), tabs: const [ Tab( height: 28, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.chat_bubble, size: 18), SizedBox(width: 4), Text('Diskusi'), ], ), ), Tab( height: 28, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.article, size: 18), SizedBox(width: 4), Text('Berita'), ], ), ), Tab( height: 28, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.eco, size: 18), SizedBox(width: 4), Text('Panduan'), ], ), ), ], ), ), actions: _currentIndex == 0 ? [ IconButton( icon: const Icon(Icons.refresh), tooltip: 'Refresh', onPressed: _refreshGroupsPreservingSelection, ), ] : null, ), body: Column( children: [ // Main content Expanded( child: TabBarView( physics: const NeverScrollableScrollPhysics(), // Mencegah swipe controller: _tabController, children: [ // Chat tab - Tampilkan daftar grup terlebih dahulu _buildGroupsListTab(), // News tab const SimpleNewsTab(), // Panduan Budidaya tab const FarmingGuideTab(), ], ), ), ], ), ); } // Widget untuk menampilkan daftar grup Widget _buildGroupsListTab() { if (_isLoadingGroups) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text( 'Memuat grup...', style: TextStyle(fontSize: 16, color: Colors.grey), ), ], ), ); } if (_userGroups.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.groups, size: 80, color: Colors.grey.shade300), const SizedBox(height: 16), Text( 'Tidak ada grup tersedia', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.grey.shade700, ), ), const SizedBox(height: 8), Text( 'Periksa koneksi internet atau coba refresh', style: TextStyle(color: Colors.grey.shade600), ), const SizedBox(height: 24), ElevatedButton.icon( onPressed: _refreshGroupsPreservingSelection, icon: const Icon(Icons.refresh), label: const Text('Refresh'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ], ), ); } return Column( children: [ // Pilihan grup if (_selectedGroupId != null && _userGroups.isNotEmpty) _buildGroupSelector(), // Daftar grup atau chat grup Expanded( child: _selectedGroupId == null ? _buildGroupsList() : GroupChatScreen( key: _groupChatKey, screenKey: _groupChatKey, isInTabView: true, groupId: _selectedGroupId!, ), ), ], ); } Widget _buildGroupsList() { return RefreshIndicator( onRefresh: _refreshGroupsPreservingSelection, child: ListView.builder( padding: const EdgeInsets.all(16), itemCount: _userGroups.length, itemBuilder: (context, index) { final group = _userGroups[index]; final isUserMember = _userGroupIds.contains(group.id); // Gunakan GroupCard yang baru dibuat return GroupCard( group: group, isUserMember: isUserMember, onTap: () => _selectGroup(group.id), onJoinLeave: () => isUserMember ? _leaveGroup(group.id) : _joinGroup(group.id), ); }, ), ); } // Navigasi ke halaman chat grup void _navigateToGroupChat(Group group) { if (_selectedGroupId != null) { Navigator.push( context, MaterialPageRoute( builder: (context) => GroupChatScreen( isInTabView: false, groupId: _selectedGroupId!, ), ), ); } } // Selector untuk grup yang dipilih Widget _buildGroupSelector() { if (_selectedGroupId == null) return const SizedBox.shrink(); // Cari grup yang dipilih final selectedGroup = _userGroups.firstWhere( (g) => g.id == _selectedGroupId, orElse: () => _userGroups.first, ); return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Row( children: [ InkWell( onTap: () => setState(() => _selectedGroupId = null), borderRadius: BorderRadius.circular(20), child: const Padding( padding: EdgeInsets.all(8.0), child: Icon(Icons.arrow_back, color: AppColors.primary), ), ), const SizedBox(width: 12), CircleAvatar( backgroundColor: AppColors.primary.withOpacity(0.2), radius: 20, child: Text( selectedGroup.name.isNotEmpty ? selectedGroup.name[0].toUpperCase() : 'G', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.bold, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( selectedGroup.name, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( '${selectedGroup.memberCount} anggota', style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ], ), ), IconButton( icon: const Icon(Icons.refresh), onPressed: _refreshGroupsPreservingSelection, color: AppColors.primary, ), ], ), ); } // Pilih grup untuk chat void _selectGroup(String groupId) { setState(() { _selectedGroupId = groupId; _showGroupSelector = true; }); // Immediate refresh without delay _triggerImmediateMessageLoad(); // Also set multiple delayed refreshes for redundancy for (var delay in [100, 500, 1000, 2000]) { Future.delayed(Duration(milliseconds: delay), () { if (mounted) { _triggerImmediateMessageLoad(); } }); } } // Trigger immediate message loading in the chat screen void _triggerImmediateMessageLoad() { try { if (_groupChatKey.currentState != null) { print('[DEBUG] Triggering immediate message load'); _groupChatKey.currentState!.forceRefresh(); } } catch (e) { print('[ERROR] Error triggering message load: $e'); } } // Gabung ke grup Future _joinGroup(String groupId) async { if (_isJoiningGroup) return; setState(() { _isJoiningGroup = true; _loadingGroupId = groupId; }); try { final supabase = Supabase.instance.client; final userId = supabase.auth.currentUser?.id; if (userId == null) { throw Exception('User not authenticated'); } // Coba bergabung dengan grup await supabase.from('group_members').insert({ 'group_id': groupId, 'user_id': userId, 'role': 'member', 'is_active': true, }); // Refresh data await _loadUserGroups(); // Set grup yang baru diikuti sebagai grup yang dipilih setState(() { _selectedGroupId = groupId; _userGroupIds.add(groupId); }); _showSuccessSnackBar('Berhasil bergabung dengan grup'); } catch (e) { print('[ERROR] Failed to join group: $e'); _showErrorSnackBar('Gagal bergabung dengan grup: ${e.toString()}'); } finally { if (mounted) { setState(() { _isJoiningGroup = false; _loadingGroupId = null; }); } } } // Keluar dari grup Future _leaveGroup(String groupId) async { // Tampilkan konfirmasi final shouldLeave = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Keluar dari Grup'), content: const Text( 'Apakah Anda yakin ingin keluar dari grup ini?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Batal'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('Keluar'), ), ], ), ) ?? false; if (!shouldLeave) return; setState(() { _isJoiningGroup = true; _loadingGroupId = groupId; }); try { final supabase = Supabase.instance.client; final userId = supabase.auth.currentUser?.id; if (userId == null) { throw Exception('User not authenticated'); } // Nonaktifkan keanggotaan await supabase .from('group_members') .update({'is_active': false}) .eq('group_id', groupId) .eq('user_id', userId); // Refresh data await _loadUserGroups(); // Jika grup saat ini yang dikeluar, kembalikan ke daftar grup if (_selectedGroupId == groupId) { setState(() { _selectedGroupId = null; }); } _showSuccessSnackBar('Berhasil keluar dari grup'); } catch (e) { print('[ERROR] Failed to leave group: $e'); _showErrorSnackBar('Gagal keluar dari grup: ${e.toString()}'); } finally { if (mounted) { setState(() { _isJoiningGroup = false; _loadingGroupId = null; }); } } } String _getTabTitle(int index) { switch (index) { case 0: return 'Diskusi'; case 1: return 'Berita'; case 2: return 'Panduan Pertanian'; default: return 'Komunitas'; } } void _showLoadingDialog(String message) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ const CircularProgressIndicator(), const SizedBox(height: 16), Text(message), ], ), ), ); } void _showErrorSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.red), ); } void _showSuccessSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.green), ); } Future _emergencyDisableRLS() async { try { final supabase = Supabase.instance.client; _showLoadingDialog('Mencoba perbaiki database...'); // Try to disable RLS on problematic tables final sql = ''' ALTER TABLE group_members DISABLE ROW LEVEL SECURITY; ALTER TABLE groups DISABLE ROW LEVEL SECURITY; '''; await supabase.rpc('exec_sql', params: {'sql': sql}); print('[DEBUG] Emergency disabled RLS on tables'); Navigator.of(context).pop(); // Close loading dialog _showSuccessSnackBar('Database diperbaiki, mencoba ulang...'); // Reload groups _loadUserGroups(); } catch (e) { Navigator.of(context).pop(); // Close loading dialog print('[ERROR] Failed to disable RLS: $e'); _showErrorSnackBar('Gagal memperbaiki database: ${e.toString()}'); } } // Make a direct database query to preload messages as early as possible Future _preloadDefaultGroupMessages() async { try { print('[PRELOAD] Attempting to preload default group messages'); final supabase = Supabase.instance.client; // First check if there are any messages in the default group final response = await supabase .rpc( 'count_messages_in_group', params: {'group_id_param': '00000000-0000-0000-0000-000000000001'}, ) .timeout(Duration(seconds: 2)); final count = response as int? ?? 0; // If no messages, create a welcome message if (count == 0) { print('[PRELOAD] No messages found, creating welcome message'); final userId = supabase.auth.currentUser?.id; if (userId != null) { // Get username from profiles String username = 'User'; String email = ''; try { final profileData = await supabase .from('profiles') .select('username, email') .eq('user_id', userId) .single() .timeout(Duration(seconds: 2)); username = profileData['username'] ?? 'User'; email = profileData['email'] ?? ''; } catch (e) { print('[ERROR] Failed to get profile: $e'); } // Create welcome message using an RPC function that bypasses RLS try { await supabase.rpc( 'create_welcome_message', params: { 'message_content': 'Selamat datang di Grup TaniSM4RT! Ini adalah pesan otomatis untuk memulai percakapan.', 'user_id_param': userId, 'username_param': username, 'email_param': email, 'group_id_param': '00000000-0000-0000-0000-000000000001', }, ); print('[PRELOAD] Created welcome message successfully via RPC'); } catch (e) { print('[ERROR] Failed to create welcome message via RPC: $e'); // Fallback to direct query with service role if available try { final adminClient = Supabase.instance.client; await adminClient.from('messages').insert({ 'id': Uuid().v4(), 'content': 'Selamat datang di Grup TaniSM4RT! Ini adalah pesan otomatis untuk memulai percakapan.', 'sender_user_id': userId, 'sender_username': username, 'sender_email': email, 'group_id': '00000000-0000-0000-0000-000000000001', 'created_at': DateTime.now().toIso8601String(), }); print('[PRELOAD] Created welcome message via admin client'); } catch (e) { print( '[ERROR] Failed to create welcome message via admin client: $e', ); } } } } else { print( '[PRELOAD] Found $count existing messages, no need to create welcome message', ); } } catch (e) { print('[ERROR] Error in preloading messages: $e'); } } // Force refresh to fix issues with message loading Future _forceRefreshAllMessages() async { try { if (!mounted) return; print('[FORCE] Attempting force refresh on GroupChatScreen'); // Wait a second to make sure GroupChatScreen is mounted Future.delayed(Duration(seconds: 2), () { if (_groupChatKey.currentState != null) { print('[FORCE] Executing force refresh on GroupChatScreen'); _groupChatKey.currentState?.setupRealTimeSubscription(); // This is a public method that exists in GroupChatScreenState _groupChatKey.currentState?.forceRefresh(); } else { print('[FORCE] GroupChatScreen not yet mounted, delaying refresh'); // Try once more after a delay Future.delayed(Duration(seconds: 1), () { if (_groupChatKey.currentState != null) { _groupChatKey.currentState?.setupRealTimeSubscription(); _groupChatKey.currentState?.forceRefresh(); } }); } }); } catch (e) { print('[ERROR] Error in force refresh: $e'); } } // This method refreshes groups but preserves the current selection Future _refreshGroupsPreservingSelection() async { // Store the current group ID before refresh to maintain selection final previouslySelectedGroupId = _selectedGroupId; print( '[DEBUG] Refreshing while preserving selection: $previouslySelectedGroupId', ); // Call the regular load method await _loadUserGroups(); // If the previously selected group exists but wasn't selected, select it now if (previouslySelectedGroupId != null && mounted) { final stillExists = _userGroups.any( (g) => g.id == previouslySelectedGroupId, ); if (stillExists && _selectedGroupId != previouslySelectedGroupId) { setState(() { _selectedGroupId = previouslySelectedGroupId; print('[DEBUG] Restored selection to: $_selectedGroupId'); }); // Force refresh the messages in the restored group if (_groupChatKey.currentState != null) { _triggerImmediateMessageLoad(); } } } } // Tambahkan fungsi setup subscription realtime void _setupGroupRealtimeSubscription() { final supabase = Supabase.instance.client; _groupRealtimeChannel = supabase.channel('public:groups_and_members'); _groupRealtimeChannel! .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'groups', callback: (payload) { print('[REALTIME] Groups table changed: $payload'); _loadUserGroups(); }, ) .onPostgresChanges( event: PostgresChangeEvent.all, schema: 'public', table: 'group_members', callback: (payload) { print('[REALTIME] Group members table changed: $payload'); _loadUserGroups(); }, ); _groupRealtimeChannel!.subscribe(); } @override bool get wantKeepAlive => true; }