import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../models/kontrakan.dart'; import '../models/laundry.dart'; import '../services/kontrakan_service.dart'; import '../services/laundry_service.dart'; import '../services/auth_service.dart'; import '../services/favorite_service.dart'; import '../utils/currency_formatter.dart'; import 'kontrakan_detail_screen.dart'; import 'laundry_detail_screen.dart'; import '../widgets/app_placeholder.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @override State createState() => _SearchScreenState(); } class _SearchScreenState extends State { final _kontrakanService = KontrakanService(); final _laundryService = LaundryService(); final _authService = AuthService(); final _favoriteService = FavoriteService(); final _searchController = TextEditingController(); List _allKontrakan = []; List _filteredKontrakan = []; List _allLaundry = []; List _filteredLaundry = []; Set _favKontrakanIds = {}; Set _favLaundryIds = {}; bool _isLoading = true; String _selectedCategory = 'Kontrakan'; String _selectedFilter = 'Semua'; // Range harga berbeda untuk kontrakan (per bulan) dan laundry (per kg) RangeValues _kontrakanPriceRange = const RangeValues( 0, 50000000, ); // Rp 0 - Rp 50juta/bulan RangeValues _laundryPriceRange = const RangeValues( 0, 500000, ); // Rp 0 - Rp 500ribu/kg // Getter untuk kompatibilitas dengan kode yang ada RangeValues get _priceRange { return _selectedCategory == 'Laundry' ? _laundryPriceRange : _kontrakanPriceRange; } set _priceRange(RangeValues value) { if (_selectedCategory == 'Laundry') { _laundryPriceRange = value; } else { _kontrakanPriceRange = value; } } @override void initState() { super.initState(); _loadData(); } @override void dispose() { _searchController.dispose(); super.dispose(); } Future _loadData() async { setState(() => _isLoading = true); try { final kontrakan = await _kontrakanService.getKontrakan(); final laundry = await _laundryService.getLaundry(); if (!mounted) return; setState(() { _allKontrakan = kontrakan; _filteredKontrakan = kontrakan; _allLaundry = laundry; _filteredLaundry = laundry; _isLoading = false; }); _loadFavoriteIds(); } catch (e) { if (!mounted) return; setState(() => _isLoading = false); } } Future _loadFavoriteIds() async { try { if (!_authService.isAuthenticated) return; final ids = await _favoriteService.getFavoriteIds(); if (mounted) { setState(() { _favKontrakanIds = (ids['kontrakan'] ?? []).toSet(); _favLaundryIds = (ids['laundry'] ?? []).toSet(); }); } } catch (e) { // Error loading favorite ids silently } } Future _toggleKontrakanFav(int id) async { final wasFav = _favKontrakanIds.contains(id); setState(() { if (wasFav) _favKontrakanIds.remove(id); else _favKontrakanIds.add(id); }); try { final result = await _favoriteService.toggleKontrakanFavorite(id); if (result['success'] != true && mounted) { setState(() { if (wasFav) _favKontrakanIds.add(id); else _favKontrakanIds.remove(id); }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(result['message'] ?? 'Gagal'), backgroundColor: Colors.red.shade600, ), ); } } catch (e) { if (mounted) { setState(() { if (wasFav) _favKontrakanIds.add(id); else _favKontrakanIds.remove(id); }); } } } Future _toggleLaundryFav(int id) async { final wasFav = _favLaundryIds.contains(id); setState(() { if (wasFav) _favLaundryIds.remove(id); else _favLaundryIds.add(id); }); try { final result = await _favoriteService.toggleLaundryFavorite(id); if (result['success'] != true && mounted) { setState(() { if (wasFav) _favLaundryIds.add(id); else _favLaundryIds.remove(id); }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(result['message'] ?? 'Gagal'), backgroundColor: Colors.red.shade600, ), ); } } catch (e) { if (mounted) { setState(() { if (wasFav) _favLaundryIds.add(id); else _favLaundryIds.remove(id); }); } } } void _filterKontrakan(String query) { if (_selectedCategory == 'Kontrakan') { setState(() { if (query.isEmpty) { _filteredKontrakan = _allKontrakan; } else { _filteredKontrakan = _allKontrakan.where((kontrakan) { return kontrakan.nama.toLowerCase().contains(query.toLowerCase()) || kontrakan.alamat.toLowerCase().contains(query.toLowerCase()); }).toList(); } _applyFilters(); }); } else { setState(() { if (query.isEmpty) { _filteredLaundry = _allLaundry; } else { _filteredLaundry = _allLaundry.where((laundry) { return laundry.nama.toLowerCase().contains(query.toLowerCase()) || laundry.alamat.toLowerCase().contains(query.toLowerCase()); }).toList(); } _applyLaundryFilters(); }); } } void _applyFilters() { if (_selectedFilter == 'Penuh') { _selectedFilter = 'Semua'; } var filtered = List.from(_allKontrakan); // Apply search if (_searchController.text.isNotEmpty) { filtered = filtered.where((k) { return k.nama.toLowerCase().contains( _searchController.text.toLowerCase(), ) || k.alamat.toLowerCase().contains( _searchController.text.toLowerCase(), ); }).toList(); } // Filter by status if (_selectedFilter == 'Tersedia') { filtered = filtered.where((k) => k.status == 'available').toList(); } // Filter by price filtered = filtered.where((k) { return k.harga >= _priceRange.start && k.harga <= _priceRange.end; }).toList(); setState(() => _filteredKontrakan = filtered); } void _applyLaundryFilters() { var filtered = List.from(_allLaundry); // Apply search if (_searchController.text.isNotEmpty) { filtered = filtered.where((l) { return l.nama.toLowerCase().contains( _searchController.text.toLowerCase(), ) || l.alamat.toLowerCase().contains( _searchController.text.toLowerCase(), ); }).toList(); } // Filter by price (laundry hargaKiloan is per-kg, no multiplication needed) filtered = filtered.where((l) { return l.hargaKiloan >= _priceRange.start && l.hargaKiloan <= _priceRange.end; }).toList(); setState(() => _filteredLaundry = filtered); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF7F8FC), body: SafeArea( child: Column( children: [ // Header with Search Bar Container( padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), decoration: const BoxDecoration( gradient: LinearGradient( colors: [ Color(0xFF1565C0), Color(0xFF0D47A1), Color(0xFF0A2E73), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24), ), ), child: Column( children: [ Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(12), ), child: const Icon( Icons.search_rounded, color: Colors.white, size: 22, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Cari & Jelajahi', style: TextStyle( fontSize: 22, fontWeight: FontWeight.w700, color: Colors.white, letterSpacing: 0.3, ), ), Text( 'Temukan kontrakan dan laundry lebih cepat', style: TextStyle( fontSize: 12, color: Colors.white.withOpacity(0.85), ), ), ], ), ), // Filter button - hanya tampil untuk Kontrakan if (_selectedCategory == 'Kontrakan') Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.12), borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.white.withOpacity(0.2), ), ), child: IconButton( icon: const Icon( Icons.tune_rounded, color: Colors.white, size: 22, ), onPressed: _showFilterDialog, ), ), ], ), const SizedBox(height: 16), // Category Tabs Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.15), borderRadius: BorderRadius.circular(14), ), child: Row( children: [ Expanded( child: _buildCategorySegment( label: 'Kontrakan', icon: Icons.home_work_rounded, selected: _selectedCategory == 'Kontrakan', activeColor: const Color(0xFF1565C0), count: _filteredKontrakan.length, onTap: () { setState(() => _selectedCategory = 'Kontrakan'); _applyFilters(); }, ), ), const SizedBox(width: 4), Expanded( child: _buildCategorySegment( label: 'Laundry', icon: Icons.local_laundry_service_rounded, selected: _selectedCategory == 'Laundry', activeColor: const Color(0xFF00897B), count: _filteredLaundry.length, onTap: () { setState(() => _selectedCategory = 'Laundry'); _applyLaundryFilters(); }, ), ), ], ), ), const SizedBox(height: 14), TextField( controller: _searchController, onChanged: _filterKontrakan, style: const TextStyle(fontSize: 14), decoration: InputDecoration( hintText: _selectedCategory == 'Kontrakan' ? 'Cari kontrakan...' : 'Cari laundry...', hintStyle: TextStyle( color: Colors.grey[400], fontSize: 14, ), prefixIcon: Icon( Icons.search_rounded, color: Colors.grey[400], size: 20, ), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: Icon( Icons.close_rounded, color: Colors.grey[400], size: 20, ), onPressed: () { _searchController.clear(); _filterKontrakan(''); }, ) : null, filled: true, fillColor: Colors.white, border: OutlineInputBorder( borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 13, ), ), ), const SizedBox(height: 12), // Quick Filters (only for Kontrakan) if (_selectedCategory == 'Kontrakan') SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ _buildFilterChip('Semua'), const SizedBox(width: 8), _buildFilterChip('Tersedia'), ], ), ), ], ), ), // Results Count if (!_isLoading) Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 6), child: Container( padding: const EdgeInsets.symmetric( horizontal: 14, vertical: 12, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFEAF0F6)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 8, offset: const Offset(0, 3), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(7), decoration: BoxDecoration( color: const Color(0xFF1565C0).withOpacity(0.1), borderRadius: BorderRadius.circular(9), ), child: const Icon( Icons.filter_alt_rounded, size: 16, color: Color(0xFF1565C0), ), ), const SizedBox(width: 10), Expanded( child: Text( _selectedCategory == 'Kontrakan' ? 'Ditemukan ${_filteredKontrakan.length} kontrakan' : 'Ditemukan ${_filteredLaundry.length} laundry', style: TextStyle( fontSize: 14, color: Colors.grey[700], fontWeight: FontWeight.w600, ), ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), decoration: BoxDecoration( color: const Color(0xFFF1F5FB), borderRadius: BorderRadius.circular(20), ), child: Text( _selectedFilter, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF4B5A70), ), ), ), ], ), ), ), // List Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _selectedCategory == 'Kontrakan' ? _buildKontrakanList() : _buildLaundryList(), ), ], ), ), ); } Widget _buildKontrakanList() { if (_filteredKontrakan.isEmpty) { return RefreshIndicator( onRefresh: _loadData, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ SizedBox(height: MediaQuery.of(context).size.height * 0.2), Icon(Icons.search_off, size: 80, color: Colors.grey[300]), const SizedBox(height: 16), Center( child: Text( 'Tidak ada hasil', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.grey[600], ), ), ), const SizedBox(height: 8), Center( child: Text( 'Coba kata kunci lain', style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ), ], ), ); } return RefreshIndicator( onRefresh: _loadData, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), itemCount: _filteredKontrakan.length, itemBuilder: (context, index) { return _buildKontrakanItem(_filteredKontrakan[index]); }, ), ); } Widget _buildLaundryList() { if (_filteredLaundry.isEmpty) { return RefreshIndicator( onRefresh: _loadData, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ SizedBox(height: MediaQuery.of(context).size.height * 0.2), Icon(Icons.search_off, size: 80, color: Colors.grey[300]), const SizedBox(height: 16), Center( child: Text( 'Tidak ada hasil', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.grey[600], ), ), ), const SizedBox(height: 8), Center( child: Text( 'Coba kata kunci lain', style: TextStyle(fontSize: 14, color: Colors.grey[500]), ), ), ], ), ); } return RefreshIndicator( onRefresh: _loadData, child: ListView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16), itemCount: _filteredLaundry.length, itemBuilder: (context, index) { return _buildLaundryItem(_filteredLaundry[index]); }, ), ); } Widget _buildFilterChip(String label) { final isSelected = _selectedFilter == label; return GestureDetector( onTap: () { setState(() => _selectedFilter = label); _applyFilters(); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: isSelected ? Colors.white : Colors.white.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(20), border: Border.all( color: isSelected ? Colors.white : Colors.white.withValues(alpha: 0.5), width: 2, ), ), child: Text( label, style: TextStyle( color: isSelected ? const Color(0xFF1565C0) : Colors.white, fontWeight: isSelected ? FontWeight.bold : FontWeight.w600, fontSize: 14, ), ), ), ); } Widget _buildCategorySegment({ required String label, required IconData icon, required bool selected, required Color activeColor, required int count, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 220), curve: Curves.easeOut, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8), decoration: BoxDecoration( color: selected ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(11), boxShadow: selected ? [ BoxShadow( color: Colors.black.withOpacity(0.08), blurRadius: 4, offset: const Offset(0, 2), ), ] : null, ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, color: selected ? activeColor : Colors.white.withOpacity(0.85), size: 18, ), const SizedBox(width: 6), Flexible( child: Text( '$label ($count)', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, fontWeight: selected ? FontWeight.w700 : FontWeight.w600, color: selected ? activeColor : Colors.white.withOpacity(0.85), ), ), ), ], ), ), ); } Widget _buildKontrakanItem(Kontrakan kontrakan) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => KontrakanDetailScreen(kontrakan: kontrakan), ), ); }, child: Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 12, offset: const Offset(0, 3), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image with favorite heart Stack( children: [ ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular(16), ), child: kontrakan.hasPhoto ? CachedNetworkImage( imageUrl: kontrakan.primaryPhoto, width: 120, height: 140, fit: BoxFit.cover, placeholder: (context, url) => Container( width: 120, height: 140, color: Colors.grey[200], child: const Center( child: CircularProgressIndicator(strokeWidth: 2), ), ), errorWidget: (context, url, error) => _buildMissingKontrakanPhoto(), ) : _buildMissingKontrakanPhoto(), ), Positioned( top: 6, left: 6, child: GestureDetector( onTap: () => _toggleKontrakanFav(kontrakan.id), child: Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, ), ], ), child: Icon( _favKontrakanIds.contains(kontrakan.id) ? Icons.favorite_rounded : Icons.favorite_border_rounded, size: 16, color: _favKontrakanIds.contains(kontrakan.id) ? Colors.red : Colors.grey[600], ), ), ), ), ], ), // Info Expanded( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( kontrakan.nama, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: kontrakan.status == 'available' ? const Color(0xFFE8F5E9) : const Color(0xFFFFF3E0), borderRadius: BorderRadius.circular(8), ), child: Text( kontrakan.status == 'available' ? 'Tersedia' : 'Penuh', style: TextStyle( color: kontrakan.status == 'available' ? const Color(0xFF2E7D32) : const Color(0xFFF57C00), fontSize: 10, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 6), Row( children: [ Icon( Icons.location_on, size: 14, color: Colors.grey[600], ), const SizedBox(width: 4), Expanded( child: Text( kontrakan.alamat, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 6), Row( children: [ Icon( Icons.bed_rounded, size: 14, color: Colors.grey[500], ), const SizedBox(width: 4), Text( '${kontrakan.jumlahKamar} Kamar', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), const SizedBox(width: 12), Icon( Icons.near_me_rounded, size: 14, color: Colors.grey[500], ), const SizedBox(width: 4), Text( '${kontrakan.jarakKampus.toStringAsFixed(1)} km', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), const SizedBox(height: 10), Text( '${kontrakan.formattedHarga}/thn', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: Color(0xFF1565C0), ), ), ], ), ), ), ], ), ), ); } Widget _buildLaundryItem(Laundry laundry) { return GestureDetector( onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => LaundryDetailScreen(laundry: laundry), ), ); }, child: Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image/Icon with favorite heart Stack( children: [ ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular(16), ), child: laundry.hasPhoto ? CachedNetworkImage( imageUrl: laundry.primaryPhoto, width: 120, height: 140, fit: BoxFit.cover, placeholder: (context, url) => Container( width: 120, height: 140, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.cyan[400]!, Colors.cyan[600]!], ), ), child: const Center( child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ), ), errorWidget: (context, url, error) => _buildMissingLaundryPhoto(), ) : _buildMissingLaundryPhoto(), ), Positioned( top: 6, left: 6, child: GestureDetector( onTap: () => _toggleLaundryFav(laundry.id), child: Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( color: Colors.white.withOpacity(0.9), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, ), ], ), child: Icon( _favLaundryIds.contains(laundry.id) ? Icons.favorite_rounded : Icons.favorite_border_rounded, size: 16, color: _favLaundryIds.contains(laundry.id) ? Colors.red : Colors.grey[600], ), ), ), ), ], ), // Info Expanded( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( laundry.nama, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 6), Row( children: [ Icon( Icons.location_on, size: 14, color: Colors.grey[600], ), const SizedBox(width: 4), Expanded( child: Text( laundry.alamat, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), ], ), const SizedBox(height: 6), Row( children: [ Icon( Icons.schedule_rounded, size: 14, color: Colors.grey[500], ), const SizedBox(width: 4), Text( '${laundry.waktuProses} jam', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), const SizedBox(width: 12), Icon( Icons.near_me_rounded, size: 14, color: Colors.grey[500], ), const SizedBox(width: 4), Text( '${laundry.jarak.toStringAsFixed(1)} km', style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), const SizedBox(height: 10), Text( '${laundry.formattedHarga}/kg', style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: Color(0xFF00897B), ), ), ], ), ), ), ], ), ), ); } void _showFilterDialog() { // Tentukan max value berdasarkan kategori final double maxPrice = _selectedCategory == 'Laundry' ? 500000 : 50000000; final String dialogTitle = _selectedCategory == 'Laundry' ? 'Filter Harga Laundry' : 'Filter Harga Kontrakan'; final int divisions = _selectedCategory == 'Laundry' ? 50 : 50; showDialog( context: context, builder: (context) => StatefulBuilder( builder: (context, setDialogState) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: Text( dialogTitle, style: const TextStyle(fontWeight: FontWeight.bold), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( '${CurrencyFormatter.formatCompact(_priceRange.start)} - ${CurrencyFormatter.formatCompact(_priceRange.end)}', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1565C0), ), ), const SizedBox(height: 8), RangeSlider( values: _priceRange, min: 0, max: maxPrice, divisions: divisions, activeColor: const Color(0xFF1565C0), labels: RangeLabels( CurrencyFormatter.formatCompact(_priceRange.start), CurrencyFormatter.formatCompact(_priceRange.end), ), onChanged: (values) { setDialogState(() => _priceRange = values); }, ), ], ), actions: [ TextButton( onPressed: () { setState(() { if (_selectedCategory == 'Laundry') { _laundryPriceRange = const RangeValues(0, 500000); } else { _kontrakanPriceRange = const RangeValues(0, 50000000); } }); Navigator.pop(context); if (_selectedCategory == 'Laundry') { _applyLaundryFilters(); } else { _applyFilters(); } }, child: const Text('Reset'), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF1565C0), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), onPressed: () { Navigator.pop(context); if (_selectedCategory == 'Laundry') { _applyLaundryFilters(); } else { _applyFilters(); } }, child: const Text('Terapkan'), ), ], ), ), ); } Widget _buildMissingKontrakanPhoto() { return Container( width: 120, height: 140, color: Colors.grey[200], child: const Center( child: AppPlaceholder(height: 56, width: 56, fit: BoxFit.contain), ), ); } Widget _buildMissingLaundryPhoto() { return Container( width: 120, height: 140, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Colors.cyan[400]!, Colors.cyan[600]!], ), ), child: const Center( child: AppPlaceholder(height: 56, width: 56, fit: BoxFit.contain), ), ); } }