feat: responsive layout, date filtering, location search, calendar fix
- Make web responsive layout use width-based mobile detection (820px) - Add date filter chips that actually filter events by date ranges - Custom calendar dialog with event dots on Date chip tap - Update location search with Kerala cities and pincode display - Fix calendar screen overflow errors and broken event indicators - Replace thumbnail indicators with clean colored dots Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
// 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';
|
||||
@@ -25,8 +24,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
|
||||
final Set<String> _markedDates = {};
|
||||
final Map<String, int> _dateCounts = {};
|
||||
final Map<String, List<String>> _dateThumbnails = {};
|
||||
|
||||
List<EventModel> _eventsOfDay = [];
|
||||
|
||||
// Scroll controller for the calendar grid
|
||||
@@ -67,7 +64,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
_loadingMonth = true;
|
||||
_markedDates.clear();
|
||||
_dateCounts.clear();
|
||||
_dateThumbnails.clear();
|
||||
_eventsOfDay = [];
|
||||
});
|
||||
|
||||
@@ -95,9 +91,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
if (_markedDates.isNotEmpty) {
|
||||
await _fetchThumbnailsForDates(_markedDates.toList());
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
|
||||
} finally {
|
||||
@@ -105,34 +98,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -381,9 +346,9 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
itemCount: totalItems,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 7,
|
||||
mainAxisSpacing: 8,
|
||||
crossAxisSpacing: 8,
|
||||
childAspectRatio: 1,
|
||||
mainAxisSpacing: 4,
|
||||
crossAxisSpacing: 4,
|
||||
childAspectRatio: 0.78,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final cellDate = firstCellDate.add(Duration(days: index));
|
||||
@@ -391,8 +356,9 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
final dayIndex = cellDate.day;
|
||||
final key = _ymKey(cellDate);
|
||||
final hasEvents = _markedDates.contains(key);
|
||||
final thumbnails = _dateThumbnails[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)
|
||||
@@ -408,52 +374,57 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// rounded date cell
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
width: 32,
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? primaryColor.withOpacity(0.14) : Colors.transparent,
|
||||
color: isSelected
|
||||
? primaryColor
|
||||
: isToday
|
||||
? primaryColor.withOpacity(0.12)
|
||||
: 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,
|
||||
fontWeight: (isSelected || isToday) ? FontWeight.w700 : FontWeight.w500,
|
||||
color: isSelected
|
||||
? Colors.white
|
||||
: isToday
|
||||
? primaryColor
|
||||
: dayTextColor,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
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(),
|
||||
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 if (hasEvents)
|
||||
Container(width: 18, height: 6, decoration: BoxDecoration(color: primaryColor, borderRadius: BorderRadius.circular(6)))
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user