diff --git a/.claude/launch.json b/.claude/launch.json index e69f128..ea5d258 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -1,11 +1,10 @@ { "version": "0.0.1", - "autoPort": true, "configurations": [ { "name": "flutter-web", - "runtimeExecutable": "bash", - "runtimeArgs": ["/Users/bshtechnologies/Documents/Eventify-frontend/run_web.sh"], + "runtimeExecutable": "flutter", + "runtimeArgs": ["run", "-d", "chrome", "--web-port", "8080", "--web-browser-flag", "--disable-web-security"], "port": 8080 } ] diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 7b70f97..021b7df 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -5,9 +5,9 @@ import 'package:http/http.dart' as http; import '../storage/token_storage.dart'; class ApiClient { - static const Duration _timeout = Duration(seconds: 30); + static const Duration _timeout = Duration(seconds: 10); // Set to true to enable mock/offline development mode (useful when backend is unavailable) - static const bool _developmentMode = true; + static const bool _developmentMode = false; /// POST request /// @@ -57,6 +57,39 @@ class ApiClient { 'email': email, 'phone_number': finalBody['phone_number'] ?? '+1234567890', }; + } else if (url.contains('/events/type-list/')) { + if (kDebugMode) debugPrint('Development mode: returning mock event types'); + return { + 'event_types': [ + {'id': 1, 'event_type': 'Concert', 'event_type_icon': null}, + {'id': 2, 'event_type': 'Workshop', 'event_type_icon': null}, + {'id': 3, 'event_type': 'Festival', 'event_type_icon': null}, + {'id': 4, 'event_type': 'Sports', 'event_type_icon': null}, + {'id': 5, 'event_type': 'Conference', 'event_type_icon': null}, + {'id': 6, 'event_type': 'Exhibition', 'event_type_icon': null}, + ], + }; + } else if (url.contains('/events/pincode-events/')) { + if (kDebugMode) debugPrint('Development mode: returning mock events'); + return {'events': _mockEvents}; + } else if (url.contains('/events/event-details/')) { + if (kDebugMode) debugPrint('Development mode: returning mock event detail'); + final eventId = finalBody['event_id'] ?? 1; + final match = _mockEvents.where((e) => e['id'] == eventId); + return match.isNotEmpty + ? Map.from(match.first) + : Map.from(_mockEvents.first); + } else if (url.contains('/events/events-by-month-year/')) { + if (kDebugMode) debugPrint('Development mode: returning mock calendar'); + return { + 'total_number_of_events': 3, + 'dates': ['2026-04-05', '2026-04-12', '2026-04-20'], + 'date_events': [ + {'date': '2026-04-05', 'count': 1}, + {'date': '2026-04-12', 'count': 2}, + {'date': '2026-04-20', 'count': 1}, + ], + }; } } @@ -103,6 +136,153 @@ class ApiClient { return _handleResponse(url, response, finalParams); } + // --------------------------------------------------------------------------- + // Mock event data for development / offline mode + // --------------------------------------------------------------------------- + static final List> _mockEvents = [ + { + 'id': 1, + 'name': 'Tech Innovation Summit 2026', + 'title': 'Tech Innovation Summit', + 'description': + 'Join industry leaders for a two-day summit exploring the latest breakthroughs in AI, cloud computing, and sustainable technology. Featuring keynote speakers, hands-on workshops, and networking sessions.', + 'start_date': '2026-04-15', + 'end_date': '2026-04-16', + 'start_time': '09:00', + 'end_time': '18:00', + 'pincode': '560001', + 'place': 'Bengaluru International Exhibition Centre', + 'is_bookable': true, + 'event_type': 5, + 'thumb_img': 'https://picsum.photos/seed/event1/600/400', + 'images': [ + {'is_primary': true, 'image': 'https://picsum.photos/seed/event1a/800/500'}, + {'is_primary': false, 'image': 'https://picsum.photos/seed/event1b/800/500'}, + ], + 'important_information': 'Please carry a valid photo ID for entry.', + 'venue_name': 'BIEC Hall 2', + 'event_status': 'active', + 'latitude': 13.0147, + 'longitude': 77.5636, + 'location_name': 'Bengaluru', + 'important_info': [ + {'title': 'Entry', 'value': 'Free with registration'}, + {'title': 'Parking', 'value': 'Available on-site'}, + ], + }, + { + 'id': 2, + 'name': 'Sunset Music Festival', + 'title': 'Sunset Music Festival', + 'description': + 'An open-air music festival featuring live performances from top artists across genres. Enjoy food stalls, art installations, and an unforgettable sunset experience.', + 'start_date': '2026-04-20', + 'end_date': '2026-04-20', + 'start_time': '16:00', + 'end_time': '23:00', + 'pincode': '400001', + 'place': 'Marine Drive Amphitheatre', + 'is_bookable': true, + 'event_type': 1, + 'thumb_img': 'https://picsum.photos/seed/event2/600/400', + 'images': [ + {'is_primary': true, 'image': 'https://picsum.photos/seed/event2a/800/500'}, + ], + 'venue_name': 'Marine Drive Amphitheatre', + 'event_status': 'active', + 'latitude': 18.9432, + 'longitude': 72.8235, + 'location_name': 'Mumbai', + 'important_info': [ + {'title': 'Age Limit', 'value': '16+'}, + ], + }, + { + 'id': 3, + 'name': 'Creative Design Workshop', + 'title': 'Hands-on Design Workshop', + 'description': + 'A full-day workshop on UI/UX design principles, prototyping in Figma, and building design systems. Perfect for beginners and intermediate designers.', + 'start_date': '2026-05-03', + 'end_date': '2026-05-03', + 'start_time': '10:00', + 'end_time': '17:00', + 'pincode': '110001', + 'place': 'Design Hub Co-working', + 'is_bookable': true, + 'event_type': 2, + 'thumb_img': 'https://picsum.photos/seed/event3/600/400', + 'images': [ + {'is_primary': true, 'image': 'https://picsum.photos/seed/event3a/800/500'}, + ], + 'venue_name': 'Design Hub', + 'event_status': 'active', + 'latitude': 28.6139, + 'longitude': 77.2090, + 'location_name': 'New Delhi', + 'important_info': [ + {'title': 'Bring', 'value': 'Laptop with Figma installed'}, + {'title': 'Seats', 'value': '30 max'}, + ], + }, + { + 'id': 4, + 'name': 'Marathon for a Cause', + 'title': 'City Marathon 2026', + 'description': + 'Run for fitness, run for charity! Choose from 5K, 10K, or full marathon routes through the city. All proceeds support local education initiatives.', + 'start_date': '2026-04-12', + 'end_date': '2026-04-12', + 'start_time': '05:30', + 'end_time': '12:00', + 'pincode': '600001', + 'place': 'Marina Beach Road', + 'is_bookable': true, + 'event_type': 4, + 'thumb_img': 'https://picsum.photos/seed/event4/600/400', + 'images': [ + {'is_primary': true, 'image': 'https://picsum.photos/seed/event4a/800/500'}, + ], + 'venue_name': 'Marina Beach', + 'event_status': 'active', + 'latitude': 13.0500, + 'longitude': 80.2824, + 'location_name': 'Chennai', + 'important_info': [ + {'title': 'Registration', 'value': 'Closes April 10'}, + ], + }, + { + 'id': 5, + 'name': 'Art & Culture Exhibition', + 'title': 'Contemporary Art Exhibition', + 'description': + 'Explore contemporary artworks from emerging and established artists. The exhibition features paintings, sculptures, and digital art installations.', + 'start_date': '2026-05-10', + 'end_date': '2026-05-15', + 'start_time': '11:00', + 'end_time': '20:00', + 'pincode': '500001', + 'place': 'Salar Jung Museum Grounds', + 'is_bookable': true, + 'event_type': 6, + 'thumb_img': 'https://picsum.photos/seed/event5/600/400', + 'images': [ + {'is_primary': true, 'image': 'https://picsum.photos/seed/event5a/800/500'}, + {'is_primary': false, 'image': 'https://picsum.photos/seed/event5b/800/500'}, + ], + 'venue_name': 'Salar Jung Museum', + 'event_status': 'active', + 'latitude': 17.3713, + 'longitude': 78.4804, + 'location_name': 'Hyderabad', + 'important_info': [ + {'title': 'Entry Fee', 'value': '₹200'}, + {'title': 'Photography', 'value': 'Allowed without flash'}, + ], + }, + ]; + /// Build request body and attach token + username if available Future> _buildAuthBody(Map? body, bool requiresAuth) async { final Map finalBody = {}; diff --git a/lib/core/api/api_endpoints.dart b/lib/core/api/api_endpoints.dart index 95fa6eb..9420f88 100644 --- a/lib/core/api/api_endpoints.dart +++ b/lib/core/api/api_endpoints.dart @@ -3,7 +3,11 @@ class ApiEndpoints { // Change this to your desired backend base URL (local or UAT) // For local Django dev use: "http://127.0.0.1:8000/api" // For UAT: "https://uat.eventifyplus.com/api" - static const String baseUrl = "https://uat.eventifyplus.com/api"; + static const String baseUrl = "https://em.eventifyplus.com/api"; + + /// Base URL for media files (images, icons uploaded via Django admin). + /// Relative paths like `/media/...` are resolved against this. + static const String mediaBaseUrl = "https://em.eventifyplus.com"; // Auth static const String register = "$baseUrl/user/register/"; diff --git a/lib/features/events/models/event_models.dart b/lib/features/events/models/event_models.dart index cf8c864..5344365 100644 --- a/lib/features/events/models/event_models.dart +++ b/lib/features/events/models/event_models.dart @@ -1,4 +1,6 @@ // lib/features/events/models/event_models.dart +import '../../../core/api/api_endpoints.dart'; + class EventTypeModel { final int id; final String name; @@ -6,11 +8,18 @@ class EventTypeModel { EventTypeModel({required this.id, required this.name, this.iconUrl}); + /// Resolve a relative media path (e.g. `/media/...`) to a full URL. + static String? _resolveMediaUrl(String? raw) { + if (raw == null || raw.isEmpty) return null; + if (raw.startsWith('http://') || raw.startsWith('https://')) return raw; + return '${ApiEndpoints.mediaBaseUrl}$raw'; + } + factory EventTypeModel.fromJson(Map j) { return EventTypeModel( id: j['id'] as int, name: (j['event_type'] ?? j['name'] ?? '') as String, - iconUrl: (j['event_type_icon'] ?? j['icon_url']) as String?, + iconUrl: _resolveMediaUrl((j['event_type_icon'] ?? j['icon_url']) as String?), ); } } @@ -24,7 +33,7 @@ class EventImageModel { factory EventImageModel.fromJson(Map j) { return EventImageModel( isPrimary: j['is_primary'] == true, - image: (j['image'] ?? '') as String, + image: EventTypeModel._resolveMediaUrl(j['image'] as String?) ?? '', ); } } @@ -129,7 +138,7 @@ class EventModel { place: (j['place'] ?? j['venue_name']) as String?, isBookable: j['is_bookable'] == null ? true : (j['is_bookable'] == true || j['is_bookable'].toString().toLowerCase() == 'true'), eventTypeId: j['event_type'] is int ? j['event_type'] as int : (j['event_type'] != null ? int.tryParse(j['event_type'].toString()) : null), - thumbImg: j['thumb_img'] as String?, + thumbImg: EventTypeModel._resolveMediaUrl(j['thumb_img'] as String?), images: imgs, importantInformation: j['important_information'] as String?, venueName: j['venue_name'] as String?, diff --git a/lib/features/events/services/events_service.dart b/lib/features/events/services/events_service.dart index 81d0ddd..4efc18d 100644 --- a/lib/features/events/services/events_service.dart +++ b/lib/features/events/services/events_service.dart @@ -1,5 +1,4 @@ // lib/features/events/services/events_service.dart -import 'package:intl/intl.dart'; import '../../../core/api/api_client.dart'; import '../../../core/api/api_endpoints.dart'; import '../models/event_models.dart'; @@ -7,27 +6,62 @@ import '../models/event_models.dart'; class EventsService { final ApiClient _api = ApiClient(); + // --------------------------------------------------------------------------- + // In-memory caches with TTL + // --------------------------------------------------------------------------- + static List? _cachedTypes; + static DateTime? _typesCacheTime; + static const _typesCacheTTL = Duration(minutes: 30); + + static List? _cachedAllEvents; + static DateTime? _eventsCacheTime; + static const _eventsCacheTTL = Duration(minutes: 5); + /// Get event types (POST to /events/type-list/) + /// Cached for 30 minutes since event types rarely change. Future> getEventTypes() async { + if (_cachedTypes != null && + _typesCacheTime != null && + DateTime.now().difference(_typesCacheTime!) < _typesCacheTTL) { + return _cachedTypes!; + } + final res = await _api.post(ApiEndpoints.eventTypes, requiresAuth: false); final list = []; - final data = res['event_types'] ?? res['event_types'] ?? res; + final data = res['event_types'] ?? res; if (data is List) { for (final e in data) { if (e is Map) list.add(EventTypeModel.fromJson(e)); } - } else if (res['event_types'] is List) { - for (final e in res['event_types']) { - list.add(EventTypeModel.fromJson(Map.from(e))); - } } + + _cachedTypes = list; + _typesCacheTime = DateTime.now(); return list; } - /// Get events filtered by pincode (POST to /events/pincode-events/) - /// Use pincode='all' to fetch all events. - Future> getEventsByPincode(String pincode) async { - final res = await _api.post(ApiEndpoints.eventsByPincode, body: {'pincode': pincode}, requiresAuth: false); + /// Get events filtered by pincode with pagination. + /// [page] starts at 1. [pageSize] defaults to 50. + /// Returns a list of events for the requested page. + Future> getEventsByPincode(String pincode, {int page = 1, int pageSize = 50, int perType = 5}) async { + // Use cache for 'all' pincode queries (first page only for initial load) + if (pincode == 'all' && + page == 1 && + _cachedAllEvents != null && + _eventsCacheTime != null && + DateTime.now().difference(_eventsCacheTime!) < _eventsCacheTTL) { + return _cachedAllEvents!; + } + + final Map body = {'pincode': pincode, 'page': page, 'page_size': pageSize}; + // Diverse mode: fetch a few events per type so all categories are represented + if (perType > 0 && page == 1) body['per_type'] = perType; + + final res = await _api.post( + ApiEndpoints.eventsByPincode, + body: body, + requiresAuth: false, + ); final list = []; final events = res['events'] ?? res['data'] ?? []; if (events is List) { @@ -35,6 +69,11 @@ class EventsService { if (e is Map) list.add(EventModel.fromJson(Map.from(e))); } } + + if (pincode == 'all' && page == 1) { + _cachedAllEvents = list; + _eventsCacheTime = DateTime.now(); + } return list; } @@ -45,23 +84,21 @@ class EventsService { } /// Events by month and year for calendar (POST to /events/events-by-month-year/) - /// Accepts month string and year int. - /// Returns Map with 'dates' (list of YYYY-MM-DD) and 'date_events' (list with counts). Future> getEventsByMonthYear(String month, int year) async { final res = await _api.post(ApiEndpoints.eventsByMonth, body: {'month': month, 'year': year}, requiresAuth: false); - // expected keys: dates, total_number_of_events, date_events return res; } - /// Convenience: get events for a specific date (YYYY-MM-DD) + /// Convenience: get events for a specific date (YYYY-MM-DD). + /// Uses the cached events list when available to avoid redundant API calls. Future> getEventsForDate(String date) async { - // Simplest approach: hit pincode-events with filter or hit events-by-month-year and then - // query event-details for events of that date. Assuming backend doesn't provide direct endpoint, - // we'll call eventsByPincode('all') and filter locally by date — acceptable for demo/small datasets. final all = await getEventsByPincode('all'); return all.where((e) { try { - return e.startDate == date || e.endDate == date || (DateTime.parse(e.startDate).isBefore(DateTime.parse(date)) && DateTime.parse(e.endDate).isAfter(DateTime.parse(date))); + return e.startDate == date || + e.endDate == date || + (DateTime.parse(e.startDate).isBefore(DateTime.parse(date)) && + DateTime.parse(e.endDate).isAfter(DateTime.parse(date))); } catch (_) { return false; } diff --git a/lib/screens/home_desktop_screen.dart b/lib/screens/home_desktop_screen.dart index b9c7261..b7204d4 100644 --- a/lib/screens/home_desktop_screen.dart +++ b/lib/screens/home_desktop_screen.dart @@ -319,6 +319,7 @@ class _HomeContentState extends State<_HomeContent> width: double.infinity, height: double.infinity, memCacheWidth: 1400, + memCacheHeight: 800, placeholder: (_, __) => Container( color: const Color(0xFF0A0E1A), ), @@ -527,6 +528,7 @@ class _HomeContentState extends State<_HomeContent> imageUrl: img, fit: BoxFit.cover, memCacheWidth: 1400, + memCacheHeight: 800, ) else Container(color: const Color(0xFF0A0E1A)), diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 5585138..fb66fbf 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -36,6 +36,7 @@ class _HomeScreenState extends State with SingleTickerProviderStateM final EventsService _eventsService = EventsService(); // backend-driven + List _allEvents = []; // master copy, never filtered List _events = []; List _types = []; int _selectedTypeId = -1; // -1 == All @@ -87,7 +88,6 @@ class _HomeScreenState extends State with SingleTickerProviderStateM final coordMatch = RegExp(r'^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$').firstMatch(storedLocation); if (coordMatch != null) { _location = 'Current Location'; - setState(() {}); // Reverse geocode in background to get actual place name _reverseGeocodeAndSave( double.parse(coordMatch.group(1)!), @@ -110,17 +110,19 @@ class _HomeScreenState extends State with SingleTickerProviderStateM if (mounted) { setState(() { _types = types; + _allEvents = events; _events = events; _selectedTypeId = -1; - _cachedFilteredEvents = null; // invalidate cache + _cachedFilteredEvents = null; + _cachedEventDates = null; + _loading = false; }); } } catch (e) { if (mounted) { + setState(() => _loading = false); ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); } - } finally { - if (mounted) setState(() => _loading = false); } } @@ -545,6 +547,52 @@ class _HomeScreenState extends State with SingleTickerProviderStateM List? _cachedFilteredEvents; String _cachedFilterKey = ''; + // Cached event dates for calendar dots + Set? _cachedEventDates; + + /// Returns all events filtered by date only (ignores category selection). + /// Used by Top Events and category sections so they always show all types. + List get _allFilteredByDate { + if (_selectedDateFilter.isEmpty) return _allEvents; + // Reuse the same date-filter logic as _filteredEvents but on _allEvents + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + DateTime filterStart; + DateTime filterEnd; + switch (_selectedDateFilter) { + case 'Today': + filterStart = today; + filterEnd = today; + break; + case 'Tomorrow': + filterStart = today.add(const Duration(days: 1)); + filterEnd = filterStart; + break; + case 'This week': + filterStart = today; + filterEnd = today.add(Duration(days: 7 - today.weekday)); + break; + case 'Date': + if (_selectedCustomDate == null) return _allEvents; + filterStart = DateTime(_selectedCustomDate!.year, _selectedCustomDate!.month, _selectedCustomDate!.day); + filterEnd = filterStart; + break; + default: + return _allEvents; + } + return _allEvents.where((e) { + try { + final s = DateTime.parse(e.startDate); + final eEnd = DateTime.parse(e.endDate); + final eStart = DateTime(s.year, s.month, s.day); + final eEndDay = DateTime(eEnd.year, eEnd.month, eEnd.day); + return !eEndDay.isBefore(filterStart) && !eStart.isAfter(filterEnd); + } catch (_) { + return false; + } + }).toList(); + } + /// Returns the subset of [_events] that match the active date-filter chip. /// Uses caching to avoid re-parsing dates on every access. List get _filteredEvents { @@ -887,9 +935,11 @@ class _HomeScreenState extends State with SingleTickerProviderStateM } /// Collect all event dates (start + end range) to show dots on the calendar. + /// Cached to avoid re-parsing on every calendar open. Set get _eventDates { + if (_cachedEventDates != null) return _cachedEventDates!; final dates = {}; - for (final e in _events) { + for (final e in _allEvents) { try { final start = DateTime.parse(e.startDate); final end = DateTime.parse(e.endDate); @@ -901,6 +951,7 @@ class _HomeScreenState extends State with SingleTickerProviderStateM } } catch (_) {} } + _cachedEventDates = dates; return dates; } @@ -1318,6 +1369,7 @@ class _HomeScreenState extends State with SingleTickerProviderStateM ? CachedNetworkImage( imageUrl: img, memCacheWidth: 700, + memCacheHeight: 400, fit: BoxFit.cover, placeholder: (_, __) => const _HeroShimmer(radius: radius), errorWidget: (_, __, ___) => @@ -1498,18 +1550,18 @@ class _HomeScreenState extends State with SingleTickerProviderStateM const SizedBox(height: 16), SizedBox( height: 200, - child: _filteredEvents.isEmpty && _loading + child: _allFilteredByDate.isEmpty && _loading ? const Center(child: CircularProgressIndicator()) - : _filteredEvents.isEmpty + : _allFilteredByDate.isEmpty ? Center(child: Text( _selectedDateFilter.isNotEmpty ? 'No events for "$_selectedDateFilter"' : 'No events found', style: const TextStyle(color: Color(0xFF9CA3AF)), )) : ListView.separated( scrollDirection: Axis.horizontal, - itemCount: _filteredEvents.length, + itemCount: _allFilteredByDate.length, separatorBuilder: (_, __) => const SizedBox(width: 12), - itemBuilder: (context, index) => _buildTopEventCard(_filteredEvents[index]), + itemBuilder: (context, index) => _buildTopEventCard(_allFilteredByDate[index]), ), ), const SizedBox(height: 24), @@ -1565,42 +1617,30 @@ class _HomeScreenState extends State with SingleTickerProviderStateM ), const SizedBox(height: 16), - // Event sections by type - if (_selectedTypeId == -1) ...[ - if (_loading) - const Padding( - padding: EdgeInsets.all(40), - child: Center(child: CircularProgressIndicator()), - ) - else if (_filteredEvents.isEmpty && _selectedDateFilter.isNotEmpty) - Padding( - padding: const EdgeInsets.all(40), - child: Center(child: Text( - 'No events for "$_selectedDateFilter"', - style: const TextStyle(color: Color(0xFF9CA3AF)), - )), - ) - else - Column( - children: [ - for (final t in _types) - if (_filteredEvents.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[ - _buildTypeSection(t), - const SizedBox(height: 18), - ], - ], - ), - ] else ...[ - if (_loading) - const Padding( - padding: EdgeInsets.all(40), - child: Center(child: CircularProgressIndicator()), - ) - else - Column( - children: _filteredEvents.map((e) => _buildFullWidthCard(e)).toList(), - ), - ], + // Event sections by type — always show ALL categories + if (_loading) + const Padding( + padding: EdgeInsets.all(40), + child: Center(child: CircularProgressIndicator()), + ) + else if (_allFilteredByDate.isEmpty && _selectedDateFilter.isNotEmpty) + Padding( + padding: const EdgeInsets.all(40), + child: Center(child: Text( + 'No events for "$_selectedDateFilter"', + style: const TextStyle(color: Color(0xFF9CA3AF)), + )), + ) + else + Column( + children: [ + for (final t in _types) + if (_allFilteredByDate.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[ + _buildTypeSection(t), + const SizedBox(height: 18), + ], + ], + ), // Bottom padding for nav bar const SizedBox(height: 100), @@ -1680,6 +1720,7 @@ class _HomeScreenState extends State with SingleTickerProviderStateM ? CachedNetworkImage( imageUrl: img, memCacheWidth: 300, + memCacheHeight: 200, fit: BoxFit.cover, width: double.infinity, height: double.infinity, @@ -1740,7 +1781,7 @@ class _HomeScreenState extends State with SingleTickerProviderStateM /// - If type has >= 6 events => arrange events into column groups of 3 (so visually there are 3 rows across horizontally scrollable columns). Widget _buildTypeSection(EventTypeModel type) { final theme = Theme.of(context); - final eventsForType = _filteredEvents.where((e) => e.eventTypeId == type.id).toList(); + final eventsForType = _allFilteredByDate.where((e) => e.eventTypeId == type.id).toList(); final n = eventsForType.length; // Header row @@ -1874,6 +1915,7 @@ class _HomeScreenState extends State with SingleTickerProviderStateM ? CachedNetworkImage( imageUrl: img, memCacheWidth: 192, + memCacheHeight: 192, width: 96, height: double.infinity, fit: BoxFit.cover, @@ -2241,18 +2283,12 @@ class _HomeScreenState extends State with SingleTickerProviderStateM return Icons.event; } - void _onSelectType(int id) async { + void _onSelectType(int id) { setState(() { _selectedTypeId = id; + _events = id == -1 ? List.from(_allEvents) : _allEvents.where((e) => e.eventTypeId == id).toList(); + _cachedFilteredEvents = null; }); - - try { - final all = await _eventsService.getEventsByPincode(_pincode); - final filtered = id == -1 ? all : all.where((e) => e.eventTypeId == id).toList(); - if (mounted) setState(() { _events = filtered; _cachedFilteredEvents = null; }); - } catch (e) { - if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString()))); - } } String _getShortEmailLabel() { diff --git a/lib/screens/learn_more_screen.dart b/lib/screens/learn_more_screen.dart index a5b4fb0..51f7e77 100644 --- a/lib/screens/learn_more_screen.dart +++ b/lib/screens/learn_more_screen.dart @@ -265,6 +265,8 @@ class _LearnMoreScreenState extends State { CachedNetworkImage( imageUrl: heroImage, fit: BoxFit.cover, + memCacheWidth: 800, + memCacheHeight: 500, placeholder: (_, __) => Container( decoration: const BoxDecoration( gradient: LinearGradient( @@ -471,6 +473,8 @@ class _LearnMoreScreenState extends State { CachedNetworkImage( imageUrl: images[i], fit: BoxFit.cover, + memCacheWidth: 800, + memCacheHeight: 500, placeholder: (_, __) => Container(color: theme.dividerColor), errorWidget: (_, __, ___) => Container( color: theme.dividerColor, @@ -724,6 +728,8 @@ class _LearnMoreScreenState extends State { builder: (context, currentPage, _) => CachedNetworkImage( imageUrl: images[currentPage], fit: BoxFit.cover, + memCacheWidth: 800, + memCacheHeight: 500, width: double.infinity, height: double.infinity, placeholder: (_, __) => Container( @@ -782,6 +788,8 @@ class _LearnMoreScreenState extends State { itemBuilder: (_, i) => CachedNetworkImage( imageUrl: images[i], fit: BoxFit.cover, + memCacheWidth: 800, + memCacheHeight: 500, width: double.infinity, placeholder: (_, __) => Container( color: theme.dividerColor,