From 4d58b66bf1ba28affbe261e01ce2a61b30ea6f9a Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 4 Dec 2025 12:59:17 -0500 Subject: [PATCH 1/3] feat: initial work for deleting lemmas from analytics --- lib/l10n/intl_en.arb | 3 +- lib/pages/chat/chat.dart | 26 +-- .../analytics_misc/construct_list_model.dart | 8 + .../get_analytics_controller.dart | 19 +- .../lemma_emoji_setter_mixin.dart | 41 ++-- .../put_analytics_controller.dart | 221 +++--------------- lib/pangea/analytics_page/analytics_page.dart | 38 ++- .../analytics_settings_extension.dart | 46 ++++ .../analytics_settings_model.dart | 34 +++ .../events/constants/pangea_event_types.dart | 2 + .../widgets/message_selection_overlay.dart | 40 ++-- .../toolbar/widgets/practice_controller.dart | 43 ++-- lib/utils/client_manager.dart | 1 + 13 files changed, 243 insertions(+), 279 deletions(-) create mode 100644 lib/pangea/analytics_settings/analytics_settings_extension.dart create mode 100644 lib/pangea/analytics_settings/analytics_settings_model.dart diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index b7e3482fd..0f3be7536 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -4990,5 +4990,6 @@ } }, "pickDifferentActivity": "Pick a different activity", - "messageLanguageMismatchMessage": "Your target language doesn't match this message. Update your target language?" + "messageLanguageMismatchMessage": "Your target language doesn't match this message. Update your target language?", + "blockLemmaConfirmation": "This vocab word will be permanently removed from your analytics" } diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 4bc911fa8..90cfaad2b 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -36,7 +36,6 @@ import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_misc/level_up/level_up_banner.dart'; import 'package:fluffychat/pangea/analytics_misc/message_analytics_feedback.dart'; -import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; import 'package:fluffychat/pangea/chat/utils/unlocked_morphs_snackbar.dart'; import 'package:fluffychat/pangea/chat/widgets/event_too_large_dialog.dart'; @@ -2106,14 +2105,11 @@ class ChatController extends State ]; _showAnalyticsFeedback(constructs, eventId); - - pangeaController.putAnalytics.setState( - AnalyticsStream( - eventId: eventId, - targetID: eventId, - roomId: room.id, - constructs: constructs, - ), + pangeaController.putAnalytics.addAnalytics( + constructs, + eventId: eventId, + targetId: eventId, + roomId: room.id, ); } } @@ -2160,13 +2156,11 @@ class ChatController extends State if (constructs.isEmpty) return; _showAnalyticsFeedback(constructs, eventId); - MatrixState.pangeaController.putAnalytics.setState( - AnalyticsStream( - eventId: eventId, - targetID: eventId, - roomId: room.id, - constructs: constructs, - ), + MatrixState.pangeaController.putAnalytics.addAnalytics( + constructs, + eventId: eventId, + targetId: eventId, + roomId: room.id, ); } catch (e, s) { ErrorHandler.logError( diff --git a/lib/pangea/analytics_misc/construct_list_model.dart b/lib/pangea/analytics_misc/construct_list_model.dart index 7e4e63ed8..a6cbb3a68 100644 --- a/lib/pangea/analytics_misc/construct_list_model.dart +++ b/lib/pangea/analytics_misc/construct_list_model.dart @@ -135,6 +135,14 @@ class ConstructListModel { level = calculateLevelWithXp(totalXP); } + void deleteLemma(String lemma, int offset) { + _uses.removeWhere((use) => use.lemma == lemma); + _constructMap.removeWhere( + (key, value) => value.lemma == lemma, + ); + updateConstructs([], offset); + } + List constructList({ConstructTypeEnum? type}) => _constructList .where( (constructUse) => type == null || constructUse.constructType == type, diff --git a/lib/pangea/analytics_misc/get_analytics_controller.dart b/lib/pangea/analytics_misc/get_analytics_controller.dart index 726826225..6b7db6d53 100644 --- a/lib/pangea/analytics_misc/get_analytics_controller.dart +++ b/lib/pangea/analytics_misc/get_analytics_controller.dart @@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; +import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart'; import 'package:fluffychat/pangea/common/constants/local.key.dart'; import 'package:fluffychat/pangea/common/controllers/base_controller.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; @@ -95,6 +96,20 @@ class GetAnalyticsController extends BaseController { final offset = _pangeaController.userController.analyticsProfile?.xpOffset ?? 0; + + final allUses = [ + ...(_getConstructsLocal() ?? []), + ..._locallyCachedConstructs, + ]; + + final Room? analyticsRoom = _client.analyticsRoomLocal(_l2!); + final blockedLemmas = analyticsRoom?.analyticsSettings?.blockedLemmas; + if (blockedLemmas != null && blockedLemmas.isNotEmpty) { + allUses.removeWhere( + (use) => blockedLemmas.contains(use.identifier.lemma), + ); + } + constructListModel.updateConstructs( [ ...(_getConstructsLocal() ?? []), @@ -129,7 +144,6 @@ class GetAnalyticsController extends BaseController { _joinSpaceSubscription = null; initCompleter = Completer(); _cache.clear(); - // perMessage.dispose(); } Future _onAnalyticsUpdate( @@ -286,8 +300,7 @@ class GetAnalyticsController extends BaseController { return formattedCache; } catch (err) { // if something goes wrong while trying to format the local data, clear it - _pangeaController.putAnalytics - .clearMessagesSinceUpdate(clearDrafts: true); + clearMessagesCache(); return {}; } } catch (exception, stackTrace) { diff --git a/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart b/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart index bccb467a3..74f0cb97f 100644 --- a/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart +++ b/lib/pangea/analytics_misc/lemma_emoji_setter_mixin.dart @@ -1,6 +1,5 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; -import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -28,27 +27,27 @@ mixin LemmaEmojiSetter { String? roomId, String? targetId, }) { - MatrixState.pangeaController.putAnalytics.setState( - AnalyticsStream( - eventId: eventId, - roomId: roomId, - targetID: targetId, - constructs: [ - OneConstructUse( - useType: ConstructUseTypeEnum.em, - lemma: constructId.lemma, - constructType: constructId.type, - metadata: ConstructUseMetaData( - roomId: roomId, - timeStamp: DateTime.now(), - eventId: eventId, - ), - category: constructId.category, - form: constructId.lemma, - xp: ConstructUseTypeEnum.em.pointValue, - ), - ], + final constructs = [ + OneConstructUse( + useType: ConstructUseTypeEnum.em, + lemma: constructId.lemma, + constructType: constructId.type, + metadata: ConstructUseMetaData( + roomId: roomId, + timeStamp: DateTime.now(), + eventId: eventId, + ), + category: constructId.category, + form: constructId.lemma, + xp: ConstructUseTypeEnum.em.pointValue, ), + ]; + + MatrixState.pangeaController.putAnalytics.addAnalytics( + constructs, + eventId: eventId, + roomId: roomId, + targetId: targetId, ); } } diff --git a/lib/pangea/analytics_misc/put_analytics_controller.dart b/lib/pangea/analytics_misc/put_analytics_controller.dart index eb1d9006c..e15bbb6e9 100644 --- a/lib/pangea/analytics_misc/put_analytics_controller.dart +++ b/lib/pangea/analytics_misc/put_analytics_controller.dart @@ -5,27 +5,24 @@ import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; -import 'package:fluffychat/pangea/common/controllers/base_controller.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; +import 'package:fluffychat/pangea/user/controllers/user_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; -enum AnalyticsUpdateType { server, local, activities, init } +enum AnalyticsUpdateType { server, local, activities, init, delete } /// 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 PutAnalyticsController extends BaseController { +class PutAnalyticsController { late PangeaController _pangeaController; StreamController analyticsUpdateStream = StreamController.broadcast(); - StreamSubscription? _analyticsStream; StreamSubscription? _languageStream; Timer? _updateTimer; @@ -53,31 +50,20 @@ class PutAnalyticsController extends BaseController { } void initialize() { - // Listen for calls to setState on the analytics stream - // and update the analytics room if necessary - _analyticsStream ??= - stateStream.listen((data) => _onNewAnalyticsData(data)); - - // Listen for changes to the user's language settings - _languageStream ??= - _pangeaController.userController.languageStream.stream.listen((update) { - _onUpdateLanguages(update.prevTargetLang); - }); + _languageStream ??= _pangeaController.userController.languageStream.stream + .listen(_onUpdateLanguages); _refreshAnalyticsIfOutdated(); } /// Reset analytics last updated time to null. - @override void dispose() { _updateTimer?.cancel(); lastUpdated = null; lastUpdatedCompleter = Completer(); - _analyticsStream?.cancel(); - _analyticsStream = null; _languageStream?.cancel(); _languageStream = null; - clearMessagesSinceUpdate(); + MatrixState.pangeaController.getAnalytics.clearMessagesCache(); } /// If analytics haven't been updated in the last day, update them @@ -112,137 +98,18 @@ class PutAnalyticsController extends BaseController { /// 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 String? eventID = data.eventId; - final String? roomID = data.roomId; - - final List constructs = []; - // if (roomID != null) { - // constructs = _getDraftUses(roomID); - // } - - constructs.addAll(data.constructs); - - if (kDebugMode) { - for (final use in constructs) { - debugPrint( - "_onNewAnalyticsData filtered use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.xp}", - ); - } - } - + void addAnalytics( + List constructs, { + String? eventId, + String? roomId, + String? targetId, + }) { final level = _pangeaController.getAnalytics.constructListModel.level; - - _addLocalMessage(eventID, constructs).then( - (_) { - if (roomID != null) _clearDraftUses(roomID); - _decideWhetherToUpdateAnalyticsRoom( - level, - data.targetID, - data.constructs, - ); - }, + _addLocalMessage(eventId, constructs).then( + (_) => _sendAnalytics(level, targetId, constructs), ); } - Future _onUpdateLanguages(LanguageModel? previousL2) async { - await sendLocalAnalyticsToAnalyticsRoom( - l2Override: previousL2, - ); - _pangeaController.resetAnalytics().then((_) { - final level = _pangeaController.getAnalytics.constructListModel.level; - _pangeaController.userController.updateAnalyticsProfile(level: level); - }); - } - - // void addDraftUses( - // List tokens, - // String roomID, - // ConstructUseTypeEnum useType, { - // String? targetID, - // }) { - // final metadata = ConstructUseMetaData( - // roomId: roomID, - // timeStamp: DateTime.now(), - // ); - - // // 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, - // lemma: token.lemma.text, - // form: token.text.content, - // constructType: ConstructTypeEnum.vocab, - // metadata: metadata, - // category: token.pos, - // ), - // ) - // .toList(); - - // // get all our grammar constructs - // for (final token in tokensToSave) { - // uses.add( - // OneConstructUse( - // useType: useType, - // lemma: token.pos, - // form: token.text.content, - // category: "POS", - // constructType: ConstructTypeEnum.morph, - // metadata: metadata, - // ), - // ); - // for (final entry in token.morph.entries) { - // uses.add( - // OneConstructUse( - // useType: useType, - // lemma: entry.value, - // form: token.text.content, - // category: entry.key, - // constructType: ConstructTypeEnum.morph, - // metadata: metadata, - // ), - // ); - // } - // } - - // if (kDebugMode) { - // for (final use in uses) { - // debugPrint( - // "Draft use: ${use.constructType.string} ${use.useType.string} ${use.lemma} ${use.useType.pointValue}", - // ); - // } - // } - - // final level = _pangeaController.getAnalytics.constructListModel.level; - - // // the list 'uses' gets altered in the _addLocalMessage method, - // // so copy it here to that the list of new uses is accurate - // final List newUses = List.from(uses); - // _addLocalMessage('draft$roomID', uses).then( - // (_) => _decideWhetherToUpdateAnalyticsRoom( - // level, - // targetID, - // newUses, - // ), - // ); - // } - - // List _getDraftUses(String roomID) { - // final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate; - // return currentCache['draft$roomID'] ?? []; - // } - - void _clearDraftUses(String roomID) { - final currentCache = _pangeaController.getAnalytics.messagesSinceUpdate; - currentCache.remove('draft$roomID'); - _setMessagesSinceUpdate(currentCache); - } - /// Add a list of construct uses for a new message to the local /// cache of recently sent messages Future _addLocalMessage( @@ -255,7 +122,7 @@ class PutAnalyticsController extends BaseController { // if this is not a draft message, add the eventId to the metadata // if it's missing (it will be missing for draft constructs) - if (cacheKey != null && !cacheKey.startsWith('draft')) { + if (cacheKey != null) { constructs = constructs.map((construct) { if (construct.metadata.eventId != null) return construct; construct.metadata.eventId = cacheKey; @@ -283,7 +150,7 @@ class PutAnalyticsController extends BaseController { /// If the addition brought the total number of messages in the cache /// to the max, or if the addition triggered a level-up, update the analytics. /// Otherwise, add a local update to the alert stream. - void _decideWhetherToUpdateAnalyticsRoom( + void _sendAnalytics( int prevLevel, String? targetID, List newConstructs, @@ -311,25 +178,14 @@ class PutAnalyticsController extends BaseController { ); } - /// Clears the local cache of recently sent constructs. Called before updating analytics - void clearMessagesSinceUpdate({clearDrafts = false}) { - if (clearDrafts) { - MatrixState.pangeaController.getAnalytics.clearMessagesCache(); - return; - } - - final localCache = _pangeaController.getAnalytics.messagesSinceUpdate; - final draftKeys = localCache.keys.where((key) => key.startsWith('draft')); - if (draftKeys.isEmpty) { - MatrixState.pangeaController.getAnalytics.clearMessagesCache(); - return; - } - - final Map> newCache = {}; - for (final key in draftKeys) { - newCache[key] = localCache[key]!; - } - _setMessagesSinceUpdate(newCache); + Future _onUpdateLanguages(LanguageUpdate update) async { + await sendLocalAnalyticsToAnalyticsRoom( + l2Override: update.prevTargetLang, + ); + _pangeaController.resetAnalytics().then((_) { + final level = _pangeaController.getAnalytics.constructListModel.level; + _pangeaController.userController.updateAnalyticsProfile(level: level); + }); } /// Save the local cache of recently sent constructs to the local storage @@ -369,7 +225,7 @@ class PutAnalyticsController extends BaseController { _updateCompleter = Completer(); try { await _updateAnalytics(l2Override: l2Override); - clearMessagesSinceUpdate(); + MatrixState.pangeaController.getAnalytics.clearMessagesCache(); lastUpdated = DateTime.now(); analyticsUpdateStream.add( @@ -434,39 +290,16 @@ class PutAnalyticsController extends BaseController { ); } - Future removeActivityAnalytics(String roomId) async { - if (_pangeaController.matrixState.client.userID == null) return; - if (_pangeaController.languageController.userL2 == null) return; - - final Room? analyticsRoom = await _client.getMyAnalyticsRoom( - _pangeaController.languageController.userL2!, - ); - if (analyticsRoom == null) return; - await analyticsRoom.removeActivityRoomId(roomId); + Future onBlockLemma() async { analyticsUpdateStream.add( AnalyticsUpdate( - AnalyticsUpdateType.activities, + AnalyticsUpdateType.delete, [], ), ); } } -class AnalyticsStream { - final String? eventId; - final String? roomId; - final String? targetID; - - final List constructs; - - AnalyticsStream({ - required this.eventId, - required this.roomId, - required this.constructs, - this.targetID, - }); -} - class AnalyticsUpdate { final AnalyticsUpdateType type; final List newConstructs; diff --git a/lib/pangea/analytics_page/analytics_page.dart b/lib/pangea/analytics_page/analytics_page.dart index 0c485224a..30031b275 100644 --- a/lib/pangea/analytics_page/analytics_page.dart +++ b/lib/pangea/analytics_page/analytics_page.dart @@ -5,15 +5,19 @@ import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_page/activity_archive.dart'; import 'package:fluffychat/pangea/analytics_page/analytics_page_constants.dart'; +import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart'; import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/analytics_summary/level_dialog_content.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; class AnalyticsPage extends StatelessWidget { @@ -28,11 +32,43 @@ class AnalyticsPage extends StatelessWidget { this.isSidebar = false, }); + Future _blockLemma(BuildContext context) async { + final resp = await showOkCancelAlertDialog( + context: context, + title: L10n.of(context).areYouSure, + message: L10n.of(context).blockLemmaConfirmation, + isDestructive: true, + ); + + if (resp != OkCancelResult.ok) return; + final res = await showFutureLoadingDialog( + context: context, + future: () => Matrix.of(context).client.blockLemma(construct!.lemma), + ); + + if (!res.isError) { + context.go("/rooms/analytics/${ConstructTypeEnum.vocab.name}"); + } + } + @override Widget build(BuildContext context) { final analyticsRoomId = GoRouterState.of(context).pathParameters['roomid']; return Scaffold( - appBar: construct != null ? AppBar() : null, + appBar: construct != null + ? AppBar( + actions: indicator == ProgressIndicatorEnum.wordsUsed + ? [ + IconButton( + icon: const Icon(Icons.delete_forever_outlined), + color: Theme.of(context).colorScheme.error, + tooltip: L10n.of(context).delete, + onPressed: () => _blockLemma(context), + ), + ] + : null, + ) + : null, body: SafeArea( child: StreamBuilder( stream: MatrixState diff --git a/lib/pangea/analytics_settings/analytics_settings_extension.dart b/lib/pangea/analytics_settings/analytics_settings_extension.dart new file mode 100644 index 000000000..1ae673a8d --- /dev/null +++ b/lib/pangea/analytics_settings/analytics_settings_extension.dart @@ -0,0 +1,46 @@ +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; +import 'package:fluffychat/pangea/analytics_settings/analytics_settings_model.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +extension AnalyticsSettingsRoomExtension on Room { + AnalyticsSettingsModel? get analyticsSettings { + final event = getState(PangeaEventTypes.analyticsSettings); + if (event == null) return null; + return AnalyticsSettingsModel.fromJson(event.content); + } + + Future setAnalyticsSettings( + AnalyticsSettingsModel settings, + ) async { + await client.setRoomStateWithKey( + id, + PangeaEventTypes.analyticsSettings, + "", + settings.toJson(), + ); + } +} + +extension AnalyticsSettingsClientExtension on Client { + Future blockLemma(String lemma) async { + final l2 = MatrixState.pangeaController.languageController.userL2!; + final analyticsRoom = await getMyAnalyticsRoom(l2); + if (analyticsRoom == null) { + throw Exception("Could not get or create analytics room"); + } + + final current = analyticsRoom.analyticsSettings; + final blockedLemmas = current?.blockedLemmas ?? {}; + final updated = current?.copyWith( + blockedLemmas: { + ...blockedLemmas, + lemma, + }, + ); + + await analyticsRoom.setAnalyticsSettings(updated!); + } +} diff --git a/lib/pangea/analytics_settings/analytics_settings_model.dart b/lib/pangea/analytics_settings/analytics_settings_model.dart new file mode 100644 index 000000000..24d923bf5 --- /dev/null +++ b/lib/pangea/analytics_settings/analytics_settings_model.dart @@ -0,0 +1,34 @@ +class AnalyticsSettingsModel { + final Set blockedLemmas; + + const AnalyticsSettingsModel({ + required this.blockedLemmas, + }); + + AnalyticsSettingsModel copyWith({ + Set? blockedLemmas, + }) { + return AnalyticsSettingsModel( + blockedLemmas: blockedLemmas ?? this.blockedLemmas, + ); + } + + factory AnalyticsSettingsModel.fromJson(Map json) { + final blockedLemmas = {}; + if (json['blocked_lemmas'] != null) { + final lemmas = json['blocked_lemmas'] as List; + for (final lemma in lemmas) { + blockedLemmas.add(lemma as String); + } + } + return AnalyticsSettingsModel( + blockedLemmas: blockedLemmas, + ); + } + + Map toJson() { + return { + 'blocked_lemmas': blockedLemmas.toList(), + }; + } +} diff --git a/lib/pangea/events/constants/pangea_event_types.dart b/lib/pangea/events/constants/pangea_event_types.dart index dbcc7ad7e..53cc39763 100644 --- a/lib/pangea/events/constants/pangea_event_types.dart +++ b/lib/pangea/events/constants/pangea_event_types.dart @@ -54,4 +54,6 @@ class PangeaEventTypes { static const courseUser = "p.course_user"; static const teacherMode = "pangea.teacher_mode"; static const courseChatList = "pangea.course_chat_list"; + + static const analyticsSettings = "pangea.analytics_settings"; } diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 9753d9718..f7da55214 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -13,7 +13,6 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; -import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart'; @@ -198,27 +197,26 @@ class MessageOverlayController extends State setState(() {}); if (selectedToken != null && isNewToken(selectedToken!)) { final token = selectedToken!; - MatrixState.pangeaController.putAnalytics.setState( - AnalyticsStream( - eventId: event.eventId, - roomId: event.room.id, - constructs: [ - OneConstructUse( - useType: ConstructUseTypeEnum.click, - lemma: token.lemma.text, - constructType: ConstructTypeEnum.vocab, - metadata: ConstructUseMetaData( - roomId: event.room.id, - timeStamp: DateTime.now(), - eventId: event.eventId, - ), - category: token.pos, - form: token.text.content, - xp: ConstructUseTypeEnum.click.pointValue, - ), - ], - targetID: "word-zoom-card-${token.text.uniqueKey}", + final constructs = [ + OneConstructUse( + useType: ConstructUseTypeEnum.click, + lemma: token.lemma.text, + constructType: ConstructTypeEnum.vocab, + metadata: ConstructUseMetaData( + roomId: event.room.id, + timeStamp: DateTime.now(), + eventId: event.eventId, + ), + category: token.pos, + form: token.text.content, + xp: ConstructUseTypeEnum.click.pointValue, ), + ]; + MatrixState.pangeaController.putAnalytics.addAnalytics( + constructs, + eventId: event.eventId, + roomId: event.room.id, + targetId: "word-zoom-card-${token.text.uniqueKey}", ); } } diff --git a/lib/pangea/toolbar/widgets/practice_controller.dart b/lib/pangea/toolbar/widgets/practice_controller.dart index 7df582262..10396bd7e 100644 --- a/lib/pangea/toolbar/widgets/practice_controller.dart +++ b/lib/pangea/toolbar/widgets/practice_controller.dart @@ -6,7 +6,6 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; -import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; @@ -125,28 +124,28 @@ class PracticeController with ChangeNotifier { final constructUseType = _activity!.practiceTarget.record.responses.last .useType(_activity!.activityType); - MatrixState.pangeaController.putAnalytics.setState( - AnalyticsStream( - eventId: pangeaMessageEvent.eventId, - roomId: pangeaMessageEvent.room.id, - constructs: [ - OneConstructUse( - useType: constructUseType, - lemma: token.lemma.text, - constructType: ConstructTypeEnum.vocab, - metadata: ConstructUseMetaData( - roomId: pangeaMessageEvent.room.id, - timeStamp: DateTime.now(), - eventId: pangeaMessageEvent.eventId, - ), - category: token.pos, - // in the case of a wrong answer, the cId doesn't match the token - form: token.text.content, - xp: constructUseType.pointValue, - ), - ], - targetID: targetId, + final constructs = [ + OneConstructUse( + useType: constructUseType, + lemma: token.lemma.text, + constructType: ConstructTypeEnum.vocab, + metadata: ConstructUseMetaData( + roomId: pangeaMessageEvent.room.id, + timeStamp: DateTime.now(), + eventId: pangeaMessageEvent.eventId, + ), + category: token.pos, + // in the case of a wrong answer, the cId doesn't match the token + form: token.text.content, + xp: constructUseType.pointValue, ), + ]; + + MatrixState.pangeaController.putAnalytics.addAnalytics( + constructs, + eventId: pangeaMessageEvent.eventId, + roomId: pangeaMessageEvent.room.id, + targetId: targetId, ); } diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 3fc7e5b35..24f1eaca6 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -152,6 +152,7 @@ abstract class ClientManager { PangeaEventTypes.courseUser, PangeaEventTypes.teacherMode, PangeaEventTypes.courseChatList, + PangeaEventTypes.analyticsSettings, // Pangea# }, logLevel: kReleaseMode ? Level.warning : Level.verbose, From 1e6cabc5d8dff5e6177128f75becc47c2ce0d528 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 4 Dec 2025 14:07:38 -0500 Subject: [PATCH 2/3] allow users to block constructs from their analytics --- lib/pages/chat/chat.dart | 2 +- .../analytics_misc/construct_list_model.dart | 6 +- .../get_analytics_controller.dart | 100 +++++++----------- .../put_analytics_controller.dart | 53 +++++++--- lib/pangea/analytics_page/analytics_page.dart | 14 +-- .../analytics_settings_extension.dart | 23 ---- .../analytics_settings_model.dart | 22 ++-- .../learning_progress_indicators.dart | 7 +- lib/pangea/common/utils/overlay.dart | 3 +- .../lemmas/lemma_highlight_emoji_row.dart | 2 +- 10 files changed, 103 insertions(+), 129 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 90cfaad2b..6c1eb16af 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -469,7 +469,7 @@ class ChatController extends State void _onAnalyticsUpdate(AnalyticsStreamUpdate update) { if (update.targetID != null) { - OverlayUtil.showPointsGained(update.targetID!, context); + OverlayUtil.showPointsGained(update.targetID!, update.points, context); } } diff --git a/lib/pangea/analytics_misc/construct_list_model.dart b/lib/pangea/analytics_misc/construct_list_model.dart index a6cbb3a68..cdf926a90 100644 --- a/lib/pangea/analytics_misc/construct_list_model.dart +++ b/lib/pangea/analytics_misc/construct_list_model.dart @@ -135,10 +135,10 @@ class ConstructListModel { level = calculateLevelWithXp(totalXP); } - void deleteLemma(String lemma, int offset) { - _uses.removeWhere((use) => use.lemma == lemma); + void deleteConstruct(ConstructIdentifier constructId, int offset) { + _uses.removeWhere((use) => use.identifier == constructId); _constructMap.removeWhere( - (key, value) => value.lemma == lemma, + (key, value) => value.id == constructId, ); updateConstructs([], offset); } diff --git a/lib/pangea/analytics_misc/get_analytics_controller.dart b/lib/pangea/analytics_misc/get_analytics_controller.dart index 6b7db6d53..a5d224ee6 100644 --- a/lib/pangea/analytics_misc/get_analytics_controller.dart +++ b/lib/pangea/analytics_misc/get_analytics_controller.dart @@ -85,6 +85,12 @@ class GetAnalyticsController extends BaseController { .putAnalytics.analyticsUpdateStream.stream .listen(_onAnalyticsUpdate); + _pangeaController.putAnalytics.savedActivitiesNotifier + .addListener(_onActivityAnalyticsUpdate); + + _pangeaController.putAnalytics.blockedConstructsNotifier + .addListener(_onBlockedConstructsUpdate); + // When a newly-joined space comes through in a sync // update, add the analytics rooms to the space _joinSpaceSubscription ??= _client.onSync.stream @@ -103,10 +109,10 @@ class GetAnalyticsController extends BaseController { ]; final Room? analyticsRoom = _client.analyticsRoomLocal(_l2!); - final blockedLemmas = analyticsRoom?.analyticsSettings?.blockedLemmas; + final blockedLemmas = analyticsRoom?.analyticsSettings?.blockedConstructs; if (blockedLemmas != null && blockedLemmas.isNotEmpty) { allUses.removeWhere( - (use) => blockedLemmas.contains(use.identifier.lemma), + (use) => blockedLemmas.contains(use.identifier), ); } @@ -124,11 +130,7 @@ class GetAnalyticsController extends BaseController { data: {}, ); } finally { - _updateAnalyticsStream( - type: AnalyticsUpdateType.init, - points: 0, - newConstructs: [], - ); + _updateAnalyticsStream(AnalyticsStreamUpdate()); if (!initCompleter.isCompleted) initCompleter.complete(); _initializing = false; } @@ -143,22 +145,16 @@ class GetAnalyticsController extends BaseController { _joinSpaceSubscription?.cancel(); _joinSpaceSubscription = null; initCompleter = Completer(); + _pangeaController.putAnalytics.savedActivitiesNotifier + .removeListener(_onActivityAnalyticsUpdate); + _pangeaController.putAnalytics.blockedConstructsNotifier + .removeListener(_onBlockedConstructsUpdate); _cache.clear(); } Future _onAnalyticsUpdate( AnalyticsUpdate analyticsUpdate, ) async { - final validTypes = [AnalyticsUpdateType.local, AnalyticsUpdateType.server]; - if (!validTypes.contains(analyticsUpdate.type)) { - _updateAnalyticsStream( - type: analyticsUpdate.type, - points: 0, - newConstructs: [], - ); - return; - } - if (analyticsUpdate.isLogout) return; final oldLevel = constructListModel.level; @@ -173,9 +169,6 @@ class GetAnalyticsController extends BaseController { ) .toSet(); - final prevUnlockedVocab = - constructListModel.unlockedLemmas(ConstructTypeEnum.vocab).toSet(); - constructListModel.updateConstructs(analyticsUpdate.newConstructs, offset); final newUnlockedMorphs = constructListModel @@ -186,11 +179,6 @@ class GetAnalyticsController extends BaseController { .toSet() .difference(prevUnlockedMorphs); - final newUnlockedVocab = constructListModel - .unlockedLemmas(ConstructTypeEnum.vocab) - .toSet() - .difference(prevUnlockedVocab); - if (analyticsUpdate.type == AnalyticsUpdateType.server) { await _getConstructs(forceUpdate: true); } @@ -206,13 +194,13 @@ class GetAnalyticsController extends BaseController { _onUnlockMorphLemmas(newUnlockedMorphs); } _updateAnalyticsStream( - type: analyticsUpdate.type, - points: analyticsUpdate.newConstructs.fold( - 0, - (previousValue, element) => previousValue + element.xp, + AnalyticsStreamUpdate( + points: analyticsUpdate.newConstructs.fold( + 0, + (previousValue, element) => previousValue + element.xp, + ), + targetID: analyticsUpdate.targetID, ), - targetID: analyticsUpdate.targetID, - newConstructs: [...newUnlockedMorphs, ...newUnlockedVocab], ); // Update public profile each time that new analytics are added. // If the level hasn't changed, this will not send an update to the server. @@ -223,20 +211,8 @@ class GetAnalyticsController extends BaseController { ); } - void _updateAnalyticsStream({ - required AnalyticsUpdateType type, - required int points, - required List newConstructs, - String? targetID, - }) => - analyticsStream.add( - AnalyticsStreamUpdate( - type: type, - points: points, - newConstructs: newConstructs, - targetID: targetID, - ), - ); + void _updateAnalyticsStream(AnalyticsStreamUpdate update) => + analyticsStream.add(update); void _onLevelUp(final int lowerLevel, final int upperLevel) { setState({ @@ -267,6 +243,21 @@ class GetAnalyticsController extends BaseController { setState({'unlocked_constructs': filtered}); } + void _onActivityAnalyticsUpdate() => + _updateAnalyticsStream(AnalyticsStreamUpdate()); + + void _onBlockedConstructsUpdate() { + final constructId = + _pangeaController.putAnalytics.blockedConstructsNotifier.value; + if (constructId == null) return; + + constructListModel.deleteConstruct( + constructId, + _pangeaController.userController.analyticsProfile?.xpOffset ?? 0, + ); + _updateAnalyticsStream(AnalyticsStreamUpdate()); + } + /// A local cache of eventIds and construct uses for messages sent since the last update. /// It's a map of eventIDs to a list of OneConstructUses. Not just a list of OneConstructUses /// because, with practice activity constructs, we might need to add to the list for a given @@ -484,19 +475,6 @@ class GetAnalyticsController extends BaseController { return newConstructCount; } -// Future -// _generateLevelUpAnalyticsAndSaveToStateEvent( -// final int lowerLevel, -// final int upperLevel, -// ) async { -// // generate level up analytics as a construct summary -// ConstructSummary summary; -// try { -// final int maxXP = constructListModel.calculateXpWithLevel(upperLevel); -// final int minXP = constructListModel.calculateXpWithLevel(lowerLevel); -// int diffXP = maxXP - minXP; -// if (diffXP < 0) diffXP = 0; - ConstructSummary? getConstructSummaryFromStateEvent() { try { final Room? analyticsRoom = _client.analyticsRoomLocal(_l2!); @@ -662,15 +640,11 @@ class AnalyticsCacheEntry { } class AnalyticsStreamUpdate { - final AnalyticsUpdateType type; final int points; - final List newConstructs; final String? targetID; AnalyticsStreamUpdate({ - required this.type, - required this.points, - required this.newConstructs, + this.points = 0, this.targetID, }); } diff --git a/lib/pangea/analytics_misc/put_analytics_controller.dart b/lib/pangea/analytics_misc/put_analytics_controller.dart index e15bbb6e9..40243ccd8 100644 --- a/lib/pangea/analytics_misc/put_analytics_controller.dart +++ b/lib/pangea/analytics_misc/put_analytics_controller.dart @@ -6,14 +6,17 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; +import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart'; +import 'package:fluffychat/pangea/analytics_settings/analytics_settings_model.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/user/controllers/user_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; -enum AnalyticsUpdateType { server, local, activities, init, delete } +enum AnalyticsUpdateType { server, local } /// handles the processing of analytics for /// 1) messages sent by the user and @@ -23,6 +26,10 @@ class PutAnalyticsController { StreamController analyticsUpdateStream = StreamController.broadcast(); + ValueNotifier> savedActivitiesNotifier = ValueNotifier([]); + ValueNotifier blockedConstructsNotifier = + ValueNotifier(null); + StreamSubscription? _languageStream; Timer? _updateTimer; @@ -50,9 +57,16 @@ class PutAnalyticsController { } void initialize() { + final Room? analyticsRoom = _client.analyticsRoomLocal( + _pangeaController.languageController.userL2!, + ); + + if (analyticsRoom != null) { + savedActivitiesNotifier.value = analyticsRoom.activityRoomIds; + } + _languageStream ??= _pangeaController.userController.languageStream.stream .listen(_onUpdateLanguages); - _refreshAnalyticsIfOutdated(); } @@ -281,22 +295,31 @@ class PutAnalyticsController { ); if (analyticsRoom == null) return; await analyticsRoom.addActivityRoomId(roomId); - - analyticsUpdateStream.add( - AnalyticsUpdate( - AnalyticsUpdateType.activities, - [], - ), - ); + savedActivitiesNotifier.value = analyticsRoom.activityRoomIds; } - Future onBlockLemma() async { - analyticsUpdateStream.add( - AnalyticsUpdate( - AnalyticsUpdateType.delete, - [], - ), + Future blockConstruct(ConstructIdentifier constructId) async { + if (_pangeaController.matrixState.client.userID == null) return; + if (_pangeaController.languageController.userL2 == null) return; + + final Room? analyticsRoom = await _client.getMyAnalyticsRoom( + _pangeaController.languageController.userL2!, ); + if (analyticsRoom == null) return; + + final current = analyticsRoom.analyticsSettings ?? + const AnalyticsSettingsModel(blockedConstructs: {}); + + final blockedConstructs = current.blockedConstructs; + final updated = current.copyWith( + blockedConstructs: { + ...blockedConstructs, + constructId, + }, + ); + + await analyticsRoom.setAnalyticsSettings(updated); + blockedConstructsNotifier.value = constructId; } } diff --git a/lib/pangea/analytics_page/analytics_page.dart b/lib/pangea/analytics_page/analytics_page.dart index 30031b275..79d98368e 100644 --- a/lib/pangea/analytics_page/analytics_page.dart +++ b/lib/pangea/analytics_page/analytics_page.dart @@ -8,10 +8,8 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_page/activity_archive.dart'; import 'package:fluffychat/pangea/analytics_page/analytics_page_constants.dart'; -import 'package:fluffychat/pangea/analytics_settings/analytics_settings_extension.dart'; import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart'; import 'package:fluffychat/pangea/analytics_summary/level_dialog_content.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; @@ -43,7 +41,8 @@ class AnalyticsPage extends StatelessWidget { if (resp != OkCancelResult.ok) return; final res = await showFutureLoadingDialog( context: context, - future: () => Matrix.of(context).client.blockLemma(construct!.lemma), + future: () => + MatrixState.pangeaController.putAnalytics.blockConstruct(construct!), ); if (!res.isError) { @@ -70,12 +69,9 @@ class AnalyticsPage extends StatelessWidget { ) : null, body: SafeArea( - child: StreamBuilder( - stream: MatrixState - .pangeaController.getAnalytics.analyticsStream.stream - .where( - (u) => u.type == AnalyticsUpdateType.init, - ), + child: FutureBuilder( + future: + MatrixState.pangeaController.getAnalytics.initCompleter.future, builder: (context, snapshot) { return Padding( padding: const EdgeInsetsGeometry.all(16.0), diff --git a/lib/pangea/analytics_settings/analytics_settings_extension.dart b/lib/pangea/analytics_settings/analytics_settings_extension.dart index 1ae673a8d..f3accbc37 100644 --- a/lib/pangea/analytics_settings/analytics_settings_extension.dart +++ b/lib/pangea/analytics_settings/analytics_settings_extension.dart @@ -1,9 +1,7 @@ import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; import 'package:fluffychat/pangea/analytics_settings/analytics_settings_model.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; -import 'package:fluffychat/widgets/matrix.dart'; extension AnalyticsSettingsRoomExtension on Room { AnalyticsSettingsModel? get analyticsSettings { @@ -23,24 +21,3 @@ extension AnalyticsSettingsRoomExtension on Room { ); } } - -extension AnalyticsSettingsClientExtension on Client { - Future blockLemma(String lemma) async { - final l2 = MatrixState.pangeaController.languageController.userL2!; - final analyticsRoom = await getMyAnalyticsRoom(l2); - if (analyticsRoom == null) { - throw Exception("Could not get or create analytics room"); - } - - final current = analyticsRoom.analyticsSettings; - final blockedLemmas = current?.blockedLemmas ?? {}; - final updated = current?.copyWith( - blockedLemmas: { - ...blockedLemmas, - lemma, - }, - ); - - await analyticsRoom.setAnalyticsSettings(updated!); - } -} diff --git a/lib/pangea/analytics_settings/analytics_settings_model.dart b/lib/pangea/analytics_settings/analytics_settings_model.dart index 24d923bf5..149964747 100644 --- a/lib/pangea/analytics_settings/analytics_settings_model.dart +++ b/lib/pangea/analytics_settings/analytics_settings_model.dart @@ -1,34 +1,36 @@ +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; + class AnalyticsSettingsModel { - final Set blockedLemmas; + final Set blockedConstructs; const AnalyticsSettingsModel({ - required this.blockedLemmas, + required this.blockedConstructs, }); AnalyticsSettingsModel copyWith({ - Set? blockedLemmas, + Set? blockedConstructs, }) { return AnalyticsSettingsModel( - blockedLemmas: blockedLemmas ?? this.blockedLemmas, + blockedConstructs: blockedConstructs ?? this.blockedConstructs, ); } factory AnalyticsSettingsModel.fromJson(Map json) { - final blockedLemmas = {}; - if (json['blocked_lemmas'] != null) { - final lemmas = json['blocked_lemmas'] as List; + final blockedConstructs = {}; + if (json['blocked_constructs'] != null) { + final lemmas = json['blocked_constructs'] as List; for (final lemma in lemmas) { - blockedLemmas.add(lemma as String); + blockedConstructs.add(ConstructIdentifier.fromJson(lemma)); } } return AnalyticsSettingsModel( - blockedLemmas: blockedLemmas, + blockedConstructs: blockedConstructs, ); } Map toJson() { return { - 'blocked_lemmas': blockedLemmas.toList(), + 'blocked_constructs': blockedConstructs.map((c) => c.toJson()).toList(), }; } } diff --git a/lib/pangea/analytics_summary/learning_progress_indicators.dart b/lib/pangea/analytics_summary/learning_progress_indicators.dart index e75c03d35..819d03fdf 100644 --- a/lib/pangea/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/analytics_summary/learning_progress_indicators.dart @@ -51,11 +51,11 @@ class LearningProgressIndicatorsState // if getAnalytics has already finished initializing, // the data is loaded and should be displayed. if (MatrixState.pangeaController.getAnalytics.initCompleter.isCompleted) { - updateData(null); + updateData(); } _analyticsSubscription = MatrixState .pangeaController.getAnalytics.analyticsStream.stream - .listen(updateData); + .listen((_) => updateData()); // rebuild when target language changes _languageSubscription = MatrixState @@ -71,10 +71,11 @@ class LearningProgressIndicatorsState _analyticsSubscription = null; _languageSubscription?.cancel(); _languageSubscription = null; + super.dispose(); } - void updateData(AnalyticsStreamUpdate? _) { + void updateData() { if (_loading) _loading = false; if (mounted) setState(() {}); } diff --git a/lib/pangea/common/utils/overlay.dart b/lib/pangea/common/utils/overlay.dart index aacabc9d8..c19259d76 100644 --- a/lib/pangea/common/utils/overlay.dart +++ b/lib/pangea/common/utils/overlay.dart @@ -289,6 +289,7 @@ class OverlayUtil { static void showPointsGained( String targetId, + int points, BuildContext context, ) { showOverlay( @@ -297,7 +298,7 @@ class OverlayUtil { targetAnchor: Alignment.bottomCenter, context: context, child: PointsGainedAnimation( - points: 2, + points: points, targetID: targetId, ), transformTargetId: targetId, diff --git a/lib/pangea/lemmas/lemma_highlight_emoji_row.dart b/lib/pangea/lemmas/lemma_highlight_emoji_row.dart index 3109a851e..d1a5dc8bb 100644 --- a/lib/pangea/lemmas/lemma_highlight_emoji_row.dart +++ b/lib/pangea/lemmas/lemma_highlight_emoji_row.dart @@ -81,7 +81,7 @@ class LemmaHighlightEmojiRowState extends State void _onAnalyticsUpdate(AnalyticsStreamUpdate update) { if (update.targetID != null) { - OverlayUtil.showPointsGained(update.targetID!, context); + OverlayUtil.showPointsGained(update.targetID!, update.points, context); } } From 06cfc6a2717f7c1309cd6eb8598fd5bb1924a1dc Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 4 Dec 2025 14:18:13 -0500 Subject: [PATCH 3/3] chore: set locally cached value for if user saw activity menu dropdown to false on logout --- .../common/controllers/pangea_controller.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index 7d8d7ef33..16dfecdd4 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -7,8 +7,10 @@ import 'package:get_storage/get_storage.dart'; import 'package:matrix/matrix.dart'; import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart'; @@ -131,6 +133,19 @@ class PangeaController { if (exclude.contains(key)) continue; futures.add(GetStorage(key).erase()); } + + if (AppConfig.showedActivityMenu) { + futures.add( + SharedPreferences.getInstance().then((prefs) async { + AppConfig.showedActivityMenu = false; + prefs.setBool( + SettingKeys.showedActivityMenu, + AppConfig.showedActivityMenu, + ); + }), + ); + } + await Future.wait(futures); }