get analytics events directly from server, cache last update time for user's l2
This commit is contained in:
parent
91a5d8414c
commit
4278f7b196
19 changed files with 327 additions and 393 deletions
|
|
@ -639,6 +639,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
pangeaController.myAnalytics.setState(
|
||||
data: {
|
||||
'eventID': msgEventId,
|
||||
'eventType': EventTypes.Message,
|
||||
'roomID': room.id,
|
||||
'originalSent': originalSent,
|
||||
'tokensSent': tokensSent,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/class_default_values.dart';
|
||||
import 'package:fluffychat/pangea/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/constants/match_rule_ids.dart';
|
||||
|
|
@ -25,16 +27,13 @@ class GetAnalyticsController {
|
|||
|
||||
Client get client => _pangeaController.matrixState.client;
|
||||
|
||||
// A local cache of eventIds and construct uses for messages sent since the last update
|
||||
/// A local cache of eventIds and construct uses for messages sent since the last update
|
||||
Map<String, List<OneConstructUse>> get messagesSinceUpdate {
|
||||
try {
|
||||
final dynamic locallySaved = _pangeaController.pStoreService.read(
|
||||
PLocalKey.messagesSinceUpdate,
|
||||
);
|
||||
if (locallySaved == null) {
|
||||
_pangeaController.myAnalytics.setMessagesSinceUpdate({});
|
||||
return {};
|
||||
}
|
||||
if (locallySaved == null) return {};
|
||||
try {
|
||||
// try to get the local cache of messages and format them as OneConstructUses
|
||||
final Map<String, List<dynamic>> cache =
|
||||
|
|
@ -47,7 +46,7 @@ class GetAnalyticsController {
|
|||
return formattedCache;
|
||||
} catch (err) {
|
||||
// if something goes wrong while trying to format the local data, clear it
|
||||
_pangeaController.myAnalytics.setMessagesSinceUpdate({});
|
||||
_pangeaController.myAnalytics.clearMessagesSinceUpdate();
|
||||
return {};
|
||||
}
|
||||
} catch (exception, stackTrace) {
|
||||
|
|
@ -70,13 +69,24 @@ class GetAnalyticsController {
|
|||
debugPrint("getting constructs");
|
||||
await client.roomsLoading;
|
||||
|
||||
// first, try to get a cached list of all uses, if it exists and is valid
|
||||
final DateTime? lastUpdated = await myAnalyticsLastUpdated();
|
||||
// don't try to get constructs until last updated time has been loaded
|
||||
await _pangeaController.myAnalytics.lastUpdatedCompleter.future;
|
||||
|
||||
// if forcing a refreshing, clear the cache
|
||||
if (forceUpdate) clearCache();
|
||||
|
||||
// get the last time the user updated their analytics for their current l2
|
||||
// then try to get local cache of construct uses. lastUpdate time is used to
|
||||
// determine if cached data is still valid.
|
||||
final DateTime? lastUpdated = _pangeaController.myAnalytics.lastUpdated ??
|
||||
await myAnalyticsLastUpdated();
|
||||
|
||||
final List<OneConstructUse>? local = getConstructsLocal(
|
||||
constructType: constructType,
|
||||
lastUpdated: lastUpdated,
|
||||
);
|
||||
if (local != null && !forceUpdate) {
|
||||
|
||||
if (local != null) {
|
||||
debugPrint("returning local constructs");
|
||||
return local;
|
||||
}
|
||||
|
|
@ -111,7 +121,12 @@ class GetAnalyticsController {
|
|||
|
||||
/// Get the last time the user updated their analytics for their current l2
|
||||
Future<DateTime?> myAnalyticsLastUpdated() async {
|
||||
if (l2Code == null) return null;
|
||||
// this function gets called soon after login, so first
|
||||
// make sure that the user's l2 is loaded, if the user has set their l2
|
||||
if (client.userID != null && l2Code == null) {
|
||||
await _pangeaController.matrixState.client.waitForAccountData();
|
||||
if (l2Code == null) return null;
|
||||
}
|
||||
final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!);
|
||||
if (analyticsRoom == null) return null;
|
||||
final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated(
|
||||
|
|
@ -120,6 +135,29 @@ class GetAnalyticsController {
|
|||
return lastUpdated;
|
||||
}
|
||||
|
||||
/// Get all the construct analytics events for the logged in user
|
||||
Future<List<ConstructAnalyticsEvent>> allMyConstructs() async {
|
||||
if (l2Code == null) return [];
|
||||
final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!);
|
||||
if (analyticsRoom == null) return [];
|
||||
return await analyticsRoom.getAnalyticsEvents(userId: client.userID!) ?? [];
|
||||
}
|
||||
|
||||
/// Filter out constructs that are not relevant to the user, specifically those from
|
||||
/// rooms in which the user is a teacher and those that are interative translation span constructs
|
||||
Future<List<OneConstructUse>> filterConstructs({
|
||||
required List<OneConstructUse> unfilteredConstructs,
|
||||
}) async {
|
||||
return unfilteredConstructs
|
||||
.where(
|
||||
(use) =>
|
||||
use.lemma != "Try interactive translation" &&
|
||||
use.lemma != "itStart" ||
|
||||
use.lemma != MatchRuleIds.interactiveTranslation,
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Get the cached construct uses for the current user, if it exists
|
||||
List<OneConstructUse>? getConstructsLocal({
|
||||
DateTime? lastUpdated,
|
||||
|
|
@ -140,28 +178,6 @@ class GetAnalyticsController {
|
|||
return null;
|
||||
}
|
||||
|
||||
/// Get all the construct analytics events for the logged in user
|
||||
Future<List<ConstructAnalyticsEvent>> allMyConstructs() async {
|
||||
if (l2Code == null) return [];
|
||||
final Room? analyticsRoom = client.analyticsRoomLocal(l2Code!);
|
||||
if (analyticsRoom == null) return [];
|
||||
return await analyticsRoom.getAnalyticsEvents(userId: client.userID!) ?? [];
|
||||
}
|
||||
|
||||
/// Filter out constructs that are not relevant to the user, specifically those from
|
||||
/// rooms in which the user is a teacher and those that are interative translation span constructs
|
||||
Future<List<OneConstructUse>> filterConstructs({
|
||||
required List<OneConstructUse> unfilteredConstructs,
|
||||
}) async {
|
||||
final List<String> adminSpaceRooms = await client.teacherRoomIds;
|
||||
return unfilteredConstructs.where((use) {
|
||||
if (adminSpaceRooms.contains(use.chatId)) return false;
|
||||
return use.lemma != "Try interactive translation" &&
|
||||
use.lemma != "itStart" ||
|
||||
use.lemma != MatchRuleIds.interactiveTranslation;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/// Cache the construct uses for the current user
|
||||
void cacheConstructs({
|
||||
required List<OneConstructUse> uses,
|
||||
|
|
@ -175,6 +191,11 @@ class GetAnalyticsController {
|
|||
);
|
||||
_cache.add(entry);
|
||||
}
|
||||
|
||||
/// Clear all cached analytics data.
|
||||
void clearCache() {
|
||||
_cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class AnalyticsCacheEntry {
|
||||
|
|
|
|||
|
|
@ -156,14 +156,6 @@ class AnalyticsController extends BaseController {
|
|||
?.cast<ConstructAnalyticsEvent>();
|
||||
final List<ConstructAnalyticsEvent> allConstructs = roomEvents ?? [];
|
||||
|
||||
final List<String> adminSpaceRooms =
|
||||
await _pangeaController.matrixState.client.teacherRoomIds;
|
||||
for (final construct in allConstructs) {
|
||||
construct.content.uses.removeWhere(
|
||||
(use) => adminSpaceRooms.contains(use.chatId),
|
||||
);
|
||||
}
|
||||
|
||||
return allConstructs
|
||||
.where((construct) => construct.content.uses.isNotEmpty)
|
||||
.toList();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/local.key.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
|
|
@ -7,10 +6,10 @@ import 'package:fluffychat/pangea/controllers/base_controller.dart';
|
|||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/extensions/client_extension/client_extension.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.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/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/practice_activities.dart/practice_activity_record_model.dart';
|
||||
import 'package:fluffychat/pangea/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/utils/error_handler.dart';
|
||||
|
|
@ -29,9 +28,16 @@ class MyAnalyticsController extends BaseController {
|
|||
|
||||
String? get userL2 => _pangeaController.languageController.activeL2Code();
|
||||
|
||||
/// the last time that matrix analytics events were updated for the user's current l2
|
||||
DateTime? lastUpdated;
|
||||
|
||||
/// Last updated completer. Used to wait for the last
|
||||
/// updated time to be set before setting analytics data.
|
||||
Completer<DateTime?> lastUpdatedCompleter = Completer<DateTime?>();
|
||||
|
||||
/// the max number of messages that will be cached before
|
||||
/// an automatic update is triggered
|
||||
final int _maxMessagesCached = 10;
|
||||
final int _maxMessagesCached = 1;
|
||||
|
||||
/// the number of minutes before an automatic update is triggered
|
||||
final int _minutesBeforeUpdate = 5;
|
||||
|
|
@ -44,8 +50,9 @@ class MyAnalyticsController extends BaseController {
|
|||
|
||||
// Wait for the next sync in the stream to ensure that the pangea controller
|
||||
// is fully initialized. It will throw an error if it is not.
|
||||
_pangeaController.matrixState.client.onSync.stream.first
|
||||
.then((_) => _refreshAnalyticsIfOutdated());
|
||||
_pangeaController.matrixState.client.onSync.stream.first.then((_) {
|
||||
_refreshAnalyticsIfOutdated();
|
||||
});
|
||||
|
||||
// Listen to a stream that provides the eventIDs
|
||||
// of new messages sent by the logged in user
|
||||
|
|
@ -55,23 +62,31 @@ class MyAnalyticsController extends BaseController {
|
|||
}
|
||||
|
||||
/// If analytics haven't been updated in the last day, update them
|
||||
Future<DateTime?> _refreshAnalyticsIfOutdated() async {
|
||||
/// wait for the initial sync to finish, so the
|
||||
/// timeline data from analytics rooms is accurate
|
||||
if (_client.prevBatch == null) {
|
||||
await _client.onSync.stream.first;
|
||||
Future<void> _refreshAnalyticsIfOutdated() async {
|
||||
// don't set anything is the user is not logged in
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
try {
|
||||
// if lastUpdated hasn't been set yet, set it
|
||||
lastUpdated ??=
|
||||
await _pangeaController.analytics.myAnalyticsLastUpdated();
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
s: s,
|
||||
e: err,
|
||||
m: "Failed to get last updated time for analytics",
|
||||
);
|
||||
} finally {
|
||||
// if this is the initial load, complete the lastUpdatedCompleter
|
||||
if (!lastUpdatedCompleter.isCompleted) {
|
||||
lastUpdatedCompleter.complete(lastUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? lastUpdated =
|
||||
await _pangeaController.analytics.myAnalyticsLastUpdated();
|
||||
final DateTime yesterday = DateTime.now().subtract(_timeSinceUpdate);
|
||||
|
||||
if (lastUpdated?.isBefore(yesterday) ?? true) {
|
||||
debugPrint("analytics out-of-date, updating");
|
||||
await updateAnalytics();
|
||||
lastUpdated = await _pangeaController.analytics.myAnalyticsLastUpdated();
|
||||
}
|
||||
return lastUpdated;
|
||||
}
|
||||
|
||||
/// Given the data from a newly sent message, format and cache
|
||||
|
|
@ -88,9 +103,12 @@ class MyAnalyticsController extends BaseController {
|
|||
// extract the relevant data about this message
|
||||
final String? eventID = data['eventID'];
|
||||
final String? roomID = data['roomID'];
|
||||
final String? eventType = data['eventType'];
|
||||
final PangeaRepresentation? originalSent = data['originalSent'];
|
||||
final PangeaMessageTokens? tokensSent = data['tokensSent'];
|
||||
final ChoreoRecord? choreo = data['choreo'];
|
||||
final PracticeActivityEvent? practiceActivity = data['practiceActivity'];
|
||||
final PracticeActivityRecordModel? recordModel = data['recordModel'];
|
||||
|
||||
if (roomID == null || eventID == null) return;
|
||||
|
||||
|
|
@ -101,24 +119,38 @@ class MyAnalyticsController extends BaseController {
|
|||
timeStamp: DateTime.now(),
|
||||
);
|
||||
|
||||
final grammarConstructs = choreo?.grammarConstructUses(metadata: metadata);
|
||||
final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata);
|
||||
final vocabUses = tokensSent != null
|
||||
? originalSent?.vocabUses(
|
||||
choreo: choreo,
|
||||
tokens: tokensSent.tokens,
|
||||
metadata: metadata,
|
||||
)
|
||||
: null;
|
||||
final List<OneConstructUse> constructs = [
|
||||
...(grammarConstructs ?? []),
|
||||
...(itConstructs ?? []),
|
||||
...(vocabUses ?? []),
|
||||
];
|
||||
addMessageSinceUpdate(
|
||||
eventID,
|
||||
constructs,
|
||||
);
|
||||
final List<OneConstructUse> constructs = [];
|
||||
|
||||
if (eventType == EventTypes.Message) {
|
||||
final grammarConstructs =
|
||||
choreo?.grammarConstructUses(metadata: metadata);
|
||||
final itConstructs = choreo?.itStepsToConstructUses(metadata: metadata);
|
||||
final vocabUses = tokensSent != null
|
||||
? originalSent?.vocabUses(
|
||||
choreo: choreo,
|
||||
tokens: tokensSent.tokens,
|
||||
metadata: metadata,
|
||||
)
|
||||
: null;
|
||||
constructs.addAll([
|
||||
...(grammarConstructs ?? []),
|
||||
...(itConstructs ?? []),
|
||||
...(vocabUses ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
if (eventType == PangeaEventTypes.activityRecord &&
|
||||
practiceActivity != null) {
|
||||
final activityConstructs = recordModel?.uses(
|
||||
practiceActivity,
|
||||
metadata: metadata,
|
||||
);
|
||||
constructs.addAll(activityConstructs ?? []);
|
||||
}
|
||||
|
||||
_pangeaController.analytics
|
||||
.filterConstructs(unfilteredConstructs: constructs)
|
||||
.then((filtered) => addMessageSinceUpdate(eventID, filtered));
|
||||
}
|
||||
|
||||
/// Add a list of construct uses for a new message to the local
|
||||
|
|
@ -129,10 +161,9 @@ class MyAnalyticsController extends BaseController {
|
|||
) {
|
||||
try {
|
||||
final currentCache = _pangeaController.analytics.messagesSinceUpdate;
|
||||
if (!currentCache.containsKey(eventID)) {
|
||||
currentCache[eventID] = constructs;
|
||||
setMessagesSinceUpdate(currentCache);
|
||||
}
|
||||
constructs.addAll(currentCache[eventID] ?? []);
|
||||
currentCache[eventID] = constructs;
|
||||
setMessagesSinceUpdate(currentCache);
|
||||
|
||||
// if the cached has reached if max-length, update analytics
|
||||
if (_pangeaController.analytics.messagesSinceUpdate.length >
|
||||
|
|
@ -151,7 +182,7 @@ class MyAnalyticsController extends BaseController {
|
|||
|
||||
/// Clears the local cache of recently sent constructs. Called before updating analytics
|
||||
void clearMessagesSinceUpdate() {
|
||||
setMessagesSinceUpdate({});
|
||||
_pangeaController.pStoreService.delete(PLocalKey.messagesSinceUpdate);
|
||||
}
|
||||
|
||||
/// Save the local cache of recently sent constructs to the local storage
|
||||
|
|
@ -168,8 +199,18 @@ class MyAnalyticsController extends BaseController {
|
|||
analyticsUpdateStream.add(null);
|
||||
}
|
||||
|
||||
/// Prevent concurrent updates to analytics
|
||||
Completer<void>? _updateCompleter;
|
||||
|
||||
/// Updates learning analytics.
|
||||
///
|
||||
/// This method is responsible for updating the analytics. It first checks if an update is already in progress
|
||||
/// by checking the completion status of the [_updateCompleter]. If an update is already in progress, it waits
|
||||
/// for the completion of the previous update and returns. Otherwise, it creates a new [_updateCompleter] and
|
||||
/// proceeds with the update process. If the update is successful, it clears any messages that were received
|
||||
/// since the last update and notifies the [analyticsUpdateStream].
|
||||
Future<void> updateAnalytics() async {
|
||||
if (_pangeaController.matrixState.client.userID == null) return;
|
||||
if (!(_updateCompleter?.isCompleted ?? true)) {
|
||||
await _updateCompleter!.future;
|
||||
return;
|
||||
|
|
@ -178,6 +219,7 @@ class MyAnalyticsController extends BaseController {
|
|||
try {
|
||||
await _updateAnalytics();
|
||||
clearMessagesSinceUpdate();
|
||||
lastUpdated = DateTime.now();
|
||||
analyticsUpdateStream.add(null);
|
||||
} catch (err, s) {
|
||||
ErrorHandler.logError(
|
||||
|
|
@ -191,119 +233,31 @@ class MyAnalyticsController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
/// top level analytics sending function. Gather recent messages and activity records,
|
||||
/// convert them into the correct formats, and send them to the analytics room
|
||||
/// Updates the analytics by sending cached analytics data to the analytics room.
|
||||
/// The analytics room is determined based on the user's current target language.
|
||||
Future<void> _updateAnalytics() async {
|
||||
// if there's no cached construct data, there's nothing to send
|
||||
if (_pangeaController.analytics.messagesSinceUpdate.isEmpty) return;
|
||||
|
||||
// if missing important info, don't send analytics. Could happen if user just signed up.
|
||||
if (userL2 == null || _client.userID == null) return;
|
||||
|
||||
// analytics room for the user and current target language
|
||||
final Room? analyticsRoom = await _client.getMyAnalyticsRoom(userL2!);
|
||||
|
||||
// get the last time analytics were updated for this room
|
||||
final DateTime? l2AnalyticsLastUpdated =
|
||||
await analyticsRoom?.analyticsLastUpdated(
|
||||
_client.userID!,
|
||||
// and send cached analytics data to the room
|
||||
await analyticsRoom?.sendConstructsEvent(
|
||||
_pangeaController.analytics.messagesSinceUpdate.values
|
||||
.expand((e) => e)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// all chats in which user is a student
|
||||
final List<Room> chats = _client.rooms
|
||||
.where((room) => !room.isSpace && !room.isAnalyticsRoom)
|
||||
.toList();
|
||||
|
||||
// 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) {
|
||||
recentMsgFutures.add(
|
||||
chat.getEventsBySender(
|
||||
type: EventTypes.Message,
|
||||
sender: _client.userID!,
|
||||
since: l2AnalyticsLastUpdated,
|
||||
),
|
||||
);
|
||||
recentActivityFutures.add(
|
||||
chat.getEventsBySender(
|
||||
type: PangeaEventTypes.activityRecord,
|
||||
sender: _client.userID!,
|
||||
since: l2AnalyticsLastUpdated,
|
||||
),
|
||||
);
|
||||
}
|
||||
final List<List<Event>> recentMsgs =
|
||||
(await Future.wait(recentMsgFutures)).toList();
|
||||
final List<PracticeActivityRecordEvent> recentActivityRecords =
|
||||
(await Future.wait(recentActivityFutures))
|
||||
.expand((e) => e)
|
||||
.map((event) => PracticeActivityRecordEvent(event: event))
|
||||
.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);
|
||||
|
||||
//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<PangeaMessageEvent> allRecentMessages =
|
||||
recentPangeaMessageEvents.expand((e) => e).toList();
|
||||
|
||||
// get constructs for messages
|
||||
final List<OneConstructUse> recentConstructUses = [];
|
||||
for (final PangeaMessageEvent message in allRecentMessages) {
|
||||
recentConstructUses.addAll(message.allConstructUses);
|
||||
}
|
||||
|
||||
// get constructs for practice activities
|
||||
final List<Future<List<OneConstructUse>>> constructFutures = [];
|
||||
for (final PracticeActivityRecordEvent activity in recentActivityRecords) {
|
||||
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;
|
||||
}
|
||||
constructFutures.add(activity.uses(timeline));
|
||||
}
|
||||
final List<List<OneConstructUse>> constructLists =
|
||||
await Future.wait(constructFutures);
|
||||
|
||||
recentConstructUses.addAll(constructLists.expand((e) => e));
|
||||
|
||||
//TODO - confirm that this is the correct construct content
|
||||
// debugger(
|
||||
// when: kDebugMode,
|
||||
// );
|
||||
// ; debugger(
|
||||
// when: kDebugMode &&
|
||||
// (allRecentMessages.isNotEmpty || recentActivityRecords.isNotEmpty),
|
||||
// );
|
||||
|
||||
if (recentConstructUses.isNotEmpty || l2AnalyticsLastUpdated == null) {
|
||||
await analyticsRoom?.sendConstructsEvent(
|
||||
recentConstructUses,
|
||||
);
|
||||
}
|
||||
/// Reset analytics last updated time to null.
|
||||
void clearCache() {
|
||||
_updateTimer?.cancel();
|
||||
lastUpdated = null;
|
||||
lastUpdatedCompleter = Completer<DateTime?>();
|
||||
_refreshAnalyticsIfOutdated();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,9 +71,12 @@ class UserController extends BaseController {
|
|||
}
|
||||
|
||||
/// Updates the user's profile with the given [update] function and saves it.
|
||||
void updateProfile(Profile Function(Profile) update) {
|
||||
Future<void> updateProfile(
|
||||
Profile Function(Profile) update, {
|
||||
waitForDataInSync = false,
|
||||
}) async {
|
||||
final Profile updatedProfile = update(profile);
|
||||
updatedProfile.saveProfileData();
|
||||
await updatedProfile.saveProfileData(waitForDataInSync: waitForDataInSync);
|
||||
}
|
||||
|
||||
/// Creates a new profile for the user with the given date of birth.
|
||||
|
|
|
|||
|
|
@ -153,16 +153,4 @@ extension AnalyticsClientExtension on Client {
|
|||
_joinAnalyticsRoomsInAllSpaces();
|
||||
});
|
||||
}
|
||||
|
||||
Future<Map<String, DateTime?>> _allAnalyticsRoomsLastUpdated() async {
|
||||
// get the last updated time for each analytics room
|
||||
final Map<String, DateTime?> lastUpdatedMap = {};
|
||||
for (final analyticsRoom in allMyAnalyticsRooms) {
|
||||
final DateTime? lastUpdated = await analyticsRoom.analyticsLastUpdated(
|
||||
userID!,
|
||||
);
|
||||
lastUpdatedMap[analyticsRoom.id] = lastUpdated;
|
||||
}
|
||||
return lastUpdatedMap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'dart:developer';
|
|||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:fluffychat/pangea/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/models/space_model.dart';
|
||||
|
|
@ -39,15 +38,10 @@ extension PangeaClient on Client {
|
|||
/// and set up those rooms to be joined by other users.
|
||||
void migrateAnalyticsRooms() => _migrateAnalyticsRooms();
|
||||
|
||||
Future<Map<String, DateTime?>> allAnalyticsRoomsLastUpdated() async =>
|
||||
await _allAnalyticsRoomsLastUpdated();
|
||||
|
||||
// spaces
|
||||
|
||||
List<Room> get spacesImTeaching => _spacesImTeaching;
|
||||
|
||||
Future<List<Room>> get chatsImAStudentIn async => await _chatsImAStudentIn;
|
||||
|
||||
List<Room> get spacesImAStudentIn => _spacesImStudyingIn;
|
||||
|
||||
List<Room> get spacesImIn => _spacesImIn;
|
||||
|
|
@ -56,8 +50,6 @@ extension PangeaClient on Client {
|
|||
|
||||
// general_info
|
||||
|
||||
Future<List<String>> get teacherRoomIds async => await _teacherRoomIds;
|
||||
|
||||
Future<List<User>> get myTeachers async => await _myTeachers;
|
||||
|
||||
Future<Room> getReportsDM(User teacher, Room space) async =>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
part of "client_extension.dart";
|
||||
|
||||
extension GeneralInfoClientExtension on Client {
|
||||
Future<List<String>> get _teacherRoomIds async {
|
||||
final List<String> adminRoomIds = [];
|
||||
for (final Room adminSpace in (await _spacesImTeaching)) {
|
||||
adminRoomIds.add(adminSpace.id);
|
||||
final List<String> adminSpaceRooms = adminSpace.allSpaceChildRoomIds;
|
||||
adminRoomIds.addAll(adminSpaceRooms);
|
||||
}
|
||||
return adminRoomIds;
|
||||
}
|
||||
|
||||
Future<List<User>> get _myTeachers async {
|
||||
final List<User> teachers = [];
|
||||
for (final classRoom in spacesImIn) {
|
||||
|
|
|
|||
|
|
@ -4,18 +4,6 @@ extension SpaceClientExtension on Client {
|
|||
List<Room> get _spacesImTeaching =>
|
||||
rooms.where((e) => e.isSpace && e.isRoomAdmin).toList();
|
||||
|
||||
Future<List<Room>> get _chatsImAStudentIn async {
|
||||
final List<String> nowteacherRoomIds = await teacherRoomIds;
|
||||
return rooms
|
||||
.where(
|
||||
(r) =>
|
||||
!r.isSpace &&
|
||||
!r.isAnalyticsRoom &&
|
||||
!nowteacherRoomIds.contains(r.id),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<Room> get _spacesImStudyingIn =>
|
||||
rooms.where((e) => e.isSpace && !e.isRoomAdmin).toList();
|
||||
|
||||
|
|
|
|||
|
|
@ -346,66 +346,51 @@ extension EventsRoomExtension on Room {
|
|||
// }
|
||||
// }
|
||||
|
||||
// fetch event of a certain type by a certain sender
|
||||
// since a certain time or up to a certain amount
|
||||
Future<List<Event>> getEventsBySender({
|
||||
required String type,
|
||||
required String sender,
|
||||
DateTime? since,
|
||||
/// Get a list of events in the room that are of type [PangeaEventTypes.construct]
|
||||
/// and have the sender as [userID]. If [count] is provided, the function will
|
||||
/// return at most [count] events.
|
||||
Future<List<Event>> getRoomAnalyticsEvents({
|
||||
String? userID,
|
||||
int? count,
|
||||
}) async {
|
||||
try {
|
||||
int numberOfSearches = 0;
|
||||
final Timeline timeline = await getTimeline();
|
||||
userID ??= client.userID;
|
||||
if (userID == null) return [];
|
||||
GetRoomEventsResponse resp = await client.getRoomEvents(
|
||||
id,
|
||||
Direction.b,
|
||||
limit: count ?? 100,
|
||||
filter: jsonEncode(
|
||||
StateFilter(
|
||||
types: [
|
||||
PangeaEventTypes.construct,
|
||||
],
|
||||
senders: [userID],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
List<Event> relevantEvents() => timeline.events
|
||||
.where((event) => event.senderId == sender && event.type == type)
|
||||
.toList();
|
||||
|
||||
bool reachedEnd() {
|
||||
if (since != null) {
|
||||
return relevantEvents().any(
|
||||
(event) => event.originServerTs.isBefore(since),
|
||||
);
|
||||
}
|
||||
if (count != null) {
|
||||
return relevantEvents().length >= count;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
while (timeline.canRequestHistory && numberOfSearches < 10) {
|
||||
await timeline.requestHistory(historyCount: 100);
|
||||
numberOfSearches += 1;
|
||||
if (!timeline.canRequestHistory) break;
|
||||
if (reachedEnd()) break;
|
||||
}
|
||||
|
||||
final List<Event> fetchedEvents = timeline.events
|
||||
.where((event) => event.senderId == sender && event.type == type)
|
||||
.toList();
|
||||
|
||||
if (since != null) {
|
||||
fetchedEvents.removeWhere(
|
||||
(event) => event.originServerTs.isBefore(since),
|
||||
);
|
||||
}
|
||||
|
||||
final List<Event> events = [];
|
||||
for (Event event in fetchedEvents) {
|
||||
if (event.relationshipType == RelationshipTypes.edit) continue;
|
||||
if (event.hasAggregatedEvents(timeline, RelationshipTypes.edit)) {
|
||||
event = event.getDisplayEvent(timeline);
|
||||
}
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
return events;
|
||||
} catch (err, s) {
|
||||
if (kDebugMode) rethrow;
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
return [];
|
||||
int numSearches = 0;
|
||||
while (numSearches < 10 && resp.end != null) {
|
||||
if (count != null && resp.chunk.length <= count) break;
|
||||
final nextResp = await client.getRoomEvents(
|
||||
id,
|
||||
Direction.b,
|
||||
limit: count ?? 100,
|
||||
filter: jsonEncode(
|
||||
StateFilter(
|
||||
types: [
|
||||
PangeaEventTypes.construct,
|
||||
],
|
||||
senders: [userID],
|
||||
),
|
||||
),
|
||||
from: resp.end,
|
||||
);
|
||||
nextResp.chunk.addAll(resp.chunk);
|
||||
resp = nextResp;
|
||||
numSearches += 1;
|
||||
}
|
||||
|
||||
return resp.chunk.map((e) => Event.fromMatrixEvent(e, this)).toList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,6 +90,16 @@ extension PangeaRoom on Room {
|
|||
|
||||
bool isMadeForLang(String langCode) => _isMadeForLang(langCode);
|
||||
|
||||
/// Sends construct events to the server.
|
||||
///
|
||||
/// The [uses] parameter is a list of [OneConstructUse] objects representing the
|
||||
/// constructs to be sent. To prevent hitting the maximum event size, the events
|
||||
/// are chunked into smaller lists. Each chunk is sent as a separate event.
|
||||
Future<void> sendConstructsEvent(
|
||||
List<OneConstructUse> uses,
|
||||
) async =>
|
||||
await _sendConstructsEvent(uses);
|
||||
|
||||
// children_and_parents
|
||||
|
||||
List<Room> get joinedChildren => _joinedChildren;
|
||||
|
|
|
|||
|
|
@ -140,33 +140,17 @@ extension AnalyticsRoomExtension on Room {
|
|||
);
|
||||
}
|
||||
|
||||
Future<ConstructAnalyticsEvent?> _getLastAnalyticsEvent(
|
||||
String userId,
|
||||
) async {
|
||||
final List<Event> events = await getEventsBySender(
|
||||
type: PangeaEventTypes.construct,
|
||||
sender: userId,
|
||||
count: 10,
|
||||
);
|
||||
if (events.isEmpty) return null;
|
||||
final Event event = events.first;
|
||||
return ConstructAnalyticsEvent(event: event);
|
||||
}
|
||||
|
||||
Future<DateTime?> _analyticsLastUpdated(String userId) async {
|
||||
final lastEvent = await _getLastAnalyticsEvent(userId);
|
||||
return lastEvent?.event.originServerTs;
|
||||
final List<Event> events = await getRoomAnalyticsEvents(count: 1);
|
||||
if (events.isEmpty) return null;
|
||||
return events.first.originServerTs;
|
||||
}
|
||||
|
||||
Future<List<ConstructAnalyticsEvent>?> _getAnalyticsEvents({
|
||||
required String userId,
|
||||
DateTime? since,
|
||||
}) async {
|
||||
final List<Event> events = await getEventsBySender(
|
||||
type: PangeaEventTypes.construct,
|
||||
sender: userId,
|
||||
since: since,
|
||||
);
|
||||
final events = await getRoomAnalyticsEvents();
|
||||
final List<ConstructAnalyticsEvent> analyticsEvents = [];
|
||||
for (final Event event in events) {
|
||||
analyticsEvents.add(ConstructAnalyticsEvent(event: event));
|
||||
|
|
@ -192,7 +176,7 @@ extension AnalyticsRoomExtension on Room {
|
|||
/// The [uses] parameter is a list of [OneConstructUse] objects representing the
|
||||
/// constructs to be sent. To prevent hitting the maximum event size, the events
|
||||
/// are chunked into smaller lists. Each chunk is sent as a separate event.
|
||||
Future<void> sendConstructsEvent(
|
||||
Future<void> _sendConstructsEvent(
|
||||
List<OneConstructUse> uses,
|
||||
) async {
|
||||
// It's possible that the user has no info to send yet, but to prevent trying
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
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';
|
||||
|
|
@ -28,64 +21,4 @@ class PracticeActivityRecordEvent {
|
|||
_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,
|
||||
metadata: ConstructUseMetaData(
|
||||
roomId: event.roomId ?? practiceEvent.roomId ?? timeline.room.id,
|
||||
eventId: practiceActivity.parentMessageId,
|
||||
timeStamp: event.originServerTs,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return uses;
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: e, s: s, data: event.toJson());
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,14 @@
|
|||
// the user might have selected multiple options before
|
||||
// finding the answer
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:fluffychat/pangea/enum/construct_use_type_enum.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/utils/error_handler.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
class PracticeActivityRecordModel {
|
||||
final String? question;
|
||||
|
|
@ -79,6 +84,56 @@ class PracticeActivityRecordModel {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a list of [OneConstructUse] objects representing the uses of the practice activity.
|
||||
///
|
||||
/// The [practiceActivity] parameter is the parent event, representing the activity itself.
|
||||
/// The [event] parameter is the record event, if available.
|
||||
/// The [metadata] parameter is the metadata for the construct use, used if the record event isn't available.
|
||||
///
|
||||
/// If [event] and [metadata] are both null, an empty list is returned.
|
||||
///
|
||||
/// The method iterates over the [tgtConstructs] of the [practiceActivity] and creates a [OneConstructUse] object for each construct.
|
||||
List<OneConstructUse> uses(
|
||||
PracticeActivityEvent practiceActivity, {
|
||||
Event? event,
|
||||
ConstructUseMetaData? metadata,
|
||||
}) {
|
||||
try {
|
||||
if (event == null && metadata == null) {
|
||||
debugger(when: kDebugMode);
|
||||
return [];
|
||||
}
|
||||
|
||||
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: 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,
|
||||
metadata: ConstructUseMetaData(
|
||||
roomId: event?.roomId ?? metadata!.roomId,
|
||||
eventId: practiceActivity.parentMessageId,
|
||||
timeStamp: event?.originServerTs ?? metadata!.timeStamp,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return uses;
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: e, s: s, data: event?.toJson());
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
void pLogoutAction(BuildContext context, {bool? isDestructiveAction}) async {
|
||||
if (await showOkCancelAlertDialog(
|
||||
useRootNavigator: false,
|
||||
|
|
@ -20,6 +18,14 @@ void pLogoutAction(BuildContext context, {bool? isDestructiveAction}) async {
|
|||
return;
|
||||
}
|
||||
final matrix = Matrix.of(context);
|
||||
|
||||
// before wiping out locally cached construct data, save it to the server
|
||||
await MatrixState.pangeaController.myAnalytics.updateAnalytics();
|
||||
|
||||
// Reset cached analytics data
|
||||
MatrixState.pangeaController.myAnalytics.clearCache();
|
||||
MatrixState.pangeaController.analytics.clearCache();
|
||||
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => matrix.client.logout(),
|
||||
|
|
|
|||
|
|
@ -42,10 +42,14 @@ class LearningProgressIndicatorsState
|
|||
/// Grammar constructs model
|
||||
ConstructListModel? errors;
|
||||
|
||||
bool loading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
updateAnalyticsData();
|
||||
updateAnalyticsData().then((_) {
|
||||
setState(() => loading = false);
|
||||
});
|
||||
// listen for changes to analytics data and update the UI
|
||||
_onAnalyticsUpdate = _pangeaController
|
||||
.myAnalytics.analyticsUpdateStream.stream
|
||||
|
|
@ -77,6 +81,7 @@ class LearningProgressIndicatorsState
|
|||
type: ConstructTypeEnum.grammar,
|
||||
uses: localUses,
|
||||
);
|
||||
setState(() {});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +98,8 @@ class LearningProgressIndicatorsState
|
|||
type: ConstructTypeEnum.grammar,
|
||||
uses: allConstructs,
|
||||
);
|
||||
setState(() {});
|
||||
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
/// Get the number of points for a given progress indicator
|
||||
|
|
@ -136,6 +142,10 @@ class LearningProgressIndicatorsState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (Matrix.of(context).client.userID == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final levelBar = Container(
|
||||
height: 20,
|
||||
width: levelBarWidth,
|
||||
|
|
@ -214,6 +224,7 @@ class LearningProgressIndicatorsState
|
|||
points: getProgressPoints(indicator),
|
||||
onTap: () {},
|
||||
progressIndicator: indicator,
|
||||
loading: loading,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
|
|
@ -222,9 +233,6 @@ class LearningProgressIndicatorsState
|
|||
),
|
||||
),
|
||||
Container(
|
||||
// decoration: BoxDecoration(
|
||||
// border: Border.all(color: Colors.green),
|
||||
// ),
|
||||
height: 36,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||
child: Stack(
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ class ProgressIndicatorBadge extends StatelessWidget {
|
|||
final int? points;
|
||||
final VoidCallback onTap;
|
||||
final ProgressIndicatorEnum progressIndicator;
|
||||
final bool loading;
|
||||
|
||||
const ProgressIndicatorBadge({
|
||||
super.key,
|
||||
required this.points,
|
||||
required this.onTap,
|
||||
required this.progressIndicator,
|
||||
required this.loading,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -33,9 +35,9 @@ class ProgressIndicatorBadge extends StatelessWidget {
|
|||
color: progressIndicator.color(context),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
points != null
|
||||
!loading
|
||||
? AnimatedCount(
|
||||
count: points!,
|
||||
count: points ?? 0,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_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';
|
||||
|
|
@ -102,6 +103,20 @@ class MessagePracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
},
|
||||
);
|
||||
return null;
|
||||
}).then((event) {
|
||||
// The record event is processed into construct uses for learning analytics, so if the
|
||||
// event went through without error, send it to analytics to be processed
|
||||
if (event != null && currentActivity != null) {
|
||||
MatrixState.pangeaController.myAnalytics.setState(
|
||||
data: {
|
||||
'eventID': widget.pangeaMessageEvent.eventId,
|
||||
'eventType': PangeaEventTypes.activityRecord,
|
||||
'roomID': event.room.id,
|
||||
'practiceActivity': currentActivity!,
|
||||
'recordModel': currentRecordModel!,
|
||||
},
|
||||
);
|
||||
}
|
||||
}).whenComplete(() => setState(() => sending = false));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,15 +91,22 @@ Future<void> pLanguageDialog(
|
|||
context: context,
|
||||
future: () async {
|
||||
try {
|
||||
pangeaController.userController
|
||||
.updateProfile((profile) {
|
||||
profile.userSettings.sourceLanguage =
|
||||
selectedSourceLanguage.langCode;
|
||||
profile.userSettings.targetLanguage =
|
||||
selectedTargetLanguage.langCode;
|
||||
return profile;
|
||||
pangeaController.userController.updateProfile(
|
||||
(profile) {
|
||||
profile.userSettings.sourceLanguage =
|
||||
selectedSourceLanguage.langCode;
|
||||
profile.userSettings.targetLanguage =
|
||||
selectedTargetLanguage.langCode;
|
||||
return profile;
|
||||
},
|
||||
waitForDataInSync: true,
|
||||
).then((_) {
|
||||
// if the profile update is successful, reset cached analytics
|
||||
// data, since analytics data corresponds to the user's L2
|
||||
pangeaController.myAnalytics.clearCache();
|
||||
pangeaController.analytics.clearCache();
|
||||
Navigator.pop(context);
|
||||
});
|
||||
Navigator.pop(context);
|
||||
} catch (err, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(e: err, s: s);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue