// lib/screens/calendar_screen.dart import 'package:flutter/material.dart'; import '../core/utils/error_utils.dart'; import 'package:intl/intl.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../features/events/services/events_service.dart'; import '../features/events/models/event_models.dart'; import 'learn_more_screen.dart'; import '../core/app_decoration.dart'; // landscape_section_header no longer needed for this screen class CalendarScreen extends StatefulWidget { const CalendarScreen({Key? key}) : super(key: key); @override State createState() => _CalendarScreenState(); } class _CalendarScreenState extends State { DateTime visibleMonth = DateTime.now(); DateTime selectedDate = DateTime.now(); final EventsService _service = EventsService(); bool _loadingMonth = true; bool _loadingDay = false; final Set _markedDates = {}; final Map _dateCounts = {}; List _eventsOfDay = []; // Scroll controller for the calendar grid final ScrollController _calendarGridController = ScrollController(); static const List monthNames = [ '', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; @override void initState() { super.initState(); _loadMonth(visibleMonth); selectedDate = DateTime.now(); WidgetsBinding.instance.addPostFrameCallback((_) => _onSelectDate(_ymKey(selectedDate))); } @override void dispose() { _calendarGridController.dispose(); super.dispose(); } Future _loadMonth(DateTime dt) async { setState(() { _loadingMonth = true; _markedDates.clear(); _dateCounts.clear(); _eventsOfDay = []; }); final monthName = DateFormat.MMMM().format(dt); final year = dt.year; try { final res = await _service.getEventsByMonthYear(monthName, year); final datesRaw = res['dates']; if (datesRaw is List) { for (final d in datesRaw) { if (d is String) _markedDates.add(d); } } final dateEvents = res['date_events']; if (dateEvents is List) { for (final item in dateEvents) { if (item is Map) { final k = item['date_of_event']?.toString(); final cnt = item['events_of_date']; if (k != null) { _dateCounts[k] = (cnt is int) ? cnt : int.tryParse(cnt?.toString() ?? '0') ?? 0; } } } } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)))); } finally { if (mounted) setState(() => _loadingMonth = false); } } Future _onSelectDate(String yyyyMMdd) async { setState(() { _loadingDay = true; _eventsOfDay = []; final parts = yyyyMMdd.split('-'); if (parts.length == 3) { final y = int.tryParse(parts[0]) ?? DateTime.now().year; final m = int.tryParse(parts[1]) ?? DateTime.now().month; final d = int.tryParse(parts[2]) ?? DateTime.now().day; selectedDate = DateTime(y, m, d); } }); try { final events = await _service.getEventsForDate(yyyyMMdd); if (mounted) setState(() => _eventsOfDay = events); } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)))); } finally { if (mounted) setState(() => _loadingDay = false); } } void _prevMonth() { setState(() => visibleMonth = DateTime(visibleMonth.year, visibleMonth.month - 1, 1)); _loadMonth(visibleMonth); } void _nextMonth() { setState(() => visibleMonth = DateTime(visibleMonth.year, visibleMonth.month + 1, 1)); _loadMonth(visibleMonth); } int _daysInMonth(DateTime d) { final next = (d.month == 12) ? DateTime(d.year + 1, 1, 1) : DateTime(d.year, d.month + 1, 1); return next.subtract(const Duration(days: 1)).day; } int _firstWeekdayOfMonth(DateTime d) { final first = DateTime(d.year, d.month, 1); return first.weekday; // 1=Mon ... 7=Sun } String _ymKey(DateTime d) => '${d.year.toString().padLeft(4, "0")}-${d.month.toString().padLeft(2, "0")}-${d.day.toString().padLeft(2, "0")}'; // show a premium modal sheet with years 2020..2050 in a 3-column grid, scrollable & draggable Future _showYearPicker(BuildContext context) async { final startYear = 2020; final endYear = 2050; final years = List.generate(endYear - startYear + 1, (i) => startYear + i); await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) { final theme = Theme.of(ctx); return DraggableScrollableSheet( initialChildSize: 0.55, minChildSize: 0.32, maxChildSize: 0.95, expand: false, builder: (context, scrollController) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 18), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: theme.cardColor, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 20, offset: Offset(0, 8))], ), child: Column( children: [ // header Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 8.0), child: Row( children: [ const SizedBox(width: 4), Expanded(child: Text('Select Year', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700))), IconButton( icon: Icon(Icons.close, color: theme.hintColor), onPressed: () => Navigator.of(ctx).pop(), ) ], ), ), const SizedBox(height: 8), // grid (scrollable using the passed scrollController) Expanded( child: GridView.builder( controller: scrollController, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, // 3 columns -> premium style mainAxisSpacing: 12, crossAxisSpacing: 12, childAspectRatio: 2.4, ), itemCount: years.length, itemBuilder: (context, index) { final y = years[index]; final isSelected = visibleMonth.year == y; return InkWell( onTap: () { setState(() { visibleMonth = DateTime(y, visibleMonth.month, 1); }); _loadMonth(visibleMonth); Navigator.of(ctx).pop(); }, borderRadius: BorderRadius.circular(10), child: Container( alignment: Alignment.center, decoration: BoxDecoration( color: isSelected ? theme.colorScheme.primary.withOpacity(0.12) : null, borderRadius: BorderRadius.circular(10), border: isSelected ? Border.all(color: theme.colorScheme.primary, width: 1.6) : Border.all(color: Colors.transparent), boxShadow: isSelected ? [BoxShadow(color: theme.colorScheme.primary.withOpacity(0.06), blurRadius: 10, offset: Offset(0, 4))] : null, ), child: Text( '$y', style: theme.textTheme.bodyLarge?.copyWith( fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600, color: isSelected ? theme.colorScheme.primary : theme.textTheme.bodyLarge?.color, fontSize: 16, ), ), ), ); }, ), ), ], ), ); }, ); }, ); } Widget _buildMonthYearHeader(BuildContext context, Color primaryColor) { final theme = Theme.of(context); return Row( children: [ IconButton(icon: Icon(Icons.chevron_left, color: primaryColor), onPressed: _prevMonth, splashRadius: 20), Expanded( child: Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ // plain month label (no dropdown) Text( monthNames[visibleMonth.month], style: const TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(width: 10), // year "dropdown" replaced with premium modal trigger InkWell( onTap: () => _showYearPicker(context), borderRadius: BorderRadius.circular(6), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4), child: Row( children: [ Text( '${visibleMonth.year}', style: const TextStyle(fontWeight: FontWeight.w600), ), const SizedBox(width: 6), Icon(Icons.arrow_drop_down, size: 22, color: theme.textTheme.bodyLarge?.color), ], ), ), ), ], ), ), ), IconButton(icon: Icon(Icons.chevron_right, color: primaryColor), onPressed: _nextMonth, splashRadius: 20), ], ); } /// Calendar card that shows full rows (including overflow prev/next month dates). Widget _calendarCard(BuildContext context) { final theme = Theme.of(context); final primaryColor = theme.colorScheme.primary; final weekdayShorts = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; // compute rows to guarantee rows * 7 cells final daysInMonth = _daysInMonth(visibleMonth); final firstWeekday = _firstWeekdayOfMonth(visibleMonth); final leadingEmpty = firstWeekday - 1; final totalCells = leadingEmpty + daysInMonth; final rows = (totalCells / 7).ceil(); final totalItems = rows * 7; // first date shown (may be prev month's date) final firstCellDate = DateTime(visibleMonth.year, visibleMonth.month, 1).subtract(Duration(days: leadingEmpty)); return Padding( padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), elevation: 8, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 12.0), child: Column( mainAxisSize: MainAxisSize.min, // tightly wrap grid children: [ // month/year header SizedBox(height: 48, child: _buildMonthYearHeader(context, primaryColor)), const SizedBox(height: 6), // weekday labels row SizedBox( height: 22, child: Row( children: List.generate(7, (i) { final isWeekend = (i == 5 || i == 6); return Expanded( child: Center( child: Text( weekdayShorts[i], style: TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: isWeekend ? Colors.redAccent.withOpacity(0.9) : (Theme.of(context).brightness == Brightness.dark ? Colors.white70 : Colors.black54), ), ), ), ); }), ), ), const SizedBox(height: 8), // GRID: shrinkWrap true ensures all rows are rendered GridView.builder( shrinkWrap: true, controller: _calendarGridController, physics: const NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: totalItems, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 7, mainAxisSpacing: 4, crossAxisSpacing: 4, childAspectRatio: 0.78, ), itemBuilder: (context, index) { final cellDate = firstCellDate.add(Duration(days: index)); final inCurrentMonth = cellDate.month == visibleMonth.month && cellDate.year == visibleMonth.year; final dayIndex = cellDate.day; final key = _ymKey(cellDate); final hasEvents = _markedDates.contains(key); final eventCount = _dateCounts[key] ?? 0; final isSelected = selectedDate.year == cellDate.year && selectedDate.month == cellDate.month && selectedDate.day == cellDate.day; final isToday = cellDate.year == DateTime.now().year && cellDate.month == DateTime.now().month && cellDate.day == DateTime.now().day; final dayTextColor = inCurrentMonth ? (Theme.of(context).brightness == Brightness.dark ? Colors.white : Colors.black87) : (Theme.of(context).brightness == Brightness.dark ? Colors.white38 : Colors.grey.shade400); return GestureDetector( onTap: () { if (!inCurrentMonth) { setState(() => visibleMonth = DateTime(cellDate.year, cellDate.month, 1)); _loadMonth(visibleMonth); } _onSelectDate(key); }, child: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ // rounded date cell Container( width: 32, height: 32, decoration: BoxDecoration( color: isSelected ? primaryColor : isToday ? primaryColor.withOpacity(0.12) : Colors.transparent, borderRadius: BorderRadius.circular(8), ), alignment: Alignment.center, child: Text( '$dayIndex', style: TextStyle( fontWeight: (isSelected || isToday) ? FontWeight.w700 : FontWeight.w500, color: isSelected ? Colors.white : isToday ? primaryColor : dayTextColor, fontSize: 13, ), ), ), const SizedBox(height: 3), // event indicator dots if (hasEvents && inCurrentMonth) Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: List.generate( eventCount.clamp(1, 3), (i) => Container( width: 5, height: 5, margin: EdgeInsets.only(left: i > 0 ? 2 : 0), decoration: BoxDecoration( color: isSelected ? primaryColor : const Color(0xFFEF4444), shape: BoxShape.circle, ), ), ), ) else const SizedBox(height: 5), ], ), ); }, ), ], ), ), ), ); } // Selected-date summary (now guaranteed outside and below the calendar card) Widget _selectedDateSummary(BuildContext context) { final theme = Theme.of(context); final shortWeekday = DateFormat('EEEE').format(selectedDate); final shortDate = DateFormat('d MMMM, yyyy').format(selectedDate); final dayNumber = selectedDate.day; final monthLabel = DateFormat('MMM').format(selectedDate).toUpperCase(); final eventsCount = _eventsOfDay.length; final primaryColor = theme.colorScheme.primary; return Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 8), child: Card( elevation: 6, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0), child: Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration(color: primaryColor.withOpacity(0.12), borderRadius: BorderRadius.circular(10)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('$dayNumber', style: theme.textTheme.headlineSmall?.copyWith(color: primaryColor, fontWeight: FontWeight.bold, fontSize: 18)), const SizedBox(height: 2), Text(monthLabel, style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor, fontSize: 11, fontWeight: FontWeight.w700)), ], ), ), const SizedBox(width: 10), Expanded( child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(shortWeekday, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, fontSize: 14)), const SizedBox(height: 2), Text(shortDate, style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor, fontSize: 12)), ]), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('$eventsCount', style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold, color: primaryColor, fontSize: 16)), const SizedBox(height: 2), Text(eventsCount == 1 ? 'Event' : 'Events', style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor, fontSize: 12)), ], ), ], ), ), ), ); } // Mobile event card (kept unchanged) Widget _eventCardMobile(EventModel e) { final theme = Theme.of(context); final imgUrl = (e.thumbImg != null && e.thumbImg!.isNotEmpty) ? e.thumbImg! : (e.images.isNotEmpty ? e.images.first.image : null); 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 GestureDetector( onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e))), child: Card( elevation: 6, margin: const EdgeInsets.fromLTRB(20, 10, 20, 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ClipRRect( borderRadius: const BorderRadius.vertical(top: Radius.circular(14)), child: imgUrl != null ? CachedNetworkImage( imageUrl: imgUrl, memCacheWidth: 400, memCacheHeight: 300, height: 150, width: double.infinity, fit: BoxFit.cover, placeholder: (_, __) => Container(height: 150, color: theme.dividerColor), errorWidget: (_, __, ___) => Container(height: 150, color: theme.dividerColor), ) : Container(height: 150, color: theme.dividerColor, child: Icon(Icons.event, size: 44, color: theme.hintColor)), ), Padding( padding: const EdgeInsets.fromLTRB(12, 12, 12, 14), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(e.title ?? e.name ?? '', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), maxLines: 2, overflow: TextOverflow.ellipsis), const SizedBox(height: 10), Row(children: [ Container(width: 26, height: 26, decoration: BoxDecoration(color: theme.colorScheme.primary.withOpacity(0.12), borderRadius: BorderRadius.circular(6)), child: Icon(Icons.calendar_today, size: 14, color: theme.colorScheme.primary)), const SizedBox(width: 8), Expanded(child: Text(dateLabel, style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor))), ]), const SizedBox(height: 8), Row(children: [ Container(width: 26, height: 26, decoration: BoxDecoration(color: theme.colorScheme.primary.withOpacity(0.12), borderRadius: BorderRadius.circular(6)), child: Icon(Icons.location_on, size: 14, color: theme.colorScheme.primary)), const SizedBox(width: 8), Expanded(child: Text(e.place ?? '', style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor))), ]), ]), ), ], ), ), ); } // ── Landscape: event card for the right panel ─────────────────────────── Widget _eventCardLandscape(EventModel e) { final theme = Theme.of(context); final imgUrl = (e.thumbImg != null && e.thumbImg!.isNotEmpty) ? e.thumbImg! : (e.images.isNotEmpty ? e.images.first.image : null); 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 GestureDetector( onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e))), child: Container( margin: const EdgeInsets.fromLTRB(16, 0, 16, 14), decoration: BoxDecoration( color: theme.cardColor, borderRadius: BorderRadius.circular(14), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.06), blurRadius: 10, offset: const Offset(0, 3))], ), child: Row( children: [ // Image ClipRRect( borderRadius: const BorderRadius.horizontal(left: Radius.circular(14)), child: imgUrl != null ? CachedNetworkImage( imageUrl: imgUrl, memCacheWidth: 300, memCacheHeight: 300, width: 100, height: 100, fit: BoxFit.cover, placeholder: (_, __) => Container(width: 100, height: 100, color: theme.dividerColor), errorWidget: (_, __, ___) => Container( width: 100, height: 100, color: theme.dividerColor, child: Icon(Icons.event, size: 32, color: theme.hintColor), ), ) : Container( width: 100, height: 100, color: theme.dividerColor, child: Icon(Icons.event, size: 32, color: theme.hintColor), ), ), // Content Expanded( child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( e.title ?? e.name ?? '', style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w700), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), // Date row with blue dot Row(children: [ Container( width: 8, height: 8, decoration: const BoxDecoration( color: Color(0xFF3B82F6), shape: BoxShape.circle, ), ), const SizedBox(width: 8), Expanded(child: Text(dateLabel, style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), maxLines: 1, overflow: TextOverflow.ellipsis)), ]), const SizedBox(height: 6), // Venue row with green dot Row(children: [ Container( width: 8, height: 8, decoration: const BoxDecoration( color: Color(0xFF22C55E), shape: BoxShape.circle, ), ), const SizedBox(width: 8), Expanded(child: Text(e.place ?? '', style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor), maxLines: 1, overflow: TextOverflow.ellipsis)), ]), ], ), ), ), ], ), ), ); } // ── Landscape: left panel content (calendar on white bg) ───────────────── Widget _landscapeLeftPanel(BuildContext context) { final theme = Theme.of(context); return SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 20), // Title Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Text( "Event's Calendar", style: theme.textTheme.titleLarge?.copyWith( fontSize: 22, fontWeight: FontWeight.w700, letterSpacing: -0.3, ), ), ), const SizedBox(height: 12), // Calendar card — reuses the mobile _calendarCard widget Expanded( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( children: [ _calendarCard(context), if (_loadingMonth) Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), child: LinearProgressIndicator( color: theme.colorScheme.primary, backgroundColor: theme.colorScheme.primary.withOpacity(0.12), ), ), ], ), ), ), ], ), ); } // ── Landscape: right panel (event list for selected day) ──────────────── Widget _landscapeRightPanel(BuildContext context) { final theme = Theme.of(context); final dayName = DateFormat('EEEE').format(selectedDate); final dateFormatted = DateFormat('d MMMM, yyyy').format(selectedDate); final count = _eventsOfDay.length; return SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Date header matching Figma: "Monday, 16 June, 2025 — 2 Events" Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$dayName, $dateFormatted', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, fontSize: 16, ), ), const SizedBox(height: 4), Text( '$count ${count == 1 ? "Event" : "Events"}', style: theme.textTheme.bodySmall?.copyWith( color: theme.hintColor, fontWeight: FontWeight.w500, ), ), ], ), ), // Divider Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Divider(height: 1, color: theme.dividerColor), ), const SizedBox(height: 12), // Scrollable event list Expanded( child: _loadingDay ? Center(child: CircularProgressIndicator(color: theme.colorScheme.primary)) : _eventsOfDay.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.event_available, size: 56, color: theme.hintColor), const SizedBox(height: 12), Text( 'No events on this date', style: theme.textTheme.bodyMedium?.copyWith(color: theme.hintColor), ), ], ), ) : ListView.builder( physics: const BouncingScrollPhysics(), padding: const EdgeInsets.only(top: 4, bottom: 32), itemCount: _eventsOfDay.length, itemBuilder: (ctx, i) => _eventCardLandscape(_eventsOfDay[i]), ), ), ], ), ); } @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; final isLandscape = width >= 820; final theme = Theme.of(context); // ── LANDSCAPE layout ────────────────────────────────────────────────── if (isLandscape) { return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: Row( children: [ // Left: Calendar panel with WHITE background (~60%) Flexible( flex: 3, child: RepaintBoundary( child: Container( color: theme.cardColor, child: _landscapeLeftPanel(context), ), ), ), // Vertical divider between panels VerticalDivider(width: 1, thickness: 1, color: theme.dividerColor), // Right: Events panel (~40%) Flexible( flex: 2, child: RepaintBoundary( child: _landscapeRightPanel(context), ), ), ], ), ); } // ── MOBILE layout ───────────────────────────────────────────────────── // (unchanged from original) return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, body: Stack( children: [ // TOP APP BAR stays fixed (title + bell icon) Positioned( top: 0, left: 0, right: 0, child: SafeArea( bottom: false, child: SizedBox( height: 56, // app bar height child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Stack( alignment: Alignment.center, children: [ // centered title Text( "Event's Calendar", style: Theme.of(context).textTheme.titleMedium?.copyWith( color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600, ), ), // notification icon at absolute top-right Positioned( right: 0, child: InkWell( onTap: () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Notifications (coming soon)'))), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(10), ), child: const Icon(Icons.notifications_none, color: Colors.white), ), ), ), ], ), ), ), ), ), // CONTENT: gradient + calendar card scroll together as one unit CustomScrollView( physics: const BouncingScrollPhysics(), slivers: [ // Gradient + calendar card in one scrollable Stack // Gradient scrolls away with content; app bar remains fixed above SliverToBoxAdapter( child: Stack( children: [ // Blue gradient banner — scrolls with content Container( height: 260, decoration: AppDecoration.blueGradient.copyWith( borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30), ), boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 12, offset: Offset(0, 6))], ), ), // Calendar card starts at y=110 (after app bar), overlapping gradient Padding( padding: const EdgeInsets.only(top: 110), child: _calendarCard(context), ), ], ), ), // Selected date summary SliverToBoxAdapter(child: _selectedDateSummary(context)), // Events area — loading / empty / list if (_loadingDay) SliverFillRemaining( hasScrollBody: false, child: Center( child: CircularProgressIndicator(color: theme.colorScheme.primary), ), ) else if (_eventsOfDay.isEmpty) SliverFillRemaining( hasScrollBody: false, child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.event_available, size: 48, color: theme.hintColor), const SizedBox(height: 10), Text( 'No events scheduled for this date', style: theme.textTheme.bodyMedium?.copyWith(color: theme.hintColor), ), ], ), ), ) else SliverList( delegate: SliverChildBuilderDelegate( (context, idx) => _eventCardMobile(_eventsOfDay[idx]), childCount: _eventsOfDay.length, ), ), // Bottom padding const SliverToBoxAdapter(child: SizedBox(height: 32)), ], ), ], ), ); } }