allow users to block constructs from their analytics
This commit is contained in:
parent
4d58b66bf1
commit
1e6cabc5d8
10 changed files with 103 additions and 129 deletions
|
|
@ -469,7 +469,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
void _onAnalyticsUpdate(AnalyticsStreamUpdate update) {
|
||||
if (update.targetID != null) {
|
||||
OverlayUtil.showPointsGained(update.targetID!, context);
|
||||
OverlayUtil.showPointsGained(update.targetID!, update.points, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<void>();
|
||||
_pangeaController.putAnalytics.savedActivitiesNotifier
|
||||
.removeListener(_onActivityAnalyticsUpdate);
|
||||
_pangeaController.putAnalytics.blockedConstructsNotifier
|
||||
.removeListener(_onBlockedConstructsUpdate);
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
Future<void> _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<int>(
|
||||
0,
|
||||
(previousValue, element) => previousValue + element.xp,
|
||||
AnalyticsStreamUpdate(
|
||||
points: analyticsUpdate.newConstructs.fold<int>(
|
||||
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<ConstructIdentifier> 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<GenerateConstructSummaryResult?>
|
||||
// _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<ConstructIdentifier> newConstructs;
|
||||
final String? targetID;
|
||||
|
||||
AnalyticsStreamUpdate({
|
||||
required this.type,
|
||||
required this.points,
|
||||
required this.newConstructs,
|
||||
this.points = 0,
|
||||
this.targetID,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AnalyticsUpdate> analyticsUpdateStream =
|
||||
StreamController.broadcast();
|
||||
|
||||
ValueNotifier<List<String>> savedActivitiesNotifier = ValueNotifier([]);
|
||||
ValueNotifier<ConstructIdentifier?> 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<void> onBlockLemma() async {
|
||||
analyticsUpdateStream.add(
|
||||
AnalyticsUpdate(
|
||||
AnalyticsUpdateType.delete,
|
||||
[],
|
||||
),
|
||||
Future<void> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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<void> 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!);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,36 @@
|
|||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
|
||||
class AnalyticsSettingsModel {
|
||||
final Set<String> blockedLemmas;
|
||||
final Set<ConstructIdentifier> blockedConstructs;
|
||||
|
||||
const AnalyticsSettingsModel({
|
||||
required this.blockedLemmas,
|
||||
required this.blockedConstructs,
|
||||
});
|
||||
|
||||
AnalyticsSettingsModel copyWith({
|
||||
Set<String>? blockedLemmas,
|
||||
Set<ConstructIdentifier>? blockedConstructs,
|
||||
}) {
|
||||
return AnalyticsSettingsModel(
|
||||
blockedLemmas: blockedLemmas ?? this.blockedLemmas,
|
||||
blockedConstructs: blockedConstructs ?? this.blockedConstructs,
|
||||
);
|
||||
}
|
||||
|
||||
factory AnalyticsSettingsModel.fromJson(Map<String, dynamic> json) {
|
||||
final blockedLemmas = <String>{};
|
||||
if (json['blocked_lemmas'] != null) {
|
||||
final lemmas = json['blocked_lemmas'] as List<dynamic>;
|
||||
final blockedConstructs = <ConstructIdentifier>{};
|
||||
if (json['blocked_constructs'] != null) {
|
||||
final lemmas = json['blocked_constructs'] as List<dynamic>;
|
||||
for (final lemma in lemmas) {
|
||||
blockedLemmas.add(lemma as String);
|
||||
blockedConstructs.add(ConstructIdentifier.fromJson(lemma));
|
||||
}
|
||||
}
|
||||
return AnalyticsSettingsModel(
|
||||
blockedLemmas: blockedLemmas,
|
||||
blockedConstructs: blockedConstructs,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'blocked_lemmas': blockedLemmas.toList(),
|
||||
'blocked_constructs': blockedConstructs.map((c) => c.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(() {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class LemmaHighlightEmojiRowState extends State<LemmaHighlightEmojiRow>
|
|||
|
||||
void _onAnalyticsUpdate(AnalyticsStreamUpdate update) {
|
||||
if (update.targetID != null) {
|
||||
OverlayUtil.showPointsGained(update.targetID!, context);
|
||||
OverlayUtil.showPointsGained(update.targetID!, update.points, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue