import 'package:flutter/material.dart'; import 'package:wisata_app/models/destination.dart'; import '../models/app_user.dart'; import '../services/auth_service.dart'; import '../services/destination_service.dart'; import '../widgets/app_logo.dart'; import '../widgets/category_filter_chip.dart'; import '../widgets/destination_card.dart'; import 'login_screen.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); static const routeName = '/dashboard'; @override State createState() => _DashboardScreenState(); } class _DashboardScreenState extends State { final AuthService _authService = AuthService(); final DestinationService _destinationService = const DestinationService(); final TextEditingController _searchController = TextEditingController(); final List _categories = const [ 'Semua Wisata', 'Gunung', 'Pantai', 'Danau', 'Air Terjun', ]; String _selectedCategory = 'Semua Wisata'; String _name = 'Wisatawan'; bool _loadedArgs = false; bool _loadingDestinations = true; String? _destinationError; List _allDestinations = []; List get _filteredDestinations { final keyword = _searchController.text.trim().toLowerCase(); return _allDestinations.where((destination) { final matchesCategory = _selectedCategory == 'Semua Wisata' || destination.category.toLowerCase() == _selectedCategory.toLowerCase(); final matchesSearch = keyword.isEmpty || destination.title.toLowerCase().contains(keyword) || destination.shortDescription.toLowerCase().contains(keyword) || destination.location.toLowerCase().contains(keyword); return matchesCategory && matchesSearch; }).toList(); } @override void initState() { super.initState(); _searchController.addListener(() => setState(() {})); _loadDestinations(); } Future _loadDestinations() async { setState(() { _loadingDestinations = true; _destinationError = null; }); try { final destinations = await _destinationService.getAllDestinations(); if (!mounted) return; setState(() { _allDestinations = destinations; _loadingDestinations = false; }); } on Exception catch (error) { if (!mounted) return; setState(() { _destinationError = error.toString(); _loadingDestinations = false; }); } } @override void didChangeDependencies() { super.didChangeDependencies(); if (_loadedArgs) return; _loadedArgs = true; final user = ModalRoute.of(context)?.settings.arguments; if (user is AppUser) { _name = _displayName(user.name); } else { _loadName(); } } Future _loadName() async { final savedName = await _authService.savedName; if (mounted) { setState(() => _name = _displayName(savedName)); } } String _displayName(String? value) { final name = value?.trim() ?? ''; return name.isEmpty ? 'Wisatawan' : name; } Future _logout() async { await _authService.logout(); if (!mounted) return; Navigator.pushNamedAndRemoveUntil( context, LoginScreen.routeName, (route) => false, ); } @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; return Scaffold( backgroundColor: const Color(0xFFF4F8FB), body: RefreshIndicator( onRefresh: _loadDestinations, child: SafeArea( child: ListView( padding: const EdgeInsets.fromLTRB(20, 16, 20, 32), children: [ _DashboardTopBar(onLogout: _logout), const SizedBox(height: 26), Text( 'Selamat Datang, $_name', style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: const Color(0xFF0B1F33), fontWeight: FontWeight.w900, ), ), const SizedBox(height: 8), Text( 'Temukan destinasi wisata Lumajang dan nikmati pengalaman Augmented Reality yang imersif.', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.onSurfaceVariant, height: 1.45, ), ), const SizedBox(height: 18), TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Cari wisata favoritmu...', prefixIcon: const Icon(Icons.search_rounded), suffixIcon: _searchController.text.isNotEmpty ? IconButton( onPressed: _searchController.clear, icon: const Icon(Icons.close_rounded), ) : null, ), ), const SizedBox(height: 18), SizedBox( height: 52, child: ListView.separated( scrollDirection: Axis.horizontal, itemBuilder: (context, index) { final category = _categories[index]; return CategoryFilterChip( label: category, icon: _categoryIcon(category), selected: category == _selectedCategory, onSelected: (_) => setState(() => _selectedCategory = category), ); }, separatorBuilder: (_, __) => const SizedBox(width: 10), itemCount: _categories.length, ), ), const SizedBox(height: 26), Row( children: [ Text( 'Destinasi Wisata', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w900, ), ), const Spacer(), Text( '${_filteredDestinations.length} wisata', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: colors.primary, fontWeight: FontWeight.w800, ), ), ], ), const SizedBox(height: 16), if (_loadingDestinations) const Padding( padding: EdgeInsets.symmetric(vertical: 40), child: Center(child: CircularProgressIndicator()), ) else if (_destinationError != null) _DestinationMessage( icon: Icons.cloud_off_rounded, title: 'Data wisata belum bisa dimuat', message: _destinationError!, ) else if (_filteredDestinations.isEmpty) const _DestinationMessage( icon: Icons.search_off_rounded, title: 'Wisata tidak ditemukan', message: 'Coba ubah pencarian atau filter kategori.', ) else ..._filteredDestinations.map( (destination) => DestinationCard( destination: destination, onTap: () => Navigator.pushNamed( context, '/detail', arguments: destination, ), ), ), ], ), ), ), ); } IconData _categoryIcon(String category) { switch (category) { case 'Gunung': return Icons.terrain_rounded; case 'Pantai': return Icons.beach_access_rounded; case 'Danau': return Icons.water_rounded; case 'Air Terjun': return Icons.waterfall_chart_rounded; default: return Icons.travel_explore_rounded; } } } class _DashboardTopBar extends StatelessWidget { const _DashboardTopBar({required this.onLogout}); final VoidCallback onLogout; @override Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24), border: Border.all(color: const Color(0xFFE1EDF5)), boxShadow: [ BoxShadow( color: const Color(0xFF0F4C81).withValues(alpha: 0.08), blurRadius: 24, offset: const Offset(0, 12), ), ], ), child: Row( children: [ const AppLogo(size: 48), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Explore Lumajang', maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: const Color(0xFF0B1F33), fontWeight: FontWeight.w900, ), ), const SizedBox(height: 3), Text( 'Wisata alam dan AR', maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.labelMedium?.copyWith( color: colors.onSurfaceVariant, fontWeight: FontWeight.w700, ), ), ], ), ), const SizedBox(width: 10), IconButton.filledTonal( tooltip: 'Keluar', onPressed: onLogout, icon: const Icon(Icons.logout_rounded), style: IconButton.styleFrom( backgroundColor: const Color(0xFFEAF3F8), foregroundColor: const Color(0xFF0F4C81), ), ), ], ), ); } } class _DestinationMessage extends StatelessWidget { const _DestinationMessage({ required this.icon, required this.title, required this.message, }); final IconData icon; final String title; final String message; @override Widget build(BuildContext context) { final colors = Theme.of(context).colorScheme; return Padding( padding: const EdgeInsets.symmetric(vertical: 40), child: Column( children: [ Icon(icon, size: 48, color: colors.onSurfaceVariant), const SizedBox(height: 12), Text( title, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), const SizedBox(height: 6), Text( message, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.onSurfaceVariant, ), ), ], ), ); } }