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'; import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; class LevelUpdate { final int prevLevel; final int newLevel; LevelUpdate({required this.prevLevel, required this.newLevel}); } class AnalyticsUpdate { final List addedConstructs; final String? targetID; AnalyticsUpdate(this.addedConstructs, {this.targetID}); } class ConstructLevelUpdate { final ConstructIdentifier constructId; final ConstructLevelEnum level; final String? targetID; ConstructLevelUpdate({ required this.constructId, required this.level, this.targetID, }); } class AnalyticsUpdateDispatcher { final AnalyticsDataService dataService; final StreamController constructUpdateStream = StreamController.broadcast(); final StreamController activityAnalyticsStream = StreamController.broadcast(); final StreamController> unlockedConstructsStream = StreamController>.broadcast(); final StreamController levelUpdateStream = StreamController.broadcast(); final StreamController> newConstructsStream = StreamController>.broadcast(); final StreamController constructLevelUpdateStream = StreamController.broadcast(); final StreamController> _lemmaInfoUpdateStream = StreamController< MapEntry >.broadcast(); AnalyticsUpdateDispatcher(this.dataService); void dispose() { constructUpdateStream.close(); activityAnalyticsStream.close(); unlockedConstructsStream.close(); levelUpdateStream.close(); constructLevelUpdateStream.close(); _lemmaInfoUpdateStream.close(); } Stream lemmaUpdateStream(ConstructIdentifier constructId) => _lemmaInfoUpdateStream.stream .where((update) => update.key == constructId) .map((update) => update.value); void sendActivityAnalyticsUpdate(String? activityAnalytics) => activityAnalyticsStream.add(activityAnalytics); void sendLemmaInfoUpdate( ConstructIdentifier constructId, UserSetLemmaInfo lemmaInfo, ) => _lemmaInfoUpdateStream.add(MapEntry(constructId, lemmaInfo)); Future sendBlockedConstructsUpdate( Set blockedConstructs, String language, ) async { for (final blockedConstruct in blockedConstructs) { await dataService.updateBlockedConstructs(blockedConstruct, language); } final update = AnalyticsStreamUpdate(blockedConstructs: blockedConstructs); constructUpdateStream.add(update); } void sendEmptyAnalyticsUpdate() { constructUpdateStream.add(AnalyticsStreamUpdate()); } Future sendServerAnalyticsUpdate( List events, String language, ) async { await dataService.updateServerAnalytics(events, language); sendEmptyAnalyticsUpdate(); } Future sendLocalAnalyticsUpdate( AnalyticsUpdate analyticsUpdate, String language, ) async { final events = await dataService.updateLocalAnalytics( analyticsUpdate, language, ); for (final event in events) { _dispatch(event); } } void _dispatch(AnalyticsUpdateEvent event) { switch (event) { case final LevelUpEvent e: _onLevelUp(e.from, e.to); break; case final MorphUnlockedEvent e: _onUnlockMorphLemmas(e.unlocked); break; case final XPGainedEvent e: _onXPGained(e.points, e.targetID); break; case final ConstructLevelUpEvent e: _onConstructLevelUp(e.constructId, e.level, e.targetID); break; case final NewConstructsEvent e: _onNewConstruct(e.newConstructs); break; } } void _onLevelUp(final int lowerLevel, final int upperLevel) { levelUpdateStream.add( LevelUpdate(prevLevel: lowerLevel, newLevel: upperLevel), ); } void _onUnlockMorphLemmas(Set unlocked) { const excludedLemmas = {'not_proper'}; final filtered = { for (final id in unlocked) if (!excludedLemmas.contains(id.lemma.toLowerCase())) id, }; if (filtered.isNotEmpty) { unlockedConstructsStream.add(filtered); } } void _onXPGained(int points, String? targetID) { final update = AnalyticsStreamUpdate(points: points, targetID: targetID); constructUpdateStream.add(update); } void _onConstructLevelUp( ConstructIdentifier constructId, ConstructLevelEnum level, String? targetID, ) { constructLevelUpdateStream.add( ConstructLevelUpdate( constructId: constructId, level: level, targetID: targetID, ), ); } void _onNewConstruct(Set constructIds) { if (constructIds.isEmpty) return; newConstructsStream.add(constructIds); } }