import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:provider/provider.dart'; import '../features/events/models/event_models.dart'; import '../features/events/services/events_service.dart'; import '../features/gamification/providers/gamification_provider.dart'; import '../widgets/responsive_shell.dart'; import 'calendar_screen.dart'; import 'profile_screen.dart'; import 'booking_screen.dart'; import 'settings_screen.dart'; import 'learn_more_screen.dart'; import 'contribute_screen.dart'; class HomeDesktopScreen extends StatefulWidget { final bool skipSidebarEntranceAnimation; const HomeDesktopScreen({Key? key, this.skipSidebarEntranceAnimation = false}) : super(key: key); @override State createState() => _HomeDesktopScreenState(); } class _HomeDesktopScreenState extends State { int _selectedMenu = 0; @override Widget build(BuildContext context) { return ResponsiveShell( currentIndex: _selectedMenu, onIndexChanged: (idx) => setState(() => _selectedMenu = idx), showTopBar: true, child: _getCurrentPage(), ); } Widget _getCurrentPage() { switch (_selectedMenu) { case 1: return const CalendarScreen(); case 2: return const ProfileScreen(); case 3: return BookingScreen(onBook: () {}, image: ''); case 4: return ChangeNotifierProvider( create: (_) => GamificationProvider(), child: const ContributeScreen(), ); case 5: return const SettingsScreen(); default: return _HomeContent( onEventTap: (eventId) { Navigator.push( context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: eventId)), ); }, ); } } } // --------------------------------------------------------------------------- // Home content — hero, categories, event grid // --------------------------------------------------------------------------- class _HomeContent extends StatefulWidget { final void Function(int eventId) onEventTap; const _HomeContent({required this.onEventTap}); @override State<_HomeContent> createState() => _HomeContentState(); } class _HomeContentState extends State<_HomeContent> with SingleTickerProviderStateMixin { final EventsService _eventsService = EventsService(); List _events = []; List _types = []; bool _loading = true; bool _loadingTypes = true; int _selectedTypeId = -1; String? _selectedTypeLabel; String _username = 'Guest'; String _pincode = 'all'; // Ken Burns hero animation late AnimationController _heroAnimController; late Animation _heroScaleAnim; int _heroImageIndex = 0; Timer? _heroRotateTimer; @override void initState() { super.initState(); _heroAnimController = AnimationController( vsync: this, duration: const Duration(seconds: 12), ); _heroScaleAnim = Tween(begin: 1.0, end: 1.08).animate( CurvedAnimation(parent: _heroAnimController, curve: Curves.easeInOut), ); _heroAnimController.repeat(reverse: true); _loadData(); } @override void dispose() { _heroAnimController.dispose(); _heroRotateTimer?.cancel(); super.dispose(); } void _startHeroRotation() { _heroRotateTimer?.cancel(); if (_events.length <= 1) return; _heroRotateTimer = Timer.periodic(const Duration(seconds: 6), (_) { if (!mounted) return; setState(() { _heroImageIndex = (_heroImageIndex + 1) % _events.length.clamp(1, 5); }); }); } Future _loadData() async { setState(() { _loading = true; _loadingTypes = true; }); final prefs = await SharedPreferences.getInstance(); _username = prefs.getString('display_name') ?? prefs.getString('username') ?? 'Guest'; _pincode = prefs.getString('pincode') ?? 'all'; await Future.wait([ _fetchEventTypes(), _fetchEvents(), ]); if (mounted) { setState(() { _loading = false; _loadingTypes = false; }); _startHeroRotation(); } } Future _fetchEventTypes() async { try { final types = await _eventsService.getEventTypes(); if (mounted) setState(() => _types = types); } catch (_) {} } Future _fetchEvents() async { try { final events = await _eventsService.getEventsByPincode(_pincode); if (mounted) setState(() => _events = events); } catch (_) {} } List get _filteredEvents { if (_selectedTypeLabel != null && _selectedTypeLabel!.toLowerCase() != 'all events') { final label = _selectedTypeLabel!.toLowerCase(); return _events.where((e) { final typeName = (e.eventTypeId != null && e.eventTypeId! > 0) ? (_types .firstWhere((t) => t.id == e.eventTypeId, orElse: () => EventTypeModel(id: -1, name: '', iconUrl: null)) .name) : ''; return [e.venueName, e.title, e.name, typeName] .any((c) => c != null && c.toLowerCase().contains(label)); }).toList(); } if (_selectedTypeId != -1) { return _events.where((e) => e.eventTypeId == _selectedTypeId).toList(); } return _events; } String? _chooseImage(EventModel e) { if (e.thumbImg != null && e.thumbImg!.trim().isNotEmpty) { return e.thumbImg!.trim(); } if (e.images.isNotEmpty && e.images.first.image.trim().isNotEmpty) { return e.images.first.image.trim(); } return null; } String _formatDateBadge(String dateStr) { try { final parts = dateStr.split('-'); if (parts.length == 3) { final day = int.parse(parts[2]); const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; final month = months[int.parse(parts[1]) - 1]; return '$day\n$month'; } } catch (_) {} return dateStr; } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( color: const Color(0xFFFAFBFC), child: RefreshIndicator( onRefresh: _loadData, color: theme.colorScheme.primary, child: CustomScrollView( physics: const BouncingScrollPhysics( parent: AlwaysScrollableScrollPhysics(), ), slivers: [ // Hero section SliverToBoxAdapter(child: _buildHeroSection(theme)), const SliverToBoxAdapter(child: SizedBox(height: 24)), // Type chips if (!_loadingTypes) SliverToBoxAdapter( child: SizedBox(height: 48, child: _buildTypeChips(theme)), ), const SliverToBoxAdapter(child: SizedBox(height: 20)), // Section header SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 8), child: Row(children: [ Expanded( child: Text( 'Events Around You', style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: const Color(0xFF111827), ), ), ), TextButton( onPressed: () {}, child: Text( 'View All', style: theme.textTheme.bodyMedium?.copyWith( color: const Color(0xFF2563EB), fontWeight: FontWeight.w600, ), ), ), ]), ), ), const SliverToBoxAdapter(child: SizedBox(height: 4)), // Event grid _buildEventGrid(theme), const SliverToBoxAdapter(child: SizedBox(height: 40)), ], ), ), ); } // ──── Hero Section (website-style: large image + Ken Burns + overlay) ──── Widget _buildHeroSection(ThemeData theme) { final heroEvent = _events.isNotEmpty ? _events[_heroImageIndex.clamp(0, _events.length - 1)] : null; final heroImg = heroEvent != null ? _chooseImage(heroEvent) : null; return Padding( padding: const EdgeInsets.fromLTRB(24, 8, 24, 0), child: ClipRRect( borderRadius: BorderRadius.circular(20), child: SizedBox( height: 400, child: Stack( fit: StackFit.expand, children: [ // Background image with Ken Burns AnimatedBuilder( animation: _heroScaleAnim, builder: (context, child) { return Transform.scale( scale: _heroScaleAnim.value, child: child, ); }, child: heroImg != null ? AnimatedSwitcher( duration: const Duration(milliseconds: 800), child: CachedNetworkImage( key: ValueKey(heroImg), imageUrl: heroImg, fit: BoxFit.cover, width: double.infinity, height: double.infinity, memCacheWidth: 1400, placeholder: (_, __) => Container( color: const Color(0xFF0A0E1A), ), errorWidget: (_, __, ___) => Container( color: const Color(0xFF0A0E1A), ), ), ) : Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF0F45CF), Color(0xFF082369)], ), ), ), ), // Dark gradient overlay Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color(0x4D000000), // 0.3 alpha Color(0x99000000), // 0.6 alpha ], ), ), ), // Content overlay Padding( padding: const EdgeInsets.all(40), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (heroEvent != null) ...[ Text( heroEvent.title ?? heroEvent.name ?? 'Discover Amazing Events', maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 36, fontWeight: FontWeight.w800, height: 1.2, ), ), const SizedBox(height: 12), if (heroEvent.place != null || heroEvent.startDate != null) Row( children: [ if (heroEvent.startDate != null) ...[ const Icon(Icons.calendar_today, size: 14, color: Colors.white70), const SizedBox(width: 6), Text( heroEvent.startDate!, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ], if (heroEvent.startDate != null && heroEvent.place != null) const Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: Text('·', style: TextStyle( color: Colors.white54, fontSize: 14)), ), if (heroEvent.place != null) ...[ const Icon(Icons.location_on, size: 14, color: Colors.white70), const SizedBox(width: 6), Flexible( child: Text( heroEvent.place!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), ), ], ], ), ] else ...[ const Text( 'Discover Amazing\nEvents Near You', style: TextStyle( color: Colors.white, fontSize: 36, fontWeight: FontWeight.w800, height: 1.2, ), ), const SizedBox(height: 8), Text( 'Find events, book tickets, and explore', style: TextStyle( color: Colors.white.withValues(alpha: 0.8), fontSize: 16, ), ), ], const SizedBox(height: 24), MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { if (heroEvent != null) { _showFeaturedModal(theme, heroEvent); } }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 14), decoration: BoxDecoration( color: const Color(0xFF2563EB), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: const Color(0xFF2563EB) .withValues(alpha: 0.4), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: Text( heroEvent != null ? 'Learn More' : 'Explore Events', style: const TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), ), // Hero image indicator dots if (_events.length > 1) Positioned( bottom: 16, right: 24, child: Row( mainAxisSize: MainAxisSize.min, children: List.generate( _events.length.clamp(0, 5), (i) => Container( width: i == _heroImageIndex ? 24 : 8, height: 8, margin: const EdgeInsets.only(left: 6), decoration: BoxDecoration( color: i == _heroImageIndex ? Colors.white : Colors.white.withValues(alpha: 0.4), borderRadius: BorderRadius.circular(4), ), ), ), ), ), ], ), ), ), ); } // ──── Featured Event Modal ──── void _showFeaturedModal(ThemeData theme, EventModel event) { final img = _chooseImage(event); showDialog( context: context, barrierColor: Colors.black87, builder: (ctx) => Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 700, maxHeight: 500), child: Material( color: Colors.transparent, child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(20), child: SizedBox( width: 700, height: 500, child: Stack( fit: StackFit.expand, children: [ if (img != null) CachedNetworkImage( imageUrl: img, fit: BoxFit.cover, memCacheWidth: 1400, ) else Container(color: const Color(0xFF0A0E1A)), // Gradient Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, Color(0xCC000000)], ), ), ), // Content Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( event.title ?? event.name ?? '', maxLines: 3, overflow: TextOverflow.ellipsis, style: const TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.w800, height: 1.2, ), ), const SizedBox(height: 12), if (event.startDate != null) Text( '${event.startDate}${event.place != null ? ' · ${event.place}' : ''}', style: const TextStyle( color: Colors.white70, fontSize: 14, ), ), const SizedBox(height: 20), MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { Navigator.of(ctx).pop(); widget.onEventTap(event.id); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 14), decoration: BoxDecoration( color: const Color(0xFF2563EB), borderRadius: BorderRadius.circular(12), ), child: const Text( 'Learn More', style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), ), ], ), ), ], ), ), ), // Close button Positioned( top: 12, right: 12, child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () => Navigator.of(ctx).pop(), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.5), shape: BoxShape.circle, ), child: const Icon(Icons.close, color: Colors.white, size: 20), ), ), ), ), ], ), ), ), ), ); } // ──── Type Chips (pill style matching website) ──── Widget _buildTypeChips(ThemeData theme) { final chips = []; final allSelected = _selectedTypeLabel == null && _selectedTypeId == -1; chips.add(_chip(theme, 'All Events', allSelected, () { setState(() { _selectedTypeId = -1; _selectedTypeLabel = null; }); })); for (final t in _types) { final selected = _selectedTypeLabel == null && _selectedTypeId == t.id; chips.add(_chip(theme, t.name, selected, () { setState(() { _selectedTypeId = t.id; _selectedTypeLabel = null; }); })); } return ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 24), scrollDirection: Axis.horizontal, itemBuilder: (_, idx) => chips[idx], separatorBuilder: (_, __) => const SizedBox(width: 10), itemCount: chips.length, ); } Widget _chip(ThemeData theme, String label, bool selected, VoidCallback onTap) { return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), height: 36, alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( color: selected ? const Color(0xFF0F45CF) : const Color(0xFFF3F4F6), borderRadius: BorderRadius.circular(999), boxShadow: selected ? [ BoxShadow( color: const Color(0xFF0F45CF).withValues(alpha: 0.3), blurRadius: 8, offset: const Offset(0, 2), ), ] : null, ), child: Text( label, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: selected ? Colors.white : const Color(0xFF111827), ), ), ), ), ); } // ──── Event Grid ──── Widget _buildEventGrid(ThemeData theme) { final list = _filteredEvents; if (_loading) { return SliverFillRemaining( child: Center( child: CircularProgressIndicator(color: theme.colorScheme.primary), ), ); } if (list.isEmpty) { return SliverFillRemaining( child: Center( child: Text('No events available', style: theme.textTheme.bodyMedium), ), ); } return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 24), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 400, mainAxisExtent: 260, crossAxisSpacing: 18, mainAxisSpacing: 18, ), delegate: SliverChildBuilderDelegate( (ctx, idx) { final e = list[idx]; final img = _chooseImage(e); return _eventCard(theme, e, img); }, childCount: list.length, ), ), ); } Widget _eventCard(ThemeData theme, EventModel e, String? img) { const double cardRadius = 16; const double imageHeight = 160; final dateLabel = (e.startDate != null && e.endDate != null && e.startDate == e.endDate) ? e.startDate! : ((e.startDate != null && e.endDate != null) ? '${e.startDate} - ${e.endDate}' : (e.startDate ?? '')); return MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () => widget.onEventTap(e.id), child: Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(cardRadius), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.06), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Image with date badge Stack( children: [ ClipRRect( borderRadius: const BorderRadius.vertical( top: Radius.circular(cardRadius)), child: img != null ? CachedNetworkImage( imageUrl: img, memCacheWidth: 600, memCacheHeight: 320, width: double.infinity, height: imageHeight, fit: BoxFit.cover, placeholder: (_, __) => Container( height: imageHeight, color: const Color(0xFFE5E7EB)), errorWidget: (_, __, ___) => Container( height: imageHeight, color: const Color(0xFFE5E7EB)), ) : Container( height: imageHeight, width: double.infinity, color: const Color(0xFFE5E7EB), ), ), // Date badge if (e.startDate != null) Positioned( top: 10, right: 10, child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 6), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.92), borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, ), ], ), child: Text( _formatDateBadge(e.startDate!), textAlign: TextAlign.center, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: Color(0xFF111827), height: 1.3, ), ), ), ), ], ), Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( e.title ?? e.name ?? '', maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontWeight: FontWeight.w700, fontSize: 15, color: Color(0xFF111827), ), ), const Spacer(), Row(children: [ const Icon(Icons.calendar_today, size: 13, color: Color(0xFF3B82F6)), const SizedBox(width: 6), Flexible( child: Text( dateLabel, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280)), ), ), ]), if (e.place != null) ...[ const SizedBox(height: 4), Row(children: [ const Icon(Icons.location_on, size: 13, color: Color(0xFF22C55E)), const SizedBox(width: 6), Flexible( child: Text( e.place!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280)), ), ), ]), ], ], ), ), ), ], ), ), ), ); } }