rough draft complete
This commit is contained in:
parent
8ceb7851e5
commit
5c8666b3e2
19 changed files with 484 additions and 474 deletions
|
|
@ -641,7 +641,7 @@ class AnalyticsController extends BaseController {
|
|||
|
||||
List<ConstructAnalyticsEvent>? getConstructsLocal({
|
||||
required TimeSpan timeSpan,
|
||||
required ConstructType constructType,
|
||||
required ConstructTypeEnum constructType,
|
||||
required AnalyticsSelected defaultSelected,
|
||||
AnalyticsSelected? selected,
|
||||
DateTime? lastUpdated,
|
||||
|
|
@ -669,7 +669,7 @@ class AnalyticsController extends BaseController {
|
|||
}
|
||||
|
||||
void cacheConstructs({
|
||||
required ConstructType constructType,
|
||||
required ConstructTypeEnum constructType,
|
||||
required List<ConstructAnalyticsEvent> events,
|
||||
required AnalyticsSelected defaultSelected,
|
||||
AnalyticsSelected? selected,
|
||||
|
|
@ -687,7 +687,7 @@ class AnalyticsController extends BaseController {
|
|||
|
||||
Future<List<ConstructAnalyticsEvent>> getMyConstructs({
|
||||
required AnalyticsSelected defaultSelected,
|
||||
required ConstructType constructType,
|
||||
required ConstructTypeEnum constructType,
|
||||
AnalyticsSelected? selected,
|
||||
}) async {
|
||||
final List<ConstructAnalyticsEvent> unfilteredConstructs =
|
||||
|
|
@ -706,7 +706,7 @@ class AnalyticsController extends BaseController {
|
|||
}
|
||||
|
||||
Future<List<ConstructAnalyticsEvent>> getSpaceConstructs({
|
||||
required ConstructType constructType,
|
||||
required ConstructTypeEnum constructType,
|
||||
required Room space,
|
||||
required AnalyticsSelected defaultSelected,
|
||||
AnalyticsSelected? selected,
|
||||
|
|
@ -768,7 +768,7 @@ class AnalyticsController extends BaseController {
|
|||
}
|
||||
|
||||
Future<List<ConstructAnalyticsEvent>?> getConstructs({
|
||||
required ConstructType constructType,
|
||||
required ConstructTypeEnum constructType,
|
||||
required AnalyticsSelected defaultSelected,
|
||||
AnalyticsSelected? selected,
|
||||
bool removeIT = true,
|
||||
|
|
@ -898,7 +898,7 @@ abstract class CacheEntry {
|
|||
}
|
||||
|
||||
class ConstructCacheEntry extends CacheEntry {
|
||||
final ConstructType type;
|
||||
final ConstructTypeEnum type;
|
||||
final List<ConstructAnalyticsEvent> events;
|
||||
|
||||
ConstructCacheEntry({
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
=======
|
||||
>>>>>>> Stashed changes
|
||||
import 'package:fluffychat/pangea/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -196,9 +192,8 @@ class MyAnalyticsController {
|
|||
|
||||
String? get userL2 => _pangeaController.languageController.activeL2Code();
|
||||
|
||||
// top level analytics sending function. Send analytics
|
||||
// for each type of analytics event
|
||||
// to each of the applicable analytics rooms
|
||||
/// top level analytics sending function. Gather recent messages and activity records,
|
||||
/// convert them into the correct formats, and send them to the analytics room
|
||||
Future<void> _updateAnalytics() async {
|
||||
// if missing important info, don't send analytics
|
||||
if (userL2 == null || _client.userID == null) {
|
||||
|
|
@ -206,151 +201,108 @@ class MyAnalyticsController {
|
|||
return;
|
||||
}
|
||||
|
||||
// get the last updated time for each analytics room
|
||||
// and the least recent update, which will be used to determine
|
||||
// how far to go back in the chat history to get messages
|
||||
final Map<String, DateTime?> lastUpdatedMap = await _pangeaController
|
||||
.matrixState.client
|
||||
.allAnalyticsRoomsLastUpdated();
|
||||
final List<DateTime> lastUpdates = lastUpdatedMap.values
|
||||
.where((lastUpdate) => lastUpdate != null)
|
||||
.cast<DateTime>()
|
||||
.toList();
|
||||
|
||||
/// Get the last time that analytics to for current target language
|
||||
/// were updated. This my present a problem is the user has analytics
|
||||
/// rooms for multiple languages, and a non-target language was updated
|
||||
/// less recently than the target language. In this case, some data may
|
||||
/// be missing, but a case like that seems relatively rare, and could
|
||||
/// result in unnecessaily going too far back in the chat history
|
||||
DateTime? l2AnalyticsLastUpdated = lastUpdatedMap[userL2];
|
||||
if (l2AnalyticsLastUpdated == null) {
|
||||
/// if the target language has never been updated, use the least
|
||||
/// recent update time
|
||||
lastUpdates.sort((a, b) => a.compareTo(b));
|
||||
l2AnalyticsLastUpdated =
|
||||
lastUpdates.isNotEmpty ? lastUpdates.first : null;
|
||||
}
|
||||
|
||||
final List<Room> chats = await _client.chatsImAStudentIn;
|
||||
|
||||
final List<PangeaMessageEvent> recentMsgs =
|
||||
await _getMessagesWithUnsavedAnalytics(
|
||||
l2AnalyticsLastUpdated,
|
||||
chats,
|
||||
);
|
||||
|
||||
final List<ActivityRecordResponse> recentActivities =
|
||||
await getRecentActivities(userL2!, l2AnalyticsLastUpdated, chats);
|
||||
|
||||
// FOR DISCUSSION:
|
||||
// we want to make sure we save something for every message send
|
||||
// however, we're currently saving analytics for messages not in the userL2
|
||||
// based on bad language detection results. maybe it would be better to
|
||||
// save the analytics for these messages in the userL2 analytics room, but
|
||||
// with useType of unknown
|
||||
|
||||
// analytics room for the user and current target language
|
||||
final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!);
|
||||
|
||||
// if there is no analytics room for this langCode, then user hadn't sent
|
||||
// message in this language at the time of the last analytics update
|
||||
// so fallback to the least recent update time
|
||||
final DateTime? lastUpdated =
|
||||
lastUpdatedMap[analyticsRoom.id] ?? l2AnalyticsLastUpdated;
|
||||
|
||||
// final String msgLangCode = (msg.originalSent?.langCode != null &&
|
||||
// msg.originalSent?.langCode != LanguageKeys.unknownLanguage)
|
||||
// ? msg.originalSent!.langCode
|
||||
// : userL2;
|
||||
|
||||
// finally, send the analytics events to the analytics room
|
||||
await _sendAnalyticsEvents(
|
||||
analyticsRoom,
|
||||
recentMsgs,
|
||||
lastUpdated,
|
||||
recentActivities,
|
||||
// get the last time analytics were updated for this room
|
||||
final DateTime? l2AnalyticsLastUpdated =
|
||||
await analyticsRoom.analyticsLastUpdated(
|
||||
PangeaEventTypes.summaryAnalytics,
|
||||
_client.userID!,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<ActivityRecordResponse>> getRecentActivities(
|
||||
String userL2,
|
||||
DateTime? lastUpdated,
|
||||
List<Room> chats,
|
||||
) async {
|
||||
// all chats in which user is a student
|
||||
final List<Room> chats = await _client.chatsImAStudentIn;
|
||||
|
||||
// get the recent message events and activity records for each chat
|
||||
final List<Future<List<Event>>> recentMsgFutures = [];
|
||||
final List<Future<List<Event>>> recentActivityFutures = [];
|
||||
for (final Room chat in chats) {
|
||||
chat.getEventsBySender(
|
||||
type: EventTypes.Message,
|
||||
sender: _client.userID!,
|
||||
since: l2AnalyticsLastUpdated,
|
||||
);
|
||||
recentActivityFutures.add(
|
||||
chat.getEventsBySender(
|
||||
type: PangeaEventTypes.activityRecord,
|
||||
sender: _client.userID!,
|
||||
since: lastUpdated,
|
||||
since: l2AnalyticsLastUpdated,
|
||||
),
|
||||
);
|
||||
}
|
||||
final List<List<Event>> recentActivityLists =
|
||||
await Future.wait(recentActivityFutures);
|
||||
final List<List<Event>> recentMsgs =
|
||||
(await Future.wait(recentMsgFutures)).toList();
|
||||
final List<PracticeActivityRecordEvent> recentActivityReconds =
|
||||
(await Future.wait(recentActivityFutures))
|
||||
.expand((e) => e)
|
||||
.map((event) => PracticeActivityRecordEvent(event: event))
|
||||
.toList();
|
||||
|
||||
return recentActivityLists
|
||||
.expand((e) => e)
|
||||
.map((e) => ActivityRecordResponse.fromJson(e.content))
|
||||
.toList();
|
||||
}
|
||||
// get the timelines for each chat
|
||||
final List<Future<Timeline>> timelineFutures = [];
|
||||
for (final chat in chats) {
|
||||
timelineFutures.add(chat.getTimeline());
|
||||
}
|
||||
final List<Timeline> timelines = await Future.wait(timelineFutures);
|
||||
final Map<String, Timeline> timelineMap =
|
||||
Map.fromIterables(chats.map((e) => e.id), timelines);
|
||||
|
||||
/// Returns the new messages that have not yet been saved to analytics.
|
||||
/// The keys in the map correspond to different categories or groups of messages,
|
||||
/// while the values are lists of [PangeaMessageEvent] objects belonging to each category.
|
||||
Future<List<PangeaMessageEvent>> _getMessagesWithUnsavedAnalytics(
|
||||
DateTime? since,
|
||||
List<Room> chats,
|
||||
) async {
|
||||
// get the recent messages for each chat
|
||||
final List<Future<List<PangeaMessageEvent>>> futures = [];
|
||||
for (final Room chat in chats) {
|
||||
futures.add(
|
||||
chat.myMessageEventsInChat(
|
||||
since: since,
|
||||
),
|
||||
//convert into PangeaMessageEvents
|
||||
final List<List<PangeaMessageEvent>> recentPangeaMessageEvents = [];
|
||||
for (final (index, eventList) in recentMsgs.indexed) {
|
||||
recentPangeaMessageEvents.add(
|
||||
eventList
|
||||
.map(
|
||||
(event) => PangeaMessageEvent(
|
||||
event: event,
|
||||
timeline: timelines[index],
|
||||
ownMessage: true,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
final List<List<PangeaMessageEvent>> recentMsgLists =
|
||||
await Future.wait(futures);
|
||||
|
||||
// flatten the list of lists of messages
|
||||
return recentMsgLists.expand((e) => e).toList();
|
||||
}
|
||||
final List<PangeaMessageEvent> allRecentMessages =
|
||||
recentPangeaMessageEvents.expand((e) => e).toList();
|
||||
|
||||
Future<void> _sendAnalyticsEvents(
|
||||
Room analyticsRoom,
|
||||
List<PangeaMessageEvent> recentMsgs,
|
||||
DateTime? lastUpdated,
|
||||
List<ActivityRecordResponse> recentActivities,
|
||||
) async {
|
||||
final List<RecentMessageRecord> summaryContent =
|
||||
SummaryAnalyticsModel.formatSummaryContent(allRecentMessages);
|
||||
// if there's new content to be sent, or if lastUpdated hasn't been
|
||||
// set yet for this room, send the analytics events
|
||||
if (summaryContent.isNotEmpty || l2AnalyticsLastUpdated == null) {
|
||||
await analyticsRoom.sendSummaryAnalyticsEvent(
|
||||
summaryContent,
|
||||
);
|
||||
}
|
||||
|
||||
// get constructs for messages
|
||||
final List<OneConstructUse> constructContent = [];
|
||||
for (final PangeaMessageEvent message in allRecentMessages) {
|
||||
constructContent.addAll(message.allConstructUses);
|
||||
}
|
||||
|
||||
if (recentMsgs.isNotEmpty) {
|
||||
// remove messages that were sent before the last update
|
||||
|
||||
// format the analytics data
|
||||
final List<RecentMessageRecord> summaryContent =
|
||||
SummaryAnalyticsModel.formatSummaryContent(recentMsgs);
|
||||
// if there's new content to be sent, or if lastUpdated hasn't been
|
||||
// set yet for this room, send the analytics events
|
||||
if (summaryContent.isNotEmpty || lastUpdated == null) {
|
||||
await analyticsRoom.sendSummaryAnalyticsEvent(
|
||||
summaryContent,
|
||||
// get constructs for practice activities
|
||||
final List<Future<List<OneConstructUse>>> constructFutures = [];
|
||||
for (final PracticeActivityRecordEvent activity in recentActivityReconds) {
|
||||
final Timeline? timeline = timelineMap[activity.event.roomId!];
|
||||
if (timeline == null) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: "PracticeActivityRecordEvent has null timeline",
|
||||
data: activity.event.toJson(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
constructContent
|
||||
.addAll(ConstructAnalyticsModel.formatConstructsContent(recentMsgs));
|
||||
constructFutures.add(activity.uses(timeline));
|
||||
}
|
||||
final List<List<OneConstructUse>> constructLists =
|
||||
await Future.wait(constructFutures);
|
||||
|
||||
if (recentActivities.isNotEmpty) {
|
||||
// TODO - Concert recentActivities into list of constructUse objects.
|
||||
// First, We need to get related practiceActivityEvent from timeline in order to get its related constructs. Alternatively we
|
||||
// could search for completed practice activities and see which have been completed by the user.
|
||||
// It's not clear which is the best approach at the moment and we should consider both.
|
||||
}
|
||||
constructContent.addAll(constructLists.expand((e) => e));
|
||||
|
||||
debugger(when: kDebugMode);
|
||||
|
||||
await analyticsRoom.sendConstructsEvent(
|
||||
constructContent,
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class PracticeGenerationController {
|
|||
PracticeActivityModel dummyModel(PangeaMessageEvent event) =>
|
||||
PracticeActivityModel(
|
||||
tgtConstructs: [
|
||||
ConstructIdentifier(lemma: "be", type: ConstructType.vocab),
|
||||
ConstructIdentifier(lemma: "be", type: ConstructTypeEnum.vocab),
|
||||
],
|
||||
activityType: ActivityTypeEnum.multipleChoice,
|
||||
langCode: event.messageDisplayLangCode,
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
enum ConstructType {
|
||||
enum ConstructTypeEnum {
|
||||
grammar,
|
||||
vocab,
|
||||
}
|
||||
|
||||
extension ConstructExtension on ConstructType {
|
||||
extension ConstructExtension on ConstructTypeEnum {
|
||||
String get string {
|
||||
switch (this) {
|
||||
case ConstructType.grammar:
|
||||
case ConstructTypeEnum.grammar:
|
||||
return 'grammar';
|
||||
case ConstructType.vocab:
|
||||
case ConstructTypeEnum.vocab:
|
||||
return 'vocab';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConstructTypeUtil {
|
||||
static ConstructType fromString(String? string) {
|
||||
static ConstructTypeEnum fromString(String? string) {
|
||||
switch (string) {
|
||||
case 'g':
|
||||
case 'grammar':
|
||||
return ConstructType.grammar;
|
||||
return ConstructTypeEnum.grammar;
|
||||
case 'v':
|
||||
case 'vocab':
|
||||
return ConstructType.vocab;
|
||||
return ConstructTypeEnum.vocab;
|
||||
default:
|
||||
return ConstructType.vocab;
|
||||
return ConstructTypeEnum.vocab;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
93
lib/pangea/enum/construct_use_type_enum.dart
Normal file
93
lib/pangea/enum/construct_use_type_enum.dart
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
enum ConstructUseTypeEnum {
|
||||
/// produced in chat by user, igc was run, and we've judged it to be a correct use
|
||||
wa,
|
||||
|
||||
/// produced in chat by user, igc was run, and we've judged it to be a incorrect use
|
||||
/// Note: if the IGC match is ignored, this is not counted as an incorrect use
|
||||
ga,
|
||||
|
||||
/// produced in chat by user and igc was not run
|
||||
unk,
|
||||
|
||||
/// selected correctly in IT flow
|
||||
corIt,
|
||||
|
||||
/// encountered as IT distractor and correctly ignored it
|
||||
ignIt,
|
||||
|
||||
/// encountered as it distractor and selected it
|
||||
incIt,
|
||||
|
||||
/// encountered in igc match and ignored match
|
||||
ignIGC,
|
||||
|
||||
/// selected correctly in IGC flow
|
||||
corIGC,
|
||||
|
||||
/// encountered as distractor in IGC flow and selected it
|
||||
incIGC,
|
||||
|
||||
/// selected correctly in practice activity flow
|
||||
corPA,
|
||||
|
||||
/// was target construct in practice activity but user did not select correctly
|
||||
incPA,
|
||||
}
|
||||
|
||||
extension ConstructUseTypeExtension on ConstructUseTypeEnum {
|
||||
String get string {
|
||||
switch (this) {
|
||||
case ConstructUseTypeEnum.ga:
|
||||
return 'ga';
|
||||
case ConstructUseTypeEnum.wa:
|
||||
return 'wa';
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
return 'corIt';
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
return 'incIt';
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
return 'ignIt';
|
||||
case ConstructUseTypeEnum.ignIGC:
|
||||
return 'ignIGC';
|
||||
case ConstructUseTypeEnum.corIGC:
|
||||
return 'corIGC';
|
||||
case ConstructUseTypeEnum.incIGC:
|
||||
return 'incIGC';
|
||||
case ConstructUseTypeEnum.unk:
|
||||
return 'unk';
|
||||
case ConstructUseTypeEnum.corPA:
|
||||
return 'corPA';
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
return 'incPA';
|
||||
}
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
switch (this) {
|
||||
case ConstructUseTypeEnum.ga:
|
||||
return Icons.check;
|
||||
case ConstructUseTypeEnum.wa:
|
||||
return Icons.thumb_up_sharp;
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
return Icons.check;
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.ignIGC:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.corIGC:
|
||||
return Icons.check;
|
||||
case ConstructUseTypeEnum.incIGC:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.corPA:
|
||||
return Icons.check;
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
return Icons.close;
|
||||
case ConstructUseTypeEnum.unk:
|
||||
return Icons.help;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -426,32 +426,6 @@ extension EventsRoomExtension on Room {
|
|||
// }
|
||||
// }
|
||||
|
||||
Future<List<PangeaMessageEvent>> myMessageEventsInChat({
|
||||
DateTime? since,
|
||||
}) async {
|
||||
try {
|
||||
final List<Event> msgEvents = await getEventsBySender(
|
||||
type: EventTypes.Message,
|
||||
sender: client.userID!,
|
||||
since: since,
|
||||
);
|
||||
final Timeline timeline = await getTimeline();
|
||||
return msgEvents
|
||||
.where((event) => (event.content['msgtype'] == MessageTypes.Text))
|
||||
.map((event) {
|
||||
return PangeaMessageEvent(
|
||||
event: event,
|
||||
timeline: timeline,
|
||||
ownMessage: true,
|
||||
);
|
||||
}).toList();
|
||||
} catch (err, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// fetch event of a certain type by a certain sender
|
||||
// since a certain time or up to a certain amount
|
||||
Future<List<Event>> getEventsBySender({
|
||||
|
|
|
|||
|
|
@ -2,14 +2,20 @@ import 'dart:convert';
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/constants/choreo_constants.dart';
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
|
||||
import 'package:fluffychat/pangea/enum/audio_encoding_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/models/lemma.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
import 'package:fluffychat/pangea/models/speech_to_text_models.dart';
|
||||
|
|
@ -658,14 +664,145 @@ class PangeaMessageEvent {
|
|||
}
|
||||
}
|
||||
|
||||
// List<SpanData> get activities =>
|
||||
//each match is turned into an activity that other students can access
|
||||
//they're not told the answer but have to find it themselves
|
||||
//the message has a blank piece which they fill in themselves
|
||||
List<OneConstructUse> get allConstructUses =>
|
||||
[...grammarConstructUses, ..._vocabUses];
|
||||
|
||||
// replication of logic from message_content.dart
|
||||
// bool get isHtml =>
|
||||
// AppConfig.renderHtml && !_event.redacted && _event.isRichMessage;
|
||||
/// [tokens] is the final list of tokens that were sent
|
||||
/// if no ga or ta,
|
||||
/// make wa use for each and return
|
||||
/// else
|
||||
/// for each saveable vocab in the final message
|
||||
/// if vocab is contained in an accepted replacement, make ga use
|
||||
/// if vocab is contained in ta choice,
|
||||
/// if selected as choice, corIt
|
||||
/// if written as customInput, corIt? (account for score in this)
|
||||
/// for each it step
|
||||
/// for each continuance
|
||||
/// if not within the final message, save ignIT/incIT
|
||||
List<OneConstructUse> get _vocabUses {
|
||||
final List<OneConstructUse> uses = [];
|
||||
|
||||
if (event.roomId == null) return uses;
|
||||
|
||||
List<OneConstructUse> lemmasToVocabUses(
|
||||
List<Lemma> lemmas,
|
||||
ConstructUseTypeEnum type,
|
||||
) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
for (final lemma in lemmas) {
|
||||
if (lemma.saveVocab) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: type,
|
||||
chatId: event.roomId!,
|
||||
timeStamp: event.originServerTs,
|
||||
lemma: lemma.text,
|
||||
form: lemma.form,
|
||||
msgId: event.eventId,
|
||||
constructType: ConstructTypeEnum.vocab,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
|
||||
List<OneConstructUse> getVocabUseForToken(PangeaToken token) {
|
||||
if (originalSent?.choreo == null) {
|
||||
final bool inUserL2 = originalSent?.langCode == l2Code;
|
||||
return lemmasToVocabUses(
|
||||
token.lemmas,
|
||||
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk,
|
||||
);
|
||||
}
|
||||
|
||||
for (final step in originalSent!.choreo!.choreoSteps) {
|
||||
/// if 1) accepted match 2) token is in the replacement and 3) replacement
|
||||
/// is in the overall step text, then token was a ga
|
||||
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted &&
|
||||
(step.acceptedOrIgnoredMatch!.match.choices?.any(
|
||||
(r) =>
|
||||
r.value.contains(token.text.content) &&
|
||||
step.text.contains(r.value),
|
||||
) ??
|
||||
false)) {
|
||||
return lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.ga);
|
||||
}
|
||||
if (step.itStep != null) {
|
||||
final bool pickedThroughIT = step.itStep!.chosenContinuance?.text
|
||||
.contains(token.text.content) ??
|
||||
false;
|
||||
if (pickedThroughIT) {
|
||||
return lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.corIt);
|
||||
//PTODO - check if added via custom input in IT flow
|
||||
}
|
||||
}
|
||||
}
|
||||
return lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.wa);
|
||||
}
|
||||
|
||||
/// for each token, record whether selected in ga, ta, or wa
|
||||
if (originalSent?.tokens != null) {
|
||||
for (final token in originalSent!.tokens!) {
|
||||
uses.addAll(getVocabUseForToken(token));
|
||||
}
|
||||
}
|
||||
|
||||
if (originalSent?.choreo == null) return uses;
|
||||
|
||||
for (final itStep in originalSent!.choreo!.itSteps) {
|
||||
for (final continuance in itStep.continuances) {
|
||||
// this seems to always be false for continuances right now
|
||||
|
||||
if (originalSent!.choreo!.finalMessage.contains(continuance.text)) {
|
||||
continue;
|
||||
}
|
||||
if (continuance.wasClicked) {
|
||||
//PTODO - account for end of flow score
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
uses.addAll(
|
||||
lemmasToVocabUses(continuance.lemmas, ConstructUseTypeEnum.incIt),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
uses.addAll(
|
||||
lemmasToVocabUses(continuance.lemmas, ConstructUseTypeEnum.ignIt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uses;
|
||||
}
|
||||
|
||||
List<OneConstructUse> get grammarConstructUses {
|
||||
final List<OneConstructUse> uses = [];
|
||||
|
||||
if (originalSent?.choreo == null || event.roomId == null) return uses;
|
||||
|
||||
for (final step in originalSent!.choreo!.choreoSteps) {
|
||||
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted) {
|
||||
final String name = step.acceptedOrIgnoredMatch!.match.rule?.id ??
|
||||
step.acceptedOrIgnoredMatch!.match.shortMessage ??
|
||||
step.acceptedOrIgnoredMatch!.match.type.typeName.name;
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseTypeEnum.ga,
|
||||
chatId: event.roomId!,
|
||||
timeStamp: event.originServerTs,
|
||||
lemma: name,
|
||||
form: name,
|
||||
msgId: event.eventId,
|
||||
constructType: ConstructTypeEnum.grammar,
|
||||
id: "${event.eventId}_${step.acceptedOrIgnoredMatch!.match.offset}_${step.acceptedOrIgnoredMatch!.match.length}",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
}
|
||||
|
||||
class URLFinder {
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
import 'package:fluffychat/pangea/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../constants/pangea_event_types.dart';
|
||||
|
||||
class PracticeActivityRecordEvent {
|
||||
Event event;
|
||||
|
||||
PracticeActivityRecordModel? _content;
|
||||
|
||||
PracticeActivityRecordEvent({required this.event}) {
|
||||
if (event.type != PangeaEventTypes.activityRecord) {
|
||||
throw Exception(
|
||||
"${event.type} should not be used to make a PracticeActivityRecordEvent",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PracticeActivityRecordModel? get record {
|
||||
_content ??= event.getPangeaContent<PracticeActivityRecordModel>();
|
||||
return _content!;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_acitivity_record_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -61,6 +61,8 @@ class PracticeActivityEvent {
|
|||
)
|
||||
.toList();
|
||||
|
||||
String get parentMessageId => event.relationshipEventId!;
|
||||
|
||||
/// Checks if there are any user records in the list for this activity,
|
||||
/// and, if so, then the activity is complete
|
||||
bool get isComplete => userRecords.isNotEmpty;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../constants/pangea_event_types.dart';
|
||||
|
||||
class PracticeActivityRecordEvent {
|
||||
Event event;
|
||||
|
||||
PracticeActivityRecordModel? _content;
|
||||
|
||||
PracticeActivityRecordEvent({required this.event}) {
|
||||
if (event.type != PangeaEventTypes.activityRecord) {
|
||||
throw Exception(
|
||||
"${event.type} should not be used to make a PracticeActivityRecordEvent",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PracticeActivityRecordModel get record {
|
||||
_content ??= event.getPangeaContent<PracticeActivityRecordModel>();
|
||||
return _content!;
|
||||
}
|
||||
|
||||
Future<List<OneConstructUse>> uses(Timeline timeline) async {
|
||||
try {
|
||||
final String? parent = event.relationshipEventId;
|
||||
if (parent == null) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: "PracticeActivityRecordEvent has null event.relationshipEventId",
|
||||
data: event.toJson(),
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
final Event? practiceEvent =
|
||||
await timeline.getEventById(event.relationshipEventId!);
|
||||
|
||||
if (practiceEvent == null) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
m: "PracticeActivityRecordEvent has null practiceActivityEvent with id $parent",
|
||||
data: event.toJson(),
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
final PracticeActivityEvent practiceActivity = PracticeActivityEvent(
|
||||
event: practiceEvent,
|
||||
timeline: timeline,
|
||||
);
|
||||
|
||||
final List<OneConstructUse> uses = [];
|
||||
|
||||
final List<ConstructIdentifier> constructIds =
|
||||
practiceActivity.practiceActivity.tgtConstructs;
|
||||
|
||||
for (final construct in constructIds) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
lemma: construct.lemma,
|
||||
constructType: construct.type,
|
||||
useType: record.useType,
|
||||
//TODO - find form of construct within the message
|
||||
//this is related to the feature of highlighting the target construct in the message
|
||||
form: construct.lemma,
|
||||
chatId: event.roomId ?? practiceEvent.roomId ?? timeline.room.id,
|
||||
msgId: practiceActivity.parentMessageId,
|
||||
timeStamp: event.originServerTs,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return uses;
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: e, s: s, data: event.toJson());
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,11 @@ abstract class AnalyticsModel {
|
|||
case PangeaEventTypes.summaryAnalytics:
|
||||
return SummaryAnalyticsModel.formatSummaryContent(recentMsgs);
|
||||
case PangeaEventTypes.construct:
|
||||
return ConstructAnalyticsModel.formatConstructsContent(recentMsgs);
|
||||
final List<OneConstructUse> uses = [];
|
||||
for (final msg in recentMsgs) {
|
||||
uses.addAll(msg.allConstructUses);
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import '../../enum/construct_type_enum.dart';
|
||||
|
|
@ -24,7 +22,7 @@ class ConstructAnalyticsModel extends AnalyticsModel {
|
|||
if (json[_usesKey] is List) {
|
||||
// This is the new format
|
||||
uses.addAll(
|
||||
json[_usesKey]
|
||||
(json[_usesKey] as List)
|
||||
.map((use) => OneConstructUse.fromJson(use))
|
||||
.cast<OneConstructUse>()
|
||||
.toList(),
|
||||
|
|
@ -39,13 +37,13 @@ class ConstructAnalyticsModel extends AnalyticsModel {
|
|||
final lemmaUses = useValue[_usesKey];
|
||||
for (final useData in lemmaUses) {
|
||||
final use = OneConstructUse(
|
||||
useType: ConstructUseType.ga,
|
||||
useType: ConstructUseTypeEnum.ga,
|
||||
chatId: useData["chatId"],
|
||||
timeStamp: DateTime.parse(useData["timeStamp"]),
|
||||
lemma: lemma,
|
||||
form: useData["form"],
|
||||
msgId: useData["msgId"],
|
||||
constructType: ConstructType.grammar,
|
||||
constructType: ConstructTypeEnum.grammar,
|
||||
);
|
||||
uses.add(use);
|
||||
}
|
||||
|
|
@ -70,122 +68,13 @@ class ConstructAnalyticsModel extends AnalyticsModel {
|
|||
_usesKey: uses.map((use) => use.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
static List<OneConstructUse> formatConstructsContent(
|
||||
List<PangeaMessageEvent> recentMsgs,
|
||||
) {
|
||||
final List<PangeaMessageEvent> filtered = List.from(recentMsgs);
|
||||
final List<OneConstructUse> 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<PangeaToken>? tokens = msg.originalSent?.tokens;
|
||||
if (tokens == null) continue;
|
||||
uses.addAll(
|
||||
msg.originalSent!.choreo!.toVocabUse(
|
||||
tokens,
|
||||
msg.room.id,
|
||||
msg.eventId,
|
||||
msg.originServerTs,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return uses;
|
||||
}
|
||||
}
|
||||
|
||||
enum ConstructUseType {
|
||||
/// produced in chat by user, igc was run, and we've judged it to be a correct use
|
||||
wa,
|
||||
|
||||
/// produced in chat by user, igc was run, and we've judged it to be a incorrect use
|
||||
/// Note: if the IGC match is ignored, this is not counted as an incorrect use
|
||||
ga,
|
||||
|
||||
/// produced in chat by user and igc was not run
|
||||
unk,
|
||||
|
||||
/// selected correctly in IT flow
|
||||
corIt,
|
||||
|
||||
/// encountered as IT distractor and correctly ignored it
|
||||
ignIt,
|
||||
|
||||
/// encountered as it distractor and selected it
|
||||
incIt,
|
||||
|
||||
/// encountered in igc match and ignored match
|
||||
ignIGC,
|
||||
|
||||
/// selected correctly in IGC flow
|
||||
corIGC,
|
||||
|
||||
/// encountered as distractor in IGC flow and selected it
|
||||
incIGC,
|
||||
}
|
||||
|
||||
extension on ConstructUseType {
|
||||
String get string {
|
||||
switch (this) {
|
||||
case ConstructUseType.ga:
|
||||
return 'ga';
|
||||
case ConstructUseType.wa:
|
||||
return 'wa';
|
||||
case ConstructUseType.corIt:
|
||||
return 'corIt';
|
||||
case ConstructUseType.incIt:
|
||||
return 'incIt';
|
||||
case ConstructUseType.ignIt:
|
||||
return 'ignIt';
|
||||
case ConstructUseType.ignIGC:
|
||||
return 'ignIGC';
|
||||
case ConstructUseType.corIGC:
|
||||
return 'corIGC';
|
||||
case ConstructUseType.incIGC:
|
||||
return 'incIGC';
|
||||
case ConstructUseType.unk:
|
||||
return 'unk';
|
||||
}
|
||||
}
|
||||
|
||||
IconData get icon {
|
||||
switch (this) {
|
||||
case ConstructUseType.ga:
|
||||
return Icons.check;
|
||||
case ConstructUseType.wa:
|
||||
return Icons.thumb_up_sharp;
|
||||
case ConstructUseType.corIt:
|
||||
return Icons.check;
|
||||
case ConstructUseType.incIt:
|
||||
return Icons.close;
|
||||
case ConstructUseType.ignIt:
|
||||
return Icons.close;
|
||||
case ConstructUseType.ignIGC:
|
||||
return Icons.close;
|
||||
case ConstructUseType.corIGC:
|
||||
return Icons.check;
|
||||
case ConstructUseType.incIGC:
|
||||
return Icons.close;
|
||||
case ConstructUseType.unk:
|
||||
return Icons.help;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class OneConstructUse {
|
||||
String? lemma;
|
||||
ConstructType? constructType;
|
||||
ConstructTypeEnum? constructType;
|
||||
String? form;
|
||||
ConstructUseType useType;
|
||||
ConstructUseTypeEnum useType;
|
||||
String chatId;
|
||||
String? msgId;
|
||||
DateTime timeStamp;
|
||||
|
|
@ -204,7 +93,7 @@ class OneConstructUse {
|
|||
|
||||
factory OneConstructUse.fromJson(Map<String, dynamic> json) {
|
||||
return OneConstructUse(
|
||||
useType: ConstructUseType.values
|
||||
useType: ConstructUseTypeEnum.values
|
||||
.firstWhere((e) => e.string == json['useType']),
|
||||
chatId: json['chatId'],
|
||||
timeStamp: DateTime.parse(json['timeStamp']),
|
||||
|
|
@ -248,7 +137,7 @@ class OneConstructUse {
|
|||
|
||||
class ConstructUses {
|
||||
final List<OneConstructUse> uses;
|
||||
final ConstructType constructType;
|
||||
final ConstructTypeEnum constructType;
|
||||
final String lemma;
|
||||
|
||||
ConstructUses({
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
|
||||
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
|
||||
|
||||
import '../constants/choreo_constants.dart';
|
||||
import '../enum/construct_type_enum.dart';
|
||||
import 'it_step.dart';
|
||||
import 'lemma.dart';
|
||||
|
||||
/// this class lives within a [PangeaIGCEvent]
|
||||
/// it always has a [RepresentationEvent] parent
|
||||
|
|
@ -111,135 +106,6 @@ class ChoreoRecord {
|
|||
openMatches: [],
|
||||
);
|
||||
|
||||
/// [tokens] is the final list of tokens that were sent
|
||||
/// if no ga or ta,
|
||||
/// make wa use for each and return
|
||||
/// else
|
||||
/// for each saveable vocab in the final message
|
||||
/// if vocab is contained in an accepted replacement, make ga use
|
||||
/// if vocab is contained in ta choice,
|
||||
/// if selected as choice, corIt
|
||||
/// if written as customInput, corIt? (account for score in this)
|
||||
/// for each it step
|
||||
/// for each continuance
|
||||
/// if not within the final message, save ignIT/incIT
|
||||
List<OneConstructUse> toVocabUse(
|
||||
List<PangeaToken> tokens,
|
||||
String chatId,
|
||||
String msgId,
|
||||
DateTime timestamp,
|
||||
) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
final DateTime now = DateTime.now();
|
||||
List<OneConstructUse> lemmasToVocabUses(
|
||||
List<Lemma> lemmas,
|
||||
ConstructUseType type,
|
||||
) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
for (final lemma in lemmas) {
|
||||
if (lemma.saveVocab) {
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: type,
|
||||
chatId: chatId,
|
||||
timeStamp: timestamp,
|
||||
lemma: lemma.text,
|
||||
form: lemma.form,
|
||||
msgId: msgId,
|
||||
constructType: ConstructType.vocab,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
|
||||
List<OneConstructUse> getVocabUseForToken(PangeaToken token) {
|
||||
for (final step in choreoSteps) {
|
||||
/// if 1) accepted match 2) token is in the replacement and 3) replacement
|
||||
/// is in the overall step text, then token was a ga
|
||||
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted &&
|
||||
(step.acceptedOrIgnoredMatch!.match.choices?.any(
|
||||
(r) =>
|
||||
r.value.contains(token.text.content) &&
|
||||
step.text.contains(r.value),
|
||||
) ??
|
||||
false)) {
|
||||
return lemmasToVocabUses(token.lemmas, ConstructUseType.ga);
|
||||
}
|
||||
if (step.itStep != null) {
|
||||
final bool pickedThroughIT = step.itStep!.chosenContinuance?.text
|
||||
.contains(token.text.content) ??
|
||||
false;
|
||||
if (pickedThroughIT) {
|
||||
return lemmasToVocabUses(token.lemmas, ConstructUseType.corIt);
|
||||
//PTODO - check if added via custom input in IT flow
|
||||
}
|
||||
}
|
||||
}
|
||||
return lemmasToVocabUses(token.lemmas, ConstructUseType.wa);
|
||||
}
|
||||
|
||||
/// for each token, record whether selected in ga, ta, or wa
|
||||
for (final token in tokens) {
|
||||
uses.addAll(getVocabUseForToken(token));
|
||||
}
|
||||
|
||||
for (final itStep in itSteps) {
|
||||
for (final continuance in itStep.continuances) {
|
||||
// this seems to always be false for continuances right now
|
||||
|
||||
if (finalMessage.contains(continuance.text)) {
|
||||
continue;
|
||||
}
|
||||
if (continuance.wasClicked) {
|
||||
//PTODO - account for end of flow score
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
uses.addAll(
|
||||
lemmasToVocabUses(continuance.lemmas, ConstructUseType.incIt),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
|
||||
uses.addAll(
|
||||
lemmasToVocabUses(continuance.lemmas, ConstructUseType.ignIt),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uses;
|
||||
}
|
||||
|
||||
List<OneConstructUse> toGrammarConstructUse(
|
||||
String msgId,
|
||||
String chatId,
|
||||
DateTime timestamp,
|
||||
) {
|
||||
final List<OneConstructUse> uses = [];
|
||||
for (final step in choreoSteps) {
|
||||
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted) {
|
||||
final String name = step.acceptedOrIgnoredMatch!.match.rule?.id ??
|
||||
step.acceptedOrIgnoredMatch!.match.shortMessage ??
|
||||
step.acceptedOrIgnoredMatch!.match.type.typeName.name;
|
||||
uses.add(
|
||||
OneConstructUse(
|
||||
useType: ConstructUseType.ga,
|
||||
chatId: chatId,
|
||||
timeStamp: timestamp,
|
||||
lemma: name,
|
||||
form: name,
|
||||
msgId: msgId,
|
||||
constructType: ConstructType.grammar,
|
||||
id: "${msgId}_${step.acceptedOrIgnoredMatch!.match.offset}_${step.acceptedOrIgnoredMatch!.match.length}",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return uses;
|
||||
}
|
||||
|
||||
List<ITStep> get itSteps =>
|
||||
choreoSteps.where((e) => e.itStep != null).map((e) => e.itStep!).toList();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
|
@ -154,32 +155,37 @@ class VocabTotals {
|
|||
void addVocabUseBasedOnUseType(List<OneConstructUse> uses) {
|
||||
for (final use in uses) {
|
||||
switch (use.useType) {
|
||||
case ConstructUseType.ga:
|
||||
case ConstructUseTypeEnum.ga:
|
||||
ga++;
|
||||
break;
|
||||
case ConstructUseType.wa:
|
||||
case ConstructUseTypeEnum.wa:
|
||||
wa++;
|
||||
break;
|
||||
case ConstructUseType.corIt:
|
||||
case ConstructUseTypeEnum.corIt:
|
||||
corIt++;
|
||||
break;
|
||||
case ConstructUseType.incIt:
|
||||
case ConstructUseTypeEnum.incIt:
|
||||
incIt++;
|
||||
break;
|
||||
case ConstructUseType.ignIt:
|
||||
case ConstructUseTypeEnum.ignIt:
|
||||
ignIt++;
|
||||
break;
|
||||
//TODO - these shouldn't be counted as such
|
||||
case ConstructUseType.ignIGC:
|
||||
case ConstructUseTypeEnum.ignIGC:
|
||||
ignIt++;
|
||||
break;
|
||||
case ConstructUseType.corIGC:
|
||||
case ConstructUseTypeEnum.corIGC:
|
||||
corIt++;
|
||||
break;
|
||||
case ConstructUseType.incIGC:
|
||||
case ConstructUseTypeEnum.incIGC:
|
||||
incIt++;
|
||||
break;
|
||||
case ConstructUseType.unk:
|
||||
//TODO if we bring back Headwords then we need to add these
|
||||
case ConstructUseTypeEnum.corPA:
|
||||
break;
|
||||
case ConstructUseTypeEnum.incPA:
|
||||
break;
|
||||
case ConstructUseTypeEnum.unk:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart';
|
|||
|
||||
class ConstructIdentifier {
|
||||
final String lemma;
|
||||
final ConstructType type;
|
||||
final ConstructTypeEnum type;
|
||||
|
||||
ConstructIdentifier({required this.lemma, required this.type});
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ class ConstructIdentifier {
|
|||
try {
|
||||
return ConstructIdentifier(
|
||||
lemma: json['lemma'] as String,
|
||||
type: ConstructType.values.firstWhere(
|
||||
type: ConstructTypeEnum.values.firstWhere(
|
||||
(e) => e.string == json['type'],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.dart';
|
||||
|
||||
class PracticeActivityRecordModel {
|
||||
final String? question;
|
||||
late List<ActivityRecordResponse> responses;
|
||||
|
|
@ -42,18 +44,25 @@ class PracticeActivityRecordModel {
|
|||
|
||||
/// get the latest response index according to the response timeStamp
|
||||
/// sort the responses by timestamp and get the index of the last response
|
||||
String? get latestResponse {
|
||||
ActivityRecordResponse? get latestResponse {
|
||||
if (responses.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
responses.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
return responses[responses.length - 1].text;
|
||||
return responses[responses.length - 1];
|
||||
}
|
||||
|
||||
ConstructUseTypeEnum get useType => latestResponse?.score != null
|
||||
? (latestResponse!.score > 0
|
||||
? ConstructUseTypeEnum.corPA
|
||||
: ConstructUseTypeEnum.incPA)
|
||||
: ConstructUseTypeEnum.unk;
|
||||
|
||||
void addResponse({
|
||||
String? text,
|
||||
Uint8List? audioBytes,
|
||||
Uint8List? imageBytes,
|
||||
required double score,
|
||||
}) {
|
||||
try {
|
||||
responses.add(
|
||||
|
|
@ -62,6 +71,7 @@ class PracticeActivityRecordModel {
|
|||
audioBytes: audioBytes,
|
||||
imageBytes: imageBytes,
|
||||
timestamp: DateTime.now(),
|
||||
score: score,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
@ -93,11 +103,13 @@ class ActivityRecordResponse {
|
|||
final Uint8List? audioBytes;
|
||||
final Uint8List? imageBytes;
|
||||
final DateTime timestamp;
|
||||
final double score;
|
||||
|
||||
ActivityRecordResponse({
|
||||
this.text,
|
||||
this.audioBytes,
|
||||
this.imageBytes,
|
||||
required this.score,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
|
|
@ -107,6 +119,10 @@ class ActivityRecordResponse {
|
|||
audioBytes: json['audio'] as Uint8List?,
|
||||
imageBytes: json['image'] as Uint8List?,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
// this has a default of 1 to make this backwards compatible
|
||||
// score was added later and is not present in all records
|
||||
// currently saved to Matrix
|
||||
score: json['score'] ?? 1.0,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -116,6 +132,7 @@ class ActivityRecordResponse {
|
|||
'audio': audioBytes,
|
||||
'image': imageBytes,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'score': score,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class BaseAnalyticsView extends StatelessWidget {
|
|||
);
|
||||
case BarChartViewSelection.grammar:
|
||||
return ConstructList(
|
||||
constructType: ConstructType.grammar,
|
||||
constructType: ConstructTypeEnum.grammar,
|
||||
defaultSelected: controller.widget.defaultSelected,
|
||||
selected: controller.selected,
|
||||
controller: controller,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class ConstructList extends StatefulWidget {
|
||||
final ConstructType constructType;
|
||||
final ConstructTypeEnum constructType;
|
||||
final AnalyticsSelected defaultSelected;
|
||||
final AnalyticsSelected? selected;
|
||||
final BaseAnalyticsController controller;
|
||||
|
|
@ -94,7 +94,7 @@ class ConstructListView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class ConstructListViewState extends State<ConstructListView> {
|
||||
final ConstructType constructType = ConstructType.grammar;
|
||||
final ConstructTypeEnum constructType = ConstructTypeEnum.grammar;
|
||||
final Map<String, Timeline> _timelinesCache = {};
|
||||
final Map<String, PangeaMessageEvent> _msgEventCache = {};
|
||||
final List<PangeaMessageEvent> _msgEvents = [];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/enum/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_acitivity_record_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_record_event.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/practice_activity_event.dart';
|
||||
import 'package:fluffychat/pangea/models/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
|
|
@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_ca
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:get_storage/get_storage.dart';
|
||||
|
||||
class PracticeActivityContent extends StatefulWidget {
|
||||
final PracticeActivityEvent practiceEvent;
|
||||
|
|
@ -65,9 +66,9 @@ class MessagePracticeActivityContentState
|
|||
recordModel = recordEvent!.record;
|
||||
|
||||
//Note that only MultipleChoice activities will have this so we probably should move this logic to the MultipleChoiceActivity widget
|
||||
selectedChoiceIndex = recordModel?.latestResponse != null
|
||||
selectedChoiceIndex = recordModel?.latestResponse?.text != null
|
||||
? widget.practiceEvent.practiceActivity.multipleChoice
|
||||
?.choiceIndex(recordModel!.latestResponse!)
|
||||
?.choiceIndex(recordModel!.latestResponse!.text!)
|
||||
: null;
|
||||
|
||||
recordSubmittedPreviousSession = true;
|
||||
|
|
@ -80,6 +81,10 @@ class MessagePracticeActivityContentState
|
|||
setState(() {
|
||||
selectedChoiceIndex = index;
|
||||
recordModel!.addResponse(
|
||||
score: widget.practiceEvent.practiceActivity.multipleChoice!
|
||||
.isCorrect(index)
|
||||
? 1
|
||||
: 0,
|
||||
text: widget
|
||||
.practiceEvent.practiceActivity.multipleChoice!.choices[index],
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue