refactoring of my analytics controller and related flows
This commit is contained in:
parent
ffbc62ba55
commit
8ceb7851e5
14 changed files with 241 additions and 270 deletions
|
|
@ -3109,7 +3109,7 @@
|
|||
"prettyGood": "Pretty good! Here's what I would have said.",
|
||||
"letMeThink": "Hmm, let's see how you did!",
|
||||
"clickMessageTitle": "Need help?",
|
||||
"clickMessageBody": "Click messages to access definitions, translations, and audio!",
|
||||
"clickMessageBody": "Click a message for language help! Click and hold to react 😀.",
|
||||
"understandingMessagesTitle": "Definitions and translations!",
|
||||
"understandingMessagesBody": "Click underlined words for definitions. Translate with message options (upper right).",
|
||||
"allDone": "All done!",
|
||||
|
|
|
|||
|
|
@ -4512,7 +4512,7 @@
|
|||
"definitions": "definiciones",
|
||||
"subscribedToUnlockTools": "Suscríbase para desbloquear herramientas lingüísticas, como",
|
||||
"clickMessageTitle": "¿Necesitas ayuda?",
|
||||
"clickMessageBody": "Haga clic en los mensajes para acceder a las definiciones, traducciones y audio.",
|
||||
"clickMessageBody": "¡Lame un mensaje para obtener ayuda con el idioma! Haz clic y mantén presionado para reaccionar 😀",
|
||||
"more": "Más",
|
||||
"translationTooltip": "Traducir",
|
||||
"audioTooltip": "Reproducir audio",
|
||||
|
|
|
|||
|
|
@ -917,7 +917,7 @@ class ChatListController extends State<ChatList>
|
|||
if (mounted) {
|
||||
GoogleAnalytics.analyticsUserUpdate(client.userID);
|
||||
await pangeaController.subscriptionController.initialize();
|
||||
await pangeaController.myAnalytics.addEventsListener();
|
||||
await pangeaController.myAnalytics.initialize();
|
||||
pangeaController.afterSyncAndFirstLoginInitialization(context);
|
||||
await pangeaController.inviteBotToExistingSpaces();
|
||||
await pangeaController.setPangeaPushRules();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
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/base_controller.dart';
|
||||
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart';
|
||||
import 'package:fluffychat/pangea/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';
|
||||
|
|
@ -18,11 +19,18 @@ import 'package:matrix/matrix.dart';
|
|||
import '../extensions/client_extension/client_extension.dart';
|
||||
import '../extensions/pangea_room_extension/pangea_room_extension.dart';
|
||||
|
||||
// controls the sending of analytics events
|
||||
class MyAnalyticsController extends BaseController {
|
||||
/// handles the processing of analytics for
|
||||
/// 1) messages sent by the user and
|
||||
/// 2) constructs used by the user, both in sending messages and doing practice activities
|
||||
class MyAnalyticsController {
|
||||
late PangeaController _pangeaController;
|
||||
Timer? _updateTimer;
|
||||
|
||||
/// the max number of messages that will be cached before
|
||||
/// an automatic update is triggered
|
||||
final int _maxMessagesCached = 10;
|
||||
|
||||
/// the number of minutes before an automatic update is triggered
|
||||
final int _minutesBeforeUpdate = 5;
|
||||
|
||||
/// the time since the last update that will trigger an automatic update
|
||||
|
|
@ -33,41 +41,50 @@ class MyAnalyticsController extends BaseController {
|
|||
}
|
||||
|
||||
/// adds the listener that handles when to run automatic updates
|
||||
/// to analytics - either after a certain number of messages sent/
|
||||
/// to analytics - either after a certain number of messages sent
|
||||
/// received or after a certain amount of time [_timeSinceUpdate] without an update
|
||||
Future<void> addEventsListener() async {
|
||||
final Client client = _pangeaController.matrixState.client;
|
||||
Future<void> initialize() async {
|
||||
final lastUpdated = await _refreshAnalyticsIfOutdated();
|
||||
|
||||
// if analytics haven't been updated in the last day, update them
|
||||
DateTime? lastUpdated = await _pangeaController.analytics
|
||||
.myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
|
||||
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(PangeaEventTypes.summaryAnalytics);
|
||||
}
|
||||
|
||||
client.onSync.stream
|
||||
// listen for new messages and updateAnalytics timer
|
||||
// we are doing this in an attempt to update analytics when activitiy is low
|
||||
// both in messages sent by this client and other clients that you're connected with
|
||||
// doesn't account for messages sent by other clients that you're not connected with
|
||||
_client.onSync.stream
|
||||
.where((SyncUpdate update) => update.rooms?.join != null)
|
||||
.listen((update) {
|
||||
updateAnalyticsTimer(update, lastUpdated);
|
||||
});
|
||||
}
|
||||
|
||||
/// given an update from sync stream, check if the update contains
|
||||
/// If analytics haven't been updated in the last day, update them
|
||||
Future<DateTime?> _refreshAnalyticsIfOutdated() async {
|
||||
DateTime? lastUpdated = await _pangeaController.analytics
|
||||
.myAnalyticsLastUpdated(PangeaEventTypes.summaryAnalytics);
|
||||
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(PangeaEventTypes.summaryAnalytics);
|
||||
}
|
||||
return lastUpdated;
|
||||
}
|
||||
|
||||
Client get _client => _pangeaController.matrixState.client;
|
||||
|
||||
/// Given an update from sync stream, check if the update contains
|
||||
/// messages for which analytics will be saved. If so, reset the timer
|
||||
/// and add the event ID to the cache of un-added event IDs
|
||||
void updateAnalyticsTimer(SyncUpdate update, DateTime? lastUpdated) {
|
||||
for (final entry in update.rooms!.join!.entries) {
|
||||
final Room room =
|
||||
_pangeaController.matrixState.client.getRoomById(entry.key)!;
|
||||
final Room room = _client.getRoomById(entry.key)!;
|
||||
|
||||
// get the new events in this sync that are messages
|
||||
final List<Event>? events = entry.value.timeline?.events
|
||||
?.map((event) => Event.fromMatrixEvent(event, room))
|
||||
.where((event) => eventHasAnalytics(event, lastUpdated))
|
||||
.where((event) => hasUserAnalyticsToCache(event, lastUpdated))
|
||||
.toList();
|
||||
|
||||
// add their event IDs to the cache of un-added event IDs
|
||||
|
|
@ -87,8 +104,9 @@ class MyAnalyticsController extends BaseController {
|
|||
}
|
||||
|
||||
// checks if event from sync update is a message that should have analytics
|
||||
bool eventHasAnalytics(Event event, DateTime? lastUpdated) {
|
||||
return (lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) &&
|
||||
bool hasUserAnalyticsToCache(Event event, DateTime? lastUpdated) {
|
||||
return event.senderId == _client.userID &&
|
||||
(lastUpdated == null || event.originServerTs.isAfter(lastUpdated)) &&
|
||||
event.type == EventTypes.Message &&
|
||||
event.messageType == MessageTypes.Text &&
|
||||
!(event.eventId.contains("web") &&
|
||||
|
|
@ -176,21 +194,18 @@ class MyAnalyticsController extends BaseController {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
Future<void> _updateAnalytics() async {
|
||||
// if the user's l2 is not sent, don't send analytics
|
||||
final String? userL2 = _pangeaController.languageController.activeL2Code();
|
||||
if (userL2 == null) {
|
||||
// if missing important info, don't send analytics
|
||||
if (userL2 == null || _client.userID == null) {
|
||||
debugger(when: kDebugMode);
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch a list of all the chats that the user is studying
|
||||
// and a list of all the spaces in which the user is studying
|
||||
await setStudentChats();
|
||||
await setStudentSpaces();
|
||||
|
||||
// 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
|
||||
|
|
@ -217,151 +232,128 @@ class MyAnalyticsController extends BaseController {
|
|||
lastUpdates.isNotEmpty ? lastUpdates.first : null;
|
||||
}
|
||||
|
||||
// for each chat the user is studying in, get all the messages
|
||||
// since the least recent update analytics update, and sort them
|
||||
// by their langCodes
|
||||
final Map<String, List<PangeaMessageEvent>> langCodeToMsgs =
|
||||
await getLangCodesToMsgs(
|
||||
userL2,
|
||||
final List<Room> chats = await _client.chatsImAStudentIn;
|
||||
|
||||
final List<PangeaMessageEvent> recentMsgs =
|
||||
await _getMessagesWithUnsavedAnalytics(
|
||||
l2AnalyticsLastUpdated,
|
||||
chats,
|
||||
);
|
||||
|
||||
final List<String> langCodes = langCodeToMsgs.keys.toList();
|
||||
for (final String langCode in langCodes) {
|
||||
// for each of the langs that the user has sent message in, get
|
||||
// the corresponding analytics room (or create it)
|
||||
final Room analyticsRoom = await _pangeaController.matrixState.client
|
||||
.getMyAnalyticsRoom(langCode);
|
||||
final List<ActivityRecordResponse> recentActivities =
|
||||
await getRecentActivities(userL2!, l2AnalyticsLastUpdated, chats);
|
||||
|
||||
// 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;
|
||||
// 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
|
||||
|
||||
// get the corresponding list of recent messages for this langCode
|
||||
final List<PangeaMessageEvent> recentMsgs =
|
||||
langCodeToMsgs[langCode] ?? [];
|
||||
final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!);
|
||||
|
||||
// finally, send the analytics events to the analytics room
|
||||
await sendAnalyticsEvents(
|
||||
analyticsRoom,
|
||||
recentMsgs,
|
||||
lastUpdated,
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<ActivityRecordResponse>> getRecentActivities(
|
||||
String userL2,
|
||||
DateTime? lastUpdated,
|
||||
List<Room> chats,
|
||||
) async {
|
||||
final List<Future<List<Event>>> recentActivityFutures = [];
|
||||
for (final Room chat in chats) {
|
||||
recentActivityFutures.add(
|
||||
chat.getEventsBySender(
|
||||
type: PangeaEventTypes.activityRecord,
|
||||
sender: _client.userID!,
|
||||
since: lastUpdated,
|
||||
),
|
||||
);
|
||||
}
|
||||
final List<List<Event>> recentActivityLists =
|
||||
await Future.wait(recentActivityFutures);
|
||||
|
||||
return recentActivityLists
|
||||
.expand((e) => e)
|
||||
.map((e) => ActivityRecordResponse.fromJson(e.content))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<Map<String, List<PangeaMessageEvent>>> getLangCodesToMsgs(
|
||||
String userL2,
|
||||
/// 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 a map of langCodes to messages for each chat the user is studying in
|
||||
final Map<String, List<PangeaMessageEvent>> langCodeToMsgs = {};
|
||||
for (final Room chat in _studentChats) {
|
||||
List<PangeaMessageEvent>? recentMsgs;
|
||||
try {
|
||||
recentMsgs = await chat.myMessageEventsInChat(
|
||||
// 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,
|
||||
);
|
||||
} catch (err) {
|
||||
debugPrint("failed to fetch messages for chat ${chat.id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// sort those messages by their langCode
|
||||
// langCode is hopefully based on the original sent rep, but if that
|
||||
// is null or unk, it will be based on the user's current l2
|
||||
for (final msg in recentMsgs) {
|
||||
final String msgLangCode = (msg.originalSent?.langCode != null &&
|
||||
msg.originalSent?.langCode != LanguageKeys.unknownLanguage)
|
||||
? msg.originalSent!.langCode
|
||||
: userL2;
|
||||
langCodeToMsgs[msgLangCode] ??= [];
|
||||
langCodeToMsgs[msgLangCode]!.add(msg);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
return langCodeToMsgs;
|
||||
final List<List<PangeaMessageEvent>> recentMsgLists =
|
||||
await Future.wait(futures);
|
||||
|
||||
// flatten the list of lists of messages
|
||||
return recentMsgLists.expand((e) => e).toList();
|
||||
}
|
||||
|
||||
Future<void> sendAnalyticsEvents(
|
||||
Future<void> _sendAnalyticsEvents(
|
||||
Room analyticsRoom,
|
||||
List<PangeaMessageEvent> recentMsgs,
|
||||
DateTime? lastUpdated,
|
||||
List<ActivityRecordResponse> recentActivities,
|
||||
) async {
|
||||
// remove messages that were sent before the last update
|
||||
if (recentMsgs.isEmpty) return;
|
||||
if (lastUpdated != null) {
|
||||
recentMsgs.removeWhere(
|
||||
(msg) => msg.event.originServerTs.isBefore(lastUpdated),
|
||||
);
|
||||
final List<OneConstructUse> constructContent = [];
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
constructContent
|
||||
.addAll(ConstructAnalyticsModel.formatConstructsContent(recentMsgs));
|
||||
}
|
||||
|
||||
// format the analytics data
|
||||
final List<RecentMessageRecord> summaryContent =
|
||||
SummaryAnalyticsModel.formatSummaryContent(recentMsgs);
|
||||
final List<OneConstructUse> constructContent =
|
||||
ConstructAnalyticsModel.formatConstructsContent(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 SummaryAnalyticsEvent.sendSummaryAnalyticsEvent(
|
||||
analyticsRoom,
|
||||
summaryContent,
|
||||
);
|
||||
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.
|
||||
}
|
||||
|
||||
if (constructContent.isNotEmpty) {
|
||||
await ConstructAnalyticsEvent.sendConstructsEvent(
|
||||
analyticsRoom,
|
||||
constructContent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<Room> _studentChats = [];
|
||||
|
||||
Future<void> setStudentChats() async {
|
||||
final List<String> teacherRoomIds =
|
||||
await _pangeaController.matrixState.client.teacherRoomIds;
|
||||
_studentChats = _pangeaController.matrixState.client.rooms
|
||||
.where(
|
||||
(r) =>
|
||||
!r.isSpace &&
|
||||
!r.isAnalyticsRoom &&
|
||||
!teacherRoomIds.contains(r.id),
|
||||
)
|
||||
.toList();
|
||||
setState(data: _studentChats);
|
||||
}
|
||||
|
||||
List<Room> get studentChats {
|
||||
try {
|
||||
if (_studentChats.isNotEmpty) return _studentChats;
|
||||
setStudentChats();
|
||||
return _studentChats;
|
||||
} catch (err) {
|
||||
debugger(when: kDebugMode);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
List<Room> _studentSpaces = [];
|
||||
|
||||
Future<void> setStudentSpaces() async {
|
||||
_studentSpaces =
|
||||
await _pangeaController.matrixState.client.spacesImStudyingIn;
|
||||
}
|
||||
|
||||
List<Room> get studentSpaces {
|
||||
try {
|
||||
if (_studentSpaces.isNotEmpty) return _studentSpaces;
|
||||
setStudentSpaces();
|
||||
return _studentSpaces;
|
||||
} catch (err) {
|
||||
debugger(when: kDebugMode);
|
||||
return [];
|
||||
}
|
||||
await analyticsRoom.sendConstructsEvent(
|
||||
constructContent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ extension PangeaClient on Client {
|
|||
|
||||
Future<List<Room>> get spacesImTeaching async => await _spacesImTeaching;
|
||||
|
||||
Future<List<Room>> get spacesImStudyingIn async => await _spacesImStudyingIn;
|
||||
Future<List<Room>> get chatsImAStudentIn async => await _chatsImAStudentIn;
|
||||
|
||||
Future<List<Room>> get spaceImAStudentIn async => await _spacesImStudyingIn;
|
||||
|
||||
List<Room> get spacesImIn => _spacesImIn;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,18 @@ extension SpaceClientExtension on Client {
|
|||
return spaces;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Future<List<Room>> get _spacesImStudyingIn async {
|
||||
final List<Room> joinedSpaces = rooms
|
||||
.where(
|
||||
|
|
|
|||
|
|
@ -429,21 +429,27 @@ extension EventsRoomExtension on Room {
|
|||
Future<List<PangeaMessageEvent>> myMessageEventsInChat({
|
||||
DateTime? since,
|
||||
}) async {
|
||||
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,
|
||||
try {
|
||||
final List<Event> msgEvents = await getEventsBySender(
|
||||
type: EventTypes.Message,
|
||||
sender: client.userID!,
|
||||
since: since,
|
||||
);
|
||||
}).toList();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/controllers/language_list_controller.dart';
|
|||
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/analytics_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/models/bot_options_model.dart';
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ extension AnalyticsRoomExtension on Room {
|
|||
return;
|
||||
}
|
||||
|
||||
for (final Room space in (await client.spacesImStudyingIn)) {
|
||||
for (final Room space in (await client.spaceImAStudentIn)) {
|
||||
if (space.spaceChildren.any((sc) => sc.roomId == id)) continue;
|
||||
await space.addAnalyticsRoomToSpace(this);
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ extension AnalyticsRoomExtension on Room {
|
|||
return;
|
||||
}
|
||||
|
||||
for (final Room space in (await client.spacesImStudyingIn)) {
|
||||
for (final Room space in (await client.spaceImAStudentIn)) {
|
||||
await space.inviteSpaceTeachersToAnalyticsRoom(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -249,4 +249,34 @@ extension AnalyticsRoomExtension on Room {
|
|||
return creationContent?.tryGet<String>(ModelKey.langCode) == langCode ||
|
||||
creationContent?.tryGet<String>(ModelKey.oldLangCode) == langCode;
|
||||
}
|
||||
|
||||
Future<String?> sendSummaryAnalyticsEvent(
|
||||
List<RecentMessageRecord> records,
|
||||
) async {
|
||||
if (records.isEmpty) return null;
|
||||
|
||||
final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel(
|
||||
messages: records,
|
||||
);
|
||||
final String? eventId = await sendEvent(
|
||||
analyticsModel.toJson(),
|
||||
type: PangeaEventTypes.summaryAnalytics,
|
||||
);
|
||||
return eventId;
|
||||
}
|
||||
|
||||
Future<String?> sendConstructsEvent(
|
||||
List<OneConstructUse> uses,
|
||||
) async {
|
||||
if (uses.isEmpty) return null;
|
||||
final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel(
|
||||
uses: uses,
|
||||
);
|
||||
|
||||
final String? eventId = await sendEvent(
|
||||
constructsModel.toJson(),
|
||||
type: PangeaEventTypes.construct,
|
||||
);
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import 'package:fluffychat/pangea/constants/pangea_event_types.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/analytics_model.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart';
|
||||
import 'package:fluffychat/pangea/models/analytics/summary_analytics_model.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -28,32 +26,4 @@ abstract class AnalyticsEvent {
|
|||
}
|
||||
return contentCache!;
|
||||
}
|
||||
|
||||
static List<String> analyticsEventTypes = [
|
||||
PangeaEventTypes.summaryAnalytics,
|
||||
PangeaEventTypes.construct,
|
||||
];
|
||||
|
||||
static Future<String?> sendEvent(
|
||||
Room analyticsRoom,
|
||||
String type,
|
||||
List<dynamic> analyticsContent,
|
||||
) async {
|
||||
String? eventId;
|
||||
switch (type) {
|
||||
case PangeaEventTypes.summaryAnalytics:
|
||||
eventId = await SummaryAnalyticsEvent.sendSummaryAnalyticsEvent(
|
||||
analyticsRoom,
|
||||
analyticsContent.cast<RecentMessageRecord>(),
|
||||
);
|
||||
break;
|
||||
case PangeaEventTypes.construct:
|
||||
eventId = await ConstructAnalyticsEvent.sendConstructsEvent(
|
||||
analyticsRoom,
|
||||
analyticsContent.cast<OneConstructUse>(),
|
||||
);
|
||||
break;
|
||||
}
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,19 +18,4 @@ class ConstructAnalyticsEvent extends AnalyticsEvent {
|
|||
contentCache ??= ConstructAnalyticsModel.fromJson(event.content);
|
||||
return contentCache as ConstructAnalyticsModel;
|
||||
}
|
||||
|
||||
static Future<String?> sendConstructsEvent(
|
||||
Room analyticsRoom,
|
||||
List<OneConstructUse> uses,
|
||||
) async {
|
||||
final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel(
|
||||
uses: uses,
|
||||
);
|
||||
|
||||
final String? eventId = await analyticsRoom.sendEvent(
|
||||
constructsModel.toJson(),
|
||||
type: PangeaEventTypes.construct,
|
||||
);
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,18 +18,4 @@ class SummaryAnalyticsEvent extends AnalyticsEvent {
|
|||
contentCache ??= SummaryAnalyticsModel.fromJson(event.content);
|
||||
return contentCache as SummaryAnalyticsModel;
|
||||
}
|
||||
|
||||
static Future<String?> sendSummaryAnalyticsEvent(
|
||||
Room analyticsRoom,
|
||||
List<RecentMessageRecord> records,
|
||||
) async {
|
||||
final SummaryAnalyticsModel analyticsModel = SummaryAnalyticsModel(
|
||||
messages: records,
|
||||
);
|
||||
final String? eventId = await analyticsRoom.sendEvent(
|
||||
analyticsModel.toJson(),
|
||||
type: PangeaEventTypes.summaryAnalytics,
|
||||
);
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ import 'dart:typed_data';
|
|||
|
||||
class PracticeActivityRecordModel {
|
||||
final String? question;
|
||||
late List<ActivityResponse> responses;
|
||||
late List<ActivityRecordResponse> responses;
|
||||
|
||||
PracticeActivityRecordModel({
|
||||
required this.question,
|
||||
List<ActivityResponse>? responses,
|
||||
List<ActivityRecordResponse>? responses,
|
||||
}) {
|
||||
if (responses == null) {
|
||||
this.responses = List<ActivityResponse>.empty(growable: true);
|
||||
this.responses = List<ActivityRecordResponse>.empty(growable: true);
|
||||
} else {
|
||||
this.responses = responses;
|
||||
}
|
||||
|
|
@ -26,7 +26,9 @@ class PracticeActivityRecordModel {
|
|||
return PracticeActivityRecordModel(
|
||||
question: json['question'] as String,
|
||||
responses: (json['responses'] as List)
|
||||
.map((e) => ActivityResponse.fromJson(e as Map<String, dynamic>))
|
||||
.map(
|
||||
(e) => ActivityRecordResponse.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
|
@ -55,7 +57,7 @@ class PracticeActivityRecordModel {
|
|||
}) {
|
||||
try {
|
||||
responses.add(
|
||||
ActivityResponse(
|
||||
ActivityRecordResponse(
|
||||
text: text,
|
||||
audioBytes: audioBytes,
|
||||
imageBytes: imageBytes,
|
||||
|
|
@ -84,7 +86,7 @@ class PracticeActivityRecordModel {
|
|||
int get hashCode => question.hashCode ^ responses.hashCode;
|
||||
}
|
||||
|
||||
class ActivityResponse {
|
||||
class ActivityRecordResponse {
|
||||
// the user's response
|
||||
// has nullable string, nullable audio bytes, nullable image bytes, and timestamp
|
||||
final String? text;
|
||||
|
|
@ -92,15 +94,15 @@ class ActivityResponse {
|
|||
final Uint8List? imageBytes;
|
||||
final DateTime timestamp;
|
||||
|
||||
ActivityResponse({
|
||||
ActivityRecordResponse({
|
||||
this.text,
|
||||
this.audioBytes,
|
||||
this.imageBytes,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
factory ActivityResponse.fromJson(Map<String, dynamic> json) {
|
||||
return ActivityResponse(
|
||||
factory ActivityRecordResponse.fromJson(Map<String, dynamic> json) {
|
||||
return ActivityRecordResponse(
|
||||
text: json['text'] as String?,
|
||||
audioBytes: json['audio'] as Uint8List?,
|
||||
imageBytes: json['image'] as Uint8List?,
|
||||
|
|
@ -121,7 +123,7 @@ class ActivityResponse {
|
|||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is ActivityResponse &&
|
||||
return other is ActivityRecordResponse &&
|
||||
other.text == text &&
|
||||
other.audioBytes == audioBytes &&
|
||||
other.imageBytes == imageBytes &&
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:fluffychat/pangea/constants/language_constants.dart';
|
||||
|
|
@ -29,49 +28,35 @@ class StudentAnalyticsPage extends StatefulWidget {
|
|||
class StudentAnalyticsController extends State<StudentAnalyticsPage> {
|
||||
final PangeaController _pangeaController = MatrixState.pangeaController;
|
||||
AnalyticsSelected? selected;
|
||||
StreamSubscription? stateSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final listFutures = [
|
||||
_pangeaController.myAnalytics.setStudentChats(),
|
||||
_pangeaController.myAnalytics.setStudentSpaces(),
|
||||
];
|
||||
Future.wait(listFutures).then((_) => setState(() {}));
|
||||
|
||||
stateSub = _pangeaController.myAnalytics.stateStream.listen((_) {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
stateSub?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
List<Room> _chats = [];
|
||||
List<Room> get chats {
|
||||
if (_pangeaController.myAnalytics.studentChats.isEmpty) {
|
||||
_pangeaController.myAnalytics.setStudentChats().then((_) {
|
||||
if (_pangeaController.myAnalytics.studentChats.isNotEmpty) {
|
||||
setState(() {});
|
||||
}
|
||||
if (_chats.isEmpty) {
|
||||
_pangeaController.matrixState.client.chatsImAStudentIn.then((result) {
|
||||
setState(() => _chats = result);
|
||||
});
|
||||
}
|
||||
return _pangeaController.myAnalytics.studentChats;
|
||||
return _chats;
|
||||
}
|
||||
|
||||
List<Room> _spaces = [];
|
||||
List<Room> get spaces {
|
||||
if (_pangeaController.myAnalytics.studentSpaces.isEmpty) {
|
||||
_pangeaController.myAnalytics.setStudentSpaces().then((_) {
|
||||
if (_pangeaController.myAnalytics.studentSpaces.isNotEmpty) {
|
||||
setState(() {});
|
||||
}
|
||||
if (_spaces.isEmpty) {
|
||||
_pangeaController.matrixState.client.spaceImAStudentIn.then((result) {
|
||||
setState(() => _spaces = result);
|
||||
});
|
||||
}
|
||||
return _pangeaController.myAnalytics.studentSpaces;
|
||||
return _spaces;
|
||||
}
|
||||
|
||||
String? get userId {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue