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); } }