From 8ceb7851e5923ec7455f2930507d53065e071e05 Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Fri, 28 Jun 2024 11:36:10 -0400 Subject: [PATCH] refactoring of my analytics controller and related flows --- assets/l10n/intl_en.arb | 2 +- assets/l10n/intl_es.arb | 2 +- lib/pages/chat_list/chat_list.dart | 2 +- .../controllers/my_analytics_controller.dart | 298 +++++++++--------- .../client_extension/client_extension.dart | 4 +- .../client_extension/space_extension.dart | 12 + .../events_extension.dart | 34 +- .../pangea_room_extension.dart | 1 + .../room_analytics_extension.dart | 34 +- .../models/analytics/analytics_event.dart | 30 -- .../models/analytics/constructs_event.dart | 15 - .../analytics/summary_analytics_event.dart | 14 - .../practice_activity_record_model.dart | 22 +- .../student_analytics/student_analytics.dart | 35 +- 14 files changed, 238 insertions(+), 267 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index f0aba0219..986cb9cb0 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3109,7 +3109,7 @@ "prettyGood": "Pretty good! Here's what I would have said.", "letMeThink": "Hmm, let's see how you did!", "clickMessageTitle": "Need help?", - "clickMessageBody": "Click messages to access definitions, translations, and audio!", + "clickMessageBody": "Click a message for language help! Click and hold to react 😀.", "understandingMessagesTitle": "Definitions and translations!", "understandingMessagesBody": "Click underlined words for definitions. Translate with message options (upper right).", "allDone": "All done!", diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index dc328294a..e771c86a3 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4512,7 +4512,7 @@ "definitions": "definiciones", "subscribedToUnlockTools": "Suscríbase para desbloquear herramientas lingüísticas, como", "clickMessageTitle": "¿Necesitas ayuda?", - "clickMessageBody": "Haga clic en los mensajes para acceder a las definiciones, traducciones y audio.", + "clickMessageBody": "¡Lame un mensaje para obtener ayuda con el idioma! Haz clic y mantén presionado para reaccionar 😀", "more": "Más", "translationTooltip": "Traducir", "audioTooltip": "Reproducir audio", diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 569eba880..89f31fd68 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -917,7 +917,7 @@ class ChatListController extends State if (mounted) { GoogleAnalytics.analyticsUserUpdate(client.userID); await pangeaController.subscriptionController.initialize(); - await pangeaController.myAnalytics.addEventsListener(); + await pangeaController.myAnalytics.initialize(); pangeaController.afterSyncAndFirstLoginInitialization(context); await pangeaController.inviteBotToExistingSpaces(); await pangeaController.setPangeaPushRules(); diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 99fdb14c7..82b7af3c7 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -1,16 +1,17 @@ import 'dart:async'; import 'dart:developer'; +<<<<<<< Updated upstream import 'package:fluffychat/pangea/constants/language_constants.dart'; +======= +>>>>>>> Stashed changes import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; -import 'package:fluffychat/pangea/controllers/base_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; +import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; @@ -18,11 +19,18 @@ import 'package:matrix/matrix.dart'; import '../extensions/client_extension/client_extension.dart'; import '../extensions/pangea_room_extension/pangea_room_extension.dart'; -// controls the sending of analytics events -class MyAnalyticsController extends BaseController { +/// handles the processing of analytics for +/// 1) messages sent by the user and +/// 2) constructs used by the user, both in sending messages and doing practice activities +class MyAnalyticsController { late PangeaController _pangeaController; Timer? _updateTimer; + + /// the max number of messages that will be cached before + /// an automatic update is triggered final int _maxMessagesCached = 10; + + /// the number of minutes before an automatic update is triggered final int _minutesBeforeUpdate = 5; /// the time since the last update that will trigger an automatic update @@ -33,41 +41,50 @@ class MyAnalyticsController extends BaseController { } /// adds the listener that handles when to run automatic updates - /// to analytics - either after a certain number of messages sent/ + /// to analytics - either after a certain number of messages sent /// received or after a certain amount of time [_timeSinceUpdate] without an update - Future addEventsListener() async { - final Client client = _pangeaController.matrixState.client; + Future initialize() async { + final lastUpdated = await _refreshAnalyticsIfOutdated(); + + // listen for new messages and updateAnalytics timer + // we are doing this in an attempt to update analytics when activitiy is low + // both in messages sent by this client and other clients that you're connected with + // doesn't account for messages sent by other clients that you're not connected with + _client.onSync.stream + .where((SyncUpdate update) => update.rooms?.join != null) + .listen((update) { + updateAnalyticsTimer(update, lastUpdated); + }); + } - // if analytics haven't been updated in the last day, update them + /// If analytics haven't been updated in the last day, update them + Future _refreshAnalyticsIfOutdated() async { DateTime? lastUpdated = await _pangeaController.analytics .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics); final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate); + if (lastUpdated?.isBefore(yesterday) ?? true) { debugPrint("analytics out-of-date, updating"); await updateAnalytics(); lastUpdated = await _pangeaController.analytics .myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics); } - - client.onSync.stream - .where((SyncUpdate update) => update.rooms?.join != null) - .listen((update) { - updateAnalyticsTimer(update, lastUpdated); - }); + return lastUpdated; } - /// given an update from sync stream, check if the update contains + Client get _client => _pangeaController.matrixState.client; + + /// Given an update from sync stream, check if the update contains /// messages for which analytics will be saved. If so, reset the timer /// and add the event ID to the cache of un-added event IDs void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) { for (final entry in update.rooms!.join!.entries) { - final Room room = - _pangeaController.matrixState.client.getRoomById(entry.key)!; + final Room room = _client.getRoomById(entry.key)!; // get the new events in this sync that are messages final List? events = entry.value.timeline?.events ?.map((event) => Event.fromMatrixEvent(event, room)) - .where((event) => eventHasAnalytics(event, lastUpdated)) + .where((event) => hasUserAnalyticsToCache(event, lastUpdated)) .toList(); // add their event IDs to the cache of un-added event IDs @@ -87,8 +104,9 @@ class MyAnalyticsController extends BaseController { } // checks if event from sync update is a message that should have analytics - bool eventHasAnalytics(Event event, DateTime? lastUpdated) { - return (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) && + bool hasUserAnalyticsToCache(Event event, DateTime? lastUpdated) { + return event.senderId == _client.userID && + (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) && event.type == EventTypes.Message && event.messageType == MessageTypes.Text && !(event.eventId.contains("web") && @@ -176,21 +194,18 @@ class MyAnalyticsController extends BaseController { } } + String? get userL2 => _pangeaController.languageController.activeL2Code(); + // top level analytics sending function. Send analytics // for each type of analytics event // to each of the applicable analytics rooms Future _updateAnalytics() async { - // if the user's l2 is not sent, don't send analytics - final String? userL2 = _pangeaController.languageController.activeL2Code(); - if (userL2 == null) { + // if missing important info, don't send analytics + if (userL2 == null || _client.userID == null) { + debugger(when: kDebugMode); return; } - // fetch a list of all the chats that the user is studying - // and a list of all the spaces in which the user is studying - await setStudentChats(); - await setStudentSpaces(); - // get the last updated time for each analytics room // and the least recent update, which will be used to determine // how far to go back in the chat history to get messages @@ -217,151 +232,128 @@ class MyAnalyticsController extends BaseController { lastUpdates.isNotEmpty ? lastUpdates.first : null; } - // for each chat the user is studying in, get all the messages - // since the least recent update analytics update, and sort them - // by their langCodes - final Map> langCodeToMsgs = - await getLangCodesToMsgs( - userL2, + final List chats = await _client.chatsImAStudentIn; + + final List recentMsgs = + await _getMessagesWithUnsavedAnalytics( l2AnalyticsLastUpdated, + chats, + ); + + final List recentActivities = + await getRecentActivities(userL2!, l2AnalyticsLastUpdated, chats); + + // FOR DISCUSSION: + // we want to make sure we save something for every message send + // however, we're currently saving analytics for messages not in the userL2 + // based on bad language detection results. maybe it would be better to + // save the analytics for these messages in the userL2 analytics room, but + // with useType of unknown + + final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!); + + // if there is no analytics room for this langCode, then user hadn't sent + // message in this language at the time of the last analytics update + // so fallback to the least recent update time + final DateTime? lastUpdated = + lastUpdatedMap[analyticsRoom.id] ?? l2AnalyticsLastUpdated; + + // final String msgLangCode = (msg.originalSent?.langCode != null && + // msg.originalSent?.langCode != LanguageKeys.unknownLanguage) + // ? msg.originalSent!.langCode + // : userL2; + + // finally, send the analytics events to the analytics room + await _sendAnalyticsEvents( + analyticsRoom, + recentMsgs, + lastUpdated, + recentActivities, ); + } - final List langCodes = langCodeToMsgs.keys.toList(); - for (final String langCode in langCodes) { - // for each of the langs that the user has sent message in, get - // the corresponding analytics room (or create it) - final Room analyticsRoom = await _pangeaController.matrixState.client - .getMyAnalyticsRoom(langCode); - - // if there is no analytics room for this langCode, then user hadn't sent - // message in this language at the time of the last analytics update - // so fallback to the least recent update time - final DateTime? lastUpdated = - lastUpdatedMap[analyticsRoom.id] ?? l2AnalyticsLastUpdated; - - // get the corresponding list of recent messages for this langCode - final List recentMsgs = - langCodeToMsgs[langCode] ?? []; - - // finally, send the analytics events to the analytics room - await sendAnalyticsEvents( - analyticsRoom, - recentMsgs, - lastUpdated, + Future> getRecentActivities( + String userL2, + DateTime? lastUpdated, + List chats, + ) async { + final List>> recentActivityFutures = []; + for (final Room chat in chats) { + recentActivityFutures.add( + chat.getEventsBySender( + type: PangeaEventTypes.activityRecord, + sender: _client.userID!, + since: lastUpdated, + ), ); } + final List> recentActivityLists = + await Future.wait(recentActivityFutures); + + return recentActivityLists + .expand((e) => e) + .map((e) => ActivityRecordResponse.fromJson(e.content)) + .toList(); } - Future>> getLangCodesToMsgs( - String userL2, + /// Returns the new messages that have not yet been saved to analytics. + /// The keys in the map correspond to different categories or groups of messages, + /// while the values are lists of [PangeaMessageEvent] objects belonging to each category. + Future> _getMessagesWithUnsavedAnalytics( DateTime? since, + List chats, ) async { - // get a map of langCodes to messages for each chat the user is studying in - final Map> langCodeToMsgs = {}; - for (final Room chat in _studentChats) { - List? recentMsgs; - try { - recentMsgs = await chat.myMessageEventsInChat( + // get the recent messages for each chat + final List>> futures = []; + for (final Room chat in chats) { + futures.add( + chat.myMessageEventsInChat( since: since, - ); - } catch (err) { - debugPrint("failed to fetch messages for chat ${chat.id}"); - continue; - } - - // sort those messages by their langCode - // langCode is hopefully based on the original sent rep, but if that - // is null or unk, it will be based on the user's current l2 - for (final msg in recentMsgs) { - final String msgLangCode = (msg.originalSent?.langCode != null && - msg.originalSent?.langCode != LanguageKeys.unknownLanguage) - ? msg.originalSent!.langCode - : userL2; - langCodeToMsgs[msgLangCode] ??= []; - langCodeToMsgs[msgLangCode]!.add(msg); - } + ), + ); } - return langCodeToMsgs; + final List> recentMsgLists = + await Future.wait(futures); + + // flatten the list of lists of messages + return recentMsgLists.expand((e) => e).toList(); } - Future sendAnalyticsEvents( + Future _sendAnalyticsEvents( Room analyticsRoom, List recentMsgs, DateTime? lastUpdated, + List recentActivities, ) async { - // remove messages that were sent before the last update - if (recentMsgs.isEmpty) return; - if (lastUpdated != null) { - recentMsgs.removeWhere( - (msg) => msg.event.originServerTs.isBefore(lastUpdated), - ); - } + final List constructContent = []; + + if (recentMsgs.isNotEmpty) { + // remove messages that were sent before the last update + + // format the analytics data + final List summaryContent = + SummaryAnalyticsModel.formatSummaryContent(recentMsgs); + // if there's new content to be sent, or if lastUpdated hasn't been + // set yet for this room, send the analytics events + if (summaryContent.isNotEmpty || lastUpdated == null) { + await analyticsRoom.sendSummaryAnalyticsEvent( + summaryContent, + ); + } - // format the analytics data - final List summaryContent = - SummaryAnalyticsModel.formatSummaryContent(recentMsgs); - final List constructContent = - ConstructAnalyticsModel.formatConstructsContent(recentMsgs); - - // if there's new content to be sent, or if lastUpdated hasn't been - // set yet for this room, send the analytics events - if (summaryContent.isNotEmpty || lastUpdated == null) { - await SummaryAnalyticsEvent.sendSummaryAnalyticsEvent( - analyticsRoom, - summaryContent, - ); + constructContent + .addAll(ConstructAnalyticsModel.formatConstructsContent(recentMsgs)); } - if (constructContent.isNotEmpty) { - await ConstructAnalyticsEvent.sendConstructsEvent( - analyticsRoom, - constructContent, - ); + if (recentActivities.isNotEmpty) { + // TODO - Concert recentActivities into list of constructUse objects. + // First, We need to get related practiceActivityEvent from timeline in order to get its related constructs. Alternatively we + // could search for completed practice activities and see which have been completed by the user. + // It's not clear which is the best approach at the moment and we should consider both. } - } - List _studentChats = []; - - Future setStudentChats() async { - final List teacherRoomIds = - await _pangeaController.matrixState.client.teacherRoomIds; - _studentChats = _pangeaController.matrixState.client.rooms - .where( - (r) => - !r.isSpace && - !r.isAnalyticsRoom && - !teacherRoomIds.contains(r.id), - ) - .toList(); - setState(data: _studentChats); - } - - List get studentChats { - try { - if (_studentChats.isNotEmpty) return _studentChats; - setStudentChats(); - return _studentChats; - } catch (err) { - debugger(when: kDebugMode); - return []; - } - } - - List _studentSpaces = []; - - Future setStudentSpaces() async { - _studentSpaces = - await _pangeaController.matrixState.client.spacesImStudyingIn; - } - - List get studentSpaces { - try { - if (_studentSpaces.isNotEmpty) return _studentSpaces; - setStudentSpaces(); - return _studentSpaces; - } catch (err) { - debugger(when: kDebugMode); - return []; - } + await analyticsRoom.sendConstructsEvent( + constructContent, + ); } } diff --git a/lib/pangea/extensions/client_extension/client_extension.dart b/lib/pangea/extensions/client_extension/client_extension.dart index 779f8ee0a..3dda99237 100644 --- a/lib/pangea/extensions/client_extension/client_extension.dart +++ b/lib/pangea/extensions/client_extension/client_extension.dart @@ -51,7 +51,9 @@ extension PangeaClient on Client { Future> get spacesImTeaching async => await _spacesImTeaching; - Future> get spacesImStudyingIn async => await _spacesImStudyingIn; + Future> get chatsImAStudentIn async => await _chatsImAStudentIn; + + Future> get spaceImAStudentIn async => await _spacesImStudyingIn; List get spacesImIn => _spacesImIn; diff --git a/lib/pangea/extensions/client_extension/space_extension.dart b/lib/pangea/extensions/client_extension/space_extension.dart index 3bef46e45..0adc70469 100644 --- a/lib/pangea/extensions/client_extension/space_extension.dart +++ b/lib/pangea/extensions/client_extension/space_extension.dart @@ -19,6 +19,18 @@ extension SpaceClientExtension on Client { return spaces; } + Future> get _chatsImAStudentIn async { + final List nowteacherRoomIds = await teacherRoomIds; + return rooms + .where( + (r) => + !r.isSpace && + !r.isAnalyticsRoom && + !nowteacherRoomIds.contains(r.id), + ) + .toList(); + } + Future> get _spacesImStudyingIn async { final List joinedSpaces = rooms .where( diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/events_extension.dart index 2d67db5b2..6cdde1ce2 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/events_extension.dart @@ -429,21 +429,27 @@ extension EventsRoomExtension on Room { Future> myMessageEventsInChat({ DateTime? since, }) async { - final List msgEvents = await getEventsBySender( - type: EventTypes.Message, - sender: client.userID!, - since: since, - ); - final Timeline timeline = await getTimeline(); - return msgEvents - .where((event) => (event.content['msgtype'] == MessageTypes.Text)) - .map((event) { - return PangeaMessageEvent( - event: event, - timeline: timeline, - ownMessage: true, + try { + final List msgEvents = await getEventsBySender( + type: EventTypes.Message, + sender: client.userID!, + since: since, ); - }).toList(); + final Timeline timeline = await getTimeline(); + return msgEvents + .where((event) => (event.content['msgtype'] == MessageTypes.Text)) + .map((event) { + return PangeaMessageEvent( + event: event, + timeline: timeline, + ownMessage: true, + ); + }).toList(); + } catch (err, s) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: s); + return []; + } } // fetch event of a certain type by a certain sender diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index 298a519ad..78ecb9cc0 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; +import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart'; diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index e3d00f8a8..04e168612 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -99,7 +99,7 @@ extension AnalyticsRoomExtension on Room { return; } - for (final Room space in (await client.spacesImStudyingIn)) { + for (final Room space in (await client.spaceImAStudentIn)) { if (space.spaceChildren.any((sc) => sc.roomId == id)) continue; await space.addAnalyticsRoomToSpace(this); } @@ -175,7 +175,7 @@ extension AnalyticsRoomExtension on Room { return; } - for (final Room space in (await client.spacesImStudyingIn)) { + for (final Room space in (await client.spaceImAStudentIn)) { await space.inviteSpaceTeachersToAnalyticsRoom(this); } } @@ -249,4 +249,34 @@ extension AnalyticsRoomExtension on Room { return creationContent?.tryGet(ModelKey.langCode) == langCode || creationContent?.tryGet(ModelKey.oldLangCode) == langCode; } + + Future sendSummaryAnalyticsEvent( + List records, + ) async { + if (records.isEmpty) return null; + + final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel( + messages: records, + ); + final String? eventId = await sendEvent( + analyticsModel.toJson(), + type: PangeaEventTypes.summaryAnalytics, + ); + return eventId; + } + + Future sendConstructsEvent( + List uses, + ) async { + if (uses.isEmpty) return null; + final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel( + uses: uses, + ); + + final String? eventId = await sendEvent( + constructsModel.toJson(), + type: PangeaEventTypes.construct, + ); + return eventId; + } } diff --git a/lib/pangea/models/analytics/analytics_event.dart b/lib/pangea/models/analytics/analytics_event.dart index 2453e62ef..7010d3591 100644 --- a/lib/pangea/models/analytics/analytics_event.dart +++ b/lib/pangea/models/analytics/analytics_event.dart @@ -1,8 +1,6 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart'; import 'package:matrix/matrix.dart'; @@ -28,32 +26,4 @@ abstract class AnalyticsEvent { } return contentCache!; } - - static List analyticsEventTypes = [ - PangeaEventTypes.summaryAnalytics, - PangeaEventTypes.construct, - ]; - - static Future sendEvent( - Room analyticsRoom, - String type, - List analyticsContent, - ) async { - String? eventId; - switch (type) { - case PangeaEventTypes.summaryAnalytics: - eventId = await SummaryAnalyticsEvent.sendSummaryAnalyticsEvent( - analyticsRoom, - analyticsContent.cast(), - ); - break; - case PangeaEventTypes.construct: - eventId = await ConstructAnalyticsEvent.sendConstructsEvent( - analyticsRoom, - analyticsContent.cast(), - ); - break; - } - return eventId; - } } diff --git a/lib/pangea/models/analytics/constructs_event.dart b/lib/pangea/models/analytics/constructs_event.dart index c2930faba..481051b1c 100644 --- a/lib/pangea/models/analytics/constructs_event.dart +++ b/lib/pangea/models/analytics/constructs_event.dart @@ -18,19 +18,4 @@ class ConstructAnalyticsEvent extends AnalyticsEvent { contentCache ??= ConstructAnalyticsModel.fromJson(event.content); return contentCache as ConstructAnalyticsModel; } - - static Future sendConstructsEvent( - Room analyticsRoom, - List uses, - ) async { - final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel( - uses: uses, - ); - - final String? eventId = await analyticsRoom.sendEvent( - constructsModel.toJson(), - type: PangeaEventTypes.construct, - ); - return eventId; - } } diff --git a/lib/pangea/models/analytics/summary_analytics_event.dart b/lib/pangea/models/analytics/summary_analytics_event.dart index e7034eaa4..a764d5597 100644 --- a/lib/pangea/models/analytics/summary_analytics_event.dart +++ b/lib/pangea/models/analytics/summary_analytics_event.dart @@ -18,18 +18,4 @@ class SummaryAnalyticsEvent extends AnalyticsEvent { contentCache ??= SummaryAnalyticsModel.fromJson(event.content); return contentCache as SummaryAnalyticsModel; } - - static Future sendSummaryAnalyticsEvent( - Room analyticsRoom, - List records, - ) async { - final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel( - messages: records, - ); - final String? eventId = await analyticsRoom.sendEvent( - analyticsModel.toJson(), - type: PangeaEventTypes.summaryAnalytics, - ); - return eventId; - } } diff --git a/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart b/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart index 3fe3e859d..e5c3a1c18 100644 --- a/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart +++ b/lib/pangea/models/practice_activities.dart/practice_activity_record_model.dart @@ -7,14 +7,14 @@ import 'dart:typed_data'; class PracticeActivityRecordModel { final String? question; - late List responses; + late List responses; PracticeActivityRecordModel({ required this.question, - List? responses, + List? responses, }) { if (responses == null) { - this.responses = List.empty(growable: true); + this.responses = List.empty(growable: true); } else { this.responses = responses; } @@ -26,7 +26,9 @@ class PracticeActivityRecordModel { return PracticeActivityRecordModel( question: json['question'] as String, responses: (json['responses'] as List) - .map((e) => ActivityResponse.fromJson(e as Map)) + .map( + (e) => ActivityRecordResponse.fromJson(e as Map), + ) .toList(), ); } @@ -55,7 +57,7 @@ class PracticeActivityRecordModel { }) { try { responses.add( - ActivityResponse( + ActivityRecordResponse( text: text, audioBytes: audioBytes, imageBytes: imageBytes, @@ -84,7 +86,7 @@ class PracticeActivityRecordModel { int get hashCode => question.hashCode ^ responses.hashCode; } -class ActivityResponse { +class ActivityRecordResponse { // the user's response // has nullable string, nullable audio bytes, nullable image bytes, and timestamp final String? text; @@ -92,15 +94,15 @@ class ActivityResponse { final Uint8List? imageBytes; final DateTime timestamp; - ActivityResponse({ + ActivityRecordResponse({ this.text, this.audioBytes, this.imageBytes, required this.timestamp, }); - factory ActivityResponse.fromJson(Map json) { - return ActivityResponse( + factory ActivityRecordResponse.fromJson(Map json) { + return ActivityRecordResponse( text: json['text'] as String?, audioBytes: json['audio'] as Uint8List?, imageBytes: json['image'] as Uint8List?, @@ -121,7 +123,7 @@ class ActivityResponse { bool operator ==(Object other) { if (identical(this, other)) return true; - return other is ActivityResponse && + return other is ActivityRecordResponse && other.text == text && other.audioBytes == audioBytes && other.imageBytes == imageBytes && diff --git a/lib/pangea/pages/analytics/student_analytics/student_analytics.dart b/lib/pangea/pages/analytics/student_analytics/student_analytics.dart index a345243ff..5c694b6ca 100644 --- a/lib/pangea/pages/analytics/student_analytics/student_analytics.dart +++ b/lib/pangea/pages/analytics/student_analytics/student_analytics.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:developer'; import 'package:fluffychat/pangea/constants/language_constants.dart'; @@ -29,49 +28,35 @@ class StudentAnalyticsPage extends StatefulWidget { class StudentAnalyticsController extends State { final PangeaController _pangeaController = MatrixState.pangeaController; AnalyticsSelected? selected; - StreamSubscription? stateSub; @override void initState() { super.initState(); - - final listFutures = [ - _pangeaController.myAnalytics.setStudentChats(), - _pangeaController.myAnalytics.setStudentSpaces(), - ]; - Future.wait(listFutures).then((_) => setState(() {})); - - stateSub = _pangeaController.myAnalytics.stateStream.listen((_) { - setState(() {}); - }); } @override void dispose() { - stateSub?.cancel(); super.dispose(); } + List _chats = []; List get chats { - if (_pangeaController.myAnalytics.studentChats.isEmpty) { - _pangeaController.myAnalytics.setStudentChats().then((_) { - if (_pangeaController.myAnalytics.studentChats.isNotEmpty) { - setState(() {}); - } + if (_chats.isEmpty) { + _pangeaController.matrixState.client.chatsImAStudentIn.then((result) { + setState(() => _chats = result); }); } - return _pangeaController.myAnalytics.studentChats; + return _chats; } + List _spaces = []; List get spaces { - if (_pangeaController.myAnalytics.studentSpaces.isEmpty) { - _pangeaController.myAnalytics.setStudentSpaces().then((_) { - if (_pangeaController.myAnalytics.studentSpaces.isNotEmpty) { - setState(() {}); - } + if (_spaces.isEmpty) { + _pangeaController.matrixState.client.spaceImAStudentIn.then((result) { + setState(() => _spaces = result); }); } - return _pangeaController.myAnalytics.studentSpaces; + return _spaces; } String? get userId {