From e96c0e34dbb355594473b5d93c4187e0de208aac Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 13 Jun 2024 09:09:24 -0400 Subject: [PATCH] constructs data model updates --- .../message_analytics_controller.dart | 92 ++++---------- .../models/analytics/constructs_event.dart | 31 +---- .../models/analytics/constructs_model.dart | 115 ++++++------------ .../pages/analytics/construct_list.dart | 62 +++++++--- 4 files changed, 105 insertions(+), 195 deletions(-) diff --git a/lib/pangea/controllers/message_analytics_controller.dart b/lib/pangea/controllers/message_analytics_controller.dart index 092d01c2a..74e6e256e 100644 --- a/lib/pangea/controllers/message_analytics_controller.dart +++ b/lib/pangea/controllers/message_analytics_controller.dart @@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/enum/time_span.dart'; import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_event.dart'; -import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; @@ -520,13 +519,11 @@ class AnalyticsController extends BaseController { List? get constructs => _constructs; - Future> allMyConstructs({ - ConstructType? type, - }) async { + Future> allMyConstructs() async { final List analyticsRooms = _pangeaController.matrixState.client.allMyAnalyticsRooms; - List allConstructs = []; + final List allConstructs = []; for (final Room analyticsRoom in analyticsRooms) { final List? roomEvents = (await analyticsRoom.getAnalyticsEvents( @@ -538,17 +535,12 @@ class AnalyticsController extends BaseController { allConstructs.addAll(roomEvents ?? []); } - allConstructs = type == null - ? allConstructs - : allConstructs.where((e) => e.content.type == type).toList(); - final List adminSpaceRooms = await _pangeaController.matrixState.client.teacherRoomIds; for (final construct in allConstructs) { - final lemmaUses = construct.content.uses; - for (final lemmaUse in lemmaUses) { - lemmaUse.uses.removeWhere((u) => adminSpaceRooms.contains(u.chatId)); - } + construct.content.uses.removeWhere( + (use) => adminSpaceRooms.contains(use.chatId), + ); } return allConstructs @@ -557,9 +549,8 @@ class AnalyticsController extends BaseController { } Future> allSpaceMemberConstructs( - Room space, { - ConstructType? type, - }) async { + Room space, + ) async { await space.postLoad(); await space.requestParticipants(); final String? langCode = _pangeaController.languageController.activeL2Code( @@ -595,19 +586,16 @@ class AnalyticsController extends BaseController { final List spaceChildrenIds = space.allSpaceChildRoomIds; final List allConstructs = []; for (final constructEvent in constructEvents) { - final lemmaUses = constructEvent.content.uses; - for (final lemmaUse in lemmaUses) { - lemmaUse.uses.removeWhere((u) => !spaceChildrenIds.contains(u.chatId)); - } + constructEvent.content.uses.removeWhere( + (use) => !spaceChildrenIds.contains(use.chatId), + ); if (constructEvent.content.uses.isNotEmpty) { allConstructs.add(constructEvent); } } - return type == null - ? allConstructs - : allConstructs.where((e) => e.content.type == type).toList(); + return allConstructs; } List filterStudentConstructs( @@ -626,10 +614,7 @@ class AnalyticsController extends BaseController { ) { final List filtered = [...unfilteredConstructs]; for (final construct in filtered) { - final lemmaUses = construct.content.uses; - for (final lemmaUse in lemmaUses) { - lemmaUse.uses.removeWhere((u) => u.chatId != roomID); - } + construct.content.uses.removeWhere((u) => u.chatId != roomID); } return filtered; } @@ -642,10 +627,9 @@ class AnalyticsController extends BaseController { final List filtered = List.from(unfilteredConstructs); for (final construct in filtered) { - final lemmaUses = construct.content.uses; - for (final lemmaUse in lemmaUses) { - lemmaUse.uses.removeWhere((u) => !directChatIds.contains(u.chatId)); - } + construct.content.uses.removeWhere( + (use) => !directChatIds.contains(use.chatId), + ); } return filtered; } @@ -664,10 +648,9 @@ class AnalyticsController extends BaseController { List.from(unfilteredConstructs); for (final construct in filtered) { - final lemmaUses = construct.content.uses; - for (final lemmaUse in lemmaUses) { - lemmaUse.uses.removeWhere((u) => !chatIds.contains(u.chatId)); - } + construct.content.uses.removeWhere( + (use) => !chatIds.contains(use.chatId), + ); } return filtered; @@ -723,9 +706,7 @@ class AnalyticsController extends BaseController { AnalyticsSelected? selected, }) async { final List unfilteredConstructs = - await allMyConstructs( - type: constructType, - ); + await allMyConstructs(); final Room? space = selected?.type == AnalyticsEntryType.space ? _pangeaController.matrixState.client.getRoomById(selected!.id) @@ -748,7 +729,6 @@ class AnalyticsController extends BaseController { final List unfilteredConstructs = await allSpaceMemberConstructs( space, - type: constructType, ); return filterConstructs( @@ -772,12 +752,9 @@ class AnalyticsController extends BaseController { for (int i = 0; i < unfilteredConstructs.length; i++) { final construct = unfilteredConstructs[i]; - final lemmaUses = construct.content.uses; - for (final lemmaUse in lemmaUses) { - lemmaUse.uses.removeWhere( - (u) => u.timeStamp.isBefore(currentAnalyticsTimeSpan.cutOffDate), - ); - } + construct.content.uses.removeWhere( + (use) => use.timeStamp.isBefore(currentAnalyticsTimeSpan.cutOffDate), + ); } unfilteredConstructs.removeWhere((e) => e.content.uses.isEmpty); @@ -918,31 +895,6 @@ class AnalyticsController extends BaseController { settingConstructs = false; return _constructs; } - - // used to aggregate ConstructEvents from - // multiple senders (students) with the same lemma - List aggregateConstructData( - List constructs, - ) { - final Map> lemmasToConstructs = {}; - for (final construct in constructs) { - for (final lemmaUses in construct.content.uses) { - lemmasToConstructs[lemmaUses.lemma] ??= []; - lemmasToConstructs[lemmaUses.lemma]!.add(lemmaUses); - } - } - - final List aggregatedConstructs = []; - for (final lemmaToConstructs in lemmasToConstructs.entries) { - final List lemmaConstructs = - lemmaToConstructs.value; - final AggregateConstructUses aggregatedData = AggregateConstructUses( - lemmaUses: lemmaConstructs, - ); - aggregatedConstructs.add(aggregatedData); - } - return aggregatedConstructs; - } } abstract class CacheEntry { diff --git a/lib/pangea/models/analytics/constructs_event.dart b/lib/pangea/models/analytics/constructs_event.dart index d297ba893..c2930faba 100644 --- a/lib/pangea/models/analytics/constructs_event.dart +++ b/lib/pangea/models/analytics/constructs_event.dart @@ -1,7 +1,5 @@ -import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/models/analytics/analytics_event.dart'; import 'package:fluffychat/pangea/models/analytics/constructs_model.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:matrix/matrix.dart'; import '../../constants/pangea_event_types.dart'; @@ -25,35 +23,8 @@ class ConstructAnalyticsEvent extends AnalyticsEvent { Room analyticsRoom, List uses, ) async { - // create a map of lemmas to their uses - final Map> lemmasToUses = {}; - for (final use in uses) { - if (use.lemma == null) { - ErrorHandler.logError( - e: "use has no lemma in sendConstructsEvent", - s: StackTrace.current, - ); - continue; - } - lemmasToUses[use.lemma!] ??= []; - lemmasToUses[use.lemma]!.add(use); - } - - // convert the map of lemmas to uses into a list of LemmaConstructsModel - // each entry in this list contains one lemma to many uses - final List lemmaUses = lemmasToUses.entries - .map( - (entry) => LemmaConstructsModel( - lemma: entry.key, - uses: entry.value, - ), - ) - .toList(); - - // finally, send the construct analytics event to the analytics room final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel( - type: ConstructType.grammar, - uses: lemmaUses, + uses: uses, ); final String? eventId = await analyticsRoom.sendEvent( diff --git a/lib/pangea/models/analytics/constructs_model.dart b/lib/pangea/models/analytics/constructs_model.dart index a16855c2b..209926f8e 100644 --- a/lib/pangea/models/analytics/constructs_model.dart +++ b/lib/pangea/models/analytics/constructs_model.dart @@ -1,17 +1,15 @@ -import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/analytics/analytics_model.dart'; +import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import '../../enum/construct_type_enum.dart'; class ConstructAnalyticsModel extends AnalyticsModel { - ConstructType type; - List uses; + List uses; ConstructAnalyticsModel({ - required this.type, this.uses = const [], }); @@ -19,24 +17,16 @@ class ConstructAnalyticsModel extends AnalyticsModel { factory ConstructAnalyticsModel.fromJson(Map json) { return ConstructAnalyticsModel( - type: ConstructTypeUtil.fromString(json['type']), uses: json[_usesKey] - .values - .map((lemmaUses) => LemmaConstructsModel.fromJson(lemmaUses)) - .cast() + .map((use) => OneConstructUse.fromJson(use)) + .cast() .toList(), ); } toJson() { - final Map usesMap = {}; - for (final use in uses) { - usesMap[use.lemma] = use.toJson(); - } - return { - 'type': type.string, - _usesKey: usesMap, + _usesKey: uses.map((use) => use.toJson()).toList(), }; } @@ -44,53 +34,34 @@ class ConstructAnalyticsModel extends AnalyticsModel { List recentMsgs, ) { final List filtered = List.from(recentMsgs); - final List uses = filtered - .map( - (msg) => msg.originalSent?.choreo?.toGrammarConstructUse( - msg.eventId, - msg.room.id, - msg.originServerTs, - ), - ) - .where((element) => element != null) - .cast>() - .expand((element) => element) - .toList(); + final List uses = []; + + for (final msg in filtered) { + if (msg.originalSent?.choreo == null) continue; + uses.addAll( + msg.originalSent!.choreo!.toGrammarConstructUse( + msg.eventId, + msg.room.id, + msg.originServerTs, + ), + ); + + final List? tokens = msg.originalSent?.tokens; + if (tokens == null) continue; + uses.addAll( + msg.originalSent!.choreo!.toVocabUse( + tokens, + msg.room.id, + msg.eventId, + msg.originServerTs, + ), + ); + } return uses; } } -class LemmaConstructsModel { - String lemma; - List uses; - - LemmaConstructsModel({ - required this.lemma, - this.uses = const [], - }); - - factory LemmaConstructsModel.fromJson(Map json) { - return LemmaConstructsModel( - lemma: json[ModelKey.lemma], - uses: (json['uses'] ?? [] as Iterable) - .map( - (use) => use != null ? OneConstructUse.fromJson(use) : null, - ) - .where((element) => element != null) - .cast() - .toList(), - ); - } - - Map toJson() { - return { - ModelKey.lemma: lemma, - 'uses': uses.map((use) => use.toJson()).toList(), - }; - } -} - enum ConstructUseType { /// produced in chat by user, igc was run, and we've judged it to be a correct use wa, @@ -206,7 +177,7 @@ class OneConstructUse { ); } - Map toJson([bool condensed = true]) { + Map toJson([bool condensed = false]) { final Map data = { 'useType': useType.string, 'chatId': chatId, @@ -234,24 +205,14 @@ class OneConstructUse { } } -class AggregateConstructUses { - final List _lemmaUses; +class ConstructUses { + final List uses; + final ConstructType constructType; + final String lemma; - AggregateConstructUses({required List lemmaUses}) - : _lemmaUses = lemmaUses; - - String get lemma { - assert( - _lemmaUses.isNotEmpty && - _lemmaUses.every( - (construct) => construct.lemma == _lemmaUses.first.lemma, - ), - ); - return _lemmaUses.first.lemma; - } - - List get uses => _lemmaUses - .map((lemmaUse) => lemmaUse.uses) - .expand((element) => element) - .toList(); + ConstructUses({ + required this.uses, + required this.constructType, + required this.lemma, + }); } diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index d555b4378..c227491b9 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -116,6 +116,7 @@ class ConstructListView extends StatefulWidget { } class ConstructListViewState extends State { + final ConstructType constructType = ConstructType.grammar; final Map _timelinesCache = {}; final Map _msgEventCache = {}; final List _msgEvents = []; @@ -128,7 +129,7 @@ class ConstructListViewState extends State { refreshSubscription = widget.refreshStream.stream.listen((forceUpdate) { widget.pangeaController.analytics .setConstructs( - constructType: ConstructType.grammar, + constructType: constructType, removeIT: true, defaultSelected: widget.defaultSelected, selected: widget.selected, @@ -218,21 +219,46 @@ class ConstructListViewState extends State { } } - List? get constructs { + List? get constructs { if (widget.pangeaController.analytics.constructs == null) { return null; } - return widget.pangeaController.analytics - .aggregateConstructData(widget.pangeaController.analytics.constructs!) - .where((lemmaUses) => lemmaUses.uses.isNotEmpty) - .sorted((a, b) { - final int cmp = b.uses.length.compareTo(a.uses.length); - if (cmp != 0) return cmp; + + final List filtered = + List.from(widget.pangeaController.analytics.constructs!) + .map((event) => event.content.uses) + .expand((uses) => uses) + .cast() + .where((use) => use.constructType == constructType) + .toList(); + + final Map> lemmaToUses = {}; + for (final use in filtered) { + if (use.lemma == null) continue; + lemmaToUses[use.lemma!] ??= []; + lemmaToUses[use.lemma!]!.add(use); + } + + final constructUses = lemmaToUses.entries + .map( + (entry) => ConstructUses( + lemma: entry.key, + uses: entry.value, + constructType: constructType, + ), + ) + .toList(); + + constructUses.sort((a, b) { + final comp = b.uses.length.compareTo(a.uses.length); + if (comp != 0) return comp; return a.lemma.compareTo(b.lemma); - }).toList(); + }); + + return constructUses; } - AggregateConstructUses? get currentConstruct => constructs?.firstWhereOrNull( + ConstructUses? get currentConstruct => constructs?.firstWhereOrNull( (element) => element.lemma == widget.controller.currentLemma, ); @@ -456,21 +482,21 @@ class ConstructMessage extends StatelessWidget { class ConstructMessageBubble extends StatelessWidget { final String errorText; final String replacementText; - final int? start; - final int? end; + final int start; + final int end; const ConstructMessageBubble({ super.key, required this.errorText, required this.replacementText, - this.start, - this.end, + required this.start, + required this.end, }); @override Widget build(BuildContext context) { final defaultStyle = TextStyle( - color: Theme.of(context).colorScheme.onBackground, + color: Theme.of(context).colorScheme.onSurface, fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor, height: 1.3, ); @@ -498,7 +524,7 @@ class ConstructMessageBubble extends StatelessWidget { vertical: 8, ), child: RichText( - text: (start == null || end == null) + text: (end == null) ? TextSpan( text: errorText, style: defaultStyle, @@ -510,7 +536,7 @@ class ConstructMessageBubble extends StatelessWidget { style: defaultStyle, ), TextSpan( - text: errorText.substring(start!, end), + text: errorText.substring(start, end), style: defaultStyle.merge( TextStyle( backgroundColor: Colors.red.withOpacity(0.25), @@ -529,7 +555,7 @@ class ConstructMessageBubble extends StatelessWidget { ), ), TextSpan( - text: errorText.substring(end!), + text: errorText.substring(end), style: defaultStyle, ), ],