From 62c91d9dcc1f6a15b9d8c4542c712f0ed9881581 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Feb 2026 11:08:58 -0500 Subject: [PATCH 1/4] chore: update language / settings streams on account data sync update --- lib/pangea/user/user_controller.dart | 35 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/pangea/user/user_controller.dart b/lib/pangea/user/user_controller.dart index da0196b64..5c8c66ce5 100644 --- a/lib/pangea/user/user_controller.dart +++ b/lib/pangea/user/user_controller.dart @@ -37,8 +37,8 @@ class LanguageUpdate { class UserController { final StreamController languageStream = StreamController.broadcast(); - final StreamController settingsUpdateStream = - StreamController.broadcast(); + final StreamController settingsUpdateStream = + StreamController.broadcast(); /// Cached version of the user profile, so it doesn't have /// to be read in from client's account data each time it is accessed. @@ -52,10 +52,26 @@ class UserController { matrix.Client get client => MatrixState.pangeaController.matrixState.client; void _onProfileUpdate(matrix.SyncUpdate sync) { + final prevTargetLang = userL2; + final prevBaseLang = userL1; + final profileData = client.accountData[ModelKey.userProfile]?.content; final Profile? fromAccountData = Profile.fromAccountData(profileData); if (fromAccountData != null) { _cachedProfile = fromAccountData; + + if ((prevTargetLang != userL2) || (prevBaseLang != userL1)) { + languageStream.add( + LanguageUpdate( + baseLang: userL1!, + targetLang: userL2!, + prevBaseLang: prevBaseLang, + prevTargetLang: prevTargetLang, + ), + ); + } else { + settingsUpdateStream.add(fromAccountData); + } } } @@ -92,8 +108,6 @@ class UserController { waitForDataInSync = false, }) async { await initialize(); - final prevTargetLang = userL2; - final prevBaseLang = userL1; final prevHash = profile.hashCode; final Profile updatedProfile = update(profile); @@ -103,19 +117,6 @@ class UserController { } await updatedProfile.saveProfileData(waitForDataInSync: waitForDataInSync); - - if ((prevTargetLang != userL2) || (prevBaseLang != userL1)) { - languageStream.add( - LanguageUpdate( - baseLang: userL1!, - targetLang: userL2!, - prevBaseLang: prevBaseLang, - prevTargetLang: prevTargetLang, - ), - ); - } else { - settingsUpdateStream.add(updatedProfile); - } } /// A completer for the profile model of a user. From 811ba58c73b94188086a96bcbd355bf3d1cb34aa Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Feb 2026 14:03:35 -0500 Subject: [PATCH 2/4] reload derived analytics on server analyics update --- .../analytics_data_service.dart | 45 ++++++++++--------- .../analytics_data/analytics_database.dart | 10 ++++- .../analytics_sync_controller.dart | 10 +---- .../analytics_update_dispatcher.dart | 12 ++++- .../analytics_update_service.dart | 2 +- .../derived_analytics_data_model.dart | 35 ++++++--------- lib/pangea/user/analytics_profile_model.dart | 3 ++ 7 files changed, 63 insertions(+), 54 deletions(-) diff --git a/lib/pangea/analytics_data/analytics_data_service.dart b/lib/pangea/analytics_data/analytics_data_service.dart index aa2851472..5adbfe5f6 100644 --- a/lib/pangea/analytics_data/analytics_data_service.dart +++ b/lib/pangea/analytics_data/analytics_data_service.dart @@ -130,29 +130,24 @@ class AnalyticsDataService { await _analyticsClientGetter.database.updateUserID(client.userID!); } - final resp = await client.getUserProfile(client.userID!); - final analyticsProfile = AnalyticsProfileModel.fromJson( - resp.additionalProperties, - ); - _syncController?.dispose(); _syncController = AnalyticsSyncController( client: client, dataService: this, ); + await _syncController!.bulkUpdate(); - final vocab = await getAggregatedConstructs(ConstructTypeEnum.vocab); - final morphs = await getAggregatedConstructs(ConstructTypeEnum.morph); - final constructs = [...vocab.values, ...morphs.values]; - final totalXP = constructs.fold(0, (total, c) => total + c.points); - await _analyticsClientGetter.database.updateDerivedStats( - DerivedAnalyticsDataModel( - totalXP: totalXP, - offset: analyticsProfile.xpOffset ?? 0, - ), + final resp = await client.getUserProfile(client.userID!); + final analyticsProfile = AnalyticsProfileModel.fromJson( + resp.additionalProperties, ); + final l2 = MatrixState.pangeaController.userController.userL2; + if (l2 != null) { + await updateXPOffset(analyticsProfile.xpOffsetByLanguage(l2) ?? 0); + } + _syncController!.start(); await _initMergeTable(); @@ -161,7 +156,7 @@ class AnalyticsDataService { } finally { Logs().i("Analytics database initialized."); initCompleter.complete(); - updateDispatcher.sendConstructAnalyticsUpdate(AnalyticsUpdate([])); + updateDispatcher.sendLocalAnalyticsUpdate(AnalyticsUpdate([])); updateDispatcher.sendActivityAnalyticsUpdate(null); } } @@ -422,7 +417,7 @@ class AnalyticsDataService { events.add(XPGainedEvent(points, update.targetID)); } - final newData = prevData.copyWith(totalXP: prevData.totalXP + points); + final newData = prevData.addXP(points); await _analyticsClientGetter.database.updateDerivedStats(newData); // Update public profile each time that new analytics are added. @@ -485,15 +480,25 @@ class AnalyticsDataService { return events; } - Future updateServerAnalytics( + Future> updateServerAnalytics( List events, ) async { + final List updates = []; + _invalidateCaches(); final blocked = blockedConstructs; for (final event in events) { _mergeTable.addConstructsByUses(event.content.uses, blocked); } await _analyticsClientGetter.database.updateServerAnalytics(events); + final vocab = await getAggregatedConstructs(ConstructTypeEnum.vocab); + final morphs = await getAggregatedConstructs(ConstructTypeEnum.morph); + final constructs = [...vocab.values, ...morphs.values]; + final totalXP = constructs.fold(0, (total, c) => total + c.points); + + await _analyticsClientGetter.database.updateTotalXP(totalXP); + updates.add(XPGainedEvent(0, null)); + return updates; } Future updateBlockedConstructs(ConstructIdentifier constructId) async { @@ -512,12 +517,10 @@ class AnalyticsDataService { level: newLevel, ); - await _analyticsClientGetter.database.updateDerivedStats( - DerivedAnalyticsDataModel(totalXP: newXP), - ); + await _analyticsClientGetter.database.updateTotalXP(newXP); _invalidateCaches(); - updateDispatcher.sendConstructAnalyticsUpdate( + updateDispatcher.sendLocalAnalyticsUpdate( AnalyticsUpdate([], blockedConstruct: constructId), ); } diff --git a/lib/pangea/analytics_data/analytics_database.dart b/lib/pangea/analytics_data/analytics_database.dart index 91515bee2..b24b8a1fb 100644 --- a/lib/pangea/analytics_data/analytics_database.dart +++ b/lib/pangea/analytics_data/analytics_database.dart @@ -468,7 +468,15 @@ class AnalyticsDatabase with DatabaseFileStorage { Future updateXPOffset(int offset) { return _transaction(() async { final stats = await getDerivedStats(); - final updatedStats = stats.copyWith(offset: offset); + final updatedStats = stats.copyWithOffset(offset); + await _derivedStatsBox.put('derived_stats', updatedStats.toJson()); + }); + } + + Future updateTotalXP(int totalXP) { + return _transaction(() async { + final stats = await getDerivedStats(); + final updatedStats = stats.copyWithTotalXP(totalXP); await _derivedStatsBox.put('derived_stats', updatedStats.toJson()); }); } diff --git a/lib/pangea/analytics_data/analytics_sync_controller.dart b/lib/pangea/analytics_data/analytics_sync_controller.dart index 6d51507a6..f06d402dd 100644 --- a/lib/pangea/analytics_data/analytics_sync_controller.dart +++ b/lib/pangea/analytics_data/analytics_sync_controller.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart'; -import 'package:fluffychat/pangea/analytics_data/analytics_update_dispatcher.dart'; import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; @@ -50,13 +49,8 @@ class AnalyticsSyncController { .toList(); if (constructEvents.isEmpty) return; - await dataService.updateServerAnalytics(constructEvents); - - // Server updates do not usually need to update the UI, since usually they are only - // transfering local data to the server. However, if a user if using multiple devices, - // we do need to update the UI when new data comes from the server. - dataService.updateDispatcher.sendConstructAnalyticsUpdate( - AnalyticsUpdate([]), + await dataService.updateDispatcher.sendServerAnalyticsUpdate( + constructEvents, ); } diff --git a/lib/pangea/analytics_data/analytics_update_dispatcher.dart b/lib/pangea/analytics_data/analytics_update_dispatcher.dart index a89586944..c30cbb295 100644 --- a/lib/pangea/analytics_data/analytics_update_dispatcher.dart +++ b/lib/pangea/analytics_data/analytics_update_dispatcher.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart'; import 'package:fluffychat/pangea/analytics_data/analytics_update_events.dart'; +import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/constructs/construct_level_enum.dart'; @@ -85,9 +86,16 @@ class AnalyticsUpdateDispatcher { UserSetLemmaInfo lemmaInfo, ) => _lemmaInfoUpdateStream.add(MapEntry(constructId, lemmaInfo)); - Future sendConstructAnalyticsUpdate( - AnalyticsUpdate analyticsUpdate, + Future sendServerAnalyticsUpdate( + List events, ) async { + final updates = await dataService.updateServerAnalytics(events); + for (final event in updates) { + _dispatch(event); + } + } + + Future sendLocalAnalyticsUpdate(AnalyticsUpdate analyticsUpdate) async { final events = await dataService.updateLocalAnalytics(analyticsUpdate); for (final event in events) { _dispatch(event); diff --git a/lib/pangea/analytics_data/analytics_update_service.dart b/lib/pangea/analytics_data/analytics_update_service.dart index f133d199b..7d11ed795 100644 --- a/lib/pangea/analytics_data/analytics_update_service.dart +++ b/lib/pangea/analytics_data/analytics_update_service.dart @@ -62,7 +62,7 @@ class AnalyticsUpdateService { List newConstructs, { bool forceUpdate = false, }) async { - await dataService.updateDispatcher.sendConstructAnalyticsUpdate( + await dataService.updateDispatcher.sendLocalAnalyticsUpdate( AnalyticsUpdate(newConstructs, targetID: targetID), ); diff --git a/lib/pangea/analytics_data/derived_analytics_data_model.dart b/lib/pangea/analytics_data/derived_analytics_data_model.dart index 0d97a2736..2b5416778 100644 --- a/lib/pangea/analytics_data/derived_analytics_data_model.dart +++ b/lib/pangea/analytics_data/derived_analytics_data_model.dart @@ -1,6 +1,5 @@ import 'dart:math'; -import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; class DerivedAnalyticsDataModel { @@ -12,7 +11,7 @@ class DerivedAnalyticsDataModel { int get totalXP => _totalXP + offset; - int get level => calculateLevelWithXp(_totalXP); + int get level => calculateLevelWithXp(totalXP); // the minimum XP required for a given level int get _minXPForLevel => calculateXpWithLevel(level); @@ -23,7 +22,7 @@ class DerivedAnalyticsDataModel { // the progress within the current level as a percentage (0.0 to 1.0) double get levelProgress { final progress = - (_totalXP - _minXPForLevel) / (minXPForNextLevel - _minXPForLevel); + (totalXP - _minXPForLevel) / (minXPForNextLevel - _minXPForLevel); return progress >= 0 ? progress : 0; } @@ -60,35 +59,29 @@ class DerivedAnalyticsDataModel { } } - DerivedAnalyticsDataModel update(List uses) { - int xp = _totalXP; - - for (final u in uses) { - xp += u.xp; - } - - return copyWith(totalXP: xp); + DerivedAnalyticsDataModel copyWithOffset(int offset) { + return DerivedAnalyticsDataModel(totalXP: _totalXP, offset: offset); } - DerivedAnalyticsDataModel merge(DerivedAnalyticsDataModel other) { + DerivedAnalyticsDataModel copyWithTotalXP(int totalXP) { + return DerivedAnalyticsDataModel(totalXP: totalXP, offset: offset); + } + + DerivedAnalyticsDataModel addXP(int xpToAdd) { return DerivedAnalyticsDataModel( - totalXP: _totalXP + other.totalXP, + totalXP: _totalXP + xpToAdd, offset: offset, ); } - DerivedAnalyticsDataModel copyWith({int? totalXP, int? offset}) { + factory DerivedAnalyticsDataModel.fromJson(Map map) { return DerivedAnalyticsDataModel( - totalXP: totalXP ?? this.totalXP, - offset: offset ?? this.offset, + totalXP: map['total_xp'] ?? 0, + offset: map['offset'] ?? 0, ); } - factory DerivedAnalyticsDataModel.fromJson(Map map) { - return DerivedAnalyticsDataModel(totalXP: map['total_xp'] ?? 0); - } - Map toJson() { - return {'total_xp': _totalXP}; + return {'total_xp': _totalXP, 'offset': offset}; } } diff --git a/lib/pangea/user/analytics_profile_model.dart b/lib/pangea/user/analytics_profile_model.dart index c7ed6c35a..a4abb9560 100644 --- a/lib/pangea/user/analytics_profile_model.dart +++ b/lib/pangea/user/analytics_profile_model.dart @@ -136,6 +136,9 @@ class AnalyticsProfileModel { int? get level => languageAnalytics?[targetLanguage]?.level; int? get xpOffset => languageAnalytics?[targetLanguage]?.xpOffset; + + int? xpOffsetByLanguage(LanguageModel language) => + languageAnalytics?[language]?.xpOffset; } class LanguageAnalyticsProfileEntry { From dfbc1ab0b9e51723a23a79e7badbdc0d09ab4461 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Feb 2026 16:04:13 -0500 Subject: [PATCH 3/4] update UI on all analytics update types --- .../analytics_data_service.dart | 12 +- .../analytics_sync_controller.dart | 120 +++++++++++++++++- .../analytics_update_dispatcher.dart | 23 ++-- .../analytics_update_events.dart | 5 - .../analytics_update_service.dart | 5 - pubspec.lock | 7 + pubspec.yaml | 4 +- 7 files changed, 138 insertions(+), 38 deletions(-) diff --git a/lib/pangea/analytics_data/analytics_data_service.dart b/lib/pangea/analytics_data/analytics_data_service.dart index 5adbfe5f6..fea3203db 100644 --- a/lib/pangea/analytics_data/analytics_data_service.dart +++ b/lib/pangea/analytics_data/analytics_data_service.dart @@ -156,7 +156,7 @@ class AnalyticsDataService { } finally { Logs().i("Analytics database initialized."); initCompleter.complete(); - updateDispatcher.sendLocalAnalyticsUpdate(AnalyticsUpdate([])); + updateDispatcher.sendEmptyAnalyticsUpdate(); updateDispatcher.sendActivityAnalyticsUpdate(null); } } @@ -408,7 +408,7 @@ class AnalyticsDataService { final newConstructs = await getConstructUses(updateIds); int points = 0; - if (update.blockedConstruct == null || updateIds.isNotEmpty) { + if (updateIds.isNotEmpty) { for (final id in updateIds) { final prevPoints = prevConstructs[id]?.points ?? 0; final newPoints = newConstructs[id]?.points ?? 0; @@ -469,10 +469,6 @@ class AnalyticsDataService { } } - if (update.blockedConstruct != null) { - events.add(ConstructBlockedEvent(update.blockedConstruct!)); - } - if (newUnusedConstructs.isNotEmpty) { events.add(NewConstructsEvent(newUnusedConstructs)); } @@ -518,11 +514,7 @@ class AnalyticsDataService { ); await _analyticsClientGetter.database.updateTotalXP(newXP); - _invalidateCaches(); - updateDispatcher.sendLocalAnalyticsUpdate( - AnalyticsUpdate([], blockedConstruct: constructId), - ); } Future clearLocalAnalytics() async { diff --git a/lib/pangea/analytics_data/analytics_sync_controller.dart b/lib/pangea/analytics_data/analytics_sync_controller.dart index f06d402dd..559db2ed0 100644 --- a/lib/pangea/analytics_data/analytics_sync_controller.dart +++ b/lib/pangea/analytics_data/analytics_sync_controller.dart @@ -5,10 +5,34 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart'; import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart'; import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart'; +import 'package:fluffychat/pangea/analytics_settings/analytics_settings_model.dart'; +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; +import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; import 'package:fluffychat/widgets/matrix.dart'; +enum _AnalyticsUpdateEvent { + constructAnalytics, + activityAnalytics, + lemmaInfo, + blockedConstruct; + + String get eventType { + switch (this) { + case _AnalyticsUpdateEvent.constructAnalytics: + return PangeaEventTypes.construct; + case _AnalyticsUpdateEvent.activityAnalytics: + return PangeaEventTypes.activityRoomIds; + case _AnalyticsUpdateEvent.lemmaInfo: + return PangeaEventTypes.userSetLemmaInfo; + case _AnalyticsUpdateEvent.blockedConstruct: + return PangeaEventTypes.analyticsSettings; + } + } +} + class AnalyticsSyncController { final Client client; final AnalyticsDataService dataService; @@ -30,15 +54,43 @@ class AnalyticsSyncController { final analyticsRoom = _getAnalyticsRoom(); if (analyticsRoom == null) return; - final events = update.rooms?.join?[analyticsRoom.id]?.timeline?.events - ?.where( - (e) => - e.type == PangeaEventTypes.construct && - e.senderId == client.userID, - ); + final roomUpdates = update.rooms?.join?[analyticsRoom.id]?.timeline?.events; + if (roomUpdates == null) return; - if (events == null || events.isEmpty) return; + for (final type in _AnalyticsUpdateEvent.values) { + await _dispatchSyncEvents(type, roomUpdates, analyticsRoom); + } + } + Future _dispatchSyncEvents( + _AnalyticsUpdateEvent type, + List events, + Room analyticsRoom, + ) async { + final updates = events + .where((e) => e.type == type.eventType && e.senderId == client.userID) + .toList(); + + switch (type) { + case _AnalyticsUpdateEvent.constructAnalytics: + await _onConstructEvents(updates, analyticsRoom); + break; + case _AnalyticsUpdateEvent.activityAnalytics: + _onActivityEvents(updates); + break; + case _AnalyticsUpdateEvent.lemmaInfo: + _onLemmaInfoEvents(updates); + break; + case _AnalyticsUpdateEvent.blockedConstruct: + await _onBlockedConstructEvents(updates); + break; + } + } + + Future _onConstructEvents( + List events, + Room analyticsRoom, + ) async { final constructEvents = events .map( (e) => ConstructAnalyticsEvent( @@ -54,6 +106,60 @@ class AnalyticsSyncController { ); } + void _onActivityEvents(List events) { + for (final event in events) { + if (event.content[ModelKey.roomIds] is! List) continue; + final roomIds = List.from( + event.content[ModelKey.roomIds]! as List, + ); + final prevContent = + event.unsigned?['prev_content'] as Map?; + final prevRoomIds = + prevContent != null && prevContent[ModelKey.roomIds] is List + ? List.from(prevContent[ModelKey.roomIds] as List) + : []; + final newRoomIds = roomIds + .where((id) => !prevRoomIds.contains(id)) + .toList(); + + for (final roomId in newRoomIds) { + dataService.updateDispatcher.sendActivityAnalyticsUpdate(roomId); + } + } + } + + void _onLemmaInfoEvents(List events) { + for (final event in events) { + if (event.stateKey == null) continue; + final cID = ConstructIdentifier.fromString(event.stateKey!); + if (cID == null) continue; + + final update = UserSetLemmaInfo.fromJson(event.content); + dataService.updateDispatcher.sendLemmaInfoUpdate(cID, update); + } + } + + Future _onBlockedConstructEvents(List events) async { + for (final event in events) { + final current = AnalyticsSettingsModel.fromJson(event.content); + final prevContent = + event.unsigned?['prev_content'] as Map?; + final prev = prevContent != null + ? AnalyticsSettingsModel.fromJson(prevContent) + : null; + + final newBlocked = current.blockedConstructs; + final prevBlocked = prev?.blockedConstructs ?? {}; + + final newlyBlocked = newBlocked.where((c) => !prevBlocked.contains(c)); + for (final constructId in newlyBlocked) { + await dataService.updateDispatcher.sendBlockedConstructUpdate( + constructId, + ); + } + } + } + Future waitForSync(String analyticsRoomId) async { await client.onSync.stream.firstWhere((update) { final roomUpdate = update.rooms?.join?[analyticsRoomId]; diff --git a/lib/pangea/analytics_data/analytics_update_dispatcher.dart b/lib/pangea/analytics_data/analytics_update_dispatcher.dart index c30cbb295..c9e4736d0 100644 --- a/lib/pangea/analytics_data/analytics_update_dispatcher.dart +++ b/lib/pangea/analytics_data/analytics_update_dispatcher.dart @@ -17,10 +17,9 @@ class LevelUpdate { class AnalyticsUpdate { final List addedConstructs; - final ConstructIdentifier? blockedConstruct; final String? targetID; - AnalyticsUpdate(this.addedConstructs, {this.blockedConstruct, this.targetID}); + AnalyticsUpdate(this.addedConstructs, {this.targetID}); } class ConstructLevelUpdate { @@ -86,6 +85,18 @@ class AnalyticsUpdateDispatcher { UserSetLemmaInfo lemmaInfo, ) => _lemmaInfoUpdateStream.add(MapEntry(constructId, lemmaInfo)); + Future sendBlockedConstructUpdate( + ConstructIdentifier blockedConstruct, + ) async { + await dataService.updateBlockedConstructs(blockedConstruct); + final update = AnalyticsStreamUpdate(blockedConstruct: blockedConstruct); + constructUpdateStream.add(update); + } + + void sendEmptyAnalyticsUpdate() { + constructUpdateStream.add(AnalyticsStreamUpdate()); + } + Future sendServerAnalyticsUpdate( List events, ) async { @@ -113,9 +124,6 @@ class AnalyticsUpdateDispatcher { case final XPGainedEvent e: _onXPGained(e.points, e.targetID); break; - case final ConstructBlockedEvent e: - _onBlockedConstruct(e.blockedConstruct); - break; case final ConstructLevelUpEvent e: _onConstructLevelUp(e.constructId, e.level, e.targetID); break; @@ -163,11 +171,6 @@ class AnalyticsUpdateDispatcher { ); } - void _onBlockedConstruct(ConstructIdentifier constructId) { - final update = AnalyticsStreamUpdate(blockedConstruct: constructId); - constructUpdateStream.add(update); - } - void _onNewConstruct(Set constructIds) { if (constructIds.isEmpty) return; newConstructsStream.add(constructIds); diff --git a/lib/pangea/analytics_data/analytics_update_events.dart b/lib/pangea/analytics_data/analytics_update_events.dart index a202baa06..e35767943 100644 --- a/lib/pangea/analytics_data/analytics_update_events.dart +++ b/lib/pangea/analytics_data/analytics_update_events.dart @@ -27,11 +27,6 @@ class XPGainedEvent extends AnalyticsUpdateEvent { XPGainedEvent(this.points, this.targetID); } -class ConstructBlockedEvent extends AnalyticsUpdateEvent { - final ConstructIdentifier blockedConstruct; - ConstructBlockedEvent(this.blockedConstruct); -} - class NewConstructsEvent extends AnalyticsUpdateEvent { final Set newConstructs; NewConstructsEvent(this.newConstructs); diff --git a/lib/pangea/analytics_data/analytics_update_service.dart b/lib/pangea/analytics_data/analytics_update_service.dart index 7d11ed795..156b035c8 100644 --- a/lib/pangea/analytics_data/analytics_update_service.dart +++ b/lib/pangea/analytics_data/analytics_update_service.dart @@ -127,9 +127,6 @@ class AnalyticsUpdateService { if (analyticsRoom == null) return; await analyticsRoom.addActivityRoomId(roomId); - if (lang.langCodeShort == _l2?.langCodeShort) { - dataService.updateDispatcher.sendActivityAnalyticsUpdate(roomId); - } } Future blockConstruct(ConstructIdentifier constructId) async { @@ -143,7 +140,6 @@ class AnalyticsUpdateService { ); await analyticsRoom.setAnalyticsSettings(updated); - await dataService.updateBlockedConstructs(constructId); } Future setLemmaInfo( @@ -160,7 +156,6 @@ class AnalyticsUpdateService { meaning: meaning, ); if (userLemmaInfo == updated) return; - dataService.updateDispatcher.sendLemmaInfoUpdate(constructId, updated); try { await analyticsRoom.setUserSetLemmaInfo(constructId, updated); diff --git a/pubspec.lock b/pubspec.lock index e7d3e9179..47cfd7155 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -538,6 +538,13 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + fcm_shared_isolate: + dependency: "direct overridden" + description: + path: "pangea_packages/fcm_shared_isolate" + relative: true + source: path + version: "0.2.0" ffi: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 188e91b74..fa51d0f34 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -170,4 +170,6 @@ flutter: # 1. Don't do it if you can avoid it or fix it upstream in a manageable time # 2. Always link an (upstream?) issue # 3. Explain how and when this can be removed (overrides must be temporarily) -dependency_overrides: \ No newline at end of file +dependency_overrides: + fcm_shared_isolate: + path: pangea_packages/fcm_shared_isolate \ No newline at end of file From 4a9e312cce9208e6a901a9d4d5494315499caa85 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 11 Feb 2026 16:30:46 -0500 Subject: [PATCH 4/4] some fixes --- .../analytics_data/analytics_data_service.dart | 6 +----- .../analytics_sync_controller.dart | 4 ++-- .../analytics_update_dispatcher.dart | 6 ++---- lib/pangea/user/user_controller.dart | 16 +++++++++++++++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lib/pangea/analytics_data/analytics_data_service.dart b/lib/pangea/analytics_data/analytics_data_service.dart index fea3203db..224c87f48 100644 --- a/lib/pangea/analytics_data/analytics_data_service.dart +++ b/lib/pangea/analytics_data/analytics_data_service.dart @@ -476,11 +476,9 @@ class AnalyticsDataService { return events; } - Future> updateServerAnalytics( + Future updateServerAnalytics( List events, ) async { - final List updates = []; - _invalidateCaches(); final blocked = blockedConstructs; for (final event in events) { @@ -493,8 +491,6 @@ class AnalyticsDataService { final totalXP = constructs.fold(0, (total, c) => total + c.points); await _analyticsClientGetter.database.updateTotalXP(totalXP); - updates.add(XPGainedEvent(0, null)); - return updates; } Future updateBlockedConstructs(ConstructIdentifier constructId) async { diff --git a/lib/pangea/analytics_data/analytics_sync_controller.dart b/lib/pangea/analytics_data/analytics_sync_controller.dart index 559db2ed0..2bd5e70b1 100644 --- a/lib/pangea/analytics_data/analytics_sync_controller.dart +++ b/lib/pangea/analytics_data/analytics_sync_controller.dart @@ -122,8 +122,8 @@ class AnalyticsSyncController { .where((id) => !prevRoomIds.contains(id)) .toList(); - for (final roomId in newRoomIds) { - dataService.updateDispatcher.sendActivityAnalyticsUpdate(roomId); + if (newRoomIds.isNotEmpty) { + dataService.updateDispatcher.sendActivityAnalyticsUpdate(null); } } } diff --git a/lib/pangea/analytics_data/analytics_update_dispatcher.dart b/lib/pangea/analytics_data/analytics_update_dispatcher.dart index c9e4736d0..c3e7a9e83 100644 --- a/lib/pangea/analytics_data/analytics_update_dispatcher.dart +++ b/lib/pangea/analytics_data/analytics_update_dispatcher.dart @@ -100,10 +100,8 @@ class AnalyticsUpdateDispatcher { Future sendServerAnalyticsUpdate( List events, ) async { - final updates = await dataService.updateServerAnalytics(events); - for (final event in updates) { - _dispatch(event); - } + await dataService.updateServerAnalytics(events); + sendEmptyAnalyticsUpdate(); } Future sendLocalAnalyticsUpdate(AnalyticsUpdate analyticsUpdate) async { diff --git a/lib/pangea/user/user_controller.dart b/lib/pangea/user/user_controller.dart index 5c8c66ce5..89483ff5c 100644 --- a/lib/pangea/user/user_controller.dart +++ b/lib/pangea/user/user_controller.dart @@ -57,10 +57,24 @@ class UserController { final profileData = client.accountData[ModelKey.userProfile]?.content; final Profile? fromAccountData = Profile.fromAccountData(profileData); - if (fromAccountData != null) { + if (fromAccountData != null && fromAccountData != _cachedProfile) { _cachedProfile = fromAccountData; if ((prevTargetLang != userL2) || (prevBaseLang != userL1)) { + if (userL1 == null || userL2 == null) { + // if either language is null, then we want to send a settings update instead of a language update + ErrorHandler.logError( + e: "One of the user languages is null. Sending settings update instead of language update.", + data: { + 'prevBaseLang': prevBaseLang?.langCode, + 'prevTargetLang': prevTargetLang?.langCode, + 'userL1': userL1?.langCode, + 'userL2': userL2?.langCode, + }, + ); + settingsUpdateStream.add(fromAccountData); + return; + } languageStream.add( LanguageUpdate( baseLang: userL1!,