import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:tugas_akhir_supabase/screens/community/models/farming_guide_model.dart'; import 'package:tugas_akhir_supabase/screens/community/data/static_guides_data.dart'; import 'package:tugas_akhir_supabase/screens/community/utils/plant_categorizer.dart'; class GuideService { final _supabase = Supabase.instance.client; // Storage bucket that should be used for images String _imageBucket = 'images'; bool _bucketsChecked = false; // Singleton pattern static final GuideService _instance = GuideService._internal(); factory GuideService() => _instance; GuideService._internal() { // Check available buckets _checkAvailableBuckets(); // Debug existing guides when service is created debugCheckExistingGuides(); } // Check available buckets and set the appropriate one to use Future _checkAvailableBuckets() async { if (_bucketsChecked) return; try { final buckets = await _supabase.storage.listBuckets(); final bucketNames = buckets.map((b) => b.name).toList(); debugPrint('Available buckets: ${bucketNames.join(', ')}'); // Preferred bucket order: images, guide_images, avatars if (bucketNames.contains('images')) { _imageBucket = 'images'; } else if (bucketNames.contains('guide_images')) { _imageBucket = 'guide_images'; } else if (bucketNames.contains('avatars')) { _imageBucket = 'avatars'; } else if (bucketNames.isNotEmpty) { _imageBucket = bucketNames.first; } debugPrint('Selected bucket for guide images: $_imageBucket'); _bucketsChecked = true; } catch (e) { debugPrint('Error checking buckets: $e'); } } // Debug function to check existing guides and their image URLs Future debugCheckExistingGuides() async { try { await _checkAvailableBuckets(); final response = await _supabase .from('farming_guides') .select('*') .timeout(const Duration(seconds: 5)); debugPrint('====== DEBUG EXISTING GUIDES ======'); debugPrint('Found ${response.length} guides in database'); debugPrint('Using bucket: $_imageBucket'); // Check each guide and its image URL for (var i = 0; i < response.length; i++) { final guide = response[i]; final id = guide['id'] ?? 'unknown'; final title = guide['title'] ?? 'No title'; final imageUrl = guide['image_url']; debugPrint('Guide #${i + 1}: $title (ID: $id)'); if (imageUrl == null) { debugPrint(' - No image URL (null)'); } else if (imageUrl.isEmpty) { debugPrint(' - Empty image URL'); } else { debugPrint(' - Original image URL: $imageUrl'); final fixedUrl = fixImageUrl(imageUrl); debugPrint(' - Fixed image URL: $fixedUrl'); // Try to fix the guide's image URL in the database if needed if (fixedUrl != null && fixedUrl != imageUrl) { try { await _supabase .from('farming_guides') .update({'image_url': fixedUrl}) .eq('id', id); debugPrint(' - Updated image URL in database'); } catch (e) { debugPrint(' - Failed to update image URL: $e'); } } } } debugPrint('====== END DEBUG ======'); } catch (e) { debugPrint('Error checking existing guides: $e'); } } // Mengambil panduan dari database dan menggabungkan dengan data statis Future> getGuides() async { try { // Mencoba mengambil data dari Supabase dengan timeout 5 detik final response = await _supabase .from('farming_guides') .select('*') .order('created_at', ascending: false) .timeout(const Duration(seconds: 5)); debugPrint('Loaded ${response.length} guides from database'); // Cek respons untuk masalah pada data gambar for (final guide in response) { if (guide['image_url'] == null) { debugPrint('Guide with title "${guide['title']}" has null image_url'); } else { debugPrint('Guide image URL: ${guide['image_url']}'); } } // Konversi ke List dengan auto-kategorisasi jika perlu final dbGuides = List>.from(response).map((map) { // Jika kategori kosong atau generic, coba kategorikan otomatis final currentCategory = map['category'] ?? ''; if (currentCategory.isEmpty || currentCategory.toLowerCase() == 'umum') { final title = map['title'] ?? ''; final content = map['content'] ?? ''; // Auto-kategorisasi berdasarkan judul dan konten final category = PlantCategorizer.categorize( title, description: content, ); map['category'] = category; debugPrint('Auto-categorized "${title}" as "$category"'); } // Cek dan perbaiki URL gambar if (map['image_url'] != null) { map['image_url'] = fixImageUrl(map['image_url']); } return FarmingGuideModel.fromMap(map); }).toList(); // Mendapatkan data statis final staticGuides = StaticGuidesData().getAllGuides(); // Gabungkan keduanya final allGuides = [...dbGuides, ...staticGuides]; // Hilangkan duplikat berdasarkan judul final uniqueTitles = {}; final uniqueGuides = []; for (final guide in allGuides) { if (uniqueTitles.add(guide.title)) { uniqueGuides.add(guide); } } return uniqueGuides; } catch (e) { // Jika terjadi error, gunakan data statis saja debugPrint('Error loading guides from database: $e'); debugPrint('Falling back to static data only'); return StaticGuidesData().getAllGuides(); } } // Mengambil panduan berdasarkan kategori Future> getGuidesByCategory(String category) async { if (category.isEmpty) { return getGuides(); } try { // Mendapatkan semua panduan terlebih dahulu final allGuides = await getGuides(); // Filter berdasarkan kategori // Ubah pencocokan menjadi case-insensitive dan juga menerima partial match return allGuides.where((guide) { // Check if the category matches (case insensitive) if (guide.category.toLowerCase() == category.toLowerCase()) { return true; } // Check if it's a partial match (for better categorization) if (guide.category.toLowerCase().contains(category.toLowerCase()) || category.toLowerCase().contains(guide.category.toLowerCase())) { return true; } return false; }).toList(); } catch (e) { // Jika terjadi error, gunakan data statis saja debugPrint('Error filtering guides by category: $e'); return StaticGuidesData().getGuidesByCategory(category); } } // Memperbaiki URL gambar jika perlu String? fixImageUrl(String? imageUrl) { if (imageUrl == null || imageUrl.isEmpty) { return null; } // Log untuk debugging debugPrint('Original image URL: $imageUrl'); // Fix URL jika perlu (pastikan URL lengkap) if (!imageUrl.startsWith('http')) { // Jika URL tidak lengkap, gunakan Storage dari Supabase String bucketName = _imageBucket; // Use the detected bucket String fileName = imageUrl; // Jika imageUrl sudah mengandung nama bucket, ekstrak if (imageUrl.contains('/')) { final parts = imageUrl.split('/'); if (parts.length >= 2) { bucketName = parts[0]; fileName = parts.sublist(1).join('/'); } } // Dapatkan URL publik yang valid try { final fixedUrl = _supabase.storage .from(bucketName) .getPublicUrl(fileName); debugPrint('Fixed image URL: $fixedUrl'); return fixedUrl; } catch (e) { debugPrint('Error fixing image URL: $e'); return imageUrl; // Kembalikan URL asli jika gagal } } return imageUrl; } }