MIF_E31222656/lib/screens/home_screen.dart

588 lines
17 KiB
Dart

import 'dart:ui';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:tugas_akhir_supabase/screens/calendar/calendar_screen.dart';
import 'package:tugas_akhir_supabase/screens/calendar/schedule_list_screen.dart';
import 'package:tugas_akhir_supabase/screens/calendar/schedule_detail_screen.dart';
import 'package:tugas_akhir_supabase/screens/community/community_screen.dart';
import 'package:tugas_akhir_supabase/screens/community/enhanced_community_screen.dart';
import 'package:tugas_akhir_supabase/screens/panen/analisis_panen_screen.dart';
import 'package:tugas_akhir_supabase/screens/profile_screen.dart';
import 'package:tugas_akhir_supabase/models/crop_schedule.dart';
import 'package:tugas_akhir_supabase/screens/home/home_content.dart';
import 'package:tugas_akhir_supabase/screens/panen/analisis_input_screen.dart';
import 'package:tugas_akhir_supabase/utils/date_formatter.dart';
import 'package:tugas_akhir_supabase/screens/image_processing/plant_scanner_screen.dart';
import 'package:tugas_akhir_supabase/services/auth_services.dart';
import 'package:tugas_akhir_supabase/services/session_manager.dart';
import 'package:tugas_akhir_supabase/utils/session_checker_mixin.dart';
import 'package:tugas_akhir_supabase/utils/fix_database_policies.dart';
import 'package:get_it/get_it.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with SessionCheckerMixin {
User? _user;
int _selectedIndex = 0;
String? _profileImageUrl;
Map<String, dynamic>? _profile;
String? _scheduleId;
String? _cropName;
bool _isLoadingSchedule = true;
DateTime? _lastBackPressed;
// Variabel untuk melacak apakah perlu refresh
bool _needsHomeRefresh = false;
bool _isAdmin = false;
List<Widget> get _screens {
final userId = _user?.id ?? '';
return [
HomeContent(
userId: userId,
// Tambahkan parameter refresh key yang akan berubah saat perlu refresh
key: ValueKey(
'home_content_${_needsHomeRefresh ? 'refresh' : 'normal'}',
),
),
KalenderTanamScreen(),
PlantScannerScreen(),
_buildAnalisisScreen(userId),
EnhancedCommunityScreen(),
];
}
@override
void initState() {
super.initState();
_user = Supabase.instance.client.auth.currentUser;
// Gunakan Future.delayed untuk memastikan UI sudah dirender sebelum operasi berat
Future.delayed(Duration(milliseconds: 300), () {
if (mounted) {
_safeInitialize();
}
});
// Initialize session checking dengan delay
Future.delayed(Duration(milliseconds: 800), () {
if (mounted) {
initSessionChecking();
}
});
}
@override
void dispose() {
// Clean up session checking
disposeSessionChecking();
super.dispose();
}
Future<void> _safeInitialize() async {
try {
// Set safety timer untuk mencegah loading yang tidak berhenti
Future.delayed(Duration(seconds: 5), () {
if (mounted && _isLoadingSchedule) {
debugPrint(
'[WARNING] Force completing schedule loading after timeout',
);
setState(() => _isLoadingSchedule = false);
}
});
// Jalankan operasi secara paralel untuk mempercepat
await Future.wait([
_loadUserProfile().timeout(
Duration(seconds: 3),
onTimeout: () {
debugPrint('[WARNING] Load user profile timed out');
},
),
_fetchScheduleIfNeeded().timeout(
Duration(seconds: 3),
onTimeout: () {
debugPrint('[WARNING] Fetch schedule timed out');
if (mounted) {
setState(() => _isLoadingSchedule = false);
}
},
),
_checkAdminStatus().timeout(
Duration(seconds: 3),
onTimeout: () {
debugPrint('[WARNING] Check admin status timed out');
},
),
_refreshUserSession().timeout(
Duration(seconds: 3),
onTimeout: () {
debugPrint('[WARNING] Refresh user session timed out');
},
),
]);
} catch (e) {
debugPrint('[ERROR] Error in safe initialize: $e');
if (mounted && _isLoadingSchedule) {
setState(() => _isLoadingSchedule = false);
}
}
}
Future<void> _refreshUserSession() async {
try {
// Update user activity timestamp
await updateUserActivity();
// Refresh Supabase session if needed
final authServices = GetIt.instance<AuthServices>();
await authServices.refreshSession();
debugPrint('Session refreshed in HomeScreen');
// Cek ulang status admin setelah refresh session
await _checkAdminStatus();
} catch (e) {
debugPrint('Error refreshing session in HomeScreen: $e');
}
}
Future<void> _loadUserProfile() async {
if (_user == null) {
debugPrint('FATAL: User is null, cannot load profile');
return;
}
debugPrint('INFO: Current user ID: ${_user!.id}');
debugPrint('INFO: Current user email: ${_user!.email}');
try {
debugPrint('INFO: Mencoba mencari profile untuk user ID: ${_user!.id}');
// Coba dengan query langsung ke tabel dengan timeout
final response = await Supabase.instance.client
.from('profiles')
.select('*')
.eq('user_id', _user!.id)
.limit(1)
.timeout(
Duration(seconds: 3),
onTimeout: () {
debugPrint('[WARNING] Profile query timed out');
throw TimeoutException('Profile query timed out');
},
);
debugPrint('QUERY RESULT: Hasil query length: ${response.length}');
debugPrint('QUERY RESULT: Response: $response');
if (response.isNotEmpty) {
final userData = response[0];
debugPrint('SUCCESS: Profile data ditemukan');
debugPrint('DATA: Full profile: $userData');
debugPrint('DATA: farm_name: ${userData['farm_name']}');
debugPrint('DATA: user_id: ${userData['user_id']}');
if (mounted) {
setState(() {
_profileImageUrl = userData['avatar_url'];
_profile = userData;
});
}
} else {
debugPrint(
'FATAL: Tidak ada data profile ditemukan untuk user_id: ${_user!.id}',
);
// Fallback: Create a temporary profile for UI
if (mounted) {
setState(() {
_profile = {
'farm_name': 'pepepe', // Gunakan nama yang kita tahu ada
'user_id': _user!.id,
};
});
}
}
} catch (e, stackTrace) {
debugPrint('ERROR: Gagal mengambil profile: $e');
debugPrint('STACKTRACE: $stackTrace');
// Fallback untuk UI
if (mounted) {
setState(() {
_profile = {
'farm_name': _user?.email?.split('@').first ?? 'Pengguna',
'user_id': _user!.id,
};
});
}
}
}
Future<bool> _onWillPop() async {
final now = DateTime.now();
if (_lastBackPressed == null ||
now.difference(_lastBackPressed!) > Duration(seconds: 2)) {
_lastBackPressed = now;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Tekan sekali lagi untuk keluar'),
duration: Duration(seconds: 2),
),
);
return false;
}
return true; // keluar aplikasi
}
Future<void> _fetchScheduleIfNeeded() async {
if (_user == null) {
setState(() => _isLoadingSchedule = false);
return;
}
try {
final schedule = await fetchActiveSchedule(_user!.id).timeout(
Duration(seconds: 3),
onTimeout: () {
debugPrint('[WARNING] Fetch active schedule timed out');
return null;
},
);
if (mounted) {
setState(() {
_scheduleId = schedule?['scheduleId'];
_cropName = schedule?['cropName'];
_isLoadingSchedule = false;
});
}
} catch (e) {
debugPrint('Error saat ambil schedule: $e');
if (mounted) {
setState(() => _isLoadingSchedule = false);
}
}
}
void _onItemTapped(int index) {
// Update user activity timestamp when switching tabs
updateUserActivity();
// Jika sebelumnya berada di tab lain dan kembali ke home tab
if (_selectedIndex != 0 && index == 0 && _needsHomeRefresh) {
// Reset flag dan rebuild HomeContent dengan key baru
setState(() {
_needsHomeRefresh = false;
});
}
setState(() {
_selectedIndex = index;
});
}
// Tandai bahwa home screen perlu di-refresh
void _markHomeNeedsRefresh() {
setState(() {
_needsHomeRefresh = true;
});
}
void _navigateToProfile() {
// Update user activity timestamp when navigating to profile
updateUserActivity();
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ProfileScreen()),
).then((_) {
// Reload profile when returning from profile screen
_loadUserProfile();
});
}
Widget _buildAnalisisScreen(String userId) {
if (_isLoadingSchedule) {
return Center(child: CircularProgressIndicator());
}
return AnalisisInputScreen(
userId: userId,
scheduleData:
_scheduleId != null
? {'id': _scheduleId, 'crop_name': _cropName}
: null,
);
}
@override
Widget build(BuildContext context) {
// Update user activity when building the screen
WidgetsBinding.instance.addPostFrameCallback((_) {
updateUserActivity();
});
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final shouldExit = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: const Text('Konfirmasi'),
content: const Text('Apakah Anda ingin keluar dari aplikasi?'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('Tidak'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Keluar'),
),
],
),
);
if (shouldExit == true) {
SystemNavigator.pop();
}
},
child: Scaffold(
backgroundColor: const Color(0xFFFAFAFA),
body: SafeArea(
child: Column(
children: [
_buildHeader(),
Expanded(
child: IndexedStack(index: _selectedIndex, children: _screens),
),
],
),
),
bottomNavigationBar: _buildBottomNavBar(),
),
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.03),
offset: const Offset(0, 1),
blurRadius: 2,
),
],
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'TaniSM4RT',
style: GoogleFonts.poppins(
fontSize: 22,
fontWeight: FontWeight.bold,
color: const Color(0xFF056839),
),
),
const SizedBox(height: 2),
Text(
_getUserDisplayName(),
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
GestureDetector(
onTap: () {
// Update user activity when tapping profile
updateUserActivity();
_navigateToProfile();
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
image:
_profileImageUrl != null
? DecorationImage(
image: NetworkImage(_profileImageUrl!),
fit: BoxFit.cover,
)
: null,
border: Border.all(color: Colors.white, width: 1.5),
),
child:
_profileImageUrl == null
? const Icon(
Icons.person,
color: Colors.grey,
size: 20,
)
: null,
),
),
],
),
],
),
);
}
Widget _buildBottomNavBar() {
return Container(
height: 60,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.04),
blurRadius: 4,
offset: const Offset(0, -1),
),
],
),
child: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: _onItemTapped,
backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed,
selectedItemColor: const Color(0xFF056839),
unselectedItemColor: Colors.grey,
selectedFontSize: 9,
unselectedFontSize: 11,
iconSize: 20,
selectedLabelStyle: GoogleFonts.poppins(fontWeight: FontWeight.w500),
unselectedLabelStyle: GoogleFonts.poppins(fontWeight: FontWeight.w500),
elevation: 0,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home_rounded),
label: 'Beranda',
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_rounded),
label: 'Kalender',
),
BottomNavigationBarItem(
icon: Icon(Icons.document_scanner_rounded),
label: 'Deteksi',
),
BottomNavigationBarItem(
icon: Icon(Icons.analytics_rounded),
label: 'Analisis',
),
BottomNavigationBarItem(
icon: Icon(Icons.forum_rounded),
label: 'Komunitas',
),
],
),
);
}
String _getUserDisplayName() {
debugPrint('DIAGNOSIS: Mencoba mendapatkan nama display');
debugPrint('DIAGNOSIS: Profile ada? ${_profile != null}');
if (_profile != null) {
debugPrint('DIAGNOSIS: Isi profile: $_profile');
debugPrint('DIAGNOSIS: Keys dalam profile: ${_profile!.keys.toList()}');
}
// Prioritaskan username dari database
if (_profile != null &&
_profile!['username'] != null &&
_profile!['username'].toString().isNotEmpty) {
final username = _profile!['username'].toString();
debugPrint('DIAGNOSIS: Menggunakan username dari database: $username');
return 'Hi, $username';
}
// Fallback ke farm_name
if (_profile != null &&
_profile!['farm_name'] != null &&
_profile!['farm_name'].toString().isNotEmpty) {
final farmName = _profile!['farm_name'].toString();
debugPrint('DIAGNOSIS: Menggunakan farm_name dari database: $farmName');
return 'Hi, $farmName';
}
// Fallback ke email
if (_user != null && _user!.email != null) {
final email = _user!.email!;
final username = email.split('@').first;
debugPrint('DIAGNOSIS: Menggunakan nama dari email: $username');
return 'Hi, $username';
}
return 'Hi, Petani';
}
Future<Map<String, String>?> fetchActiveSchedule(String userId) async {
try {
final response =
await Supabase.instance.client
.from('crop_schedules')
.select('id, crop_name')
.eq('user_id', userId)
.order('created_at', ascending: false)
.limit(1)
.single();
if (response['id'] != null && response['crop_name'] != null) {
return {
'scheduleId': response['id'],
'cropName': response['crop_name'],
};
}
} catch (e) {
debugPrint('Gagal fetch schedule: $e');
}
return null;
}
Future<void> _checkAdminStatus() async {
try {
final authServices = GetIt.instance<AuthServices>();
final isAdmin = await authServices.isAdmin();
if (mounted) {
setState(() {
_isAdmin = isAdmin;
});
}
debugPrint('Admin status checked: $_isAdmin');
} catch (e) {
debugPrint('Error checking admin status: $e');
}
}
}