From 5b98f4159644d245662c0c3e5017a671323f266a Mon Sep 17 00:00:00 2001 From: Sicherhaven Date: Sat, 14 Mar 2026 20:15:55 +0530 Subject: [PATCH] feat: add bottom sheet for date filter chips on home screen - Clicking Today/Tomorrow/This week opens a draggable bottom sheet showing filtered events matching the selected period - Clicking Date opens calendar picker, then shows events for that date - Bottom sheet matches web design: lavender bg, drag handle, title with count, close X button, scrollable event cards - Event cards show image, title, date, location, and price label - Sheet auto-clears filter on dismiss Co-Authored-By: Claude Opus 4.6 --- lib/screens/home_screen.dart | 254 ++++++++++++++++++++++++++++++++++- 1 file changed, 253 insertions(+), 1 deletion(-) diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 8d605d6..388ad25 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -502,6 +502,9 @@ class _HomeScreenState extends State with SingleTickerProviderStateM _selectedCustomDate = picked; _selectedDateFilter = 'Date'; }); + _showFilteredEventsSheet( + '${picked.day.toString().padLeft(2, '0')} ${_monthName(picked.month)} ${picked.year}', + ); } else if (_selectedDateFilter == 'Date') { setState(() { _selectedDateFilter = ''; @@ -510,12 +513,261 @@ class _HomeScreenState extends State with SingleTickerProviderStateM } } else { setState(() { - _selectedDateFilter = _selectedDateFilter == label ? '' : label; + _selectedDateFilter = label; _selectedCustomDate = null; }); + _showFilteredEventsSheet(label); } } + String _monthName(int m) { + const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + return months[m - 1]; + } + + /// Shows a bottom sheet with events matching the current filter chip. + void _showFilteredEventsSheet(String title) { + final theme = Theme.of(context); + final filtered = _filteredEvents; + final count = filtered.length; + + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + barrierColor: Colors.black.withValues(alpha: 0.5), + builder: (ctx) { + return DraggableScrollableSheet( + expand: false, + initialChildSize: 0.55, + minChildSize: 0.3, + maxChildSize: 0.85, + builder: (context, scrollController) { + return Container( + decoration: const BoxDecoration( + color: Color(0xFFEAEFFE), // lavender sheet bg matching web + borderRadius: BorderRadius.vertical(top: Radius.circular(24)), + ), + child: Column( + children: [ + // Drag handle + Padding( + padding: const EdgeInsets.only(top: 12, bottom: 8), + child: Center( + child: Container( + width: 40, + height: 5, + decoration: BoxDecoration( + color: Colors.grey.shade400, + borderRadius: BorderRadius.circular(3), + ), + ), + ), + ), + + // Header row: title + close button + Padding( + padding: const EdgeInsets.fromLTRB(20, 4, 12, 12), + child: Row( + children: [ + Expanded( + child: Text( + '$title ($count)', + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: Color(0xFF1A1A1A), + ), + ), + ), + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + setState(() { + _selectedDateFilter = ''; + _selectedCustomDate = null; + }); + }, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: Colors.grey.shade300, + shape: BoxShape.circle, + ), + child: const Icon(Icons.close, size: 18, color: Color(0xFF1A1A1A)), + ), + ), + ], + ), + ), + + // Events list + Expanded( + child: filtered.isEmpty + ? Padding( + padding: const EdgeInsets.all(24), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: const Text( + '\u{1F3D7}\u{FE0F} No events scheduled for this period', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Color(0xFF6B7280), + ), + ), + ), + ) + : ListView.builder( + controller: scrollController, + padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), + itemCount: filtered.length, + itemBuilder: (ctx, idx) { + final ev = filtered[idx]; + return _buildSheetEventCard(ev, theme); + }, + ), + ), + ], + ), + ); + }, + ); + }, + ).whenComplete(() { + // Clear filter when sheet is dismissed + setState(() { + _selectedDateFilter = ''; + _selectedCustomDate = null; + }); + }); + } + + /// Builds an event card for the filter bottom sheet, matching web design. + Widget _buildSheetEventCard(EventModel ev, ThemeData theme) { + final title = ev.title ?? ev.name ?? ''; + final dateLabel = ev.startDate ?? ''; + final location = ev.place ?? 'Location'; + final imageUrl = (ev.thumbImg != null && ev.thumbImg!.isNotEmpty) + ? ev.thumbImg! + : (ev.images.isNotEmpty ? ev.images.first.image : null); + + Widget imageWidget; + if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('http')) { + imageWidget = ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.network( + imageUrl, + width: 80, + height: 80, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Container( + width: 80, height: 80, + decoration: BoxDecoration(color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12)), + child: Icon(Icons.image, color: Colors.grey.shade400), + ), + ), + ); + } else { + imageWidget = Container( + width: 80, height: 80, + decoration: BoxDecoration(color: Colors.grey.shade200, borderRadius: BorderRadius.circular(12)), + child: Icon(Icons.image, color: Colors.grey.shade400), + ); + } + + return GestureDetector( + onTap: () { + Navigator.of(context).pop(); + if (ev.id != null) { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id))); + } + }, + child: Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.04), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + imageWidget, + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w700, + color: Color(0xFF1A1A1A), + ), + ), + const SizedBox(height: 4), + Row( + children: [ + Icon(Icons.calendar_today, size: 13, color: Colors.grey.shade500), + const SizedBox(width: 4), + Expanded( + child: Text( + dateLabel, + style: TextStyle(fontSize: 13, color: Colors.grey.shade500), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 2), + Row( + children: [ + Icon(Icons.location_on_outlined, size: 13, color: Colors.grey.shade500), + const SizedBox(width: 4), + Expanded( + child: Text( + location, + style: TextStyle(fontSize: 13, color: Colors.grey.shade500), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + 'Free', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: theme.colorScheme.primary, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + /// Collect all event dates (start + end range) to show dots on the calendar. Set get _eventDates { final dates = {};