diff --git a/lib/main.dart b/lib/main.dart index 9f5e656bd..847b012b2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,7 +23,7 @@ void main() async { // #Pangea try { - await dotenv.load(fileName: ".env"); + await dotenv.load(fileName: ".env.local_choreo"); } catch (e) { Logs().e('Failed to load .env file', e); } diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 091b35047..071c5f10c 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -15,8 +15,8 @@ import 'package:fluffychat/pages/chat/event_info_dialog.dart'; import 'package:fluffychat/pages/chat/recording_dialog.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/message_mode_enum.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; @@ -679,18 +679,15 @@ class ChatController extends State ); if (msgEventId != null) { - pangeaController.myAnalytics.setState( + pangeaController.putAnalytics.setState( AnalyticsStream( eventId: msgEventId, roomId: room.id, - constructs: [ - ...(choreo!.grammarConstructUses(metadata: metadata)), - ...(originalSent!.vocabUses( - choreo: choreo, - tokens: tokensSent!.tokens, - metadata: metadata, - )), - ], + constructs: originalSent!.vocabUses( + choreo: choreo, + tokens: tokensSent!.tokens, + metadata: metadata, + ), origin: AnalyticsUpdateOrigin.sendMessage, ), ); diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index e3df6a0e2..7b2a3cf2b 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -9,7 +9,7 @@ import 'package:fluffychat/pages/chat/pinned_events.dart'; import 'package:fluffychat/pages/chat/reply_display.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart'; import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/widgets/animations/gain_points.dart'; import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart'; diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 2d1302993..770d9204f 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -1016,8 +1016,9 @@ class ChatListController extends State } // #Pangea - MatrixState.pangeaController.myAnalytics.initialize(); - MatrixState.pangeaController.analytics.initialize(); + //@ggurdin why is are these two initialized separately? why not in the _initPangeaControllers? + MatrixState.pangeaController.putAnalytics.initialize(); + MatrixState.pangeaController.getAnalytics.initialize(); await _initPangeaControllers(client); // Pangea# if (!mounted) return; diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index fce7c79ed..f2d3e1ed4 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -3,7 +3,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/enum/edit_type.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; @@ -318,7 +318,7 @@ class ITController { .toList(); // Save those choices' tokens to local construct analytics as ignored tokens - choreographer.pangeaController.myAnalytics.addDraftUses( + choreographer.pangeaController.putAnalytics.addDraftUses( ignoredTokens ?? [], choreographer.roomId, ConstructUseTypeEnum.ignIt, diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 76050b226..ff7ae8d31 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -7,7 +7,7 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart'; import 'package:fluffychat/pangea/choreographer/widgets/translation_finished_flow.dart'; import 'package:fluffychat/pangea/constants/choreo_constants.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; @@ -369,7 +369,7 @@ class ITChoices extends StatelessWidget { ); } if (!continuance.wasClicked) { - controller.choreographer.pangeaController.myAnalytics.addDraftUses( + controller.choreographer.pangeaController.putAnalytics.addDraftUses( continuance.tokens, controller.choreographer.roomId, continuance.level > 1 diff --git a/lib/pangea/controllers/get_analytics_controller.dart b/lib/pangea/controllers/get_analytics_controller.dart index 1f891ccd9..dba450091 100644 --- a/lib/pangea/controllers/get_analytics_controller.dart +++ b/lib/pangea/controllers/get_analytics_controller.dart @@ -4,8 +4,8 @@ import 'dart:math'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/local.key.dart'; import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; @@ -70,10 +70,10 @@ class GetAnalyticsController { void initialize() { _analyticsUpdateSubscription ??= _pangeaController - .myAnalytics.analyticsUpdateStream.stream + .putAnalytics.analyticsUpdateStream.stream .listen(onAnalyticsUpdate); - _pangeaController.myAnalytics.lastUpdatedCompleter.future.then((_) { + _pangeaController.putAnalytics.lastUpdatedCompleter.future.then((_) { getConstructs().then((_) => updateAnalyticsStream()); }); } @@ -127,9 +127,10 @@ class GetAnalyticsController { uses: constructs, type: ConstructTypeEnum.vocab, ); + final errors = ConstructListModel( uses: constructs, - type: ConstructTypeEnum.grammar, + type: ConstructTypeEnum.morph, ); return words.points + errors.points; } @@ -168,7 +169,7 @@ class GetAnalyticsController { return formattedCache; } catch (err) { // if something goes wrong while trying to format the local data, clear it - _pangeaController.myAnalytics + _pangeaController.putAnalytics .clearMessagesSinceUpdate(clearDrafts: true); return {}; } @@ -205,7 +206,7 @@ class GetAnalyticsController { await client.roomsLoading; // don't try to get constructs until last updated time has been loaded - await _pangeaController.myAnalytics.lastUpdatedCompleter.future; + await _pangeaController.putAnalytics.lastUpdatedCompleter.future; // if forcing a refreshing, clear the cache if (forceUpdate) _cache.clear(); @@ -273,6 +274,9 @@ class GetAnalyticsController { /// Filter out constructs that are not relevant to the user, specifically those from /// rooms in which the user is a teacher and those that are interative translation span constructs + /// @ggurdin - is this still relevant now that we're not doing grammar constructs? + /// maybe it should actually be filtering all grammar uses, though this is maybe more efficiently done + /// in the fromJson of the model reading the event content, then maybe we can get rid of that enum entry entirely Future> filterConstructs({ required List unfilteredConstructs, }) async { @@ -295,7 +299,7 @@ class GetAnalyticsController { ); if (index > -1) { - final DateTime? lastUpdated = _pangeaController.myAnalytics.lastUpdated; + final DateTime? lastUpdated = _pangeaController.putAnalytics.lastUpdated; if (_cache[index].needsUpdate(lastUpdated)) { _cache.removeAt(index); return null; diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart deleted file mode 100644 index 29cbef9ea..000000000 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ /dev/null @@ -1,535 +0,0 @@ -import 'dart:async'; - -import 'package:fluffychat/pangea/constants/match_rule_ids.dart'; -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:flutter/foundation.dart'; -import 'package:matrix/matrix.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; - -import '../constants/class_default_values.dart'; -import '../extensions/client_extension/client_extension.dart'; -import '../extensions/pangea_room_extension/pangea_room_extension.dart'; -import 'base_controller.dart'; -import 'pangea_controller.dart'; - -// controls the fetching of analytics data -class AnalyticsController extends BaseController { - late PangeaController _pangeaController; - final List _cachedConstructs = []; - - AnalyticsController(PangeaController pangeaController) : super() { - _pangeaController = pangeaController; - } - - String get langCode => - _pangeaController.languageController.userL2?.langCode ?? - _pangeaController.pLanguageStore.targetOptions.first.langCode; - - // String get _analyticsTimeSpanKey => "ANALYTICS_TIME_SPAN_KEY"; - - // TimeSpan get currentAnalyticsTimeSpan { - // try { - // final String? str = _pangeaController.pStoreService.read( - // _analyticsTimeSpanKey, - // ); - // return str != null - // ? TimeSpan.values.firstWhere((e) { - // final spanString = e.toString(); - // return spanString == str; - // }) - // : ClassDefaultValues.defaultTimeSpan; - // } catch (err) { - // debugger(when: kDebugMode); - // return ClassDefaultValues.defaultTimeSpan; - // } - // } - - // Future setCurrentAnalyticsTimeSpan(TimeSpan timeSpan) async { - // await _pangeaController.pStoreService.save( - // _analyticsTimeSpanKey, - // timeSpan.toString(), - // ); - // setState(); - // } - - // String get _analyticsSpaceLangKey => "ANALYTICS_SPACE_LANG_KEY"; - - // LanguageModel get currentAnalyticsLang { - // try { - // final String? str = _pangeaController.pStoreService.read( - // _analyticsSpaceLangKey, - // ); - // return str != null - // ? PangeaLanguage.byLangCode(str) - // : _pangeaController.languageController.userL2 ?? - // _pangeaController.pLanguageStore.targetOptions.first; - // } catch (err) { - // debugger(when: kDebugMode); - // return _pangeaController.pLanguageStore.targetOptions.first; - // } - // } - - // Future setCurrentAnalyticsLang(LanguageModel lang) async { - // await _pangeaController.pStoreService.save( - // _analyticsSpaceLangKey, - // lang.langCode, - // ); - // setState(); - // } - - /// Get the last time the user updated their analytics. - /// Tries to get the last time the user updated analytics for their current L2. - /// If there isn't yet an analytics room reacted for their L2, checks if the - /// user has any other analytics rooms and returns the most recent update time. - Future myAnalyticsLastUpdated() async { - final List analyticsRooms = - _pangeaController.matrixState.client.allMyAnalyticsRooms; - - final Map langCodeLastUpdates = {}; - for (final Room analyticsRoom in analyticsRooms) { - final String? roomLang = analyticsRoom.madeForLang; - if (roomLang == null) continue; - final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated( - _pangeaController.matrixState.client.userID!, - ); - if (lastUpdated != null) { - langCodeLastUpdates[roomLang] = lastUpdated; - } - } - - if (langCodeLastUpdates.isEmpty) return null; - final String? l2Code = - _pangeaController.languageController.userL2?.langCode; - if (l2Code != null && langCodeLastUpdates.containsKey(l2Code)) { - return langCodeLastUpdates[l2Code]; - } - return langCodeLastUpdates.values.reduce( - (check, mostRecent) => check.isAfter(mostRecent) ? check : mostRecent, - ); - } - - /// check if any students have recently updated their analytics - /// if any have, then the cache needs to be updated - Future spaceAnalyticsLastUpdated( - Room space, - ) async { - await space.requestParticipants(); - - final List> lastUpdatedFutures = []; - for (final student in space.students) { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(langCode, student.id); - if (analyticsRoom == null) continue; - lastUpdatedFutures.add( - analyticsRoom.analyticsLastUpdated(student.id), - ); - } - - final List lastUpdatedWithNulls = - await Future.wait(lastUpdatedFutures); - final List lastUpdates = - lastUpdatedWithNulls.where((e) => e != null).cast().toList(); - if (lastUpdates.isNotEmpty) { - return lastUpdates.reduce( - (check, mostRecent) => check.isAfter(mostRecent) ? check : mostRecent, - ); - } - return null; - } - - Future> allMyConstructs( - TimeSpan timeSpan, - ) async { - final Room? analyticsRoom = - _pangeaController.matrixState.client.analyticsRoomLocal(langCode); - if (analyticsRoom == null) return []; - - final List? roomEvents = - (await analyticsRoom.getAnalyticsEvents( - since: timeSpan.cutOffDate, - userId: _pangeaController.matrixState.client.userID!, - )) - ?.cast(); - final List allConstructs = roomEvents ?? []; - - return allConstructs - .where((construct) => construct.content.uses.isNotEmpty) - .toList(); - } - - Future> allSpaceMemberConstructs( - Room space, - TimeSpan timeSpan, - ) async { - await space.requestParticipants(); - final List constructEvents = []; - for (final student in space.students) { - final Room? analyticsRoom = _pangeaController.matrixState.client - .analyticsRoomLocal(langCode, student.id); - if (analyticsRoom != null) { - final List? roomEvents = - (await analyticsRoom.getAnalyticsEvents( - since: timeSpan.cutOffDate, - userId: student.id, - )) - ?.cast(); - constructEvents.addAll(roomEvents ?? []); - } - } - - final List spaceChildrenIds = space.allSpaceChildRoomIds; - final List allConstructs = []; - for (final constructEvent in constructEvents) { - constructEvent.content.uses.removeWhere( - (use) => !spaceChildrenIds.contains(use.chatId), - ); - - if (constructEvent.content.uses.isNotEmpty) { - allConstructs.add(constructEvent); - } - } - - return allConstructs; - } - - List filterStudentConstructs( - List unfilteredConstructs, - String? studentId, - ) { - final List filtered = - List.from(unfilteredConstructs); - filtered.removeWhere((element) => element.event.senderId != studentId); - return filtered; - } - - List filterRoomConstructs( - List unfilteredConstructs, - String? roomID, - ) { - final List filtered = [...unfilteredConstructs]; - for (final construct in filtered) { - construct.content.uses.removeWhere((u) => u.chatId != roomID); - } - return filtered; - } - - Future> filterPrivateChatConstructs( - List unfilteredConstructs, - Room space, - ) async { - final List privateChatIds = space.allSpaceChildRoomIds; - final resp = await space.client.getSpaceHierarchy(space.id); - final List chatIds = resp.rooms.map((room) => room.roomId).toList(); - for (final id in chatIds) { - privateChatIds.removeWhere((e) => e == id); - } - final List filtered = - List.from(unfilteredConstructs); - for (final construct in filtered) { - construct.content.uses.removeWhere( - (use) => !privateChatIds.contains(use.chatId), - ); - } - return filtered; - } - - Future> filterSpaceConstructs( - List unfilteredConstructs, - Room space, - ) async { - final resp = await space.client.getSpaceHierarchy(space.id); - final List chatIds = resp.rooms.map((room) => room.roomId).toList(); - final List filtered = - List.from(unfilteredConstructs); - - for (final construct in filtered) { - construct.content.uses.removeWhere( - (use) => !chatIds.contains(use.chatId), - ); - } - - return filtered; - } - - List? getConstructsLocal({ - required TimeSpan timeSpan, - required AnalyticsSelected defaultSelected, - AnalyticsSelected? selected, - DateTime? lastUpdated, - ConstructTypeEnum? constructType, - }) { - final index = _cachedConstructs.indexWhere( - (e) => - e.timeSpan == timeSpan && - e.type == constructType && - e.defaultSelected.id == defaultSelected.id && - e.defaultSelected.type == defaultSelected.type && - e.selected?.id == selected?.id && - e.selected?.type == selected?.type && - e.langCode == langCode, - ); - - if (index > -1) { - if (_cachedConstructs[index].needsUpdate(lastUpdated)) { - _cachedConstructs.removeAt(index); - return null; - } - return _cachedConstructs[index].events; - } - - return null; - } - - void cacheConstructs({ - required List events, - required AnalyticsSelected defaultSelected, - required TimeSpan timeSpan, - AnalyticsSelected? selected, - ConstructTypeEnum? constructType, - }) { - final entry = ConstructCacheEntry( - timeSpan: timeSpan, - type: constructType, - events: List.from(events), - defaultSelected: defaultSelected, - selected: selected, - langCode: langCode, - ); - _cachedConstructs.add(entry); - } - - Future> getMyConstructs({ - required AnalyticsSelected defaultSelected, - required TimeSpan timeSpan, - ConstructTypeEnum? constructType, - AnalyticsSelected? selected, - }) async { - final List unfilteredConstructs = - await allMyConstructs(timeSpan); - - final Room? space = selected?.type == AnalyticsEntryType.space - ? _pangeaController.matrixState.client.getRoomById(selected!.id) - : null; - - return filterConstructs( - unfilteredConstructs: unfilteredConstructs, - space: space, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: timeSpan, - ); - } - - Future> getSpaceConstructs({ - required Room space, - required AnalyticsSelected defaultSelected, - required TimeSpan timeSpan, - AnalyticsSelected? selected, - ConstructTypeEnum? constructType, - }) async { - final List unfilteredConstructs = - await allSpaceMemberConstructs( - space, - timeSpan, - ); - - return filterConstructs( - unfilteredConstructs: unfilteredConstructs, - space: space, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: timeSpan, - ); - } - - Future> filterConstructs({ - required List unfilteredConstructs, - required AnalyticsSelected defaultSelected, - required TimeSpan timeSpan, - Room? space, - AnalyticsSelected? selected, - }) async { - if ([AnalyticsEntryType.privateChats, AnalyticsEntryType.space] - .contains(selected?.type)) { - assert(space != null); - } - - for (int i = 0; i < unfilteredConstructs.length; i++) { - final construct = unfilteredConstructs[i]; - construct.content.uses.removeWhere( - (use) => use.timeStamp.isBefore(timeSpan.cutOffDate), - ); - } - - unfilteredConstructs.removeWhere((e) => e.content.uses.isEmpty); - - switch (selected?.type) { - case null: - return unfilteredConstructs; - case AnalyticsEntryType.student: - if (defaultSelected.type != AnalyticsEntryType.space) { - throw Exception( - "student filtering not available for default filter ${defaultSelected.type}", - ); - } - return filterStudentConstructs(unfilteredConstructs, selected!.id); - case AnalyticsEntryType.room: - return filterRoomConstructs(unfilteredConstructs, selected?.id); - case AnalyticsEntryType.privateChats: - return defaultSelected.type == AnalyticsEntryType.student - ? throw "private chat filtering not available for my analytics" - : await filterPrivateChatConstructs(unfilteredConstructs, space!); - case AnalyticsEntryType.space: - return await filterSpaceConstructs(unfilteredConstructs, space!); - default: - throw Exception("invalid filter type - ${selected?.type}"); - } - } - - Future?> getConstructs({ - required AnalyticsSelected defaultSelected, - required TimeSpan timeSpan, - AnalyticsSelected? selected, - bool removeIT = true, - bool forceUpdate = false, - ConstructTypeEnum? constructType, - }) async { - debugPrint("getting constructs"); - await _pangeaController.matrixState.client.roomsLoading; - - Room? space; - if (defaultSelected.type == AnalyticsEntryType.space) { - space = _pangeaController.matrixState.client.getRoomById( - defaultSelected.id, - ); - if (space == null) { - ErrorHandler.logError( - m: "space not found in setConstructs", - data: { - "defaultSelected": defaultSelected, - "selected": selected, - }, - ); - return []; - } - } - - DateTime? lastUpdated; - if (defaultSelected.type != AnalyticsEntryType.space) { - // if default selected view is my analytics, check for the last - // time the logged in user updated their analytics events - // this gets passed to getAnalyticsLocal to determine if the cached - // entry is out-of-date - lastUpdated = await myAnalyticsLastUpdated(); - } else { - // else, get the last time a student in the space updated their analytics - lastUpdated = await spaceAnalyticsLastUpdated( - space!, - ); - } - - final List? local = getConstructsLocal( - timeSpan: timeSpan, - constructType: constructType, - defaultSelected: defaultSelected, - selected: selected, - lastUpdated: lastUpdated, - ); - if (local != null && !forceUpdate) { - debugPrint("returning local constructs"); - return local; - } - debugPrint("fetching new constructs"); - - final filteredConstructs = space == null - ? await getMyConstructs( - constructType: constructType, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: timeSpan, - ) - : await getSpaceConstructs( - constructType: constructType, - space: space, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: timeSpan, - ); - - if (removeIT) { - for (final construct in filteredConstructs) { - construct.content.uses.removeWhere( - (element) => - element.lemma == "Try interactive translation" || - element.lemma == "itStart" || - element.lemma == MatchRuleIds.interactiveTranslation, - ); - } - } - - if (local == null) { - cacheConstructs( - constructType: constructType, - events: filteredConstructs, - defaultSelected: defaultSelected, - selected: selected, - timeSpan: timeSpan, - ); - } - - return filteredConstructs; - } -} - -abstract class CacheEntry { - final String langCode; - final TimeSpan timeSpan; - final AnalyticsSelected defaultSelected; - AnalyticsSelected? selected; - late final DateTime _createdAt; - - CacheEntry({ - required this.timeSpan, - required this.defaultSelected, - required this.langCode, - this.selected, - }) { - _createdAt = DateTime.now(); - } - - bool get isExpired => - DateTime.now().difference(_createdAt).inMinutes > - ClassDefaultValues.minutesDelayToMakeNewChartAnalytics; - - bool needsUpdate(DateTime? lastEventUpdated) { - // cache entry is invalid if it's older than the last event update - // if lastEventUpdated is null, that would indicate that no events - // of this type have been sent to the room. In this case, there - // shouldn't be any cached data. - if (lastEventUpdated == null) { - Sentry.addBreadcrumb( - Breadcrumb(message: "lastEventUpdated is null in needsUpdate"), - ); - return false; - } - return _createdAt.isBefore(lastEventUpdated); - } -} - -class ConstructCacheEntry extends CacheEntry { - final ConstructTypeEnum? type; - final List events; - - ConstructCacheEntry({ - required this.events, - required super.timeSpan, - required super.langCode, - required super.defaultSelected, - this.type, - super.selected, - }); -} diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index c24e8b3d2..e0b33b054 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -12,10 +12,10 @@ import 'package:fluffychat/pangea/controllers/language_controller.dart'; import 'package:fluffychat/pangea/controllers/language_detection_controller.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/controllers/message_data_controller.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/permissions_controller.dart'; import 'package:fluffychat/pangea/controllers/practice_activity_generation_controller.dart'; import 'package:fluffychat/pangea/controllers/practice_activity_record_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/speech_to_text_controller.dart'; import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart'; @@ -44,11 +44,12 @@ class PangeaController { late LanguageController languageController; late ClassController classController; late PermissionsController permissionsController; - // late AnalyticsController analytics; - late GetAnalyticsController analytics; - late MyAnalyticsController myAnalytics; + late GetAnalyticsController getAnalytics; + late PutAnalyticsController putAnalytics; late WordController wordNet; late MessageDataController messageData; + + // TODO: make these static so we can remove from here late ContextualDefinitionController definitions; late ITFeedbackController itFeedback; late InstructionsController instructions; @@ -93,9 +94,8 @@ class PangeaController { languageController = LanguageController(this); classController = ClassController(this); permissionsController = PermissionsController(this); - // analytics = AnalyticsController(this); - analytics = GetAnalyticsController(this); - myAnalytics = MyAnalyticsController(this); + getAnalytics = GetAnalyticsController(this); + putAnalytics = PutAnalyticsController(this); messageData = MessageDataController(this); wordNet = WordController(this); definitions = ContextualDefinitionController(this); @@ -146,13 +146,13 @@ class PangeaController { case LoginState.loggedOut: case LoginState.softLoggedOut: // Reset cached analytics data - MatrixState.pangeaController.myAnalytics.dispose(); - MatrixState.pangeaController.analytics.dispose(); + MatrixState.pangeaController.putAnalytics.dispose(); + MatrixState.pangeaController.getAnalytics.dispose(); break; case LoginState.loggedIn: // Initialize analytics data - MatrixState.pangeaController.myAnalytics.initialize(); - MatrixState.pangeaController.analytics.initialize(); + MatrixState.pangeaController.putAnalytics.initialize(); + MatrixState.pangeaController.getAnalytics.initialize(); break; } if (state != LoginState.loggedIn) { @@ -169,28 +169,6 @@ class PangeaController { GoogleAnalytics.analyticsUserUpdate(matrixState.client.userID); } - // void startChatWithBotIfNotPresent() { - // Future.delayed(const Duration(milliseconds: 5000), () async { - // try { - // if (pStoreService.read("started_bot_chat", addClientIdToKey: false) ?? - // false) { - // return; - // } - // await pStoreService.save("started_bot_chat", true, - // addClientIdToKey: false); - // final rooms = matrixState.client.rooms; - - // await matrixState.client.startDirectChat( - // BotName.byEnvironment, - // enableEncryption: false, - // ); - // } catch (err, stack) { - // debugger(when: kDebugMode); - // ErrorHandler.logError(e: err, s: stack); - // } - // }); - // } - void startChatWithBotIfNotPresent() { Future.delayed(const Duration(milliseconds: 10000), () async { // check if user is logged in diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/put_analytics_controller.dart similarity index 86% rename from lib/pangea/controllers/my_analytics_controller.dart rename to lib/pangea/controllers/put_analytics_controller.dart index 9c7523991..37768cdf0 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/put_analytics_controller.dart @@ -19,7 +19,7 @@ enum AnalyticsUpdateType { server, local } /// 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 extends BaseController { +class PutAnalyticsController extends BaseController { late PangeaController _pangeaController; CachedStreamController analyticsUpdateStream = CachedStreamController(); @@ -47,13 +47,13 @@ class MyAnalyticsController extends BaseController { /// the time since the last update that will trigger an automatic update final Duration _timeSinceUpdate = const Duration(days: 1); - MyAnalyticsController(PangeaController pangeaController) { + PutAnalyticsController(PangeaController pangeaController) { _pangeaController = pangeaController; } void initialize() { - // Listen to a stream that provides the eventIDs - // of new messages sent by the logged in user + // Listen for calls to setState on the analytics stream + // and update the analytics room if necessary _analyticsStream ??= stateStream.listen((data) => _onNewAnalyticsData(data)); @@ -79,7 +79,7 @@ class MyAnalyticsController extends BaseController { try { // if lastUpdated hasn't been set yet, set it lastUpdated ??= - await _pangeaController.analytics.myAnalyticsLastUpdated(); + await _pangeaController.getAnalytics.myAnalyticsLastUpdated(); } catch (err, s) { ErrorHandler.logError( s: s, @@ -100,8 +100,9 @@ class MyAnalyticsController extends BaseController { } } - /// Given the data from a newly sent message, format and cache - /// the message's construct data locally and reset the update timer + /// Given new construct uses, format and cache + /// the data locally and reset the update timer + /// Decide whether to update the analytics room void _onNewAnalyticsData(AnalyticsStream data) { final List constructs = _getDraftUses(data.roomId); @@ -110,25 +111,24 @@ class MyAnalyticsController extends BaseController { final String eventID = data.eventId; final String roomID = data.roomId; - _pangeaController.analytics - .filterConstructs(unfilteredConstructs: constructs) - .then((filtered) { - for (final use in filtered) { + if (kDebugMode) { + for (final use in constructs) { debugPrint( "_onNewAnalyticsData filtered use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}", ); } - if (filtered.isEmpty) return; + } - final level = _pangeaController.analytics.level; + if (constructs.isEmpty) return; - _addLocalMessage(eventID, filtered).then( - (_) { - _clearDraftUses(roomID); - _decideWhetherToUpdateAnalyticsRoom(level, data.origin); - }, - ); - }); + final level = _pangeaController.getAnalytics.level; + + _addLocalMessage(eventID, constructs).then( + (_) { + _clearDraftUses(roomID); + _decideWhetherToUpdateAnalyticsRoom(level, data.origin); + }, + ); } void addDraftUses( @@ -142,8 +142,12 @@ class MyAnalyticsController extends BaseController { timeStamp: DateTime.now(), ); - final uses = tokens - .where((token) => token.lemma.saveVocab) + // we only save those with saveVocab == true + final tokensToSave = + tokens.where((token) => token.lemma.saveVocab).toList(); + + // get all our vocab constructs + final uses = tokensToSave .map( (token) => OneConstructUse( useType: useType, @@ -155,7 +159,8 @@ class MyAnalyticsController extends BaseController { ) .toList(); - for (final token in tokens) { + // get all our grammar constructs + for (final token in tokensToSave) { for (final entry in token.morph.entries) { uses.add( OneConstructUse( @@ -177,19 +182,19 @@ class MyAnalyticsController extends BaseController { } } - final level = _pangeaController.analytics.level; + final level = _pangeaController.getAnalytics.level; _addLocalMessage('draft$roomID', uses).then( (_) => _decideWhetherToUpdateAnalyticsRoom(level, origin), ); } List _getDraftUses(String roomID) { - final currentCache = _pangeaController.analytics.messagesSinceUpdate; + final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate; return currentCache['draft$roomID'] ?? []; } void _clearDraftUses(String roomID) { - final currentCache = _pangeaController.analytics.messagesSinceUpdate; + final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate; currentCache.remove('draft$roomID'); _setMessagesSinceUpdate(currentCache); } @@ -201,7 +206,7 @@ class MyAnalyticsController extends BaseController { List constructs, ) async { try { - final currentCache = _pangeaController.analytics.messagesSinceUpdate; + final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate; constructs.addAll(currentCache[cacheKey] ?? []); currentCache[cacheKey] = constructs; @@ -231,14 +236,14 @@ class MyAnalyticsController extends BaseController { sendLocalAnalyticsToAnalyticsRoom(); }); - if (_pangeaController.analytics.messagesSinceUpdate.length > + if (_pangeaController.getAnalytics.messagesSinceUpdate.length > _maxMessagesCached) { debugPrint("reached max messages, updating"); sendLocalAnalyticsToAnalyticsRoom(); return; } - final int newLevel = _pangeaController.analytics.level; + final int newLevel = _pangeaController.getAnalytics.level; newLevel > prevLevel ? sendLocalAnalyticsToAnalyticsRoom() : analyticsUpdateStream.add( @@ -253,7 +258,7 @@ class MyAnalyticsController extends BaseController { return; } - final localCache = _pangeaController.analytics.messagesSinceUpdate; + final localCache = _pangeaController.getAnalytics.messagesSinceUpdate; final draftKeys = localCache.keys.where((key) => key.startsWith('draft')); if (draftKeys.isEmpty) { _pangeaController.pStoreService.delete(PLocalKey.messagesSinceUpdate); @@ -328,7 +333,7 @@ class MyAnalyticsController extends BaseController { /// The analytics room is determined based on the user's current target language. Future _updateAnalytics() async { // if there's no cached construct data, there's nothing to send - final cachedConstructs = _pangeaController.analytics.messagesSinceUpdate; + final cachedConstructs = _pangeaController.getAnalytics.messagesSinceUpdate; final bool onlyDraft = cachedConstructs.length == 1 && cachedConstructs.keys.single.startsWith('draft'); if (cachedConstructs.isEmpty || onlyDraft) return; @@ -341,7 +346,7 @@ class MyAnalyticsController extends BaseController { // and send cached analytics data to the room await analyticsRoom?.sendConstructsEvent( - _pangeaController.analytics.locallyCachedSentConstructs, + _pangeaController.getAnalytics.locallyCachedSentConstructs, ); } } diff --git a/lib/pangea/enum/construct_type_enum.dart b/lib/pangea/enum/construct_type_enum.dart index 2b346b235..fe70d27ab 100644 --- a/lib/pangea/enum/construct_type_enum.dart +++ b/lib/pangea/enum/construct_type_enum.dart @@ -1,16 +1,19 @@ +import 'dart:developer'; + import 'package:fluffychat/pangea/constants/analytics_constants.dart'; +import 'package:flutter/foundation.dart'; enum ConstructTypeEnum { - grammar, + /// for vocabulary words vocab, + + /// for morphs, actually called "Grammar" in the UI... :P morph, } extension ConstructExtension on ConstructTypeEnum { String get string { switch (this) { - case ConstructTypeEnum.grammar: - return 'grammar'; case ConstructTypeEnum.vocab: return 'vocab'; case ConstructTypeEnum.morph: @@ -20,8 +23,6 @@ extension ConstructExtension on ConstructTypeEnum { int get maxXPPerLemma { switch (this) { - case ConstructTypeEnum.grammar: - return 0; case ConstructTypeEnum.vocab: return AnalyticsConstants.vocabUseMaxXP; case ConstructTypeEnum.morph: @@ -33,9 +34,6 @@ extension ConstructExtension on ConstructTypeEnum { class ConstructTypeUtil { static ConstructTypeEnum fromString(String? string) { switch (string) { - case 'g': - case 'grammar': - return ConstructTypeEnum.grammar; case 'v': case 'vocab': return ConstructTypeEnum.vocab; @@ -43,6 +41,7 @@ class ConstructTypeUtil { case 'morph': return ConstructTypeEnum.morph; default: + debugger(when: kDebugMode); return ConstructTypeEnum.vocab; } } 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 162b4f238..2d631ceb7 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -37,13 +37,13 @@ import '../../models/choreo_record.dart'; import '../../models/representation_content_model.dart'; import '../client_extension/client_extension.dart'; -part "children_and_parents_extension.dart"; -part "events_extension.dart"; part "room_analytics_extension.dart"; +part "room_children_and_parents_extension.dart"; +part "room_events_extension.dart"; part "room_information_extension.dart"; part "room_settings_extension.dart"; -part "space_settings_extension.dart"; -part "user_permissions_extension.dart"; +part "room_space_settings_extension.dart"; +part "room_user_permissions_extension.dart"; extension PangeaRoom on Room { // analytics diff --git a/lib/pangea/extensions/pangea_room_extension/children_and_parents_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_children_and_parents_extension.dart similarity index 100% rename from lib/pangea/extensions/pangea_room_extension/children_and_parents_extension.dart rename to lib/pangea/extensions/pangea_room_extension/room_children_and_parents_extension.dart diff --git a/lib/pangea/extensions/pangea_room_extension/events_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_events_extension.dart similarity index 83% rename from lib/pangea/extensions/pangea_room_extension/events_extension.dart rename to lib/pangea/extensions/pangea_room_extension/room_events_extension.dart index d8e2545ff..10435e6cd 100644 --- a/lib/pangea/extensions/pangea_room_extension/events_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_events_extension.dart @@ -282,70 +282,6 @@ extension EventsRoomExtension on Room { ); } - // ConstructEvent? _vocabEventLocal(String lemma) { - // if (!isAnalyticsRoom) throw Exception("not an analytics room"); - - // final Event? matrixEvent = getState(PangeaEventTypes.vocab, lemma); - - // return matrixEvent != null ? ConstructEvent(event: matrixEvent) : null; - // } - - // Future _vocabEvent( - // String lemma, - // ConstructType type, [ - // bool makeIfNull = false, - // ]) async { - // try { - // if (!isAnalyticsRoom) throw Exception("not an analytics room"); - - // ConstructEvent? localEvent = _vocabEventLocal(lemma); - - // if (localEvent != null) return localEvent; - - // await postLoad(); - // localEvent = _vocabEventLocal(lemma); - - // if (localEvent == null && isRoomOwner && makeIfNull) { - // final Event matrixEvent = await _createVocabEvent(lemma, type); - // localEvent = ConstructEvent(event: matrixEvent); - // } - - // return localEvent!; - // } catch (err) { - // debugger(when: kDebugMode); - // rethrow; - // } - // } - - // Future _createVocabEvent(String lemma, ConstructType type) async { - // try { - // if (!isRoomOwner) { - // throw Exception( - // "Tried to create vocab event in room where user is not owner", - // ); - // } - // final String eventId = await client.setRoomStateWithKey( - // id, - // PangeaEventTypes.vocab, - // lemma, - // ConstructUses(lemma: lemma, type: type).toJson(), - // ); - // final Event? event = await getEventById(eventId); - - // if (event == null) { - // debugger(when: kDebugMode); - // throw Exception( - // "null event after creation with eventId $eventId in _createVocabEvent", - // ); - // } - // return event; - // } catch (err, stack) { - // debugger(when: kDebugMode); - // ErrorHandler.logError(e: err, s: stack, data: powerLevels); - // rethrow; - // } - // } - /// Get a list of events in the room that are of type [PangeaEventTypes.construct] /// and have the sender as [userID]. If [count] is provided, the function will /// return at most [count] events. diff --git a/lib/pangea/extensions/pangea_room_extension/space_settings_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_space_settings_extension.dart similarity index 100% rename from lib/pangea/extensions/pangea_room_extension/space_settings_extension.dart rename to lib/pangea/extensions/pangea_room_extension/room_space_settings_extension.dart diff --git a/lib/pangea/extensions/pangea_room_extension/user_permissions_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_user_permissions_extension.dart similarity index 100% rename from lib/pangea/extensions/pangea_room_extension/user_permissions_extension.dart rename to lib/pangea/extensions/pangea_room_extension/room_user_permissions_extension.dart diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index a06d4df7e..5d76a7fc1 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart'; import 'package:fluffychat/pangea/enum/audio_encoding_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/choreo_record.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/models/representation_content_model.dart'; @@ -639,34 +638,4 @@ class PangeaMessageEvent { /// Returns a list of [PracticeActivityEvent] for the user's active l2. List get practiceActivities => l2Code == null ? [] : practiceActivitiesByLangCode(l2Code!); - - /// all construct uses for the message, including vocab and grammar - List get allConstructUses => [ - ..._grammarConstructUses, - ..._vocabUses, - ]; - - /// get construct uses of type vocab for the message - List get _vocabUses { - if (originalSent?.tokens != null) { - return originalSent!.content.vocabUses( - event: event, - choreo: originalSent!.choreo, - tokens: originalSent!.tokens!, - ); - } - return []; - } - - /// get construct uses of type grammar for the message - List get _grammarConstructUses => - originalSent?.choreo?.grammarConstructUses(event: event) ?? []; -} - -class URLFinder { - static Iterable getMatches(String text) { - final RegExp exp = - RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'); - return exp.allMatches(text); - } } diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index 191683140..c472752b5 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -18,53 +18,29 @@ class ConstructAnalyticsModel { factory ConstructAnalyticsModel.fromJson(Map json) { final List uses = []; + if (json[_usesKey] is List) { // This is the new format - uses.addAll( - (json[_usesKey] as List) - .map((use) => OneConstructUse.fromJson(use)) - .cast() - .toList(), - ); - } else { - // This is the old format. No data on production should be - // structured this way, but it's useful for testing. - try { - final useValues = (json[_usesKey] as Map).values; - for (final useValue in useValues) { - final lemma = useValue['lemma']; - final lemmaUses = useValue[_usesKey]; - for (final useData in lemmaUses) { - final use = OneConstructUse( - useType: ConstructUseTypeEnum.ga, - lemma: lemma, - form: useData["form"], - constructType: ConstructTypeEnum.grammar, - metadata: ConstructUseMetaData( - eventId: useData["msgId"], - roomId: useData["chatId"], - timeStamp: DateTime.parse(useData["timeStamp"]), - ), - ); - uses.add(use); - } + for (final useJson in json[_usesKey]) { + // grammar construct uses are deprecated so but some are saved + // here we're filtering from data + if (["grammar", "g"].contains(useJson['constructType'])) { + continue; + } else { + uses.add(OneConstructUse.fromJson(useJson)); } - } catch (err, s) { - debugPrint("Error parsing ConstructAnalyticsModel"); - ErrorHandler.logError( - e: err, - s: s, - m: "Error parsing ConstructAnalyticsModel", - ); - debugger(when: kDebugMode); } + } else { + debugger(when: kDebugMode); + ErrorHandler.logError(m: "Analytics room with non-list uses"); } + return ConstructAnalyticsModel( uses: uses, ); } - toJson() { + Map toJson() { return { _usesKey: uses.map((use) => use.toJson()).toList(), }; diff --git a/lib/pangea/models/choreo_record.dart b/lib/pangea/models/choreo_record.dart index fe95dfc09..3586fcee1 100644 --- a/lib/pangea/models/choreo_record.dart +++ b/lib/pangea/models/choreo_record.dart @@ -1,10 +1,6 @@ import 'dart:convert'; -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; -import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:matrix/matrix.dart'; import 'it_step.dart'; @@ -115,44 +111,6 @@ class ChoreoRecord { String get finalMessage => choreoSteps.isNotEmpty ? choreoSteps.last.text : ""; - - /// Get construct uses of type grammar for the message from this ChoreoRecord. - /// Takes either an event (typically when the Representation itself is - /// available) or construct use metadata (when the event is not available, - /// i.e. immediately after message send) to create the construct uses. - List grammarConstructUses({ - Event? event, - ConstructUseMetaData? metadata, - }) { - final List uses = []; - if (event?.roomId == null && metadata?.roomId == null) { - return uses; - } - metadata ??= ConstructUseMetaData( - roomId: event!.roomId!, - eventId: event.eventId, - timeStamp: event.originServerTs, - ); - - for (final step in choreoSteps) { - if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted) { - final String name = step.acceptedOrIgnoredMatch!.match.rule?.id ?? - step.acceptedOrIgnoredMatch!.match.shortMessage ?? - step.acceptedOrIgnoredMatch!.match.type.typeName.name; - uses.add( - OneConstructUse( - useType: ConstructUseTypeEnum.ga, - lemma: name, - form: name, - constructType: ConstructTypeEnum.grammar, - id: "${metadata.eventId}_${step.acceptedOrIgnoredMatch!.match.offset}_${step.acceptedOrIgnoredMatch!.match.length}", - metadata: metadata, - ), - ); - } - } - return uses; - } } /// A new ChoreoRecordStep is saved in the following cases: diff --git a/lib/pangea/models/representation_content_model.dart b/lib/pangea/models/representation_content_model.dart index 9fb96f1e9..9c6f57c19 100644 --- a/lib/pangea/models/representation_content_model.dart +++ b/lib/pangea/models/representation_content_model.dart @@ -120,7 +120,7 @@ class PangeaRepresentation { tokens.where((token) => token.lemma.saveVocab).toList(); for (final token in tokensToSave) { uses.addAll( - getUsesForToken( + _getUsesForToken( token, metadata, choreo: choreo, @@ -138,7 +138,7 @@ class PangeaRepresentation { /// If the [token] is in the [choreo.acceptedOrIgnoredMatch], it is considered to be a [ConstructUseTypeEnum.ga]. /// If the [token] is in the [choreo.acceptedOrIgnoredMatch.choices], it is considered to be a [ConstructUseTypeEnum.corIt]. /// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa]. - List getUsesForToken( + List _getUsesForToken( PangeaToken token, ConstructUseMetaData metadata, { ChoreoRecord? choreo, diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart deleted file mode 100644 index b859b991b..000000000 --- a/lib/pangea/pages/analytics/construct_list.dart +++ /dev/null @@ -1,572 +0,0 @@ -import 'dart:async'; - -import 'package:collection/collection.dart'; -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; -import 'package:fluffychat/pangea/enum/time_span.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart'; -import 'package:fluffychat/pangea/models/analytics/construct_list_model.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/utils/date_time_extension.dart'; -import 'package:fluffychat/utils/string_color.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - -class ConstructList extends StatefulWidget { - final ConstructTypeEnum constructType; - final AnalyticsSelected defaultSelected; - final AnalyticsSelected? selected; - final TimeSpan timeSpan; - final PangeaController pangeaController; - final StreamController refreshStream; - - const ConstructList({ - super.key, - required this.constructType, - required this.defaultSelected, - required this.pangeaController, - required this.refreshStream, - required this.timeSpan, - this.selected, - }); - - @override - State createState() => ConstructListState(); -} - -class ConstructListState extends State { - String? langCode; - String? error; - - @override - Widget build(BuildContext context) { - return error != null - ? Center( - child: Text(error!), - ) - : Column( - children: [ - ConstructListView( - pangeaController: widget.pangeaController, - defaultSelected: widget.defaultSelected, - selected: widget.selected, - refreshStream: widget.refreshStream, - timeSpan: widget.timeSpan, - ), - ], - ); - } -} - -// list view of construct events -// parameters -// 1) a list of construct events and -// 2) a boolean indicating whether the list has been initialized -// if not initialized, show loading indicator -// for each tile, -// title = construct.content.lemma -// subtitle = total uses, equal to construct.content.uses.length -// list has a fixed height of 400 and is scrollable -class ConstructListView extends StatefulWidget { - final PangeaController pangeaController; - final AnalyticsSelected defaultSelected; - final TimeSpan timeSpan; - final AnalyticsSelected? selected; - final StreamController refreshStream; - - const ConstructListView({ - super.key, - required this.pangeaController, - required this.defaultSelected, - required this.timeSpan, - required this.refreshStream, - this.selected, - }); - - @override - State createState() => ConstructListViewState(); -} - -class ConstructListViewState extends State { - final ConstructTypeEnum constructType = ConstructTypeEnum.grammar; - final Map _timelinesCache = {}; - final Map _msgEventCache = {}; - final List _msgEvents = []; - bool fetchingConstructs = true; - bool fetchingUses = false; - StreamSubscription? refreshSubscription; - String? currentLemma; - - @override - void initState() { - super.initState(); - widget.pangeaController.analytics - .getConstructs( - constructType: constructType, - forceUpdate: true, - ) - .whenComplete(() => setState(() => fetchingConstructs = false)) - .then( - (value) => setState( - () => constructs = ConstructListModel( - type: constructType, - uses: value, - ), - ), - ); - - refreshSubscription = widget.refreshStream.stream.listen((forceUpdate) { - // postframe callback to let widget rebuild with the new selected parameter - // before sending selected to getConstructs function - WidgetsBinding.instance.addPostFrameCallback((_) { - widget.pangeaController.analytics - .getConstructs( - constructType: constructType, - forceUpdate: true, - ) - .then( - (value) => setState(() { - ConstructListModel( - type: constructType, - uses: value, - ); - }), - ); - }); - }); - } - - @override - void dispose() { - refreshSubscription?.cancel(); - super.dispose(); - } - - void setCurrentLemma(String? lemma) { - currentLemma = lemma; - setState(() {}); - } - - Future getMessageEvent( - OneConstructUse use, - ) async { - final Client client = Matrix.of(context).client; - PangeaMessageEvent msgEvent; - if (_msgEventCache.containsKey(use.msgId)) { - return _msgEventCache[use.msgId]!; - } - final Room? msgRoom = use.getRoom(client); - if (msgRoom == null) { - return null; - } - - Timeline? timeline; - if (_timelinesCache.containsKey(use.chatId)) { - timeline = _timelinesCache[use.chatId]; - } else { - timeline = msgRoom.timeline ?? await msgRoom.getTimeline(); - _timelinesCache[use.chatId] = timeline; - } - - final Event? event = await use.getEvent(client); - if (event == null || timeline == null) { - return null; - } - - msgEvent = PangeaMessageEvent( - event: event, - timeline: timeline, - ownMessage: event.senderId == client.userID, - ); - _msgEventCache[use.msgId] = msgEvent; - return msgEvent; - } - - Future fetchUses() async { - if (fetchingUses) return; - if (currentLemma == null) { - setState(() => _msgEvents.clear()); - return; - } - - setState(() => fetchingUses = true); - try { - final List uses = constructs?.constructList - .firstWhereOrNull( - (element) => element.lemma == currentLemma, - ) - ?.uses ?? - []; - _msgEvents.clear(); - - for (final OneConstructUse use in uses) { - final PangeaMessageEvent? msgEvent = await getMessageEvent(use); - final RepresentationEvent? repEvent = - msgEvent?.originalSent ?? msgEvent?.originalWritten; - if (repEvent?.choreo == null) { - continue; - } - _msgEvents.add(msgEvent!); - } - setState(() => fetchingUses = false); - } catch (err, s) { - setState(() => fetchingUses = false); - debugPrint("Error fetching uses: $err"); - ErrorHandler.logError( - e: err, - s: s, - m: "Failed to fetch uses for current construct $currentLemma", - ); - } - } - - ConstructListModel? constructs; - - // given the current lemma and list of message events, return a list of - // MessageEventMatch objects, which contain one PangeaMessageEvent to one PangeaMatch - // this is because some message events may have has more than one PangeaMatch of a - // given lemma type. - List getMessageEventMatches() { - if (currentLemma == null) return []; - final List allMsgErrorSteps = []; - - for (final msgEvent in _msgEvents) { - if (allMsgErrorSteps.any( - (element) => element.msgEvent.eventId == msgEvent.eventId, - )) { - continue; - } - // get all the pangea matches in that message which have that lemma - final List? msgErrorSteps = msgEvent.errorSteps( - currentLemma!, - ); - if (msgErrorSteps == null) continue; - - allMsgErrorSteps.addAll( - msgErrorSteps.map( - (errorStep) => MessageEventMatch( - msgEvent: msgEvent, - lemmaMatch: errorStep, - ), - ), - ); - } - return allMsgErrorSteps; - } - - Future showConstructMessagesDialog() async { - await showDialog( - context: context, - builder: (c) => ConstructMessagesDialog(controller: this), - ); - } - - @override - Widget build(BuildContext context) { - if (fetchingConstructs || fetchingUses) { - return const Expanded( - child: Center(child: CircularProgressIndicator()), - ); - } - - if (constructs?.constructList.isEmpty ?? true) { - return Expanded( - child: Center(child: Text(L10n.of(context)!.noDataFound)), - ); - } - - return Expanded( - child: ListView.builder( - itemCount: constructs!.constructList.length, - itemBuilder: (context, index) { - return ListTile( - title: Text( - constructs!.constructList[index].lemma, - ), - subtitle: Text( - '${L10n.of(context)!.total} ${constructs!.constructList[index].uses.length}', - ), - onTap: () async { - final String lemma = constructs!.constructList[index].lemma; - setCurrentLemma(lemma); - fetchUses().then((_) => showConstructMessagesDialog()); - }, - ); - }, - ), - ); - } -} - -class ConstructMessagesDialog extends StatelessWidget { - final ConstructListViewState controller; - const ConstructMessagesDialog({ - super.key, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - if (controller.currentLemma == null || controller.constructs == null) { - return const AlertDialog(content: CircularProgressIndicator.adaptive()); - } - - final msgEventMatches = controller.getMessageEventMatches(); - - final currentConstruct = - controller.constructs!.constructList.firstWhereOrNull( - (construct) => construct.lemma == controller.currentLemma, - ); - final noData = currentConstruct == null || - currentConstruct.uses.length > controller._msgEvents.length; - - return AlertDialog( - title: Center(child: Text(controller.currentLemma!)), - content: SizedBox( - height: noData ? 90 : 250, - width: noData ? 200 : 400, - child: Column( - children: [ - if (noData) - Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(L10n.of(context)!.roomDataMissing), - ), - ), - Expanded( - child: ListView( - children: [ - ...msgEventMatches.mapIndexed( - (index, event) => Column( - children: [ - ConstructMessage( - msgEvent: event.msgEvent, - lemma: controller.currentLemma!, - errorMessage: event.lemmaMatch, - ), - if (index < msgEventMatches.length - 1) - const Divider(height: 1), - ], - ), - ), - ], - ), - ), - ], - ), - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context, rootNavigator: false).pop(), - child: Text( - L10n.of(context)!.close.toUpperCase(), - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ); - } -} - -class ConstructMessage extends StatelessWidget { - final PangeaMessageEvent msgEvent; - final PangeaMatch errorMessage; - final String lemma; - - const ConstructMessage({ - super.key, - required this.msgEvent, - required this.errorMessage, - required this.lemma, - }); - - @override - Widget build(BuildContext context) { - final String? chosen = errorMessage.match.choices - ?.firstWhereOrNull( - (element) => element.selected == true, - ) - ?.value; - - if (chosen == null) { - return const SizedBox.shrink(); - } - - return Padding( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - ConstructMessageMetadata(msgEvent: msgEvent), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FutureBuilder( - future: msgEvent.event.fetchSenderUser(), - builder: (context, snapshot) { - final displayname = snapshot.data?.calcDisplayname() ?? - msgEvent.event.senderFromMemoryOrFallback - .calcDisplayname(); - return Text( - displayname, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: (Theme.of(context).brightness == - Brightness.light - ? displayname.color - : displayname.lightColorText), - ), - ); - }, - ), - ConstructMessageBubble( - errorText: errorMessage.match.fullText, - replacementText: chosen, - start: errorMessage.match.offset, - end: - errorMessage.match.offset + errorMessage.match.length, - ), - ], - ), - ], - ), - ), - ], - ), - ); - } -} - -class ConstructMessageBubble extends StatelessWidget { - final String errorText; - final String replacementText; - final int start; - final int end; - - const ConstructMessageBubble({ - super.key, - required this.errorText, - required this.replacementText, - required this.start, - required this.end, - }); - - @override - Widget build(BuildContext context) { - final defaultStyle = TextStyle( - color: Theme.of(context).colorScheme.onSurface, - fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor, - height: 1.3, - ); - - return IntrinsicWidth( - child: Material( - color: Theme.of(context).colorScheme.primaryContainer, - clipBehavior: Clip.antiAlias, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(4), - topRight: Radius.circular(AppConfig.borderRadius), - bottomLeft: Radius.circular(AppConfig.borderRadius), - bottomRight: Radius.circular(AppConfig.borderRadius), - ), - ), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - ), - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: RichText( - text: TextSpan( - children: [ - TextSpan( - text: errorText.substring(0, start), - style: defaultStyle, - ), - TextSpan( - text: errorText.substring(start, end), - style: defaultStyle.merge( - TextStyle( - backgroundColor: Colors.red.withOpacity(0.25), - decoration: TextDecoration.lineThrough, - decorationThickness: 2.5, - ), - ), - ), - const TextSpan(text: " "), - TextSpan( - text: replacementText, - style: defaultStyle.merge( - TextStyle( - backgroundColor: Colors.green.withOpacity(0.25), - ), - ), - ), - TextSpan( - text: errorText.substring(end), - style: defaultStyle, - ), - ], - ), - ), - ), - ), - ); - } -} - -class ConstructMessageMetadata extends StatelessWidget { - final PangeaMessageEvent msgEvent; - - const ConstructMessageMetadata({ - super.key, - required this.msgEvent, - }); - - @override - Widget build(BuildContext context) { - final String roomName = msgEvent.event.room.getLocalizedDisplayname(); - return Padding( - padding: const EdgeInsets.fromLTRB(10, 0, 30, 0), - child: Column( - children: [ - Text( - msgEvent.event.originServerTs.localizedTime(context), - style: TextStyle(fontSize: 13 * AppConfig.fontSizeFactor), - ), - Text(roomName), - ], - ), - ); - } -} - -class MessageEventMatch { - final PangeaMessageEvent msgEvent; - final PangeaMatch lemmaMatch; - - MessageEventMatch({ - required this.msgEvent, - required this.lemmaMatch, - }); -} diff --git a/lib/pangea/pages/analytics/list_summary_analytics.dart b/lib/pangea/pages/analytics/list_summary_analytics.dart deleted file mode 100644 index 02bb5d3fb..000000000 --- a/lib/pangea/pages/analytics/list_summary_analytics.dart +++ /dev/null @@ -1,101 +0,0 @@ -// import 'dart:math'; - -// import 'package:fluffychat/pangea/models/analytics/chart_analytics_model.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_gen/gen_l10n/l10n.dart'; - -// import '../../enum/use_type.dart'; - -// class ListSummaryAnalytics extends StatelessWidget { -// final ChartAnalyticsModel? chartAnalytics; - -// const ListSummaryAnalytics({super.key, this.chartAnalytics}); - -// TimeSeriesTotals? get totals => chartAnalytics?.totals; - -// String spacer(int baseLength, int number) => -// " " * max(baseLength - number.toString().length, 0); - -// WidgetSpan spacerIconText( -// String toolTip, -// String space, -// IconData icon, -// int value, -// Color? color, [ -// percentage = true, -// ]) => -// WidgetSpan( -// child: Tooltip( -// message: toolTip, -// child: RichText( -// text: TextSpan( -// children: [ -// TextSpan( -// text: space, -// ), -// WidgetSpan(child: Icon(icon, size: 14, color: color)), -// TextSpan( -// text: " $value${percentage ? "%" : ""}", -// style: TextStyle(color: color), -// ), -// ], -// ), -// ), -// ), -// ); - -// @override -// Widget build(BuildContext context) { -// if (totals == null) { -// return const LinearProgressIndicator(); -// } -// final l10n = L10n.of(context); - -// return RichText( -// text: TextSpan( -// children: [ -// spacerIconText( -// L10n.of(context) != null -// ? L10n.of(context)!.totalMessages -// : "Total messages sent", -// "", -// Icons.chat_bubble, -// totals!.all, -// Theme.of(context).textTheme.bodyLarge!.color, -// false, -// ), -// if (totals!.all != 0) ...[ -// spacerIconText( -// l10n != null ? l10n.taTooltip : "With translation assistance", -// spacer(8, totals!.all), -// UseType.ta.iconData, -// totals!.taPercent, -// UseType.ta.color(context), -// ), -// spacerIconText( -// l10n != null ? l10n.gaTooltip : "With grammar assistance", -// spacer(4, totals!.taPercent), -// UseType.ga.iconData, -// totals!.gaPercent, -// UseType.ga.color(context), -// ), -// spacerIconText( -// l10n != null ? l10n.waTooltip : "Without assistance", -// spacer(4, totals!.gaPercent), -// UseType.wa.iconData, -// totals!.waPercent, -// UseType.wa.color(context), -// ), -// spacerIconText( -// l10n != null ? l10n.unTooltip : "Other", -// spacer(4, totals!.waPercent), -// UseType.un.iconData, -// totals!.unPercent, -// UseType.un.color(context), -// ), -// ], -// ], -// ), -// ); -// } -// } diff --git a/lib/pangea/utils/logout.dart b/lib/pangea/utils/logout.dart index def632828..f9f816893 100644 --- a/lib/pangea/utils/logout.dart +++ b/lib/pangea/utils/logout.dart @@ -20,7 +20,7 @@ void pLogoutAction(BuildContext context, {bool? isDestructiveAction}) async { final matrix = Matrix.of(context); // before wiping out locally cached construct data, save it to the server - await MatrixState.pangeaController.myAnalytics + await MatrixState.pangeaController.putAnalytics .sendLocalAnalyticsToAnalyticsRoom(onLogout: true); await showFutureLoadingDialog( diff --git a/lib/pangea/widgets/animations/gain_points.dart b/lib/pangea/widgets/animations/gain_points.dart index d9d9a0111..c41b048bf 100644 --- a/lib/pangea/widgets/animations/gain_points.dart +++ b/lib/pangea/widgets/animations/gain_points.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:fluffychat/pangea/controllers/get_analytics_controller.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -29,8 +29,8 @@ class PointsGainedAnimationState extends State late Animation _fadeAnimation; StreamSubscription? _pointsSubscription; - int? get _prevXP => MatrixState.pangeaController.analytics.prevXP; - int? get _currentXP => MatrixState.pangeaController.analytics.currentXP; + int? get _prevXP => MatrixState.pangeaController.getAnalytics.prevXP; + int? get _currentXP => MatrixState.pangeaController.getAnalytics.currentXP; int? _addedPoints; @override @@ -62,7 +62,7 @@ class PointsGainedAnimationState extends State ); _pointsSubscription = MatrixState - .pangeaController.analytics.analyticsStream.stream + .pangeaController.getAnalytics.analyticsStream.stream .listen(_showPointsGained); } diff --git a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart index 980ee1488..a9798d1ca 100644 --- a/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/widgets/chat_list/analytics_summary/learning_progress_indicators.dart @@ -54,21 +54,21 @@ class LearningProgressIndicatorsState // int get totalXP => _pangeaController.analytics.currentXP; // int get level => _pangeaController.analytics.level; List currentConstructs = []; - int get currentXP => _pangeaController.analytics.calcXP(currentConstructs); - int get localXP => _pangeaController.analytics.calcXP( - _pangeaController.analytics.locallyCachedConstructs, + int get currentXP => _pangeaController.getAnalytics.calcXP(currentConstructs); + int get localXP => _pangeaController.getAnalytics.calcXP( + _pangeaController.getAnalytics.locallyCachedConstructs, ); int get serverXP => currentXP - localXP; - int get level => _pangeaController.analytics.level; + int get level => _pangeaController.getAnalytics.level; @override void initState() { super.initState(); updateAnalyticsData( - _pangeaController.analytics.analyticsStream.value?.constructs ?? [], + _pangeaController.getAnalytics.analyticsStream.value?.constructs ?? [], ); _analyticsUpdateSubscription = _pangeaController - .analytics.analyticsStream.stream + .getAnalytics.analyticsStream.stream .listen((update) => updateAnalyticsData(update.constructs)); } @@ -146,12 +146,12 @@ class LearningProgressIndicatorsState ? const Color.fromARGB(255, 0, 190, 83) : Theme.of(context).colorScheme.primary, currentPoints: currentXP, - widthMultiplier: _pangeaController.analytics.levelProgress, + widthMultiplier: _pangeaController.getAnalytics.levelProgress, ), LevelBarDetails( fillColor: Theme.of(context).colorScheme.primary, currentPoints: serverXP, - widthMultiplier: _pangeaController.analytics.serverLevelProgress, + widthMultiplier: _pangeaController.getAnalytics.serverLevelProgress, ), ], ); diff --git a/lib/pangea/widgets/igc/span_card.dart b/lib/pangea/widgets/igc/span_card.dart index c40062738..9454a2517 100644 --- a/lib/pangea/widgets/igc/span_card.dart +++ b/lib/pangea/widgets/igc/span_card.dart @@ -1,7 +1,7 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/enum/span_data_type.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; @@ -125,7 +125,7 @@ class SpanCardState extends State { selectedChoiceIndex = index; if (selectedChoice != null) { if (!selectedChoice!.selected) { - MatrixState.pangeaController.myAnalytics.addDraftUses( + MatrixState.pangeaController.putAnalytics.addDraftUses( selectedChoice!.tokens, widget.roomId, selectedChoice!.isBestCorrection @@ -158,7 +158,7 @@ class SpanCardState extends State { /// Adds the ignored tokens to locally cached analytics void addIgnoredTokenUses() { - MatrixState.pangeaController.myAnalytics.addDraftUses( + MatrixState.pangeaController.putAnalytics.addDraftUses( ignoredTokens ?? [], widget.roomId, ConstructUseTypeEnum.ignIGC, diff --git a/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart b/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart index d37084283..2eb4c3523 100644 --- a/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart +++ b/lib/pangea/widgets/practice_activity/multiple_choice_activity.dart @@ -2,7 +2,7 @@ import 'dart:developer'; import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; @@ -73,7 +73,7 @@ class MultipleChoiceActivityState extends State { return; } - MatrixState.pangeaController.myAnalytics.setState( + MatrixState.pangeaController.putAnalytics.setState( AnalyticsStream( // note - this maybe should be the activity event id eventId: diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index 00b11f658..64de20714 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'dart:developer'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/controllers/practice_activity_generation_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/enum/activity_type_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart'; diff --git a/lib/pangea/widgets/practice_activity/target_tokens_controller.dart b/lib/pangea/widgets/practice_activity/target_tokens_controller.dart index 69be7f6c2..65aec4702 100644 --- a/lib/pangea/widgets/practice_activity/target_tokens_controller.dart +++ b/lib/pangea/widgets/practice_activity/target_tokens_controller.dart @@ -26,7 +26,7 @@ class TargetTokensController { _targetTokens = await _initialize(pangeaMessageEvent); final allConstructs = MatrixState - .pangeaController.analytics.analyticsStream.value?.constructs; + .pangeaController.getAnalytics.analyticsStream.value?.constructs; await updateTokensWithConstructs( allConstructs ?? [], pangeaMessageEvent, diff --git a/lib/pangea/widgets/practice_activity/word_focus_listening_activity.dart b/lib/pangea/widgets/practice_activity/word_focus_listening_activity.dart index 810074a76..168758137 100644 --- a/lib/pangea/widgets/practice_activity/word_focus_listening_activity.dart +++ b/lib/pangea/widgets/practice_activity/word_focus_listening_activity.dart @@ -1,7 +1,7 @@ import 'dart:developer'; import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/pangea/controllers/my_analytics_controller.dart'; +import 'package:fluffychat/pangea/controllers/put_analytics_controller.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/multiple_choice_activity_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart'; import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart'; @@ -69,7 +69,7 @@ class WordFocusListeningActivityState return; } - MatrixState.pangeaController.myAnalytics.setState( + MatrixState.pangeaController.putAnalytics.setState( AnalyticsStream( // note - this maybe should be the activity event id eventId: diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index 80dd7fa9d..b0276e11c 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -93,7 +93,12 @@ Future pLanguageDialog( context: context, future: () async { try { - pangeaController.myAnalytics + //@ggurdin while this is obviously working, it feels pretty hidden + //and could lead to errors if someone where to change the user L2 via some + // other means. with analytics being dependent on languages, it probably + // would make sense for analytics to listen to the language stateStream + // and update in this case + pangeaController.putAnalytics .sendLocalAnalyticsToAnalyticsRoom() .then((_) { pangeaController.userController.updateProfile( @@ -109,11 +114,11 @@ Future pLanguageDialog( }).then((_) { // if the profile update is successful, reset cached analytics // data, since analytics data corresponds to the user's L2 - pangeaController.myAnalytics.dispose(); - pangeaController.analytics.dispose(); + pangeaController.putAnalytics.dispose(); + pangeaController.getAnalytics.dispose(); - pangeaController.myAnalytics.initialize(); - pangeaController.analytics.initialize(); + pangeaController.putAnalytics.initialize(); + pangeaController.getAnalytics.initialize(); Navigator.pop(context); });