From 3845b65cf94f87e26ff37d2d49919a487f4e1342 Mon Sep 17 00:00:00 2001 From: Nexerate Date: Sun, 20 Oct 2024 16:52:55 +0200 Subject: [PATCH] Refactor client. FIx foldouts --- lib/core/client/client.dart | 234 ++++++++---------- lib/core/models/event_attendance_model.dart | 38 +++ lib/core/models/event_model.dart | 11 + lib/main.dart | 20 +- lib/pages/event/cards/event_card_buttons.dart | 3 - lib/pages/feed/feed_page.dart | 17 +- lib/pages/menu/menu_page.dart | 7 +- lib/router.dart | 26 +- pubspec.yaml | 2 +- 9 files changed, 193 insertions(+), 165 deletions(-) create mode 100644 lib/core/models/event_attendance_model.dart diff --git a/lib/core/client/client.dart b/lib/core/client/client.dart index e21e67e..c0128eb 100644 --- a/lib/core/client/client.dart +++ b/lib/core/client/client.dart @@ -2,54 +2,17 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'package:online/core/models/hobby_model.dart'; +import 'package:http/http.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../models/event_model.dart'; import '/core/models/article_model.dart'; import '/core/models/attendee_info_model.dart'; import '/core/models/attendees_list.dart'; +import '/core/models/hobby_model.dart'; import '/core/models/user_model.dart'; import '/services/authenticator.dart'; - -/// Info about user's attendance at an event. -/// Is the event paid, has the user paid for the event, did the user show up, etc. -class EventAttendanceModel { - final int id; - final bool attended; - final String timestamp; - final bool isPaidEvent; - final bool hasPaidForEvent; - - EventAttendanceModel({ - required this.id, - required this.attended, - required this.timestamp, - required this.isPaidEvent, - required this.hasPaidForEvent, - }); - - factory EventAttendanceModel.fromJson(Map json) { - return EventAttendanceModel( - id: json['event'], - attended: json['attended'], - timestamp: json['timestamp'], - isPaidEvent: json['paid'], - hasPaidForEvent: json['has_paid'], - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - if (other is! EventAttendanceModel) return false; - if (other.runtimeType != runtimeType) return false; - return id == other.id; - } - - @override - int get hashCode => id.hashCode; -} +import '../models/event_attendance_model.dart'; +import '../models/event_model.dart'; abstract class Client { static const endpoint = 'https://old.online.ntnu.no'; @@ -63,57 +26,68 @@ abstract class Client { } static final ValueNotifier> eventsCache = ValueNotifier({}); - // static final ValueNotifier> eventsIdsCache = ValueNotifier({}); static final ValueNotifier userCache = ValueNotifier(null); static final ValueNotifier> eventAttendanceCache = ValueNotifier({}); static final ValueNotifier> hobbiesCache = ValueNotifier({}); + static ValueNotifier> articlesCache = ValueNotifier({}); - static Future?> getEvents({List pages = const [1, 2, 3, 4]}) async { - Map eventMap = {}; + static Future> fetchEventsForPage(int page) async { + String url = '$endpoint/api/v1/event/events/?page=$page'; + final response = await http.get(Uri.parse(url)); - // TODO: Optimize using Future.wait (no reason for these to happen after each other) + if (response.statusCode == 200) { + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body); + final List events = + bodyJson['results'].map((eventJson) => EventModel.fromJson(eventJson)).toList(); + + Map eventMap = {}; + for (int i = 0; i < events.length; i++) { + final event = events[i]; + final id = event.id.toString(); + eventMap.putIfAbsent(id, () => event); + } + return eventMap; + } else { + return {}; + } + } - for (int page in pages) { - String url = '$endpoint/api/v1/event/events/?page=$page'; - final response = await http.get(Uri.parse(url)); + static Future?> getEvents({List pages = const [1, 2, 3, 4]}) async { + List>> futures = pages.map((page) => fetchEventsForPage(page)).toList(); + List> results = await Future.wait(futures); - if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody); - final List events = - jsonResponse['results'].map((eventJson) => EventModel.fromJson(eventJson)).toList(); - - for (int i = 0; i < events.length; i++) { - final event = events[i]; - final id = event.id.toString(); - eventMap.putIfAbsent(id, () => event); - } - } + Map combinedEventMap = {}; + for (var result in results) { + combinedEventMap.addAll(result); } - eventsCache.value = Map.from(eventsCache.value)..addAll(eventMap); + eventsCache.value = Map.from(eventsCache.value)..addAll(combinedEventMap); return eventsCache.value; } + static Future getEventWithId(int eventId) async { + String url = '$endpoint/api/v1/event/events/$eventId'; + try { + final response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body); + return EventModel.fromJson(bodyJson); + } else { + return null; + } + } catch (error) { + print('Error fetching event with id $eventId: $error'); + return null; + } + } + static Future?> getEventsWithIds({List eventIds = const []}) async { Map eventMap = {}; - var futures = eventIds.map((eventId) { - String url = '$endpoint/api/v1/event/events/$eventId'; - return http.get(Uri.parse(url)).then((response) { - if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody); - return EventModel.fromJson(jsonResponse); - } else { - return null; - } - }).catchError((error) { - print('Error fetching event with id $eventId: $error'); - return null; - }); - }); + var futures = eventIds.map((eventId) => getEventWithId(eventId)); final results = await Future.wait(futures); @@ -143,16 +117,16 @@ abstract class Client { ); if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody); - userCache.value = UserModel.fromJson(jsonResponse); + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body); + userCache.value = UserModel.fromJson(bodyJson); return userCache.value; } return null; } - /// Get all events that user has attended or is attending + /// Get all events that user has attended or is attending. static Future> getAttendanceEvents({ required int userId, required int page, @@ -178,7 +152,7 @@ abstract class Client { if (jsonResults != null) { for (var json in jsonResults) { var event = EventAttendanceModel.fromJson(json); - if (!eventAttendanceCache.value.any((cachedEvent) => cachedEvent.id == event.id)) { + if (!eventAttendanceCache.value.any((cachedEvent) => cachedEvent.eventId == event.eventId)) { results.add(event); } } @@ -198,9 +172,9 @@ abstract class Client { ); if (response.statusCode == 200) { - final String decodedResponseBody = utf8.decode(response.bodyBytes); - final jsonResponse = jsonDecode(decodedResponseBody); - return AttendeeInfoModel.fromJson(jsonResponse); + final body = utf8.decode(response.bodyBytes); + final bodyJson = jsonDecode(body); + return AttendeeInfoModel.fromJson(bodyJson); } else { return null; } @@ -221,9 +195,9 @@ abstract class Client { ); if (response.statusCode == 200) { - final String decodedResponseBody = utf8.decode(response.bodyBytes); - final jsonResponse = jsonDecode(decodedResponseBody); - return AttendeeInfoModel.fromJson(jsonResponse); + final body = utf8.decode(response.bodyBytes); + final bodyJson = jsonDecode(body); + return AttendeeInfoModel.fromJson(bodyJson); } else { return null; } @@ -244,10 +218,10 @@ abstract class Client { ); if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody) as List; + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body) as List; - return jsonResponse.map((json) => AttendeesList.fromJson(json)).toList(); + return bodyJson.map((json) => AttendeesList.fromJson(json)).toList(); } else { return []; } @@ -268,17 +242,15 @@ abstract class Client { ); if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody) as List; + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body) as List; - return jsonResponse.map((json) => AttendeesList.fromJson(json)).toList(); + return bodyJson.map((json) => AttendeesList.fromJson(json)).toList(); } else { return []; } } - static ValueNotifier> articlesCache = ValueNotifier({}); - static Future> fetchArticles(int pageNumber) async { final articles = await fetch( '$endpoint/api/v1/articles/?ordering=-created_date&page=$pageNumber', @@ -302,48 +274,56 @@ abstract class Client { final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody); + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body); // DO NOT CHANGE. HOLY LINE - return jsonResponse['results'].map((json) => jsonReviver(json)).cast().toList(); + return bodyJson['results'].map((json) => jsonReviver(json)).cast().toList(); } return null; } + static Future _getGroupPageCount() async { + String url = '$endpoint/api/v1/hobbys/?ordering=-priority&page=1'; + Response response = await http.get(Uri.parse(url)); + + if (response.statusCode != 200) return 0; + + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body); + final totalCount = bodyJson['count']; + final pageSize = bodyJson['results'].length; + final pageCount = (totalCount / pageSize).ceil(); + + return pageCount; + } + static Future getGroups() async { Map groupMap = {}; - String initialUrl = '$endpoint/api/v1/hobbys/?ordering=-priority&page=1'; - var initialResponse = await http.get(Uri.parse(initialUrl)); - if (initialResponse.statusCode == 200) { - final initialResponseBody = utf8.decode(initialResponse.bodyBytes, allowMalformed: true); - final initialJsonResponse = jsonDecode(initialResponseBody); - final totalCount = initialJsonResponse['count']; - final pageSize = initialJsonResponse['results'].length; - final pageCount = (totalCount / pageSize).ceil(); - - List pages = List.generate(pageCount, (index) => index + 1); - - var futures = pages.map((page) { - String url = '$endpoint/api/v1/hobbys/?page=$page'; - return http.get(Uri.parse(url)); - }).toList(); - - var responses = await Future.wait(futures); - - for (var response in responses) { - if (response.statusCode == 200) { - final responseBody = utf8.decode(response.bodyBytes, allowMalformed: true); - final jsonResponse = jsonDecode(responseBody); - final groups = - jsonResponse['results'].map((hobbyJson) => GroupModel.fromJson(hobbyJson)).toList(); - - for (var group in groups) { - groupMap.putIfAbsent(group.id.toString(), () => group); - } - } + int pageCount = await _getGroupPageCount(); + + if (pageCount == 0) return; + + List pages = List.generate(pageCount, (index) => index + 1); + + var futures = pages.map((page) { + String url = '$endpoint/api/v1/hobbys/?page=$page'; + return http.get(Uri.parse(url)); + }).toList(); + + var responses = await Future.wait(futures); + + for (Response response in responses) { + if (response.statusCode != 200) continue; + + final body = utf8.decode(response.bodyBytes, allowMalformed: true); + final bodyJson = jsonDecode(body); + final groups = bodyJson['results'].map((hobbyJson) => GroupModel.fromJson(hobbyJson)).toList(); + + for (GroupModel group in groups) { + groupMap.putIfAbsent(group.id.toString(), () => group); } } diff --git a/lib/core/models/event_attendance_model.dart b/lib/core/models/event_attendance_model.dart new file mode 100644 index 0000000..18c94c3 --- /dev/null +++ b/lib/core/models/event_attendance_model.dart @@ -0,0 +1,38 @@ +/// Info about user's attendance at an event. +/// Is the event paid, has the user paid for the event, did the user show up, etc. +class EventAttendanceModel { + final int eventId; + final bool attended; + final String timestamp; + final bool isPaidEvent; + final bool hasPaidForEvent; + + EventAttendanceModel({ + required this.eventId, + required this.attended, + required this.timestamp, + required this.isPaidEvent, + required this.hasPaidForEvent, + }); + + factory EventAttendanceModel.fromJson(Map json) { + return EventAttendanceModel( + eventId: json['event'], + attended: json['attended'], + timestamp: json['timestamp'], + isPaidEvent: json['paid'], + hasPaidForEvent: json['has_paid'], + ); + } + + @override + bool operator ==(Object other) { + // if (identical(this, other)) return true; + if (other is! EventAttendanceModel) return false; + // if (other.runtimeType != runtimeType) return false; + return eventId == other.eventId; + } + + @override + int get hashCode => eventId.hashCode; +} diff --git a/lib/core/models/event_model.dart b/lib/core/models/event_model.dart index d1c3a24..fbbdf6d 100644 --- a/lib/core/models/event_model.dart +++ b/lib/core/models/event_model.dart @@ -1,5 +1,6 @@ import 'image_model.dart'; +/// Info about an event. Event title, info, organizer, location, date, etc. class EventModel { final int id; final String title; @@ -12,13 +13,23 @@ class EventModel { final String location; final int eventType; final String eventTypeDisplay; + + /// Who's responsible for organizing this event? See event_organizers.dart final int organizer; final Author? author; final List images; final List companies; + + /// Is this an event where the users have to register to attend? final bool isAttendanceEvent; + + /// Maximum number of attendees for the event (if any). final int? maxCapacity; + + /// Is there a waitlist for the event? final bool? waitlist; + + /// Number of seats taken for the event. final int? numberOfSeatsTaken; EventModel({ diff --git a/lib/main.dart b/lib/main.dart index d058215..73c0d75 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -57,14 +57,11 @@ Future main() async { await Authenticator.fetchStoredCredentials(); if (Authenticator.isLoggedIn()) { - await Client.getUserProfile(); + final user = await Client.getUserProfile(); + fetchAttendeeInfo(user!.id); } - Client.getGroups(); - //No await here, because it's not necessary to wait for this to finish - if (Authenticator.isLoggedIn()) { - fetchAttendeeInfo(); - } + Client.getGroups(); } Future _configureFirebase() async { @@ -87,18 +84,15 @@ Future _configureFirebase() async { await FirebaseMessaging.instance.getToken(); } -Future fetchAttendeeInfo() async { - final user = Client.userCache.value; - - if (user == null) return; +Future fetchAttendeeInfo(int userId) async { List attendedEventIds = []; - await Client.getAttendanceEvents(userId: user.id, page: 1); + await Client.getAttendanceEvents(userId: userId, page: 1); for (final event in Client.eventAttendanceCache.value) { - attendedEventIds.add(event.id); + attendedEventIds.add(event.eventId); } - // print('Number of pages is: $numberOfPages'); + Map? fetchedEvents = await Client.getEventsWithIds(eventIds: attendedEventIds); if (fetchedEvents != null) { allAttendedEvents = fetchedEvents.values.toList(); diff --git a/lib/pages/event/cards/event_card_buttons.dart b/lib/pages/event/cards/event_card_buttons.dart index d93a095..4688bc0 100644 --- a/lib/pages/event/cards/event_card_buttons.dart +++ b/lib/pages/event/cards/event_card_buttons.dart @@ -52,9 +52,6 @@ class _EventCardButtonsState extends State { final dir = GoRouter.of(context).routeInformationProvider.value.uri.toString(); context.go(dir); } - - // AppNavigator.replaceWithPage(EventPage(model: widget.model)); - // print("Successfully unregistered from the event"); } else { // Handle error print("Failed to unregister from the event: ${response.body}"); diff --git a/lib/pages/feed/feed_page.dart b/lib/pages/feed/feed_page.dart index d69a9df..7e3ef69 100644 --- a/lib/pages/feed/feed_page.dart +++ b/lib/pages/feed/feed_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:online/components/online_scaffold.dart'; +import 'package:online/pages/events/not_logged_in_page.dart'; import 'package:online/pages/feed/no_feed_events.dart'; +import 'package:online/services/authenticator.dart'; import '/components/animated_button.dart'; import '/core/client/client.dart'; import '/core/models/event_model.dart'; @@ -62,7 +64,7 @@ class FeedPageState extends State { final events = Client.eventAttendanceCache.value; if (events.isNotEmpty) { for (final event in events) { - attendedEventIds.add(event.id); + attendedEventIds.add(event.eventId); } } @@ -229,11 +231,20 @@ class FeedPageState extends State { } } -class FeedPageDisplay extends ScrollablePage { +class FeedPageDisplay extends StaticPage { const FeedPageDisplay({super.key}); @override Widget content(BuildContext context) { - return const FeedPage(); + return ValueListenableBuilder( + valueListenable: Authenticator.loggedIn, + builder: (context, loggedIn, child) { + if (loggedIn) { + return SingleChildScrollView(padding: EdgeInsets.zero, child: const FeedPage()); + } else { + return const NotLoggedInPage(); + } + }, + ); } } diff --git a/lib/pages/menu/menu_page.dart b/lib/pages/menu/menu_page.dart index a2d2e2c..fd51712 100644 --- a/lib/pages/menu/menu_page.dart +++ b/lib/pages/menu/menu_page.dart @@ -1,9 +1,8 @@ import 'package:appwrite/appwrite.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_platform_alert/flutter_platform_alert.dart'; import 'package:go_router/go_router.dart'; -import 'package:native_ios_dialog/native_ios_dialog.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:flutter_platform_alert/flutter_platform_alert.dart'; import '/components/animated_button.dart'; import '/components/online_scaffold.dart'; @@ -54,8 +53,8 @@ class FoldoutState extends State { } Widget header() { - return Listener( - onPointerUp: (event) { + return GestureDetector( + onTap: () { setState(() { open = !open; }); diff --git a/lib/router.dart b/lib/router.dart index 1b0ee28..1b73ead 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -3,27 +3,26 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; -import 'package:online/pages/games/bits/bits_home_page.dart'; -import 'package:online/pages/games/dice.dart'; -import 'package:online/pages/games/hundred_questions/hundred_questions_page.dart'; -import 'package:online/pages/games/roulette_page.dart'; -import 'package:online/pages/games/songs/fader_abraham.dart'; -import 'package:online/pages/games/songs/himmelseng.dart'; -import 'package:online/pages/games/songs/kamerater_hev_glasset.dart'; -import 'package:online/pages/games/songs/lambo.dart'; -import 'package:online/pages/games/songs/nu_klinger.dart'; -import 'package:online/pages/games/songs/studenter_visen.dart'; -import 'package:online/pages/games/songs/we_like_to_drink.dart'; -import 'package:online/pages/games/spin_line_page.dart'; import 'package:url_launcher/url_launcher.dart'; +import '/pages/games/bits/bits_home_page.dart'; +import '/pages/games/dice.dart'; +import '/pages/games/hundred_questions/hundred_questions_page.dart'; +import '/pages/games/roulette_page.dart'; +import '/pages/games/songs/fader_abraham.dart'; +import '/pages/games/songs/himmelseng.dart'; +import '/pages/games/songs/kamerater_hev_glasset.dart'; +import '/pages/games/songs/lambo.dart'; +import '/pages/games/songs/nu_klinger.dart'; +import '/pages/games/songs/studenter_visen.dart'; +import '/pages/games/songs/we_like_to_drink.dart'; +import '/pages/games/spin_line_page.dart'; import 'components/animated_button.dart'; import 'components/navbar.dart'; import 'core/client/client.dart'; import 'pages/article/article_page.dart'; import 'pages/event/event_page.dart'; import 'pages/events/events_page.dart'; -import 'pages/events/not_logged_in_page.dart'; import 'pages/feed/feed_page.dart'; import 'pages/games/games_page.dart'; import 'pages/hobbies/hobby_page.dart'; @@ -31,7 +30,6 @@ import 'pages/home/home_page.dart'; import 'pages/home/info_page.dart'; import 'pages/menu/menu_page.dart'; import 'pages/profile/profile_page.dart'; -import 'services/authenticator.dart'; import 'theme/theme.dart'; GlobalKey rootNavigator = GlobalKey(); diff --git a/pubspec.yaml b/pubspec.yaml index 07b516e..81d1e75 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: online description: Online App publish_to: "none" -version: 1.1.5 +version: 1.1.6 environment: sdk: ">=3.1.0 <4.0.0"