Compare commits
2 Commits
6503d9bc1b
...
b55f02e057
| Author | SHA1 | Date | |
|---|---|---|---|
| b55f02e057 | |||
| 87cc56dc64 |
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"autoPort": true,
|
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "flutter-web",
|
"name": "flutter-web",
|
||||||
"runtimeExecutable": "bash",
|
"runtimeExecutable": "flutter",
|
||||||
"runtimeArgs": ["/Users/bshtechnologies/Documents/Eventify-frontend/run_web.sh"],
|
"runtimeArgs": ["run", "-d", "chrome", "--web-port", "8080", "--web-browser-flag", "--disable-web-security"],
|
||||||
"port": 8080
|
"port": 8080
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import 'package:http/http.dart' as http;
|
|||||||
import '../storage/token_storage.dart';
|
import '../storage/token_storage.dart';
|
||||||
|
|
||||||
class ApiClient {
|
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)
|
// 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
|
/// POST request
|
||||||
///
|
///
|
||||||
@@ -57,6 +57,39 @@ class ApiClient {
|
|||||||
'email': email,
|
'email': email,
|
||||||
'phone_number': finalBody['phone_number'] ?? '+1234567890',
|
'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<String, dynamic>.from(match.first)
|
||||||
|
: Map<String, dynamic>.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);
|
return _handleResponse(url, response, finalParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Mock event data for development / offline mode
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static final List<Map<String, dynamic>> _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
|
/// Build request body and attach token + username if available
|
||||||
Future<Map<String, dynamic>> _buildAuthBody(Map<String, dynamic>? body, bool requiresAuth) async {
|
Future<Map<String, dynamic>> _buildAuthBody(Map<String, dynamic>? body, bool requiresAuth) async {
|
||||||
final Map<String, dynamic> finalBody = {};
|
final Map<String, dynamic> finalBody = {};
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ class ApiEndpoints {
|
|||||||
// Change this to your desired backend base URL (local or UAT)
|
// Change this to your desired backend base URL (local or UAT)
|
||||||
// For local Django dev use: "http://127.0.0.1:8000/api"
|
// For local Django dev use: "http://127.0.0.1:8000/api"
|
||||||
// For UAT: "https://uat.eventifyplus.com/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
|
// Auth
|
||||||
static const String register = "$baseUrl/user/register/";
|
static const String register = "$baseUrl/user/register/";
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// lib/features/events/models/event_models.dart
|
// lib/features/events/models/event_models.dart
|
||||||
|
import '../../../core/api/api_endpoints.dart';
|
||||||
|
|
||||||
class EventTypeModel {
|
class EventTypeModel {
|
||||||
final int id;
|
final int id;
|
||||||
final String name;
|
final String name;
|
||||||
@@ -6,11 +8,18 @@ class EventTypeModel {
|
|||||||
|
|
||||||
EventTypeModel({required this.id, required this.name, this.iconUrl});
|
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) {
|
factory EventTypeModel.fromJson(Map<String, dynamic> j) {
|
||||||
return EventTypeModel(
|
return EventTypeModel(
|
||||||
id: j['id'] as int,
|
id: j['id'] as int,
|
||||||
name: (j['event_type'] ?? j['name'] ?? '') as String,
|
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) {
|
factory EventImageModel.fromJson(Map<String, dynamic> j) {
|
||||||
return EventImageModel(
|
return EventImageModel(
|
||||||
isPrimary: j['is_primary'] == true,
|
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?,
|
place: (j['place'] ?? j['venue_name']) as String?,
|
||||||
isBookable: j['is_bookable'] == null ? true : (j['is_bookable'] == true || j['is_bookable'].toString().toLowerCase() == 'true'),
|
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),
|
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,
|
images: imgs,
|
||||||
importantInformation: j['important_information'] as String?,
|
importantInformation: j['important_information'] as String?,
|
||||||
venueName: j['venue_name'] as String?,
|
venueName: j['venue_name'] as String?,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// lib/features/events/services/events_service.dart
|
// lib/features/events/services/events_service.dart
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import '../../../core/api/api_client.dart';
|
import '../../../core/api/api_client.dart';
|
||||||
import '../../../core/api/api_endpoints.dart';
|
import '../../../core/api/api_endpoints.dart';
|
||||||
import '../models/event_models.dart';
|
import '../models/event_models.dart';
|
||||||
@@ -7,27 +6,62 @@ import '../models/event_models.dart';
|
|||||||
class EventsService {
|
class EventsService {
|
||||||
final ApiClient _api = ApiClient();
|
final ApiClient _api = ApiClient();
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// In-memory caches with TTL
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
static List<EventTypeModel>? _cachedTypes;
|
||||||
|
static DateTime? _typesCacheTime;
|
||||||
|
static const _typesCacheTTL = Duration(minutes: 30);
|
||||||
|
|
||||||
|
static List<EventModel>? _cachedAllEvents;
|
||||||
|
static DateTime? _eventsCacheTime;
|
||||||
|
static const _eventsCacheTTL = Duration(minutes: 5);
|
||||||
|
|
||||||
/// Get event types (POST to /events/type-list/)
|
/// Get event types (POST to /events/type-list/)
|
||||||
|
/// Cached for 30 minutes since event types rarely change.
|
||||||
Future<List<EventTypeModel>> getEventTypes() async {
|
Future<List<EventTypeModel>> getEventTypes() async {
|
||||||
|
if (_cachedTypes != null &&
|
||||||
|
_typesCacheTime != null &&
|
||||||
|
DateTime.now().difference(_typesCacheTime!) < _typesCacheTTL) {
|
||||||
|
return _cachedTypes!;
|
||||||
|
}
|
||||||
|
|
||||||
final res = await _api.post(ApiEndpoints.eventTypes, requiresAuth: false);
|
final res = await _api.post(ApiEndpoints.eventTypes, requiresAuth: false);
|
||||||
final list = <EventTypeModel>[];
|
final list = <EventTypeModel>[];
|
||||||
final data = res['event_types'] ?? res['event_types'] ?? res;
|
final data = res['event_types'] ?? res;
|
||||||
if (data is List) {
|
if (data is List) {
|
||||||
for (final e in data) {
|
for (final e in data) {
|
||||||
if (e is Map<String, dynamic>) list.add(EventTypeModel.fromJson(e));
|
if (e is Map<String, dynamic>) list.add(EventTypeModel.fromJson(e));
|
||||||
}
|
}
|
||||||
} else if (res['event_types'] is List) {
|
|
||||||
for (final e in res['event_types']) {
|
|
||||||
list.add(EventTypeModel.fromJson(Map<String, dynamic>.from(e)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_cachedTypes = list;
|
||||||
|
_typesCacheTime = DateTime.now();
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get events filtered by pincode (POST to /events/pincode-events/)
|
/// Get events filtered by pincode with pagination.
|
||||||
/// Use pincode='all' to fetch all events.
|
/// [page] starts at 1. [pageSize] defaults to 50.
|
||||||
Future<List<EventModel>> getEventsByPincode(String pincode) async {
|
/// Returns a list of events for the requested page.
|
||||||
final res = await _api.post(ApiEndpoints.eventsByPincode, body: {'pincode': pincode}, requiresAuth: false);
|
Future<List<EventModel>> 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<String, dynamic> 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 = <EventModel>[];
|
final list = <EventModel>[];
|
||||||
final events = res['events'] ?? res['data'] ?? [];
|
final events = res['events'] ?? res['data'] ?? [];
|
||||||
if (events is List) {
|
if (events is List) {
|
||||||
@@ -35,33 +69,36 @@ class EventsService {
|
|||||||
if (e is Map<String, dynamic>) list.add(EventModel.fromJson(Map<String, dynamic>.from(e)));
|
if (e is Map<String, dynamic>) list.add(EventModel.fromJson(Map<String, dynamic>.from(e)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pincode == 'all' && page == 1) {
|
||||||
|
_cachedAllEvents = list;
|
||||||
|
_eventsCacheTime = DateTime.now();
|
||||||
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Event details
|
/// Event details
|
||||||
Future<EventModel> getEventDetails(int eventId) async {
|
Future<EventModel> getEventDetails(int eventId) async {
|
||||||
final res = await _api.post(ApiEndpoints.eventDetails, body: {'event_id': eventId}, requiresAuth: false);
|
final res = await _api.post(ApiEndpoints.eventDetails, body: {'event_id': eventId}, requiresAuth: true);
|
||||||
return EventModel.fromJson(Map<String, dynamic>.from(res));
|
return EventModel.fromJson(Map<String, dynamic>.from(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Events by month and year for calendar (POST to /events/events-by-month-year/)
|
/// 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<Map<String, dynamic>> getEventsByMonthYear(String month, int year) async {
|
Future<Map<String, dynamic>> getEventsByMonthYear(String month, int year) async {
|
||||||
final res = await _api.post(ApiEndpoints.eventsByMonth, body: {'month': month, 'year': year}, requiresAuth: false);
|
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;
|
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<List<EventModel>> getEventsForDate(String date) async {
|
Future<List<EventModel>> 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');
|
final all = await getEventsByPincode('all');
|
||||||
return all.where((e) {
|
return all.where((e) {
|
||||||
try {
|
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 (_) {
|
} catch (_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -503,7 +503,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
|||||||
: (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(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id))),
|
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e))),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 6,
|
elevation: 6,
|
||||||
margin: const EdgeInsets.fromLTRB(20, 10, 20, 10),
|
margin: const EdgeInsets.fromLTRB(20, 10, 20, 10),
|
||||||
@@ -563,7 +563,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
|||||||
: (e.startDate ?? ''));
|
: (e.startDate ?? ''));
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id))),
|
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e))),
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 14),
|
margin: const EdgeInsets.fromLTRB(16, 0, 16, 14),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ class _HomeDesktopScreenState extends State<HomeDesktopScreen> {
|
|||||||
return const SettingsScreen();
|
return const SettingsScreen();
|
||||||
default:
|
default:
|
||||||
return _HomeContent(
|
return _HomeContent(
|
||||||
onEventTap: (eventId) {
|
onEventTap: (eventId, event) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: eventId)),
|
MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: eventId, initialEvent: event)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -70,7 +70,7 @@ class _HomeDesktopScreenState extends State<HomeDesktopScreen> {
|
|||||||
// Home content — hero, categories, event grid
|
// Home content — hero, categories, event grid
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
class _HomeContent extends StatefulWidget {
|
class _HomeContent extends StatefulWidget {
|
||||||
final void Function(int eventId) onEventTap;
|
final void Function(int eventId, EventModel event) onEventTap;
|
||||||
const _HomeContent({required this.onEventTap});
|
const _HomeContent({required this.onEventTap});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -319,6 +319,7 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
memCacheWidth: 1400,
|
memCacheWidth: 1400,
|
||||||
|
memCacheHeight: 800,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
color: const Color(0xFF0A0E1A),
|
color: const Color(0xFF0A0E1A),
|
||||||
),
|
),
|
||||||
@@ -527,6 +528,7 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
memCacheWidth: 1400,
|
memCacheWidth: 1400,
|
||||||
|
memCacheHeight: 800,
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Container(color: const Color(0xFF0A0E1A)),
|
Container(color: const Color(0xFF0A0E1A)),
|
||||||
@@ -573,7 +575,7 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(ctx).pop();
|
Navigator.of(ctx).pop();
|
||||||
widget.onEventTap(event.id);
|
widget.onEventTap(event.id, event);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@@ -753,7 +755,7 @@ class _HomeContentState extends State<_HomeContent>
|
|||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => widget.onEventTap(e.id),
|
onTap: () => widget.onEventTap(e.id, e),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
final EventsService _eventsService = EventsService();
|
final EventsService _eventsService = EventsService();
|
||||||
|
|
||||||
// backend-driven
|
// backend-driven
|
||||||
|
List<EventModel> _allEvents = []; // master copy, never filtered
|
||||||
List<EventModel> _events = [];
|
List<EventModel> _events = [];
|
||||||
List<EventTypeModel> _types = [];
|
List<EventTypeModel> _types = [];
|
||||||
int _selectedTypeId = -1; // -1 == All
|
int _selectedTypeId = -1; // -1 == All
|
||||||
@@ -87,7 +88,6 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
final coordMatch = RegExp(r'^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$').firstMatch(storedLocation);
|
final coordMatch = RegExp(r'^(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$').firstMatch(storedLocation);
|
||||||
if (coordMatch != null) {
|
if (coordMatch != null) {
|
||||||
_location = 'Current Location';
|
_location = 'Current Location';
|
||||||
setState(() {});
|
|
||||||
// Reverse geocode in background to get actual place name
|
// Reverse geocode in background to get actual place name
|
||||||
_reverseGeocodeAndSave(
|
_reverseGeocodeAndSave(
|
||||||
double.parse(coordMatch.group(1)!),
|
double.parse(coordMatch.group(1)!),
|
||||||
@@ -110,17 +110,19 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_types = types;
|
_types = types;
|
||||||
|
_allEvents = events;
|
||||||
_events = events;
|
_events = events;
|
||||||
_selectedTypeId = -1;
|
_selectedTypeId = -1;
|
||||||
_cachedFilteredEvents = null; // invalidate cache
|
_cachedFilteredEvents = null;
|
||||||
|
_cachedEventDates = null;
|
||||||
|
_loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
setState(() => _loading = false);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(e.toString())));
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (mounted) setState(() => _loading = false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +397,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
if (ev.id != null) {
|
if (ev.id != null) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id)));
|
Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id, initialEvent: ev)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -545,6 +547,52 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
List<EventModel>? _cachedFilteredEvents;
|
List<EventModel>? _cachedFilteredEvents;
|
||||||
String _cachedFilterKey = '';
|
String _cachedFilterKey = '';
|
||||||
|
|
||||||
|
// Cached event dates for calendar dots
|
||||||
|
Set<DateTime>? _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<EventModel> 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.
|
/// Returns the subset of [_events] that match the active date-filter chip.
|
||||||
/// Uses caching to avoid re-parsing dates on every access.
|
/// Uses caching to avoid re-parsing dates on every access.
|
||||||
List<EventModel> get _filteredEvents {
|
List<EventModel> get _filteredEvents {
|
||||||
@@ -805,7 +853,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
if (ev.id != null) {
|
if (ev.id != null) {
|
||||||
Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id)));
|
Navigator.of(context).push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id, initialEvent: ev)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -887,9 +935,11 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all event dates (start + end range) to show dots on the calendar.
|
/// Collect all event dates (start + end range) to show dots on the calendar.
|
||||||
|
/// Cached to avoid re-parsing on every calendar open.
|
||||||
Set<DateTime> get _eventDates {
|
Set<DateTime> get _eventDates {
|
||||||
|
if (_cachedEventDates != null) return _cachedEventDates!;
|
||||||
final dates = <DateTime>{};
|
final dates = <DateTime>{};
|
||||||
for (final e in _events) {
|
for (final e in _allEvents) {
|
||||||
try {
|
try {
|
||||||
final start = DateTime.parse(e.startDate);
|
final start = DateTime.parse(e.startDate);
|
||||||
final end = DateTime.parse(e.endDate);
|
final end = DateTime.parse(e.endDate);
|
||||||
@@ -901,6 +951,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
_cachedEventDates = dates;
|
||||||
return dates;
|
return dates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1303,7 +1354,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (event.id != null) {
|
if (event.id != null) {
|
||||||
Navigator.push(context,
|
Navigator.push(context,
|
||||||
MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: event.id)));
|
MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: event.id, initialEvent: event)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -1318,6 +1369,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 700,
|
memCacheWidth: 700,
|
||||||
|
memCacheHeight: 400,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (_, __) => const _HeroShimmer(radius: radius),
|
placeholder: (_, __) => const _HeroShimmer(radius: radius),
|
||||||
errorWidget: (_, __, ___) =>
|
errorWidget: (_, __, ___) =>
|
||||||
@@ -1498,18 +1550,18 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 200,
|
height: 200,
|
||||||
child: _filteredEvents.isEmpty && _loading
|
child: _allFilteredByDate.isEmpty && _loading
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: _filteredEvents.isEmpty
|
: _allFilteredByDate.isEmpty
|
||||||
? Center(child: Text(
|
? Center(child: Text(
|
||||||
_selectedDateFilter.isNotEmpty ? 'No events for "$_selectedDateFilter"' : 'No events found',
|
_selectedDateFilter.isNotEmpty ? 'No events for "$_selectedDateFilter"' : 'No events found',
|
||||||
style: const TextStyle(color: Color(0xFF9CA3AF)),
|
style: const TextStyle(color: Color(0xFF9CA3AF)),
|
||||||
))
|
))
|
||||||
: ListView.separated(
|
: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: _filteredEvents.length,
|
itemCount: _allFilteredByDate.length,
|
||||||
separatorBuilder: (_, __) => const SizedBox(width: 12),
|
separatorBuilder: (_, __) => const SizedBox(width: 12),
|
||||||
itemBuilder: (context, index) => _buildTopEventCard(_filteredEvents[index]),
|
itemBuilder: (context, index) => _buildTopEventCard(_allFilteredByDate[index]),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
@@ -1565,14 +1617,13 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Event sections by type
|
// Event sections by type — always show ALL categories
|
||||||
if (_selectedTypeId == -1) ...[
|
|
||||||
if (_loading)
|
if (_loading)
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.all(40),
|
padding: EdgeInsets.all(40),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
)
|
)
|
||||||
else if (_filteredEvents.isEmpty && _selectedDateFilter.isNotEmpty)
|
else if (_allFilteredByDate.isEmpty && _selectedDateFilter.isNotEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(40),
|
padding: const EdgeInsets.all(40),
|
||||||
child: Center(child: Text(
|
child: Center(child: Text(
|
||||||
@@ -1584,23 +1635,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
for (final t in _types)
|
for (final t in _types)
|
||||||
if (_filteredEvents.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[
|
if (_allFilteredByDate.where((e) => e.eventTypeId == t.id).isNotEmpty) ...[
|
||||||
_buildTypeSection(t),
|
_buildTypeSection(t),
|
||||||
const SizedBox(height: 18),
|
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(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
// Bottom padding for nav bar
|
// Bottom padding for nav bar
|
||||||
const SizedBox(height: 100),
|
const SizedBox(height: 100),
|
||||||
@@ -1663,7 +1703,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (event.id != null) {
|
if (event.id != null) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: event.id)));
|
Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: event.id, initialEvent: event)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -1680,6 +1720,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 300,
|
memCacheWidth: 300,
|
||||||
|
memCacheHeight: 200,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
@@ -1740,7 +1781,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
/// - If type has >= 6 events => arrange events into column groups of 3 (so visually there are 3 rows across horizontally scrollable columns).
|
/// - 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) {
|
Widget _buildTypeSection(EventTypeModel type) {
|
||||||
final theme = Theme.of(context);
|
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;
|
final n = eventsForType.length;
|
||||||
|
|
||||||
// Header row
|
// Header row
|
||||||
@@ -1855,7 +1896,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (e.id != null) Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id)));
|
if (e.id != null) Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e)));
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.symmetric(vertical: 0),
|
margin: const EdgeInsets.symmetric(vertical: 0),
|
||||||
@@ -1874,6 +1915,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: img,
|
imageUrl: img,
|
||||||
memCacheWidth: 192,
|
memCacheWidth: 192,
|
||||||
|
memCacheHeight: 192,
|
||||||
width: 96,
|
width: 96,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@@ -1924,7 +1966,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (e.id != null) Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id)));
|
if (e.id != null) Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e)));
|
||||||
},
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 220,
|
width: 220,
|
||||||
@@ -2090,7 +2132,7 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (e.id != null) Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id)));
|
if (e.id != null) Navigator.push(context, MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: e.id, initialEvent: e)));
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(bottom: 18),
|
margin: const EdgeInsets.only(bottom: 18),
|
||||||
@@ -2241,18 +2283,12 @@ class _HomeScreenState extends State<HomeScreen> with SingleTickerProviderStateM
|
|||||||
return Icons.event;
|
return Icons.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectType(int id) async {
|
void _onSelectType(int id) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedTypeId = id;
|
_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() {
|
String _getShortEmailLabel() {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import '../core/constants.dart';
|
|||||||
|
|
||||||
class LearnMoreScreen extends StatefulWidget {
|
class LearnMoreScreen extends StatefulWidget {
|
||||||
final int eventId;
|
final int eventId;
|
||||||
const LearnMoreScreen({Key? key, required this.eventId}) : super(key: key);
|
final EventModel? initialEvent;
|
||||||
|
const LearnMoreScreen({Key? key, required this.eventId, this.initialEvent}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LearnMoreScreen> createState() => _LearnMoreScreenState();
|
State<LearnMoreScreen> createState() => _LearnMoreScreenState();
|
||||||
@@ -48,8 +49,14 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_pageNotifier = ValueNotifier(0);
|
_pageNotifier = ValueNotifier(0);
|
||||||
|
if (widget.initialEvent != null) {
|
||||||
|
_event = widget.initialEvent;
|
||||||
|
_loading = false;
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _startAutoScroll());
|
||||||
|
} else {
|
||||||
_loadEvent();
|
_loadEvent();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -258,6 +265,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
|||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: heroImage,
|
imageUrl: heroImage,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
memCacheWidth: 800,
|
||||||
|
memCacheHeight: 500,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
@@ -464,6 +473,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
|||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
imageUrl: images[i],
|
imageUrl: images[i],
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
memCacheWidth: 800,
|
||||||
|
memCacheHeight: 500,
|
||||||
placeholder: (_, __) => Container(color: theme.dividerColor),
|
placeholder: (_, __) => Container(color: theme.dividerColor),
|
||||||
errorWidget: (_, __, ___) => Container(
|
errorWidget: (_, __, ___) => Container(
|
||||||
color: theme.dividerColor,
|
color: theme.dividerColor,
|
||||||
@@ -717,6 +728,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
|||||||
builder: (context, currentPage, _) => CachedNetworkImage(
|
builder: (context, currentPage, _) => CachedNetworkImage(
|
||||||
imageUrl: images[currentPage],
|
imageUrl: images[currentPage],
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
memCacheWidth: 800,
|
||||||
|
memCacheHeight: 500,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
@@ -775,6 +788,8 @@ class _LearnMoreScreenState extends State<LearnMoreScreen> {
|
|||||||
itemBuilder: (_, i) => CachedNetworkImage(
|
itemBuilder: (_, i) => CachedNetworkImage(
|
||||||
imageUrl: images[i],
|
imageUrl: images[i],
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
memCacheWidth: 800,
|
||||||
|
memCacheHeight: 500,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
placeholder: (_, __) => Container(
|
placeholder: (_, __) => Container(
|
||||||
color: theme.dividerColor,
|
color: theme.dividerColor,
|
||||||
|
|||||||
@@ -576,7 +576,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (ev.id != null) {
|
if (ev.id != null) {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id)));
|
.push(MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id, initialEvent: ev)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
@@ -1388,7 +1388,7 @@ class _ProfileScreenState extends State<ProfileScreen>
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
if (ev.id != null) {
|
if (ev.id != null) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id)),
|
MaterialPageRoute(builder: (_) => LearnMoreScreen(eventId: ev.id, initialEvent: ev)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user