Skip to content

Commit

Permalink
Merge pull request #192 from pangeachat/analytics-rooms
Browse files Browse the repository at this point in the history
ensure that users' analytics rooms are consistently made for users an…
  • Loading branch information
ggurdin authored May 16, 2024
2 parents dc46857 + ba72857 commit 4b9065b
Show file tree
Hide file tree
Showing 20 changed files with 502 additions and 97 deletions.
4 changes: 3 additions & 1 deletion assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -3945,6 +3945,8 @@
"accuracy": "Accuracy",
"points": "Points",
"noPaymentInfo": "No payment info necessary!",
"studentAnalyticsNotAvailable": "Student data not currently available",
"roomDataMissing": "Some data may be missing from rooms in which you are not a member.",
"updatePhoneOS": "You may need to update your device's OS version.",
"wordsPerMinute": "Words per minute"
}
}
6 changes: 4 additions & 2 deletions lib/pages/chat/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,14 @@ class ChatController extends State<ChatPageWithRoom>
useType: useType,
)
.then(
(String? msgEventId) {
(String? msgEventId) async {
// #Pangea
setState(() {
if (previousEdit != null) {
edittingEvents.add(previousEdit.eventId);
}
});
// Pangea#

GoogleAnalytics.sendMessage(
room.id,
room.classCode,
Expand All @@ -644,6 +644,8 @@ class ChatController extends State<ChatPageWithRoom>
return;
}

// ensure that analytics room exists / is created for the active langCode
await room.ensureAnalyticsRoomExists();
pangeaController.myAnalytics.handleMessage(
room,
RecentMessageRecord(
Expand Down
8 changes: 7 additions & 1 deletion lib/pages/chat/chat_input_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:animations/animations.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
import 'package:fluffychat/pangea/choreographer/widgets/send_button.dart';
import 'package:fluffychat/pangea/constants/language_keys.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart';
Expand Down Expand Up @@ -330,7 +331,12 @@ class ChatInputRow extends StatelessWidget {
bottom: 6.0,
top: 3.0,
),
hintText: activel1 != null && activel2 != null
hintText: activel1 != null &&
activel2 != null &&
activel1.langCode !=
LanguageKeys.unknownLanguage &&
activel2.langCode !=
LanguageKeys.unknownLanguage
? L10n.of(context)!.writeAMessageFlag(
activel1.languageEmoji ??
activel1.getDisplayName(context) ??
Expand Down
45 changes: 35 additions & 10 deletions lib/pages/chat_list/chat_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import 'package:collection/collection.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
import 'package:fluffychat/pangea/constants/pangea_room_types.dart';
import 'package:fluffychat/pangea/controllers/pangea_controller.dart';
import 'package:fluffychat/pangea/extensions/client_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/utils/add_to_space.dart';
import 'package:fluffychat/pangea/utils/chat_list_handle_space_tap.dart';
Expand Down Expand Up @@ -521,25 +523,47 @@ class ChatListController extends State<ChatList>
_invitedSpaceSubscription = pangeaController
.matrixState.client.onSync.stream
.where((event) => event.rooms?.invite != null)
.listen((event) {
.listen((event) async {
for (final inviteEntry in event.rooms!.invite!.entries) {
if (inviteEntry.value.inviteState == null) continue;
final bool isSpace = inviteEntry.value.inviteState!.any(
(event) =>
event.type == EventTypes.RoomCreate &&
event.content['type'] == 'm.space',
);
if (!isSpace) continue;
final String spaceId = inviteEntry.key;
final Room? space = pangeaController.matrixState.client.getRoomById(
spaceId,
final bool isAnalytics = inviteEntry.value.inviteState!.any(
(event) =>
event.type == EventTypes.RoomCreate &&
event.content['type'] == PangeaRoomTypes.analytics,
);
if (space != null) {
chatListHandleSpaceTap(
context,
this,
space,

if (isSpace) {
final String spaceId = inviteEntry.key;
final Room? space = pangeaController.matrixState.client.getRoomById(
spaceId,
);
if (space != null) {
chatListHandleSpaceTap(
context,
this,
space,
);
}
}

if (isAnalytics) {
final Room? analyticsRoom =
pangeaController.matrixState.client.getRoomById(inviteEntry.key);
try {
await analyticsRoom?.join();
} catch (err, s) {
ErrorHandler.logError(
m: "Failed to join analytics room",
e: err,
s: s,
);
}
return;
}
}
});
Expand Down Expand Up @@ -819,6 +843,7 @@ class ChatListController extends State<ChatList>
pangeaController.afterSyncAndFirstLoginInitialization(context);
await pangeaController.inviteBotToExistingSpaces();
await pangeaController.setPangeaPushRules();
await client.migrateAnalyticsRooms();
} else {
ErrorHandler.logError(
m: "didn't run afterSyncAndFirstLoginInitialization because not mounted",
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/chat_list/client_chooser_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class ClientChooserButton extends StatelessWidget {
),
),
PopupMenuItem(
enabled: matrix.client.classesAndExchangesImIn.isNotEmpty,
enabled: matrix.client.allMyAnalyticsRooms.isNotEmpty,
value: SettingsAction.myAnalytics,
child: Row(
children: [
Expand Down
4 changes: 2 additions & 2 deletions lib/pages/invitation_selection/invitation_selection.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ class InvitationSelectionController extends State<InvitationSelection> {
//#Pangea
// future: () => room.invite(id),
future: () async {
await room.invite(id);
if (mode == InvitationSelectionMode.admin) {
await inviteTeacherAction(room, id);
}
Expand All @@ -175,7 +174,8 @@ class InvitationSelectionController extends State<InvitationSelection> {

// #Pangea
Future<void> inviteTeacherAction(Room room, String id) async {
room.setPower(id, ClassDefaultValues.powerLevelOfAdmin);
await room.invite(id);
await room.setPower(id, ClassDefaultValues.powerLevelOfAdmin);
if (room.isSpace) {
for (final spaceChild in room.spaceChildren) {
if (spaceChild.roomId == null) continue;
Expand Down
3 changes: 2 additions & 1 deletion lib/pangea/choreographer/controllers/choreographer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@ class Choreographer {
final CanSendStatus canSendStatus =
pangeaController.subscriptionController.canSendStatus;

if (canSendStatus != CanSendStatus.subscribed) {
if (canSendStatus != CanSendStatus.subscribed ||
(!igcEnabled && !itEnabled)) {
return;
}

Expand Down
30 changes: 25 additions & 5 deletions lib/pangea/controllers/class_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -120,21 +120,41 @@ class ClassController extends BaseController {

if (classChunk == null) {
ClassCodeUtil.messageSnack(
context, L10n.of(context)!.unableToFindClass);
context,
L10n.of(context)!.unableToFindClass,
);
return;
}

if (Matrix.of(context)
.client
.rooms
if (_pangeaController.matrixState.client.rooms
.any((room) => room.id == classChunk.roomId)) {
setActiveSpaceIdInChatListController(classChunk.roomId);
ClassCodeUtil.messageSnack(context, L10n.of(context)!.alreadyInClass);
return;
}
await _pangeaController.matrixState.client.joinRoom(classChunk.roomId);

setActiveSpaceIdInChatListController(classChunk.roomId);
if (_pangeaController.matrixState.client.getRoomById(classChunk.roomId) ==
null) {
await _pangeaController.matrixState.client.waitForRoomInSync(
classChunk.roomId,
join: true,
);
}

// add the user's analytics room to this joined space
// so their teachers can join them via the space hierarchy
final Room? joinedSpace =
_pangeaController.matrixState.client.getRoomById(classChunk.roomId);

// ensure that the user has an analytics room for this space's language
await joinedSpace?.ensureAnalyticsRoomExists();

// when possible, add user's analytics room the to space they joined
await joinedSpace?.addAnalyticsRoomsToSpace();

// and invite the space's teachers to the user's analytics rooms
await joinedSpace?.inviteSpaceTeachersToAnalyticsRooms();
GoogleAnalytics.joinClass(classCode);
return;
} catch (err) {
Expand Down
2 changes: 1 addition & 1 deletion lib/pangea/controllers/my_analytics_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class MyAnalyticsController {
}
final Room analyticsRoom = await _pangeaController.matrixState.client
.getMyAnalyticsRoom(langCode);
analyticsRoom.makeSureTeachersAreInvitedToAnalyticsRoom();

final List<Future<void>> saveFutures = [];
for (final uses in aggregatedVocabUse.entries) {
debugPrint("saving of type ${uses.value.first.constructType}");
Expand Down
11 changes: 11 additions & 0 deletions lib/pangea/controllers/pangea_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:fluffychat/pangea/controllers/text_to_speech_controller.dart';
import 'package:fluffychat/pangea/controllers/user_controller.dart';
import 'package:fluffychat/pangea/controllers/word_net_controller.dart';
import 'package:fluffychat/pangea/extensions/client_extension.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
import 'package:fluffychat/pangea/guard/p_vguard.dart';
import 'package:fluffychat/pangea/utils/bot_name.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
Expand Down Expand Up @@ -272,6 +273,16 @@ class PangeaController {
}

Future<void> setPangeaPushRules() async {
final List<Room> analyticsRooms =
matrixState.client.rooms.where((room) => room.isAnalyticsRoom).toList();

for (final Room room in analyticsRooms) {
final pushRule = room.pushRuleState;
if (pushRule != PushRuleState.dontNotify) {
await room.setPushRuleState(PushRuleState.dontNotify);
}
}

if (!(matrixState.client.globalPushRules?.override?.any(
(element) => element.ruleId == PangeaEventTypes.textToSpeechRule,
) ??
Expand Down
95 changes: 91 additions & 4 deletions lib/pangea/extensions/client_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ extension PangeaClient on Client {
for (final classRoom in classesAndExchangesImIn) {
for (final teacher in await classRoom.teachers) {
// If person requesting list of teachers is a teacher in another classroom, don't add them to the list
if (!teachers.any((e) => e.id == teacher.id) && userID != teacher.id) {
if (!teachers.any((e) => e.id == teacher.id) && userID != teacher.id) {
teachers.add(teacher);
}
}
Expand Down Expand Up @@ -123,7 +123,7 @@ extension PangeaClient on Client {
for (final room in rooms) {
if (room.partial) await room.postLoad();
}

final Room? analyticsRoom = analyticsRoomLocal(langCode);

if (analyticsRoom != null) return analyticsRoom;
Expand Down Expand Up @@ -168,14 +168,20 @@ extension PangeaClient on Client {
// BotName.localBot,
BotName.byEnvironment,
],
visibility: Visibility.private,
roomAliasName: "${userID!.localpart}_${langCode}_analytics",
);
if (getRoomById(roomID) == null) {
// Wait for room actually appears in sync
await waitForRoomInSync(roomID, join: true);
}

final Room? analyticsRoom = getRoomById(roomID);

// add this analytics room to all spaces so teachers can join them
// via the space hierarchy
await analyticsRoom?.addAnalyticsRoomToSpaces();

// and invite all teachers to new analytics room
await analyticsRoom?.inviteTeachersToAnalyticsRoom();
return getRoomById(roomID)!;
}

Expand Down Expand Up @@ -245,4 +251,85 @@ extension PangeaClient on Client {
editEvents.add(originalEvent);
return editEvents.slice(1).map((e) => e.eventId).toList();
}

// Get all my analytics rooms
List<Room> get allMyAnalyticsRooms => rooms
.where(
(e) => e.isAnalyticsRoomOfUser(userID!),
)
.toList();

// migration function to change analytics rooms' vsibility to public
// so they will appear in the space hierarchy
Future<void> updateAnalyticsRoomVisibility() async {
final List<Future> makePublicFutures = [];
for (final Room room in allMyAnalyticsRooms) {
final visability = await getRoomVisibilityOnDirectory(room.id);
if (visability != Visibility.public) {
await setRoomVisibilityOnDirectory(
room.id,
visibility: Visibility.public,
);
}
}
await Future.wait(makePublicFutures);
}

// Add all the users' analytics room to all the spaces the student studies in
// So teachers can join them via space hierarchy
// Will not always work, as there may be spaces where students don't have permission to add chats
// But allows teachers to join analytics rooms without being invited
Future<void> addAnalyticsRoomsToAllSpaces() async {
final List<Future> addFutures = [];
for (final Room room in allMyAnalyticsRooms) {
addFutures.add(room.addAnalyticsRoomToSpaces());
}
await Future.wait(addFutures);
}

// Invite teachers to all my analytics room
// Handles case when students cannot add analytics room to space(s)
// So teacher is still able to get analytics data for this student
Future<void> inviteAllTeachersToAllAnalyticsRooms() async {
final List<Future> inviteFutures = [];
for (final Room analyticsRoom in allMyAnalyticsRooms) {
inviteFutures.add(analyticsRoom.inviteTeachersToAnalyticsRoom());
}
await Future.wait(inviteFutures);
}

// Join all analytics rooms in all spaces
// Allows teachers to join analytics rooms without being invited
Future<void> joinAnalyticsRoomsInAllSpaces() async {
final List<Future> joinFutures = [];
for (final Room space in (await classesAndExchangesImTeaching)) {
joinFutures.add(space.joinAnalyticsRoomsInSpace());
}
await Future.wait(joinFutures);
}

// Join invited analytics rooms
// Checks for invites to any student analytics rooms
// Handles case of analytics rooms that can't be added to some space(s)
Future<void> joinInvitedAnalyticsRooms() async {
for (final Room room in rooms) {
if (room.membership == Membership.invite && room.isAnalyticsRoom) {
try {
await room.join();
} catch (err) {
debugPrint("Failed to join analytics room ${room.id}");
}
}
}
}

// helper function to join all relevant analytics rooms
// and set up those rooms to be joined by relevant teachers
Future<void> migrateAnalyticsRooms() async {
await updateAnalyticsRoomVisibility();
await addAnalyticsRoomsToAllSpaces();
await inviteAllTeachersToAllAnalyticsRooms();
await joinInvitedAnalyticsRooms();
await joinAnalyticsRoomsInAllSpaces();
}
}
Loading

0 comments on commit 4b9065b

Please sign in to comment.