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 {