perf: optimize loading time — paginated API, slim payloads, local category filtering

Backend: Rewrote EventListAPI to query per-type with DB-level LIMIT
instead of loading all 734 events into memory. Added slim serializer
(32KB vs 154KB). Added DB indexes on event_type_id and pincode.

Frontend: Category chips now filter locally from _allEvents (instant,
no API call). Top Events and category sections always show all types
regardless of selected category. Added TTL caching for event types
(30min) and events (5min). Reduced API timeout from 30s to 10s.
Added memCacheHeight to all CachedNetworkImage widgets. Batched
setState calls from 5 to 2 during startup. Cached _eventDates getter.

Switched baseUrl to em.eventifyplus.com (Django via Nginx+SSL).
Added initialEvent param to LearnMoreScreen for instant detail views.
Resolved relative media URLs for category icons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-30 10:05:23 +05:30
parent c32f343558
commit a7f3b215e4
8 changed files with 357 additions and 82 deletions

View File

@@ -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<String, dynamic> 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<String, dynamic> 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?,