Files
Eventify-frontend/lib/screens/calendar_screen.dart

733 lines
30 KiB
Dart
Raw Permalink Normal View History

2026-01-31 15:23:18 +05:30
// lib/screens/calendar_screen.dart
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:intl/intl.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';
class CalendarScreen extends StatefulWidget {
const CalendarScreen({Key? key}) : super(key: key);
@override
State<CalendarScreen> createState() => _CalendarScreenState();
}
class _CalendarScreenState extends State<CalendarScreen> {
DateTime visibleMonth = DateTime.now();
DateTime selectedDate = DateTime.now();
final EventsService _service = EventsService();
bool _loadingMonth = true;
bool _loadingDay = false;
final Set<String> _markedDates = {};
final Map<String, int> _dateCounts = {};
final Map<String, List<String>> _dateThumbnails = {};
List<EventModel> _eventsOfDay = [];
// Scroll controller for the calendar grid
final ScrollController _calendarGridController = ScrollController();
static const List<String> 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<void> _loadMonth(DateTime dt) async {
setState(() {
_loadingMonth = true;
_markedDates.clear();
_dateCounts.clear();
_dateThumbnails.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;
}
}
}
}
if (_markedDates.isNotEmpty) {
await _fetchThumbnailsForDates(_markedDates.toList());
}
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
} finally {
if (mounted) setState(() => _loadingMonth = false);
}
}
Future<void> _fetchThumbnailsForDates(List<String> dates) async {
for (final date in dates) {
try {
final events = await _service.getEventsForDate(date);
final thumbs = <String>[];
for (final e in events) {
String? url;
if (e.thumbImg != null && e.thumbImg!.trim().isNotEmpty) {
url = e.thumbImg!.trim();
} else if (e.images.isNotEmpty && e.images.first.image.trim().isNotEmpty) {
url = e.images.first.image.trim();
}
if (url != null && url.isNotEmpty) thumbs.add(url);
if (thumbs.length >= 3) break;
}
if (thumbs.isNotEmpty) {
if (mounted) {
setState(() => _dateThumbnails[date] = thumbs);
} else {
_dateThumbnails[date] = thumbs;
}
}
} catch (_) {
// ignore per-date errors
}
}
}
Future<void> _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(e.toString())));
} 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<void> _showYearPicker(BuildContext context) async {
final startYear = 2020;
final endYear = 2050;
final years = List<int>.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: 8,
crossAxisSpacing: 8,
childAspectRatio: 1,
),
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 thumbnails = _dateThumbnails[key] ?? [];
final isSelected = selectedDate.year == cellDate.year && selectedDate.month == cellDate.month && selectedDate.day == cellDate.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,
children: [
// rounded date cell
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: isSelected ? primaryColor.withOpacity(0.14) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: Text(
'$dayIndex',
style: TextStyle(
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w600,
color: isSelected ? primaryColor : dayTextColor,
fontSize: 14,
),
),
),
const SizedBox(height: 6),
// small event indicators (thumbnail overlap or dot)
if (hasEvents && thumbnails.isNotEmpty)
SizedBox(
height: 14,
child: Row(
mainAxisSize: MainAxisSize.min,
children: thumbnails.take(2).toList().asMap().entries.map((entry) {
final i = entry.key;
final url = entry.value;
return Transform.translate(
offset: Offset(i * -6.0, 0),
child: Container(
margin: const EdgeInsets.only(left: 4),
width: 14,
height: 14,
decoration: BoxDecoration(shape: BoxShape.circle, border: Border.all(color: Theme.of(context).cardColor, width: 1.0)),
child: ClipOval(child: Image.network(url, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Container(color: Theme.of(context).dividerColor))),
),
);
}).toList(),
),
)
else if (hasEvents)
Container(width: 18, height: 6, decoration: BoxDecoration(color: primaryColor, borderRadius: BorderRadius.circular(6)))
else
const SizedBox.shrink(),
],
),
);
},
),
],
),
),
),
);
}
// 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))),
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 ? Image.network(imgUrl, height: 150, width: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => 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))),
]),
]),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
final isMobile = width < 700;
final theme = Theme.of(context);
// For non-mobile, keep original split layout
if (!isMobile) {
return Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
body: SafeArea(
child: Row(
children: [
Expanded(flex: 3, child: Padding(padding: const EdgeInsets.all(24.0), child: _calendarCard(context))),
Expanded(flex: 1, child: _detailsPanel()),
],
),
),
);
}
// MOBILE layout
// Stack: extended gradient panel (below appbar) that visually extends behind the calendar.
return Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
body: Stack(
children: [
// Extended blue gradient panel behind calendar (matches reference)
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 260, // controls how much gradient shows behind calendar
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))],
),
// leave child empty — title and bell are placed above
child: const SizedBox.shrink(),
),
),
// TOP APP BAR (title centered + notification at top-right) - unchanged placement
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 (demo)'))),
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: calendar card overlapped on gradient, then summary and list
Column(
children: [
const SizedBox(height: 110), // leave space for appbar + some gradient top
_calendarCard(context), // calendar card sits visually on top of the gradient
_selectedDateSummary(context),
Expanded(
child: _loadingDay
? Center(child: CircularProgressIndicator(color: theme.colorScheme.primary))
: _eventsOfDay.isEmpty
? 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)),
],
),
)
: ListView.builder(
padding: const EdgeInsets.only(top: 6, bottom: 32),
itemCount: _eventsOfDay.length,
itemBuilder: (context, idx) => _eventCardMobile(_eventsOfDay[idx]),
),
),
],
),
],
),
);
}
Widget _detailsPanel() {
final theme = Theme.of(context);
final shortDate = DateFormat('EEE, d MMM').format(selectedDate);
final eventsCount = _eventsOfDay.length;
Widget _buildHeaderCompact() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12.0),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: AppDecoration.blueGradientRounded(10),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Text('${selectedDate.day}', style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 2), Text(monthNames[selectedDate.month].substring(0, 3), style: const TextStyle(color: Colors.white70, fontSize: 10))]),
),
const SizedBox(width: 12),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(shortDate, style: Theme.of(context).textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w600)), const SizedBox(height: 4), Text('$eventsCount ${eventsCount == 1 ? "Event" : "Events"}', style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Theme.of(context).hintColor))]),
const Spacer(),
IconButton(onPressed: () {}, icon: Icon(Icons.more_horiz, color: Theme.of(context).iconTheme.color)),
],
),
);
}
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
_buildHeaderCompact(),
Divider(height: 1, color: theme.dividerColor),
Expanded(
child: _loadingDay
? Center(child: CircularProgressIndicator(color: theme.colorScheme.primary))
: _eventsOfDay.isEmpty
? const SizedBox.shrink()
: ListView.builder(padding: const EdgeInsets.only(top: 6, bottom: 24), itemCount: _eventsOfDay.length, itemBuilder: (context, idx) => _eventCardMobile(_eventsOfDay[idx])),
)
]),
);
}
}