import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_core/firebase_core.dart'; import '../../core/theme/app_theme.dart'; import '../../core/config/app_config.dart'; import '../../core/constants/app_constants.dart'; import '../../models/user_model.dart'; import '../../services/firebase_service.dart'; import '../../services/dialog_service.dart'; import '../auth/login_screen.dart'; import 'preferences_screen.dart'; import 'notification_settings_screen.dart'; import 'privacy_security_screen.dart'; import '../onboarding/daily_checkin_screen.dart'; class ProfileScreen extends ConsumerStatefulWidget { const ProfileScreen({super.key}); @override ConsumerState createState() => _ProfileScreenState(); } class _ProfileScreenState extends ConsumerState { UserModel? _userData; bool _isLoading = true; String? _error; @override void initState() { super.initState(); _loadUserData(); } Future _loadUserData() async { setState(() { _isLoading = true; _error = null; }); try { final firebaseService = FirebaseService(); final currentUser = firebaseService.currentUser; if (currentUser == null) { throw Exception('Pengguna tidak terautentikasi'); } final userData = await firebaseService.getUserData(currentUser.uid); // 🔍 DEBUG — hapus setelah debugging selesai debugPrint('=== PROFILE DEBUG ==='); debugPrint('userData: ${userData != null ? "OK" : "NULL"}'); debugPrint( 'favoriteCategories: ${userData?.preferences.favoriteCategories}', ); debugPrint('allergies: ${userData?.preferences.allergies}'); debugPrint('spiceLevel: ${userData?.preferences.spiceLevel}'); debugPrint('budgetRange: ${userData?.preferences.budgetRange}'); debugPrint('dailyPreferences: ${userData?.dailyPreferences}'); debugPrint('===================='); setState(() { _userData = userData; _isLoading = false; }); } catch (e) { debugPrint('Profile load error: $e'); setState(() { _error = e.toString(); _isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppTheme.warmWhite, appBar: AppBar( title: const Text( 'Profil', style: TextStyle( color: AppTheme.primaryBrown, fontWeight: FontWeight.bold, ), ), backgroundColor: AppTheme.warmWhite, elevation: 0, iconTheme: const IconThemeData(color: AppTheme.primaryBrown), ), body: _isLoading ? _buildLoadingState() : _error != null ? _buildErrorState() : _buildProfileContent(), ); } Widget _buildLoadingState() { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Memuat profil...'), ], ), ); } Widget _buildErrorState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64, color: AppTheme.error), const SizedBox(height: 16), Text( 'Gagal memuat profil', style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( _error ?? 'Terjadi kesalahan', style: Theme.of( context, ).textTheme.bodyMedium?.copyWith(color: AppTheme.grey600), textAlign: TextAlign.center, ), const SizedBox(height: 24), ElevatedButton( onPressed: _loadUserData, child: const Text('Coba Lagi'), ), ], ), ); } Widget _buildProfileContent() { final user = FirebaseService().currentUser; final displayName = _userData?.displayName ?? user?.displayName ?? 'Pengguna'; final email = _userData?.email ?? user?.email ?? ''; // Extra bottom padding agar konten tidak tertutup navbar (extendBody: true) final bottomPadding = MediaQuery.of(context).padding.bottom + kBottomNavigationBarHeight + 16.0; return SingleChildScrollView( padding: EdgeInsets.fromLTRB( AppConstants.defaultPadding, AppConstants.defaultPadding, AppConstants.defaultPadding, bottomPadding, ), child: Column( children: [ // Profile Header _buildProfileHeader(displayName, email, user?.photoURL), const SizedBox(height: 32), // Preferences Summary _buildPreferencesCard(), const SizedBox(height: 16), // Menu Options _buildMenuOptions(), const SizedBox(height: 32), // Sign Out Button _buildSignOutButton(), ], ), ); } Widget _buildProfileHeader( String displayName, String email, String? photoUrl, ) { final firstName = displayName.split(' ').first; return Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppTheme.primaryBrown, AppTheme.coffeeBrown], ), borderRadius: BorderRadius.circular(AppConstants.borderRadius), boxShadow: [ BoxShadow( color: AppTheme.primaryBrown.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 8), ), ], ), child: Column( children: [ // Profile Picture CircleAvatar( radius: 50, backgroundColor: Colors.white.withOpacity(0.2), backgroundImage: photoUrl != null ? NetworkImage(photoUrl) : null, child: photoUrl == null ? Text( firstName.isNotEmpty ? firstName[0].toUpperCase() : 'U', style: const TextStyle( color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold, ), ) : null, ), const SizedBox(height: 16), // Name Text( displayName, style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: Colors.white, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 4), // Email Text( email, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.white.withOpacity(0.9), ), ), const SizedBox(height: 16), // Member Since Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(16), ), child: Text( 'Member sejak ${_userData?.createdAt.year ?? DateTime.now().year}', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ], ), ); } Widget _buildPreferencesCard() { final preferences = _userData?.preferences ?? UserPreferences(); final daily = _userData?.dailyPreferences; final lastCheckin = _userData?.lastCheckinDate; // Kategori Favorit final kategori = preferences.favoriteCategories.isNotEmpty ? preferences.favoriteCategories.join(', ') : 'Belum diatur'; // Alergi final alergi = preferences.allergies.isNotEmpty ? preferences.allergies.join(', ') : 'Tidak ada'; // Mood: dari daily check-in, fallback preferences String moodText = 'Belum check-in hari ini'; if (daily != null && daily['mood'] != null) { moodText = daily['mood'].toString(); } else if (preferences.mood.isNotEmpty && preferences.mood != 'neutral') { moodText = preferences.mood; } // Level Pedas final levelPedas = _formatSpiceLevel(preferences.spiceLevel); // Budget: dari daily check-in lalu fallback ke preferences String budgetText = 'Belum diatur'; if (daily != null && daily['budget'] != null) { final b = daily['budget']; budgetText = 'Rp ${_formatBudget(b is int ? b : (b as num).toInt())}'; } else if (preferences.budgetRange > 0) { budgetText = 'Rp ${_formatBudget(preferences.budgetRange)}'; } return Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(AppConstants.borderRadius), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header row Row( children: [ Icon(Icons.tune, color: AppTheme.primaryBrown, size: 20), const SizedBox(width: 8), Text( 'Preferensi Saya', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: AppTheme.primaryBrown, ), ), const Spacer(), TextButton( onPressed: () => _navigateToPreferences(), child: const Text('Edit'), ), ], ), // Sync label if (lastCheckin != null) Padding( padding: const EdgeInsets.only(bottom: 6), child: Row( children: [ Icon(Icons.sync, size: 12, color: AppTheme.grey500), const SizedBox(width: 4), Text( _formatCheckinLabel(lastCheckin), style: TextStyle( fontSize: 11, color: AppTheme.grey500, fontStyle: FontStyle.italic, ), ), ], ), ), const SizedBox(height: 8), _buildPreferenceItem( 'Kategori Favorit', kategori, Icons.favorite_outline, ), _buildPreferenceItem('Alergi', alergi, Icons.warning_outlined), _buildPreferenceItem( 'Mood Saat Ini', moodText, Icons.mood_outlined, valueColor: AppTheme.primaryBrown, ), _buildPreferenceItem( 'Level Pedas', levelPedas, Icons.local_fire_department_outlined, ), _buildPreferenceItem( 'Budget', budgetText, Icons.account_balance_wallet_outlined, ), ], ), ); } /// Label tanggal check-in yang mudah dibaca String _formatCheckinLabel(DateTime dt) { final now = DateTime.now(); final isToday = dt.year == now.year && dt.month == now.month && dt.day == now.day; if (isToday) return 'Mood diperbarui hari ini'; const months = [ '', 'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des', ]; return 'Terakhir diperbarui ${dt.day} ${months[dt.month]}'; } /// Format angka ke format Rp (misal 30000 → "30.000") String _formatBudget(int amount) { final s = amount.toString(); final buffer = StringBuffer(); int count = 0; for (int i = s.length - 1; i >= 0; i--) { if (count > 0 && count % 3 == 0) buffer.write('.'); buffer.write(s[i]); count++; } return buffer.toString().split('').reversed.join(); } Widget _buildPreferenceItem( String title, String value, IconData icon, { Color? valueColor, }) { return Padding( padding: const EdgeInsets.only(bottom: 12), child: Row( children: [ Icon(icon, size: 16, color: AppTheme.grey600), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF757575), // grey600 ), ), const SizedBox(height: 2), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: valueColor ?? const Color(0xFF424242), // grey800 ), ), ], ), ), ], ), ); } String _formatSpiceLevel(String level) { switch (level.toLowerCase()) { case 'mild': return '🌱 Tidak Pedas'; case 'medium': return '🌶️ Sedang'; case 'hot': return '🔥 Pedas'; case 'very hot': return '🌋 Sangat Pedas'; default: return 'Belum diatur'; } } Widget _buildMenuOptions() { return Column( children: [ _buildMenuOption( icon: Icons.edit_calendar_outlined, title: 'Isi Ulang Check-in Harian', subtitle: 'Perbarui mood, keinginan, & kondisi hari ini', onTap: _navigateToCheckin, highlight: true, ), _buildMenuOption( icon: Icons.notifications_outlined, title: 'Notifikasi', subtitle: 'Pengaturan notifikasi aplikasi', onTap: _showNotificationSettings, ), _buildMenuOption( icon: Icons.privacy_tip_outlined, title: 'Privasi & Keamanan', subtitle: 'Pengaturan privasi dan keamanan data', onTap: _showPrivacySettings, ), _buildMenuOption( icon: Icons.help_outline, title: 'Bantuan & FAQ', subtitle: 'Pertanyaan yang sering diajukan', onTap: _showHelp, ), _buildMenuOption( icon: Icons.info_outline, title: 'Tentang Aplikasi', subtitle: 'Informasi aplikasi dan versi', onTap: _showAbout, ), ], ); } Widget _buildMenuOption({ required IconData icon, required String title, required String subtitle, required VoidCallback onTap, bool highlight = false, }) { return Container( margin: const EdgeInsets.only(bottom: 8), decoration: BoxDecoration( color: highlight ? AppTheme.primaryBrown.withOpacity(0.06) : Colors.white, border: highlight ? Border.all(color: AppTheme.primaryBrown.withOpacity(0.3)) : null, borderRadius: BorderRadius.circular(AppConstants.borderRadius), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: ListTile( leading: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: AppTheme.primaryBrown.withOpacity(highlight ? 0.2 : 0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: AppTheme.primaryBrown, size: 20), ), title: Text( title, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, color: highlight ? AppTheme.primaryBrown : null, ), ), subtitle: Text( subtitle, style: Theme.of( context, ).textTheme.bodySmall?.copyWith(color: AppTheme.grey600), ), trailing: const Icon(Icons.chevron_right, color: AppTheme.grey400), onTap: onTap, ), ); } Future _navigateToCheckin() async { final result = await Navigator.of(context).push( MaterialPageRoute( builder: (_) => const DailyCheckinScreen(isMandatory: false), ), ); // Refresh halaman profil setelah check-in selesai agar data terbaru tampil if (result == true && mounted) { _loadUserData(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text( '✅ Check-in berhasil! Preferensi profil diperbarui.', style: TextStyle(fontFamily: 'Poppins'), ), backgroundColor: AppTheme.primaryBrown, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), ); } } } Widget _buildSignOutButton() { return SizedBox( width: double.infinity, child: OutlinedButton.icon( onPressed: _signOut, icon: const Icon(Icons.logout, color: AppTheme.error), label: const Text('Keluar', style: TextStyle(color: AppTheme.error)), style: OutlinedButton.styleFrom( side: const BorderSide(color: AppTheme.error), padding: const EdgeInsets.symmetric(vertical: 12), ), ), ); } void _navigateToPreferences() async { // Sinkronkan data preferensi dengan daily check-in (jika ada override) final currentPrefs = _userData?.preferences ?? UserPreferences(); if (_userData?.dailyPreferences?['budget'] != null) { final overrideBudget = (_userData!.dailyPreferences!['budget'] as num).toInt(); // Hanya ubah untuk UI di PreferencesScreen agar sama persis currentPrefs.budgetRange = overrideBudget; } await Navigator.of(context).push( MaterialPageRoute( builder: (context) => PreferencesScreen( userPreferences: currentPrefs, onPreferencesUpdated: (preferences) { // Update state langsung dari callback (optimistic update) setState(() { if (_userData != null) { _userData = _userData!.copyWith(preferences: preferences); // Juga sinkronkan state daily override di UI profil if (_userData!.dailyPreferences != null) { _userData!.dailyPreferences!['budget'] = preferences.budgetRange; } } }); }, ), ), ); // Setelah kembali dari halaman preferensi, selalu reload dari Firebase // untuk memastikan data terbaru tersinkron _loadUserData(); } void _showNotificationSettings() { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const NotificationSettingsScreen()), ); } void _showPrivacySettings() { Navigator.of( context, ).push(MaterialPageRoute(builder: (_) => const PrivacySecurityScreen())); } void _showHelp() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Bantuan & FAQ'), content: const SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( 'Cara menggunakan aplikasi:', style: TextStyle(fontWeight: FontWeight.bold), ), SizedBox(height: 8), Text('1. Ambil foto menu kafe'), Text('2. Tunggu AI menganalisis menu'), Text('3. Dapatkan rekomendasi personal'), Text('4. Chat dengan AI untuk pertanyaan lanjutan'), SizedBox(height: 16), Text( 'Tips untuk hasil terbaik:', style: TextStyle(fontWeight: FontWeight.bold), ), SizedBox(height: 8), Text('• Pastikan foto jelas dan tidak buram'), Text('• Gunakan pencahayaan yang cukup'), Text('• Atur preferensi dengan lengkap'), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Tutup'), ), ], ), ); } void _showAbout() { showAboutDialog( context: context, applicationName: AppConstants.appName, applicationVersion: AppConstants.appVersion, applicationIcon: Container( width: 64, height: 64, decoration: BoxDecoration( color: AppTheme.primaryBrown, borderRadius: BorderRadius.circular(16), ), child: const Icon(Icons.local_cafe, color: Colors.white, size: 32), ), children: [ const Text('Aplikasi rekomendasi menu kafe menggunakan AI dan OCR'), const SizedBox(height: 16), Text('© 2024 ${AppConstants.kafeName}'), ], ); } Future _signOut() async { final confirmed = await DialogService().showConfirmDialog( title: 'Keluar', message: 'Apakah Anda yakin ingin keluar dari aplikasi?', isDanger: true, context: context, ); if (confirmed == true) { try { await FirebaseService().signOut(); if (mounted) { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => const LoginScreen()), (route) => false, ); } } catch (e) { if (mounted) { DialogService().showError('Gagal keluar: $e'); } } } } }