From 308bd9ee498ca07ea455b2401d13b2e0fe7a2a8c Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 14 May 2024 11:12:23 -0400 Subject: [PATCH 01/48] ensure that users' analytics rooms are consistently made for users and that teachers are added to analytics rooms are soon as possible --- assets/l10n/intl_en.arb | 4 +- lib/pages/chat/chat.dart | 6 +- lib/pages/chat/chat_input_row.dart | 8 +- lib/pages/chat_list/chat_list.dart | 45 ++- .../chat_list/client_chooser_button.dart | 2 +- lib/pages/chat_list/space_view.dart | 34 +- .../invitation_selection.dart | 4 +- .../controllers/choreographer.dart | 3 +- lib/pangea/controllers/class_controller.dart | 30 +- .../controllers/my_analytics_controller.dart | 2 +- lib/pangea/controllers/pangea_controller.dart | 11 + lib/pangea/extensions/client_extension.dart | 95 ++++- .../extensions/pangea_room_extension.dart | 324 ++++++++++++++++-- .../pages/analytics/analytics_list_tile.dart | 29 +- .../pages/analytics/base_analytics.dart | 26 +- .../pages/analytics/base_analytics_view.dart | 9 + .../class_analytics/class_analytics.dart | 7 +- .../class_analytics/class_analytics_view.dart | 2 +- .../pages/analytics/construct_list.dart | 51 +-- .../utils/chat_list_handle_space_tap.dart | 3 + lib/pangea/widgets/chat/message_toolbar.dart | 12 +- 21 files changed, 597 insertions(+), 110 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 21874cc9e..8e01db0a6 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3944,5 +3944,7 @@ "score": "Score", "accuracy": "Accuracy", "points": "Points", - "noPaymentInfo": "No payment info necessary!" + "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." } \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 40701e742..82a366008 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -613,14 +613,14 @@ class ChatController extends State useType: useType, ) .then( - (String? msgEventId) { + (String? msgEventId) async { // #Pangea setState(() { if (previousEdit != null) { edittingEvents.add(previousEdit.eventId); } }); - // Pangea# + GoogleAnalytics.sendMessage( room.id, room.classCode, @@ -635,6 +635,8 @@ class ChatController extends State return; } + // ensure that analytics room exists / is created for the active langCode + await room.ensureAnalyticsRoomExists(); pangeaController.myAnalytics.handleMessage( room, RecentMessageRecord( diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 0af014aef..fd8d95b5b 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -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'; @@ -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) ?? diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index c39298d6c..f96baa040 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -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'; @@ -521,7 +523,7 @@ class ChatListController extends State _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( @@ -529,17 +531,39 @@ class ChatListController extends State 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; } } }); @@ -819,6 +843,7 @@ class ChatListController extends State pangeaController.afterSyncAndFirstLoginInitialization(context); await pangeaController.inviteBotToExistingSpaces(); await pangeaController.setPangeaPushRules(); + await client.migrateAnalyticsRooms(); } else { ErrorHandler.logError( m: "didn't run afterSyncAndFirstLoginInitialization because not mounted", diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 896b6dc36..966fa0789 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -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: [ diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index e7d793e3d..1d333228d 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -7,6 +7,7 @@ import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; +import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/extensions/sync_update_extension.dart'; import 'package:fluffychat/pangea/utils/archive_space.dart'; @@ -411,6 +412,18 @@ class _SpaceViewState extends State { } setState(() => refreshing = false); } + + bool includeSpaceChild(sc, matchingSpaceChildren) { + final bool isAnalyticsRoom = sc.roomType == PangeaRoomTypes.analytics; + final bool isMember = [Membership.join, Membership.invite] + .contains(Matrix.of(context).client.getRoomById(sc.roomId)?.membership); + final bool isSuggested = matchingSpaceChildren.any( + (matchingSpaceChild) => + matchingSpaceChild.roomId == sc.roomId && + matchingSpaceChild.suggested == true, + ); + return !isAnalyticsRoom && (isMember || isSuggested); + } // Pangea# @override @@ -479,7 +492,7 @@ class _SpaceViewState extends State { ) : L10n.of(context)!.youreInvited, ), - if (rootSpace.locked ?? false) + if (rootSpace.locked) const Padding( padding: EdgeInsets.only(left: 4.0), child: Icon( @@ -618,24 +631,17 @@ class _SpaceViewState extends State { .contains(spaceChild.roomId), ) .toList(); + spaceChildren = spaceChildren .where( - (spaceChild) => - matchingSpaceChildren.any( - (matchingSpaceChild) => - matchingSpaceChild.roomId == - spaceChild.roomId && - matchingSpaceChild.suggested == true, - ) || - [Membership.join, Membership.invite].contains( - Matrix.of(context) - .client - .getRoomById(spaceChild.roomId) - ?.membership, - ), + (sc) => includeSpaceChild( + sc, + matchingSpaceChildren, + ), ) .toList(); } + spaceChildren.sort((a, b) { final bool aIsSpace = a.roomType == 'm.space'; final bool bIsSpace = b.roomType == 'm.space'; diff --git a/lib/pages/invitation_selection/invitation_selection.dart b/lib/pages/invitation_selection/invitation_selection.dart index 52e128ddc..5f2bd5027 100644 --- a/lib/pages/invitation_selection/invitation_selection.dart +++ b/lib/pages/invitation_selection/invitation_selection.dart @@ -157,7 +157,6 @@ class InvitationSelectionController extends State { //#Pangea // future: () => room.invite(id), future: () async { - await room.invite(id); if (mode == InvitationSelectionMode.admin) { await inviteTeacherAction(room, id); } @@ -175,7 +174,8 @@ class InvitationSelectionController extends State { // #Pangea Future 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; diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 156f36cc8..3a26676c6 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -211,7 +211,8 @@ class Choreographer { final CanSendStatus canSendStatus = pangeaController.subscriptionController.canSendStatus; - if (canSendStatus != CanSendStatus.subscribed) { + if (canSendStatus != CanSendStatus.subscribed || + (!igcEnabled && !itEnabled)) { return; } diff --git a/lib/pangea/controllers/class_controller.dart b/lib/pangea/controllers/class_controller.dart index 7706d2194..c0a8e6093 100644 --- a/lib/pangea/controllers/class_controller.dart +++ b/lib/pangea/controllers/class_controller.dart @@ -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) { diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 4b7c8cc96..808355b47 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -90,7 +90,7 @@ class MyAnalyticsController { } final Room analyticsRoom = await _pangeaController.matrixState.client .getMyAnalyticsRoom(langCode); - analyticsRoom.makeSureTeachersAreInvitedToAnalyticsRoom(); + final List> saveFutures = []; for (final uses in aggregatedVocabUse.entries) { debugPrint("saving of type ${uses.value.first.constructType}"); diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index e2ddc6714..204ef9307 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -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'; @@ -272,6 +273,16 @@ class PangeaController { } Future setPangeaPushRules() async { + final List 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, ) ?? diff --git a/lib/pangea/extensions/client_extension.dart b/lib/pangea/extensions/client_extension.dart index 6a6c3359d..b259d0c9e 100644 --- a/lib/pangea/extensions/client_extension.dart +++ b/lib/pangea/extensions/client_extension.dart @@ -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); } } @@ -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; @@ -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)!; } @@ -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 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 updateAnalyticsRoomVisibility() async { + final List 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 addAnalyticsRoomsToAllSpaces() async { + final List 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 inviteAllTeachersToAllAnalyticsRooms() async { + final List 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 joinAnalyticsRoomsInAllSpaces() async { + final List 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 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 migrateAnalyticsRooms() async { + await updateAnalyticsRoomVisibility(); + await addAnalyticsRoomsToAllSpaces(); + await inviteAllTeachersToAllAnalyticsRooms(); + await joinInvitedAnalyticsRooms(); + await joinAnalyticsRoomsInAllSpaces(); + } } diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 4e4f773db..1dec2bcbb 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:developer'; +import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; @@ -442,6 +443,7 @@ extension PangeaRoom on Room { /// save RoomAnalytics object to PangeaEventTypes.analyticsSummary event Future _createStudentAnalyticsEvent() async { try { + await postLoad(); if (!pangeaCanSendEvent(PangeaEventTypes.studentAnalyticsSummary)) { ErrorHandler.logError( m: "null powerLevels in createStudentAnalytics", @@ -453,7 +455,7 @@ extension PangeaRoom on Room { debugger(when: kDebugMode); throw Exception("null userId in createStudentAnalytics"); } - await postLoad(); + final String eventId = await client.setRoomStateWithKey( id, PangeaEventTypes.studentAnalyticsSummary, @@ -791,31 +793,6 @@ extension PangeaRoom on Room { } } - Future makeSureTeachersAreInvitedToAnalyticsRoom() async { - try { - if (!isAnalyticsRoom) { - throw Exception("not an analytics room"); - } - if (!participantListComplete) { - await requestParticipants(); - } - final toAdd = [ - ...getParticipants([Membership.invite, Membership.join]) - .map((e) => e.id), - BotName.byEnvironment, - ]; - for (final teacher in (await client.myTeachers)) { - if (!toAdd.contains(teacher.id)) { - debugPrint("inviting ${teacher.id} to analytics room"); - await invite(teacher.id); - } - } - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: stack); - } - } - /// update state event and return eventId Future updateStateEvent(Event stateEvent) { if (stateEvent.stateKey == null) { @@ -1059,4 +1036,299 @@ extension PangeaRoom on Room { getState(PangeaEventTypes.botOptions)?.content ?? {}, ); } + + // Add analytics room to all spaces the user is a student in (1 analytics room to all spaces) + // 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 addAnalyticsRoomToSpaces() async { + if (!isAnalyticsRoomOfUser(client.userID!)) { + debugPrint("addAnalyticsRoomToSpaces called on non-analytics room"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "addAnalyticsRoomToSpaces called on non-analytics room", + ), + ); + return; + } + + for (final Room space in (await client.classesAndExchangesImStudyingIn)) { + if (space.spaceChildren.any((sc) => sc.roomId == id)) continue; + if (space.canIAddSpaceChild(null)) { + try { + await space.setSpaceChild(id); + } catch (err) { + debugPrint( + "Failed to add analytics room for student ${client.userID} to space ${space.id}", + ); + Sentry.addBreadcrumb( + Breadcrumb( + message: "Failed to add analytics room to space ${space.id}", + ), + ); + } + } + } + } + + // Add all analytics rooms to space + // Similar to addAnalyticsRoomToSpaces, but all analytics room to 1 space + Future addAnalyticsRoomsToSpace() async { + if (!isSpace) { + debugPrint("addAnalyticsRoomsToSpace called on non-space room"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "addAnalyticsRoomsToSpace called on non-space room", + ), + ); + return; + } + + await postLoad(); + if (!canIAddSpaceChild(null)) { + debugPrint( + "addAnalyticsRoomsToSpace called on space without add permission", + ); + Sentry.addBreadcrumb( + Breadcrumb( + message: + "addAnalyticsRoomsToSpace called on space without add permission", + ), + ); + return; + } + + final List allMyAnalyticsRooms = client.allMyAnalyticsRooms; + for (final Room analyticsRoom in allMyAnalyticsRooms) { + // add analytics room to space if it hasn't already been added + if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) continue; + try { + await setSpaceChild(analyticsRoom.id); + } catch (err) { + debugPrint( + "Failed to add analytics room ${analyticsRoom.id} to space $id", + ); + Sentry.addBreadcrumb( + Breadcrumb( + message: "Failed to add analytics room to space $id", + ), + ); + } + } + } + + // Invite all teachers to 1 analytics room + // Handles case when students cannot add analytics room to space + // So teacher is still able to get analytics data for this student + Future inviteTeachersToAnalyticsRoom() async { + if (client.userID == null) { + debugPrint("inviteTeachersToAnalyticsRoom called with null userId"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "inviteTeachersToAnalyticsRoom called with null userId", + ), + ); + return; + } + + if (!isAnalyticsRoomOfUser(client.userID!)) { + debugPrint("inviteTeachersToAnalyticsRoom called on non-analytics room"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "inviteTeachersToAnalyticsRoom called on non-analytics room", + ), + ); + return; + } + + // load all participants of analytics room + if (!participantListComplete) { + await requestParticipants(); + } + final List participants = getParticipants(); + + // invite any teachers who are not already in the room + for (final teacher in (await client.myTeachers)) { + if (!participants.any((p) => p.id == teacher.id)) { + try { + await invite(teacher.id); + } catch (err, s) { + debugPrint( + "Failed to invite teacher ${teacher.id} to analytics room $id", + ); + ErrorHandler.logError( + e: err, + m: "Failed to invite teacher ${teacher.id} to analytics room $id", + s: s, + ); + } + } + } + } + + // Invite teachers of 1 space to all users' analytics rooms + Future inviteSpaceTeachersToAnalyticsRooms() async { + if (!isSpace) { + debugPrint( + "inviteSpaceTeachersToAllAnalyticsRoom called on non-space room", + ); + Sentry.addBreadcrumb( + Breadcrumb( + message: + "inviteSpaceTeachersToAllAnalyticsRoom called on non-space room", + ), + ); + return; + } + + for (final Room analyticsRoom in client.allMyAnalyticsRooms) { + if (!analyticsRoom.participantListComplete) { + await analyticsRoom.requestParticipants(); + } + final List participants = analyticsRoom.getParticipants(); + for (final User teacher in (await teachers)) { + if (!participants.any((p) => p.id == teacher.id)) { + try { + await analyticsRoom.invite(teacher.id); + } catch (err, s) { + debugPrint( + "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", + ); + ErrorHandler.logError( + e: err, + m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", + s: s, + ); + } + } + } + } + } + + // Join analytics rooms in space + // Allows teachers to join analytics rooms without being invited + Future joinAnalyticsRoomsInSpace() async { + if (!isSpace) { + debugPrint("joinAnalyticsRoomsInSpace called on non-space room"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "joinAnalyticsRoomsInSpace called on non-space room", + ), + ); + return; + } + + // added delay because without it power levels don't load and user is not + // recognized as admin + await Future.delayed(const Duration(milliseconds: 500)); + await postLoad(); + + if (!isRoomAdmin) { + debugPrint("joinAnalyticsRoomsInSpace called by non-admin"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "joinAnalyticsRoomsInSpace called by non-admin", + ), + ); + return; + } + + final spaceHierarchy = await client.getSpaceHierarchy( + id, + maxDepth: 1, + ); + + final List analyticsRoomIds = spaceHierarchy.rooms + .where( + (r) => r.roomType == PangeaRoomTypes.analytics, + ) + .map((r) => r.roomId) + .toList(); + + for (final String roomID in analyticsRoomIds) { + try { + await joinSpaceChild(roomID); + } catch (err, s) { + debugPrint("Failed to join analytics room $roomID in space $id"); + ErrorHandler.logError( + e: err, + m: "Failed to join analytics room $roomID in space $id", + s: s, + ); + } + } + } + + Future joinSpaceChild(String roomID) async { + final Room? child = client.getRoomById(roomID); + if (child == null) { + await client.joinRoom( + roomID, + serverName: spaceChildren + .firstWhereOrNull((child) => child.roomId == roomID) + ?.via, + ); + if (client.getRoomById(roomID) == null) { + await client.waitForRoomInSync(roomID, join: true); + } + return; + } + + if (![Membership.invite, Membership.join].contains(child.membership)) { + final waitForRoom = client.waitForRoomInSync( + roomID, + join: true, + ); + await child.join(); + await waitForRoom; + } + } + + // check if analytics room exists for a given language code + // and if not, create it + Future ensureAnalyticsRoomExists() async { + await postLoad(); + if (firstLanguageSettings?.targetLanguage == null) return; + await client.getMyAnalyticsRoom(firstLanguageSettings!.targetLanguage); + } + + // Check if teacher is in students' analytics rooms + // To warn teachers if some data might be missing because they have + // not yet joined a students' analytics room + // Future areAllStudentAnalyticsAvailable() async { + // if (!isSpace) { + // debugPrint("areAllStudentAnalyticsAvailable called on non-space room"); + // Sentry.addBreadcrumb( + // Breadcrumb( + // message: "areAllStudentAnalyticsAvailable called on non-space room", + // ), + // ); + // return false; + // } + + // final String? spaceLangCode = firstLanguageSettings?.targetLanguage; + // if (spaceLangCode == null) { + // debugPrint( + // "areAllStudentAnalyticsAvailable called on space without language settings", + // ); + // Sentry.addBreadcrumb( + // Breadcrumb( + // message: + // "areAllStudentAnalyticsAvailable called on space without language settings", + // ), + // ); + // return false; + // } + + // for (final User student in students) { + // final Room? studentAnalyticsRoom = client.analyticsRoomLocal( + // spaceLangCode, + // student.id, + // ); + // if (studentAnalyticsRoom == null) { + // return false; + // } + // } + // return true; + // } } diff --git a/lib/pangea/pages/analytics/analytics_list_tile.dart b/lib/pangea/pages/analytics/analytics_list_tile.dart index ae81e957c..ab35b2610 100644 --- a/lib/pangea/pages/analytics/analytics_list_tile.dart +++ b/lib/pangea/pages/analytics/analytics_list_tile.dart @@ -52,7 +52,11 @@ class AnalyticsListTileState extends State { child: Opacity( opacity: widget.enabled ? 1 : 0.5, child: Tooltip( - message: widget.enabled ? "" : L10n.of(context)!.joinToView, + message: widget.enabled + ? "" + : widget.type == AnalyticsEntryType.room + ? L10n.of(context)!.joinToView + : L10n.of(context)!.studentAnalyticsNotAvailable, child: ListTile( leading: widget.type == AnalyticsEntryType.privateChats ? CircleAvatar( @@ -101,18 +105,19 @@ class AnalyticsListTileState extends State { : null, selected: widget.selected, enabled: widget.enabled, - onTap: () => - (room?.isSpace ?? false) && widget.allowNavigateOnSelect - ? context.go( - '/rooms/analytics/${room!.id}', - ) - : widget.onTap( - AnalyticsSelected( - widget.id, - widget.type, - widget.displayName, - ), + onTap: () { + (room?.isSpace ?? false) && widget.allowNavigateOnSelect + ? context.go( + '/rooms/analytics/${room!.id}', + ) + : widget.onTap( + AnalyticsSelected( + widget.id, + widget.type, + widget.displayName, ), + ); + }, trailing: (room?.isSpace ?? false) && widget.type != AnalyticsEntryType.privateChats && widget.allowNavigateOnSelect diff --git a/lib/pangea/pages/analytics/base_analytics.dart b/lib/pangea/pages/analytics/base_analytics.dart index 7182bc6ab..634a39980 100644 --- a/lib/pangea/pages/analytics/base_analytics.dart +++ b/lib/pangea/pages/analytics/base_analytics.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; +import 'package:fluffychat/pangea/extensions/client_extension.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics_view.dart'; import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; import 'package:flutter/material.dart'; @@ -142,14 +143,29 @@ class BaseAnalyticsController extends State { } bool enableSelection(AnalyticsSelected? selectedParam) { - return selectedView == BarChartViewSelection.grammar && - selectedParam?.type == AnalyticsEntryType.room - ? Matrix.of(context) + if (selectedView == BarChartViewSelection.grammar) { + if (selectedParam?.type == AnalyticsEntryType.room) { + return Matrix.of(context) .client .getRoomById(selectedParam!.id) ?.membership == - Membership.join - : true; + Membership.join; + } + + if (selectedParam?.type == AnalyticsEntryType.student) { + final String? langCode = + pangeaController.languageController.activeL2Code( + roomID: widget.defaultSelected.id, + ); + if (langCode == null) return false; + return Matrix.of(context).client.analyticsRoomLocal( + langCode, + selectedParam?.id, + ) != + null; + } + } + return true; } @override diff --git a/lib/pangea/pages/analytics/base_analytics_view.dart b/lib/pangea/pages/analytics/base_analytics_view.dart index 1f331d2d5..86f179829 100644 --- a/lib/pangea/pages/analytics/base_analytics_view.dart +++ b/lib/pangea/pages/analytics/base_analytics_view.dart @@ -246,6 +246,15 @@ class BaseAnalyticsView extends StatelessWidget { .widget .tabs[1] .allowNavigateOnSelect, + enabled: + controller.enableSelection( + AnalyticsSelected( + item.id, + controller + .widget.tabs[1].type, + "", + ), + ), ), ) .toList(), diff --git a/lib/pangea/pages/analytics/class_analytics/class_analytics.dart b/lib/pangea/pages/analytics/class_analytics/class_analytics.dart index 0316d02cd..877ae1788 100644 --- a/lib/pangea/pages/analytics/class_analytics/class_analytics.dart +++ b/lib/pangea/pages/analytics/class_analytics/class_analytics.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/enum/construct_type_enum.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/chart_analytics_model.dart'; @@ -103,7 +104,11 @@ class ClassAnalyticsV2Controller extends State { students = classRoom!.students; chats = response.rooms - .where((room) => room.roomId != classRoom!.id) + .where( + (room) => + room.roomId != classRoom!.id && + room.roomType != PangeaRoomTypes.analytics, + ) .toList(); chats.sort((a, b) => a.roomType == 'm.space' ? -1 : 1); } diff --git a/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart b/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart index 0c0550172..dfb44e106 100644 --- a/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart +++ b/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart @@ -33,7 +33,7 @@ class ClassAnalyticsView extends StatelessWidget { .map( (s) => TabItem( avatar: s.avatarUrl, - displayName: s.displayName ?? "unknown", + displayName: s.calcDisplayname(), id: s.id, ), ) diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index dd6c26618..838f766a3 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_representation_ev import 'package:fluffychat/pangea/models/constructs_analytics_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -54,7 +55,7 @@ class ConstructListState extends State { selected: widget.selected, forceUpdate: true, ) - .then((_) => setState(() => initialized = true)); + .whenComplete(() => setState(() => initialized = true)); } @override @@ -160,11 +161,11 @@ class ConstructListViewState extends State { stateSub?.cancel(); } - @override - void didUpdateWidget(ConstructListView oldWidget) { - super.didUpdateWidget(oldWidget); - fetchUses(); - } + // @override + // void didUpdateWidget(ConstructListView oldWidget) { + // super.didUpdateWidget(oldWidget); + // fetchUses(); + // } int get lemmaIndex => constructs?.indexWhere( @@ -215,19 +216,29 @@ class ConstructListViewState extends State { } setState(() => fetchingUses = true); - final List uses = currentConstruct!.content.uses; - _msgEvents.clear(); + try { + final List uses = currentConstruct!.content.uses; + _msgEvents.clear(); - for (final OneConstructUse use in uses) { - final PangeaMessageEvent? msgEvent = await getMessageEvent(use); - final RepresentationEvent? repEvent = - msgEvent?.originalSent ?? msgEvent?.originalWritten; - if (repEvent?.choreo == null) { - continue; + for (final OneConstructUse use in uses) { + final PangeaMessageEvent? msgEvent = await getMessageEvent(use); + final RepresentationEvent? repEvent = + msgEvent?.originalSent ?? msgEvent?.originalWritten; + if (repEvent?.choreo == null) { + continue; + } + _msgEvents.add(msgEvent!); } - _msgEvents.add(msgEvent!); + setState(() => fetchingUses = false); + } catch (err, s) { + setState(() => fetchingUses = false); + debugPrint("Error fetching uses: $err"); + ErrorHandler.logError( + e: err, + s: s, + m: "Failed to fetch uses for current construct ${currentConstruct?.content.lemma}", + ); } - setState(() => fetchingUses = false); } List? get constructs => @@ -278,12 +289,10 @@ class ConstructListViewState extends State { children: [ if (constructs![lemmaIndex].content.uses.length > _msgEvents.length) - const Center( + Center( child: Padding( - padding: EdgeInsets.all(8.0), - child: Text( - "Some data may be missing from rooms in which you are not a member.", - ), + padding: const EdgeInsets.all(8.0), + child: Text(L10n.of(context)!.roomDataMissing), ), ), Expanded( diff --git a/lib/pangea/utils/chat_list_handle_space_tap.dart b/lib/pangea/utils/chat_list_handle_space_tap.dart index ef0865634..b647458cd 100644 --- a/lib/pangea/utils/chat_list_handle_space_tap.dart +++ b/lib/pangea/utils/chat_list_handle_space_tap.dart @@ -65,6 +65,9 @@ void chatListHandleSpaceTap( context: context, future: () async { await space.join(); + if (space.isSpace) { + await space.joinAnalyticsRoomsInSpace(); + } setActiveSpaceAndCloseChat(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 5c2f083d5..b2c61a354 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/widgets/chat/message_translation_card.dart'; import 'package:fluffychat/pangea/widgets/chat/message_unsubscribed_card.dart'; import 'package:fluffychat/pangea/widgets/chat/overlay_message.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; +import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -62,6 +63,10 @@ class ToolbarDisplayController { if (controller.selectMode) { controller.clearSelectedEvents(); } + if (!MatrixState.pangeaController.languageController.languagesSet) { + pLanguageDialog(context, () {}); + return; + } focusNode.requestFocus(); final LayerLinkAndKey layerLinkAndKey = @@ -345,8 +350,11 @@ class MessageToolbarState extends State { Row( mainAxisSize: MainAxisSize.min, children: MessageMode.values.map((mode) { - if ([MessageMode.definition, MessageMode.textToSpeech, MessageMode.translation] - .contains(mode) && + if ([ + MessageMode.definition, + MessageMode.textToSpeech, + MessageMode.translation, + ].contains(mode) && widget.pangeaMessageEvent.isAudioMessage) { return const SizedBox.shrink(); } From 61bcf059d3f909bd54e3cd0ae92ffc71513c1041 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 14 May 2024 11:14:04 -0400 Subject: [PATCH 02/48] removed unused function --- .../extensions/pangea_room_extension.dart | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index 1dec2bcbb..efdcbc3bd 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -1291,44 +1291,4 @@ extension PangeaRoom on Room { if (firstLanguageSettings?.targetLanguage == null) return; await client.getMyAnalyticsRoom(firstLanguageSettings!.targetLanguage); } - - // Check if teacher is in students' analytics rooms - // To warn teachers if some data might be missing because they have - // not yet joined a students' analytics room - // Future areAllStudentAnalyticsAvailable() async { - // if (!isSpace) { - // debugPrint("areAllStudentAnalyticsAvailable called on non-space room"); - // Sentry.addBreadcrumb( - // Breadcrumb( - // message: "areAllStudentAnalyticsAvailable called on non-space room", - // ), - // ); - // return false; - // } - - // final String? spaceLangCode = firstLanguageSettings?.targetLanguage; - // if (spaceLangCode == null) { - // debugPrint( - // "areAllStudentAnalyticsAvailable called on space without language settings", - // ); - // Sentry.addBreadcrumb( - // Breadcrumb( - // message: - // "areAllStudentAnalyticsAvailable called on space without language settings", - // ), - // ); - // return false; - // } - - // for (final User student in students) { - // final Room? studentAnalyticsRoom = client.analyticsRoomLocal( - // spaceLangCode, - // student.id, - // ); - // if (studentAnalyticsRoom == null) { - // return false; - // } - // } - // return true; - // } } From 536d3ddfd8d5c76268ab3e4c25c98592b9e2d6a2 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Tue, 14 May 2024 12:16:00 -0400 Subject: [PATCH 03/48] added helper functions to reduce duplications in functions to add analytics rooms to spaces and to join analytics rooms via spaces --- .../extensions/pangea_room_extension.dart | 160 +++++++----------- 1 file changed, 62 insertions(+), 98 deletions(-) diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index efdcbc3bd..bf152012a 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -1037,6 +1037,35 @@ extension PangeaRoom on Room { ); } + // add 1 analytics room to 1 space + Future addAnalyticsRoomToSpace(Room analyticsRoom) async { + if (!isSpace) { + debugPrint("addAnalyticsRoomToSpace called on non-space room"); + Sentry.addBreadcrumb( + Breadcrumb( + message: "addAnalyticsRoomToSpace called on non-space room", + ), + ); + return Future.value(); + } + + if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) return; + if (canIAddSpaceChild(null)) { + try { + await setSpaceChild(analyticsRoom.id); + } catch (err) { + debugPrint( + "Failed to add analytics room ${analyticsRoom.id} for student to space $id", + ); + Sentry.addBreadcrumb( + Breadcrumb( + message: "Failed to add analytics room to space $id", + ), + ); + } + } + } + // Add analytics room to all spaces the user is a student in (1 analytics room to all spaces) // 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 @@ -1054,65 +1083,52 @@ extension PangeaRoom on Room { for (final Room space in (await client.classesAndExchangesImStudyingIn)) { if (space.spaceChildren.any((sc) => sc.roomId == id)) continue; - if (space.canIAddSpaceChild(null)) { - try { - await space.setSpaceChild(id); - } catch (err) { - debugPrint( - "Failed to add analytics room for student ${client.userID} to space ${space.id}", - ); - Sentry.addBreadcrumb( - Breadcrumb( - message: "Failed to add analytics room to space ${space.id}", - ), - ); - } - } + await space.addAnalyticsRoomToSpace(this); } } // Add all analytics rooms to space // Similar to addAnalyticsRoomToSpaces, but all analytics room to 1 space Future addAnalyticsRoomsToSpace() async { - if (!isSpace) { - debugPrint("addAnalyticsRoomsToSpace called on non-space room"); - Sentry.addBreadcrumb( - Breadcrumb( - message: "addAnalyticsRoomsToSpace called on non-space room", - ), - ); - return; - } - await postLoad(); - if (!canIAddSpaceChild(null)) { + final List allMyAnalyticsRooms = client.allMyAnalyticsRooms; + for (final Room analyticsRoom in allMyAnalyticsRooms) { + await addAnalyticsRoomToSpace(analyticsRoom); + } + } + + // invite teachers of 1 space to 1 analytics room + Future inviteSpaceTeachersToAnalyticsRoom(Room analyticsRoom) async { + if (!isSpace) { debugPrint( - "addAnalyticsRoomsToSpace called on space without add permission", + "inviteSpaceTeachersToAnalyticsRoom called on non-space room", ); Sentry.addBreadcrumb( Breadcrumb( message: - "addAnalyticsRoomsToSpace called on space without add permission", + "inviteSpaceTeachersToAnalyticsRoom called on non-space room", ), ); return; } - - final List allMyAnalyticsRooms = client.allMyAnalyticsRooms; - for (final Room analyticsRoom in allMyAnalyticsRooms) { - // add analytics room to space if it hasn't already been added - if (spaceChildren.any((sc) => sc.roomId == analyticsRoom.id)) continue; - try { - await setSpaceChild(analyticsRoom.id); - } catch (err) { - debugPrint( - "Failed to add analytics room ${analyticsRoom.id} to space $id", - ); - Sentry.addBreadcrumb( - Breadcrumb( - message: "Failed to add analytics room to space $id", - ), - ); + if (!analyticsRoom.participantListComplete) { + await analyticsRoom.requestParticipants(); + } + final List participants = analyticsRoom.getParticipants(); + for (final User teacher in (await teachers)) { + if (!participants.any((p) => p.id == teacher.id)) { + try { + await analyticsRoom.invite(teacher.id); + } catch (err, s) { + debugPrint( + "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", + ); + ErrorHandler.logError( + e: err, + m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", + s: s, + ); + } } } } @@ -1141,67 +1157,15 @@ extension PangeaRoom on Room { return; } - // load all participants of analytics room - if (!participantListComplete) { - await requestParticipants(); - } - final List participants = getParticipants(); - - // invite any teachers who are not already in the room - for (final teacher in (await client.myTeachers)) { - if (!participants.any((p) => p.id == teacher.id)) { - try { - await invite(teacher.id); - } catch (err, s) { - debugPrint( - "Failed to invite teacher ${teacher.id} to analytics room $id", - ); - ErrorHandler.logError( - e: err, - m: "Failed to invite teacher ${teacher.id} to analytics room $id", - s: s, - ); - } - } + for (final Room space in (await client.classesAndExchangesImStudyingIn)) { + await space.inviteSpaceTeachersToAnalyticsRoom(this); } } // Invite teachers of 1 space to all users' analytics rooms Future inviteSpaceTeachersToAnalyticsRooms() async { - if (!isSpace) { - debugPrint( - "inviteSpaceTeachersToAllAnalyticsRoom called on non-space room", - ); - Sentry.addBreadcrumb( - Breadcrumb( - message: - "inviteSpaceTeachersToAllAnalyticsRoom called on non-space room", - ), - ); - return; - } - for (final Room analyticsRoom in client.allMyAnalyticsRooms) { - if (!analyticsRoom.participantListComplete) { - await analyticsRoom.requestParticipants(); - } - final List participants = analyticsRoom.getParticipants(); - for (final User teacher in (await teachers)) { - if (!participants.any((p) => p.id == teacher.id)) { - try { - await analyticsRoom.invite(teacher.id); - } catch (err, s) { - debugPrint( - "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", - ); - ErrorHandler.logError( - e: err, - m: "Failed to invite teacher ${teacher.id} to analytics room ${analyticsRoom.id}", - s: s, - ); - } - } - } + await inviteSpaceTeachersToAnalyticsRoom(analyticsRoom); } } From 326d2b5fbd494cf4aa1a26d5c307a8080230378d Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Wed, 15 May 2024 10:57:34 -0400 Subject: [PATCH 04/48] adding words per minute to speech to text feedback --- assets/l10n/intl_en.arb | 4 + lib/config/app_config.dart | 2 + .../pangea_message_event.dart | 14 +- lib/pangea/models/speech_to_text_models.dart | 23 ++- .../chat/message_speech_to_text_card.dart | 18 ++- needed-translations.txt | 144 ++++++++++++------ 6 files changed, 137 insertions(+), 68 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 41eeb498d..643ee9a98 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3945,5 +3945,9 @@ "accuracy": "Accuracy", "points": "Points", "noPaymentInfo": "No payment info necessary!", +<<<<<<< Updated upstream "updatePhoneOS": "You may need to update your device's OS version." +======= + "wordsPerMinute": "Words per minute" +>>>>>>> Stashed changes } \ No newline at end of file diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index 2b3e74f96..a21a2caad 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -29,6 +29,8 @@ abstract class AppConfig { static const Color primaryColorLight = Color(0xFFDBC9FF); static const Color secondaryColor = Color(0xFF41a2bc); static const Color activeToggleColor = Color(0xFF33D057); + static const Color success = Color(0xFF33D057); + static const Color warning = Color.fromARGB(255, 210, 124, 12); // static String _privacyUrl = // 'https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md'; static String _privacyUrl = "https://www.pangeachat.com/privacy"; diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 3f2f23616..bea286ba8 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -296,14 +296,14 @@ class PangeaMessageEvent { return null; } - final SpeechToTextModel? speechToTextLocal = representations - .firstWhereOrNull( - (element) => element.content.speechToText != null, - ) - ?.content - .speechToText; + // final SpeechToTextModel? speechToTextLocal = representations + // .firstWhereOrNull( + // (element) => element.content.speechToText != null, + // ) + // ?.content + // .speechToText; - if (speechToTextLocal != null) return speechToTextLocal; + // if (speechToTextLocal != null) return speechToTextLocal; final matrixFile = await _event.downloadAndDecryptAttachment(); // Pangea# diff --git a/lib/pangea/models/speech_to_text_models.dart b/lib/pangea/models/speech_to_text_models.dart index ad5fd96dd..005d2371f 100644 --- a/lib/pangea/models/speech_to_text_models.dart +++ b/lib/pangea/models/speech_to_text_models.dart @@ -1,11 +1,14 @@ import 'dart:convert'; +import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/enum/audio_encoding_enum.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; +const int THRESHOLD_FOR_GREEN = 80; + class SpeechToTextAudioConfigModel { final AudioEncodingEnum encoding; final int sampleRateHertz; @@ -93,13 +96,10 @@ class STTToken { ? Colors.white : Colors.black); } - if (confidence! > 80) { - return const Color.fromARGB(255, 0, 152, 0); + if (confidence! > THRESHOLD_FOR_GREEN) { + return AppConfig.success; } - if (confidence! > 50) { - return const Color.fromARGB(255, 184, 142, 43); - } - return Colors.red; + return AppConfig.warning; } factory STTToken.fromJson(Map json) { @@ -148,6 +148,7 @@ class STTToken { class Transcript { final String text; final int confidence; + final double? wordsPerMinute; final List sttTokens; final String langCode; @@ -156,6 +157,7 @@ class Transcript { required this.confidence, required this.sttTokens, required this.langCode, + required this.wordsPerMinute, }); factory Transcript.fromJson(Map json) => Transcript( @@ -167,6 +169,7 @@ class Transcript { .map((e) => STTToken.fromJson(e)) .toList(), langCode: json['lang_code'], + wordsPerMinute: json['words_per_minute'], ); Map toJson() => { @@ -174,7 +177,15 @@ class Transcript { "confidence": confidence, "stt_tokens": sttTokens.map((e) => e.toJson()).toList(), "lang_code": langCode, + "words_per_minute": wordsPerMinute, }; + + Color color(BuildContext context) { + if (confidence > THRESHOLD_FOR_GREEN) { + return AppConfig.success; + } + return AppConfig.warning; + } } class SpeechToTextResult { diff --git a/lib/pangea/widgets/chat/message_speech_to_text_card.dart b/lib/pangea/widgets/chat/message_speech_to_text_card.dart index c516d4447..6fc02ffe1 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -133,6 +133,9 @@ class MessageSpeechToTextCardState extends State { getSpeechToText(); } + String? get wordsPerMinuteString => + speechToTextResponse?.transcript.wordsPerMinute?.toString(); + @override Widget build(BuildContext context) { if (_fetchingTranscription) { @@ -158,11 +161,11 @@ class MessageSpeechToTextCardState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - IconNumberWidget( - icon: Icons.abc, - number: (selectedToken == null ? words : 1).toString(), - toolTip: L10n.of(context)!.words, - ), + // IconNumberWidget( + // icon: Icons.abc, + // number: (selectedToken == null ? words : 1).toString(), + // toolTip: L10n.of(context)!.words, + // ), IconNumberWidget( icon: Symbols.target, number: @@ -171,8 +174,9 @@ class MessageSpeechToTextCardState extends State { ), IconNumberWidget( icon: Icons.speed, - number: (selectedToken?.confidence ?? total).toString(), - toolTip: L10n.of(context)!.points, + number: + wordsPerMinuteString != null ? "$wordsPerMinuteString" : "??", + toolTip: L10n.of(context)!.wordsPerMinute, ), ], ), diff --git a/needed-translations.txt b/needed-translations.txt index c93f52d05..74f684f74 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -820,7 +820,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "be": [ @@ -2239,7 +2240,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "bn": [ @@ -3120,7 +3122,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "bo": [ @@ -4001,7 +4004,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ca": [ @@ -4882,7 +4886,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "cs": [ @@ -5763,7 +5768,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "de": [ @@ -6591,7 +6597,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "el": [ @@ -7472,7 +7479,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "eo": [ @@ -8353,7 +8361,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "es": [ @@ -8382,7 +8391,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "et": [ @@ -9206,7 +9216,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "eu": [ @@ -10030,7 +10041,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "fa": [ @@ -10911,7 +10923,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "fi": [ @@ -11792,7 +11805,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "fr": [ @@ -12673,7 +12687,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ga": [ @@ -13554,7 +13569,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "gl": [ @@ -14378,7 +14394,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "he": [ @@ -15259,7 +15276,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "hi": [ @@ -16140,7 +16158,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "hr": [ @@ -17008,7 +17027,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "hu": [ @@ -17889,7 +17909,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ia": [ @@ -19294,7 +19315,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "id": [ @@ -20175,7 +20197,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ie": [ @@ -21056,7 +21079,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "it": [ @@ -21922,7 +21946,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ja": [ @@ -22803,7 +22828,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ko": [ @@ -23684,7 +23710,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "lt": [ @@ -24565,7 +24592,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "lv": [ @@ -25446,7 +25474,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "nb": [ @@ -26327,7 +26356,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "nl": [ @@ -27208,7 +27238,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "pl": [ @@ -28089,7 +28120,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "pt": [ @@ -28970,7 +29002,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "pt_BR": [ @@ -29820,7 +29853,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "pt_PT": [ @@ -30701,7 +30735,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ro": [ @@ -31582,7 +31617,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ru": [ @@ -32406,7 +32442,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "sk": [ @@ -33287,7 +33324,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "sl": [ @@ -34168,7 +34206,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "sr": [ @@ -35049,7 +35088,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "sv": [ @@ -35895,7 +35935,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "ta": [ @@ -36776,7 +36817,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "th": [ @@ -37657,7 +37699,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "tr": [ @@ -38523,7 +38566,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "uk": [ @@ -39347,7 +39391,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "vi": [ @@ -40228,7 +40273,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "zh": [ @@ -41052,7 +41098,8 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ], "zh_Hant": [ @@ -41933,6 +41980,7 @@ "score", "accuracy", "points", - "noPaymentInfo" + "noPaymentInfo", + "wordsPerMinute" ] } From daaa83b00df2dd2bf57145da105ec14a1733f4a1 Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Wed, 15 May 2024 11:01:20 -0400 Subject: [PATCH 05/48] fixing two oops --- assets/l10n/intl_en.arb | 5 +---- .../pangea_message_event.dart | 14 +++++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 643ee9a98..2ea0bc307 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3945,9 +3945,6 @@ "accuracy": "Accuracy", "points": "Points", "noPaymentInfo": "No payment info necessary!", -<<<<<<< Updated upstream - "updatePhoneOS": "You may need to update your device's OS version." -======= + "updatePhoneOS": "You may need to update your device's OS version.", "wordsPerMinute": "Words per minute" ->>>>>>> Stashed changes } \ No newline at end of file diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index bea286ba8..3f2f23616 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -296,14 +296,14 @@ class PangeaMessageEvent { return null; } - // final SpeechToTextModel? speechToTextLocal = representations - // .firstWhereOrNull( - // (element) => element.content.speechToText != null, - // ) - // ?.content - // .speechToText; + final SpeechToTextModel? speechToTextLocal = representations + .firstWhereOrNull( + (element) => element.content.speechToText != null, + ) + ?.content + .speechToText; - // if (speechToTextLocal != null) return speechToTextLocal; + if (speechToTextLocal != null) return speechToTextLocal; final matrixFile = await _event.downloadAndDecryptAttachment(); // Pangea# From 8f7f9a8a18d3e05747e0d73ed882ffd4d5a9a896 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 15 May 2024 11:24:56 -0400 Subject: [PATCH 06/48] spanish translations --- assets/l10n/intl_es.arb | 51 +++++++++++++++++++++++++-- needed-translations.txt | 77 +++++++++++++++++++++++++---------------- 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index f9f596f16..795a83ef7 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4587,5 +4587,52 @@ "refresh": "Actualizar", "joinToView": "Únete a esta sala para ver los detalles", "autoPlayTitle": "Reproducción automática de mensajes", - "autoPlayDesc": "Cuando está activado, el audio de texto a voz de los mensajes se reproducirá automáticamente cuando se seleccione." -} \ No newline at end of file + "autoPlayDesc": "Cuando está activado, el audio de texto a voz de los mensajes se reproducirá automáticamente cuando se seleccione.", + "presenceStyle": "Presencia:", + "presencesToggle": "Mostrar mensajes de estado de otros usuarios", + "writeAMessageFlag": "Escribe un mensaje en {l1flag} o {l2flag}...", + "@writeAMessageFlag": { + "type": "text", + "placeholders": { + "l1flag": {}, + "l2flag": {} + } + }, + "youInvitedToBy": "📩 Has sido invitado a través de un enlace a:\n{alias}", + "@youInvitedToBy": { + "placeholders": { + "alias": {} + } + }, + "hidePresences": "¿Ocultar la lista de estados?", + "sendReadReceipts": "Enviar recibos de lectura", + "sendTypingNotificationsDescription": "Los demás participantes en un chat pueden ver cuándo estás escribiendo un nuevo mensaje.", + "sendReadReceiptsDescription": "Los demás participantes en un chat pueden ver cuándo has leído un mensaje.", + "formattedMessages": "Mensajes con formato", + "formattedMessagesDescription": "Mostrar contenido de mensajes enriquecido como texto en negrita utilizando markdown.", + "verifyOtherUser": "🔐 Verificar otro usuario", + "verifyOtherUserDescription": "Si verificas a otro usuario, puedes estar seguro de saber a quién estás escribiendo realmente. 💪\n\nCuando inicies una verificación, tú y el otro usuario veréis una ventana emergente en la aplicación. Allí veréis una serie de emojis o números que tendréis que comparar entre vosotros.\n\nLa mejor forma de hacerlo es quedar o iniciar una videollamada. 👭", + "verifyOtherDevice": "🔐 Verificar otro dispositivo", + "verifyOtherDeviceDescription": "Cuando verificas otro dispositivo, esos dispositivos pueden intercambiar claves, aumentando tu seguridad general. 💪 Cuando inicies una verificación, aparecerá una ventana emergente en la app de ambos dispositivos. Allí verás entonces una serie de emojis o números que tienes que comparar entre sí. Lo mejor es que tengas ambos dispositivos a mano antes de iniciar la verificación. 🤳", + "transparent": "Transparente", + "incomingMessages": "Mensajes entrantes", + "stickers": "Pegatinas", + "commandHint_ignore": "Ignorar el ID de matriz dado", + "commandHint_unignore": "Designorar el ID de matriz dado", + "unreadChatsInApp": "{appname}: {unread} chats no leídos", + "@unreadChatsInApp": { + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } + }, + "messageAnalytics": "Análisis de mensajes", + "words": "Palabras", + "score": "Puntuación", + "accuracy": "Precisión", + "points": "Puntos", + "noPaymentInfo": "No se necesitan datos de pago.", + "updatePhoneOS": "Puede que necesites actualizar la versión del sistema operativo de tu dispositivo.", + "wordsPerMinute": "Palabras por minuto" +} diff --git a/needed-translations.txt b/needed-translations.txt index 74f684f74..7cbcc8a05 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -821,6 +821,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -2241,6 +2242,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -3123,6 +3125,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -4005,6 +4008,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -4887,6 +4891,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -5769,6 +5774,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -6598,6 +6604,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -7480,6 +7487,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -8362,36 +8370,7 @@ "accuracy", "points", "noPaymentInfo", - "wordsPerMinute" - ], - - "es": [ - "presenceStyle", - "presencesToggle", - "writeAMessageFlag", - "youInvitedToBy", - "hidePresences", - "sendReadReceipts", - "sendTypingNotificationsDescription", - "sendReadReceiptsDescription", - "formattedMessages", - "formattedMessagesDescription", - "verifyOtherUser", - "verifyOtherUserDescription", - "verifyOtherDevice", - "verifyOtherDeviceDescription", - "transparent", - "incomingMessages", - "stickers", - "commandHint_ignore", - "commandHint_unignore", - "unreadChatsInApp", - "messageAnalytics", - "words", - "score", - "accuracy", - "points", - "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -9217,6 +9196,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -10042,6 +10022,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -10924,6 +10905,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -11806,6 +11788,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -12688,6 +12671,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -13570,6 +13554,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -14395,6 +14380,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -15277,6 +15263,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -16159,6 +16146,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -17028,6 +17016,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -17910,6 +17899,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -19316,6 +19306,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -20198,6 +20189,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -21080,6 +21072,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -21947,6 +21940,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -22829,6 +22823,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -23711,6 +23706,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -24593,6 +24589,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -25475,6 +25472,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -26357,6 +26355,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -27239,6 +27238,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -28121,6 +28121,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -29003,6 +29004,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -29854,6 +29856,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -30736,6 +30739,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -31618,6 +31622,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -32443,6 +32448,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -33325,6 +33331,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -34207,6 +34214,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -35089,6 +35097,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -35936,6 +35945,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -36818,6 +36828,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -37700,6 +37711,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -38567,6 +38579,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -39392,6 +39405,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -40274,6 +40288,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -41099,6 +41114,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ], @@ -41981,6 +41997,7 @@ "accuracy", "points", "noPaymentInfo", + "updatePhoneOS", "wordsPerMinute" ] } From 7131fd47a8b693cbac8a38014d2a26b84a930bdb Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 15 May 2024 13:43:21 -0400 Subject: [PATCH 07/48] fix for speech to text matrix event --- .../speech_to_text_controller.dart | 27 ++++++++++--------- lib/pangea/models/pangea_token_model.dart | 2 +- lib/pangea/models/speech_to_text_models.dart | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/pangea/controllers/speech_to_text_controller.dart b/lib/pangea/controllers/speech_to_text_controller.dart index 6b302b101..8b61da79e 100644 --- a/lib/pangea/controllers/speech_to_text_controller.dart +++ b/lib/pangea/controllers/speech_to_text_controller.dart @@ -74,18 +74,21 @@ class SpeechToTextController { } debugPrint('Saving transcript as matrix event'); - requestModel.audioEvent?.room.sendPangeaEvent( - content: PangeaRepresentation( - langCode: response.langCode, - text: response.transcript.text, - originalSent: false, - originalWritten: false, - speechToText: response, - ).toJson(), - parentEventId: requestModel.audioEvent!.eventId, - type: PangeaEventTypes.representation, - ); - debugPrint('Transcript saved as matrix event'); + requestModel.audioEvent?.room + .sendPangeaEvent( + content: PangeaRepresentation( + langCode: response.langCode, + text: response.transcript.text, + originalSent: false, + originalWritten: false, + speechToText: response, + ).toJson(), + parentEventId: requestModel.audioEvent!.eventId, + type: PangeaEventTypes.representation, + ) + .then( + (_) => debugPrint('Transcript saved as matrix event'), + ); return Future.value(null); } diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index 19eaba750..a671256ab 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -60,7 +60,7 @@ class PangeaToken { static const String _lemmaKey = ModelKey.lemma; Map toJson() => { - _textKey: text, + _textKey: text.toJson(), _hasInfoKey: hasInfo, _lemmaKey: lemmas.map((e) => e.toJson()).toList(), }; diff --git a/lib/pangea/models/speech_to_text_models.dart b/lib/pangea/models/speech_to_text_models.dart index 005d2371f..7d21fd718 100644 --- a/lib/pangea/models/speech_to_text_models.dart +++ b/lib/pangea/models/speech_to_text_models.dart @@ -117,7 +117,7 @@ class STTToken { } Map toJson() => { - "token": token, + "token": token.toJson(), "start_time": startTime?.inMilliseconds, "end_time": endTime?.inMilliseconds, "confidence": confidence, From 36c1eaaf47540c467bdeab04a5b05a0828bef42c Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 15 May 2024 13:50:35 -0400 Subject: [PATCH 08/48] Update intl_en.arb Change Settings/Learning Settings to Main Menu/My Learning Settings --- assets/l10n/intl_en.arb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 2ea0bc307..ec8261740 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2504,7 +2504,7 @@ "type": "text", "placeholders": {} }, - "interactiveTranslatorAllowedDesc": "Students can choose whether to use translation assistance in space group chats in Settings > Learning Settings.", + "interactiveTranslatorAllowedDesc": "Students can choose whether to use translation assistance in space group chats in Main Menu > My Learning Settings.", "@interactiveTranslatorAllowedDesc": { "type": "text", "placeholders": {} @@ -2984,9 +2984,9 @@ "errorDisableLanguageAssistanceClassDesc": "Translation assistance and grammar assistance are turned off for the space that this chat is in.", "itIsDisabled": "Interactive Translation is disabled", "igcIsDisabled": "Interactive Grammar Checking is disabled", - "goToLearningSettings": "Go to Learning Settings", + "goToLearningSettings": "Go to My Learning Settings", "error405Title": "Languages not set", - "error405Desc": "Please set your languages in Settings > Learning Settings", + "error405Desc": "Please set your languages in Main Menu > My Learning Settings.", "loginOrSignup": "Sign in with", "@loginOrSignup": { "type": "text", @@ -3049,7 +3049,7 @@ "type": "text", "placeholders": {} }, - "learningSettings": "Learning Settings", + "learningSettings": "My Learning Settings", "classNameRequired": "Please enter a space name", "@classNameRequired": { "type": "text", @@ -3947,4 +3947,4 @@ "noPaymentInfo": "No payment info necessary!", "updatePhoneOS": "You may need to update your device's OS version.", "wordsPerMinute": "Words per minute" -} \ No newline at end of file +} From cd0545058129e16e8e46971721b1cdd68f079a93 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 15 May 2024 13:52:55 -0400 Subject: [PATCH 09/48] Removed the Learning Settings button --- lib/pages/settings/settings_view.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 36766ea65..cc52f0ef0 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -181,14 +181,6 @@ class SettingsView extends StatelessWidget { trailing: const Icon(Icons.chevron_right_outlined), ), // #Pangea - ListTile( - leading: const Icon(Icons.account_circle_outlined), - title: Text(L10n.of(context)!.learningSettings), - onTap: () => context.go('/rooms/settings/learning'), - trailing: const Icon( - Icons.chevron_right_outlined, - ), - ), ListTile( leading: const Icon(Icons.account_circle_outlined), title: Text(L10n.of(context)!.subscriptionManagement), From bc47e84de26576ecff39e62f32e063fac11cbfdd Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 15 May 2024 13:58:57 -0400 Subject: [PATCH 10/48] uncommented error handling so warning message shows on card, instead of loading circle --- .../chat/message_speech_to_text_card.dart | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/pangea/widgets/chat/message_speech_to_text_card.dart b/lib/pangea/widgets/chat/message_speech_to_text_card.dart index 6fc02ffe1..bc2cf3327 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -1,9 +1,13 @@ +import 'dart:developer'; + import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/common/icon_number_widget.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -41,27 +45,27 @@ class MessageSpeechToTextCardState extends State { // look for transcription in message event // if not found, call API to transcribe audio Future getSpeechToText() async { - // try { - if (l1Code == null || l2Code == null) { - throw Exception('Language selection not found'); - } - speechToTextResponse ??= - await widget.messageEvent.getSpeechToText(l1Code!, l2Code!); + try { + if (l1Code == null || l2Code == null) { + throw Exception('Language selection not found'); + } + speechToTextResponse ??= + await widget.messageEvent.getSpeechToText(l1Code!, l2Code!); - debugPrint( - 'Speech to text transcript: ${speechToTextResponse?.transcript.text}', - ); - // } catch (e, s) { - // debugger(when: kDebugMode); - // error = e; - // ErrorHandler.logError( - // e: e, - // s: s, - // data: widget.messageEvent.event.content, - // ); - // } finally { - setState(() => _fetchingTranscription = false); - // } + debugPrint( + 'Speech to text transcript: ${speechToTextResponse?.transcript.text}', + ); + } catch (e, s) { + debugger(when: kDebugMode); + error = e; + ErrorHandler.logError( + e: e, + s: s, + data: widget.messageEvent.event.content, + ); + } finally { + setState(() => _fetchingTranscription = false); + } } TextSpan _buildTranscriptText(BuildContext context) { From 09e4feb6c22b42e45f3ce00103ecfde96413b6f3 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 15 May 2024 14:01:17 -0400 Subject: [PATCH 11/48] Added My Learning Settings button to main menu --- lib/pages/chat_list/client_chooser_button.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 896b6dc36..af21a177a 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -154,6 +154,18 @@ class ClientChooserButton extends StatelessWidget { ], ), ), + // #Pangea + PopupMenuItem( + value: SettingsAction.learning, + child: Row( + children: [ + const Icon(Icons.psychology_outlined), + const SizedBox(width: 18), + Expanded(child: Text(L10n.of(context)!.learningSettings)), + ], + ), + ), + // Pangea# PopupMenuItem( value: SettingsAction.settings, child: Row( @@ -382,6 +394,9 @@ class ClientChooserButton extends StatelessWidget { case SettingsAction.setStatus: controller.setStatus(); // #Pangea + case SettingsAction.learning: + context.go('/rooms/settings/learning'); + break; case SettingsAction.newClass: context.go('/rooms/newspace'); break; @@ -493,6 +508,7 @@ enum SettingsAction { settings, archive, // #Pangea + learning, joinWithClassCode, classAnalytics, myAnalytics, From b26d786d9505561e42bb22ea91df43ad4b905f7f Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 15 May 2024 14:18:08 -0400 Subject: [PATCH 12/48] fix for JSON datatype error --- lib/pangea/models/speech_to_text_models.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pangea/models/speech_to_text_models.dart b/lib/pangea/models/speech_to_text_models.dart index 7d21fd718..dd07f3fc6 100644 --- a/lib/pangea/models/speech_to_text_models.dart +++ b/lib/pangea/models/speech_to_text_models.dart @@ -148,7 +148,7 @@ class STTToken { class Transcript { final String text; final int confidence; - final double? wordsPerMinute; + final int wordsPerMinute; final List sttTokens; final String langCode; @@ -169,7 +169,7 @@ class Transcript { .map((e) => STTToken.fromJson(e)) .toList(), langCode: json['lang_code'], - wordsPerMinute: json['words_per_minute'], + wordsPerMinute: json['words_per_minute'].round(), ); Map toJson() => { From f15d79179d75c61df02aa2525c7c0c66b8a347a4 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 15 May 2024 15:03:35 -0400 Subject: [PATCH 13/48] usability test updates - don't stack space details, always suggest new chats in spaces --- assets/l10n/intl_en.arb | 2 +- lib/config/routes.dart | 25 ---- lib/pages/chat/chat.dart | 11 +- lib/pages/chat_details/chat_details_view.dart | 17 ++- lib/pages/chat_list/chat_list_view.dart | 7 +- lib/pages/chat_list/space_view.dart | 26 +++- lib/pages/new_group/new_group_view.dart | 2 +- lib/pages/new_space/new_space.dart | 4 +- lib/pages/new_space/new_space_view.dart | 2 +- .../class_settings/class_settings_page.dart | 96 -------------- .../class_settings/class_settings_view.dart | 85 ------------ .../class_settings_button.dart | 41 ------ lib/pangea/pages/new_class/new_class.dart | 122 ------------------ .../pages/new_class/new_class_view.dart | 88 ------------- lib/pangea/utils/add_to_space.dart | 15 +-- .../utils/chat_list_handle_space_tap.dart | 5 +- 16 files changed, 66 insertions(+), 482 deletions(-) delete mode 100644 lib/pangea/pages/class_settings/class_settings_page.dart delete mode 100644 lib/pangea/pages/class_settings/class_settings_view.dart delete mode 100644 lib/pangea/pages/class_settings/p_class_widgets/class_settings_button.dart delete mode 100644 lib/pangea/pages/new_class/new_class.dart delete mode 100644 lib/pangea/pages/new_class/new_class_view.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 41eeb498d..f15a81fa4 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3674,7 +3674,7 @@ "bestAnswerFeedback": "That's correct!", "definitionDefaultPrompt": "What does this word mean?", "practiceDefaultPrompt": "What is the best answer?", - "correctionDefaultPrompt": "What is the best correction?", + "correctionDefaultPrompt": "What is the best replacement?", "itStartDefaultPrompt": "Do you want help translating?", "languageLevelWarning": "Please select a class language level", "lockedChatWarning": "🔒 This chat has been locked", diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 8b69aca1e..8d4d4f181 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -26,7 +26,6 @@ import 'package:fluffychat/pages/settings_security/settings_security.dart'; import 'package:fluffychat/pages/settings_style/settings_style.dart'; import 'package:fluffychat/pangea/guard/p_vguard.dart'; import 'package:fluffychat/pangea/pages/analytics/student_analytics/student_analytics.dart'; -import 'package:fluffychat/pangea/pages/class_settings/class_settings_page.dart'; import 'package:fluffychat/pangea/pages/exchange/add_exchange_to_class.dart'; import 'package:fluffychat/pangea/pages/find_partner/find_partner.dart'; import 'package:fluffychat/pangea/pages/p_user_age/p_user_age.dart'; @@ -150,19 +149,6 @@ abstract class AppRoutes { : child, ), routes: [ - // #Pangea - GoRoute( - path: '/spaces/:roomid', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - ChatDetails( - roomId: state.pathParameters['roomid']!, - ), - ), - redirect: loggedOutRedirect, - ), - // Pangea# GoRoute( path: '/rooms', redirect: loggedOutRedirect, @@ -521,17 +507,6 @@ abstract class AppRoutes { ), redirect: loggedOutRedirect, ), - // #Pangea - GoRoute( - path: 'class_settings', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const ClassSettingsPage(), - ), - redirect: loggedOutRedirect, - ), - // Pangea# GoRoute( path: 'invite', pageBuilder: (context, state) => defaultPageBuilder( diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 40701e742..907f9c5bf 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -481,7 +481,16 @@ class ChatController extends State } // Do not send read markers when app is not in foreground - if (kIsWeb && !Matrix.of(context).webHasFocus) return; + // #Pangea + try { + // Pangea# + if (kIsWeb && !Matrix.of(context).webHasFocus) return; + // #Pangea + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); + return; + } + // Pangea# if (!kIsWeb && WidgetsBinding.instance.lifecycleState != AppLifecycleState.resumed) { return; diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 9dbdc640c..2d46df11d 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -61,11 +61,22 @@ class ChatDetailsView extends StatelessWidget { ); return Scaffold( appBar: AppBar( - leading: controller.widget.embeddedCloseButton ?? - const Center(child: BackButton()), + leading: + // #Pangea + !room.isSpace + ? + // Pangea# + controller.widget.embeddedCloseButton ?? + const Center(child: BackButton()) + // #Pangea + : BackButton( + onPressed: () => context.go("/rooms"), + ) + // Pangea# + , elevation: Theme.of(context).appBarTheme.elevation, actions: [ - // #Pangeas + // #Pangea //if (room.canonicalAlias.isNotEmpty) // IconButton( // tooltip: L10n.of(context)!.share, diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index dc8389db0..6ad9feef3 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -75,7 +75,12 @@ class ChatListView extends StatelessWidget { label: L10n.of(context)!.allChats, // Pangea# ), - if (controller.spaces.isNotEmpty) + if (controller.spaces.isNotEmpty + // #Pangea + && + !FluffyThemes.isColumnMode(context) + // Pangea# + ) // #Pangea // const NavigationDestination( // icon: Icon(Icons.workspaces_outlined), diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index e7d793e3d..fae04dcff 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; @@ -173,7 +174,7 @@ class _SpaceViewState extends State { if (spaceChild.roomId == widget.controller.activeSpaceId) { // #Pangea // context.go('/rooms/${spaceChild.roomId}'); - context.push('/spaces/${spaceChild.roomId}'); + context.go('/rooms/${spaceChild.roomId}/details'); // Pangea# } else { widget.controller.setActiveSpace(spaceChild.roomId); @@ -545,12 +546,25 @@ class _SpaceViewState extends State { titleSpacing: 0, title: ListTile( leading: BackButton( - onPressed: () => - widget.controller.setActiveSpace(parentSpace?.id), + // #Pangea + onPressed: () { + !FluffyThemes.isColumnMode(context) || + parentSpace?.id != null + ? widget.controller.setActiveSpace(parentSpace?.id) + : widget.controller.onDestinationSelected(0); + }, + // onPressed: () => + // widget.controller.setActiveSpace(parentSpace?.id), + // Pangea# ), title: Text( parentSpace == null - ? L10n.of(context)!.allSpaces + // #Pangea + // ? L10n.of(context)!.allSpaces + ? !FluffyThemes.isColumnMode(context) + ? L10n.of(context)!.allSpaces + : L10n.of(context)!.allChats + // Pangea# : parentSpace.getLocalizedDisplayname( MatrixLocals(L10n.of(context)!), ), @@ -721,8 +735,8 @@ class _SpaceViewState extends State { ), // #Pangea // onTap: () => _onJoinSpaceChild(spaceChild), - onTap: () => context.push( - '/spaces/${spaceChild.roomId}', + onTap: () => context.go( + '/rooms/${spaceChild.roomId}/details', ), // Pangea# ), diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index 822b89214..ac3d9dacc 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -91,7 +91,7 @@ class NewGroupView extends StatelessWidget { const Divider(height: 1), AddToSpaceToggles( key: controller.addToSpaceKey, - startOpen: false, + startOpen: true, activeSpaceId: controller.activeSpaceId, mode: AddToClassMode.chat, ), diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index be3e32dc5..a310769cc 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -216,7 +216,7 @@ class NewSpaceController extends State { ); MatrixState.pangeaController.classController .setActiveSpaceIdInChatListController(spaceId); - context.push('/spaces/$spaceId'); + context.go("/rooms/$spaceId/details"); return; } @@ -245,7 +245,7 @@ class NewSpaceController extends State { // context.pop(spaceId); MatrixState.pangeaController.classController .setActiveSpaceIdInChatListController(spaceId); - context.push('/spaces/$spaceId'); + context.go("/rooms/$spaceId/details"); // Pangea# } catch (e) { setState(() { diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index 71f887ef6..e550d0019 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -138,7 +138,7 @@ class NewSpaceView extends StatelessWidget { if (!controller.newClassMode) AddToSpaceToggles( key: controller.addToSpaceKey, - startOpen: false, + startOpen: true, mode: !controller.newClassMode ? AddToClassMode.exchange : AddToClassMode.chat, diff --git a/lib/pangea/pages/class_settings/class_settings_page.dart b/lib/pangea/pages/class_settings/class_settings_page.dart deleted file mode 100644 index d0c2d385d..000000000 --- a/lib/pangea/pages/class_settings/class_settings_page.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'dart:async'; -import 'dart:developer'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; -import 'package:fluffychat/widgets/layouts/empty_page.dart'; -import '../../../widgets/matrix.dart'; -import '../../utils/error_handler.dart'; -import '../../utils/set_class_name.dart'; -import '../../widgets/space/class_settings.dart'; -import 'class_settings_view.dart'; -import 'p_class_widgets/room_rules_editor.dart'; - -class ClassSettingsPage extends StatefulWidget { - const ClassSettingsPage({super.key}); - - @override - State createState() => ClassSettingsController(); -} - -class ClassSettingsController extends State { - PangeaController pangeaController = MatrixState.pangeaController; - - final GlobalKey rulesEditorKey = GlobalKey(); - final GlobalKey classSettingsKey = - GlobalKey(); - - Room? room; - - String? get roomId => GoRouterState.of(context).pathParameters['roomid']; - - Future handleSave(BuildContext context) async { - if (classSettingsKey.currentState!.sameLanguages) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.noIdenticalLanguages), - ), - ); - return; - } - if (rulesEditorKey.currentState != null) { - await rulesEditorKey.currentState?.setRoomRules(roomId!); - } else { - debugger(when: kDebugMode); - ErrorHandler.logError(m: "Null rules editor state"); - } - if (classSettingsKey.currentState != null) { - await classSettingsKey.currentState?.setClassSettings( - roomId!, - ); - } else { - debugger(when: kDebugMode); - ErrorHandler.logError(m: "Null class settings state"); - } - } - - void goback(BuildContext context) { - context.push("/spaces/$roomId"); - } - - String get className => - Matrix.of(context).client.getRoomById(roomId!)?.name ?? ''; - - @override - void initState() { - // TODO: implement initState - super.initState(); - - Future.delayed(Duration.zero, () { - room = Matrix.of(context).client.getRoomById(roomId!); - if (room == null) { - debugger(when: kDebugMode); - context.pop(); - } - setState(() {}); - }); - } - //PTODO - show loading widget - - void setDisplaynameAction() => setClassDisplayname(context, roomId); - - bool showEditNameIcon = false; - void hoverEditNameIcon(bool hovering) => - setState(() => showEditNameIcon = !showEditNameIcon); - - @override - Widget build(BuildContext context) => room != null - ? ClassSettingsPageView(controller: this) - : const EmptyPage(); -} diff --git a/lib/pangea/pages/class_settings/class_settings_view.dart b/lib/pangea/pages/class_settings/class_settings_view.dart deleted file mode 100644 index f6fa434e9..000000000 --- a/lib/pangea/pages/class_settings/class_settings_view.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:go_router/go_router.dart'; - -import 'package:fluffychat/pangea/pages/class_settings/class_settings_page.dart'; -import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/room_rules_editor.dart'; -import 'package:fluffychat/pangea/widgets/space/class_settings.dart'; -import '../../../widgets/layouts/max_width_body.dart'; - -class ClassSettingsPageView extends StatelessWidget { - final ClassSettingsController controller; - const ClassSettingsPageView({super.key, required this.controller}); - - @override - Widget build(BuildContext context) { - debugPrint("in class settings page with roomId ${controller.roomId}"); - // PTODO-Lala - make the page scrollable anywhere, not just in the area of the elements - // so like, the user should be able scroll using the mouse wheel from anywhere within this view - // currently, your cursor needs be horizontally within the tiles in order to scroll - return Scaffold( - appBar: AppBar( - leading: GoRouterState.of(context).path?.startsWith('/spaces/') ?? false - ? null - : IconButton( - icon: const Icon(Icons.close_outlined), - onPressed: () => controller.goback(context), - ), - centerTitle: true, - title: Text(L10n.of(context)!.classSettings), - ), - body: ListView( - children: [ - MaxWidthBody( - child: ListTile( - title: Center( - child: TextButton.icon( - onPressed: controller.setDisplaynameAction, - onHover: controller.hoverEditNameIcon, - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 25), - ), - label: Visibility( - visible: controller.showEditNameIcon, - child: Icon( - Icons.edit, - color: Theme.of(context).colorScheme.onBackground, - ), - ), - icon: Text( - controller.className, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - MaxWidthBody( - child: Column( - children: [ - ClassSettings( - roomId: controller.roomId, - startOpen: true, - ), - RoomRulesEditor(roomId: controller.roomId), - ], - ), - ), - ], - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () => showFutureLoadingDialog( - context: context, - future: () => controller.handleSave(context), - ), - label: Text(L10n.of(context)!.saveChanges), - icon: const Icon(Icons.save_outlined), - ), - ); - } -} diff --git a/lib/pangea/pages/class_settings/p_class_widgets/class_settings_button.dart b/lib/pangea/pages/class_settings/p_class_widgets/class_settings_button.dart deleted file mode 100644 index 3915d0ca0..000000000 --- a/lib/pangea/pages/class_settings/p_class_widgets/class_settings_button.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; - -class ClassSettingsButton extends StatelessWidget { - const ClassSettingsButton({super.key}); - - // final PangeaController _pangeaController = MatrixState.pangeaController; - - @override - Widget build(BuildContext context) { - // final roomId = GoRouterState.of(context).pathParameters['roomid']; - - final iconColor = Theme.of(context).textTheme.bodyLarge!.color; - return Column( - children: [ - ListTile( - // enabled: roomId != null && - // _pangeaController.classController - // .getClassModelBySpaceIdLocal(roomId) != - // null, - title: Text( - L10n.of(context)!.classSettings, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - subtitle: Text(L10n.of(context)!.classSettingsDesc), - leading: CircleAvatar( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - foregroundColor: iconColor, - child: const Icon(Icons.settings_outlined), - ), - onTap: () => context.go('/class_settings'), - ), - ], - ); - } -} diff --git a/lib/pangea/pages/new_class/new_class.dart b/lib/pangea/pages/new_class/new_class.dart deleted file mode 100644 index 55b7d083d..000000000 --- a/lib/pangea/pages/new_class/new_class.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'dart:developer'; - -import 'package:flutter/foundation.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:go_router/go_router.dart'; -import 'package:matrix/matrix.dart' as sdk; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pangea/pages/new_class/new_class_view.dart'; -import 'package:fluffychat/pangea/utils/class_code.dart'; -import 'package:fluffychat/pangea/utils/error_handler.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../../controllers/pangea_controller.dart'; -import '../../widgets/space/class_settings.dart'; -import '../class_settings/p_class_widgets/room_rules_editor.dart'; - -class NewClass extends StatefulWidget { - const NewClass({super.key}); - - @override - NewClassController createState() => NewClassController(); -} - -class NewClassController extends State { - TextEditingController controller = TextEditingController(); - - final PangeaController pangeaController = MatrixState.pangeaController; - final GlobalKey rulesEditorKey = GlobalKey(); - final GlobalKey classSettingsKey = - GlobalKey(); - - void submitAction([_]) async { - //TODO: validate that object is complete - final matrix = Matrix.of(context); - if (controller.text.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.classNameRequired), - ), - ); - return; - } - if (classSettingsKey.currentState == null) { - debugger(when: kDebugMode); - } - if (classSettingsKey.currentState!.sameLanguages) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(L10n.of(context)!.noIdenticalLanguages), - ), - ); - return; - } - - final roomID = await showFutureLoadingDialog( - context: context, - future: () async { - final String roomID = await matrix.client.createRoom( - //PTODO - investigate effects of changing visibility from public - preset: sdk.CreateRoomPreset.publicChat, - creationContent: { - 'type': RoomCreationTypes.mSpace, - }, - visibility: sdk.Visibility.public, - // roomAliasName: controller.text.isNotEmpty - // ? "${matrix.client.userID!.localpart}-${controller.text.trim().toLowerCase().replaceAll(' ', '_')}" - // : null, - roomAliasName: ClassCodeUtil.generateClassCode(), - name: controller.text.isNotEmpty ? controller.text : null, - ); - - if (rulesEditorKey.currentState != null) { - await rulesEditorKey.currentState!.setRoomRules(roomID); - } else { - debugger(when: kDebugMode); - ErrorHandler.logError(m: "Null rules editor state"); - } - if (classSettingsKey.currentState != null) { - await classSettingsKey.currentState!.setClassSettings( - roomID, - ); - } else { - debugger(when: kDebugMode); - ErrorHandler.logError(m: "Null class settings state"); - } - return roomID; - }, - onError: (e) { - debugger(when: kDebugMode); - return e; - }, - ); - - if (roomID.error == null && roomID.result is String) { - pangeaController.classController.setActiveSpaceIdInChatListController( - roomID.result!, - ); - context.push('/spaces/${roomID.result!}'); - } else { - debugger(when: kDebugMode); - ErrorHandler.logError(e: roomID.error, s: StackTrace.current); - } - } - - @override - void initState() { - // TODO: implement initState - super.initState(); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) => NewSpaceView(this); -} diff --git a/lib/pangea/pages/new_class/new_class_view.dart b/lib/pangea/pages/new_class/new_class_view.dart deleted file mode 100644 index 13796e2d8..000000000 --- a/lib/pangea/pages/new_class/new_class_view.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/constants/class_default_values.dart'; -import 'package:fluffychat/pangea/pages/new_class/new_class.dart'; -import 'package:fluffychat/widgets/layouts/max_width_body.dart'; -import '../../widgets/space/class_settings.dart'; -import '../class_settings/p_class_widgets/room_rules_editor.dart'; - -class NewSpaceView extends StatelessWidget { - // #Pangea - // final NewSpaceController controller; - final NewClassController controller; - // Pangea# - - const NewSpaceView(this.controller, {super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - // #Pangea - centerTitle: true, - // Pangea# - title: Text(L10n.of(context)!.createNewClass), - ), - body: MaxWidthBody( - // #Pangea - child: ListView( - // child: Column( - // mainAxisSize: MainAxisSize.min, - // #Pangea - children: [ - Padding( - padding: const EdgeInsets.all(12.0), - child: TextField( - // #Pangea - maxLength: ClassDefaultValues.maxClassName, - maxLengthEnforcement: MaxLengthEnforcement.enforced, - // #Pangea - controller: controller.controller, - autofocus: true, - autocorrect: false, - textInputAction: TextInputAction.go, - onSubmitted: controller.submitAction, - decoration: InputDecoration( - labelText: L10n.of(context)!.spaceName, - prefixIcon: const Icon(Icons.people_outlined), - hintText: L10n.of(context)!.enterASpacepName, - ), - ), - ), - // #Pangea - ClassSettings( - key: controller.classSettingsKey, - roomId: null, - startOpen: true, - ), - RoomRulesEditor( - key: controller.rulesEditorKey, - roomId: null, - ), - const SizedBox(height: 45), - // SwitchListTile.adaptive( - // title: Text(L10n.of(context)!.spaceIsPublic), - // value: controller.publicGroup, - // onChanged: controller.setPublicGroup, - // ), - // ListTile( - // trailing: const Padding( - // padding: EdgeInsets.symmetric(horizontal: 16.0), - // child: Icon(Icons.info_outlined), - // ), - // subtitle: Text(L10n.of(context)!.newSpaceDescription), - // ), - // #Pangea - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: controller.submitAction, - child: const Icon(Icons.arrow_forward_outlined), - ), - ); - } -} diff --git a/lib/pangea/utils/add_to_space.dart b/lib/pangea/utils/add_to_space.dart index 92458856c..f35b8f308 100644 --- a/lib/pangea/utils/add_to_space.dart +++ b/lib/pangea/utils/add_to_space.dart @@ -1,10 +1,8 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; bool canAddToSpace(Room space, PangeaController pangeaController) { final bool pangeaPermission = @@ -27,8 +25,9 @@ Future pangeaAddToSpace( Room space, List selectedRoomIds, BuildContext context, - PangeaController pangeaController, -) async { + PangeaController pangeaController, { + bool suggested = true, +}) async { if (!canAddToSpace(space, pangeaController)) { throw L10n.of(context)!.noAddToSpacePermissions; } @@ -37,6 +36,6 @@ Future pangeaAddToSpace( if (room != null && chatIsInSpace(room, space)) { throw L10n.of(context)!.alreadyInSpace; } - await space.setSpaceChild(roomId); + await space.setSpaceChild(roomId, suggested: suggested); } } diff --git a/lib/pangea/utils/chat_list_handle_space_tap.dart b/lib/pangea/utils/chat_list_handle_space_tap.dart index ef0865634..982800661 100644 --- a/lib/pangea/utils/chat_list_handle_space_tap.dart +++ b/lib/pangea/utils/chat_list_handle_space_tap.dart @@ -21,7 +21,7 @@ void chatListHandleSpaceTap( controller.setActiveSpace(space.id); if (FluffyThemes.isColumnMode(context)) { - context.push('/spaces/${space.id}'); + context.go('/rooms/${space.id}/details'); } else if (controller.activeChat != null && !space.isFirstOrSecondChild(controller.activeChat!)) { context.go("/rooms"); @@ -108,6 +108,9 @@ void chatListHandleSpaceTap( showAlertDialog(context); } break; + case Membership.leave: + autoJoin(space); + break; default: setActiveSpaceAndCloseChat(); ErrorHandler.logError( From 2d4e2892051a667e113b67a5c0d4b64e0ae07603 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 15 May 2024 15:07:01 -0400 Subject: [PATCH 14/48] updated spanish copy --- assets/l10n/intl_es.arb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 795a83ef7..59f5719b0 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4389,7 +4389,7 @@ "bestAnswerFeedback": "¡Correcto!", "definitionDefaultPrompt": "¿Qué significa esta palabra?", "practiceDefaultPrompt": "¿Cuál es la mejor respuesta?", - "correctionDefaultPrompt": "¿Cuál es la mejor corrección?", + "correctionDefaultPrompt": "¿Cuál es el mejor reemplazo?", "itStartDefaultPrompt": "¿Quiere ayuda para traducir?", "suggestTo": "Sugerir a {spaceName}", "suggestChatDesc": "Los chats sugeridos aparecerán en la lista de chats de {spaceName}.", @@ -4592,17 +4592,17 @@ "presencesToggle": "Mostrar mensajes de estado de otros usuarios", "writeAMessageFlag": "Escribe un mensaje en {l1flag} o {l2flag}...", "@writeAMessageFlag": { - "type": "text", - "placeholders": { - "l1flag": {}, - "l2flag": {} - } + "type": "text", + "placeholders": { + "l1flag": {}, + "l2flag": {} + } }, "youInvitedToBy": "📩 Has sido invitado a través de un enlace a:\n{alias}", "@youInvitedToBy": { - "placeholders": { - "alias": {} - } + "placeholders": { + "alias": {} + } }, "hidePresences": "¿Ocultar la lista de estados?", "sendReadReceipts": "Enviar recibos de lectura", @@ -4621,11 +4621,11 @@ "commandHint_unignore": "Designorar el ID de matriz dado", "unreadChatsInApp": "{appname}: {unread} chats no leídos", "@unreadChatsInApp": { - "type": "text", - "placeholders": { - "appname": {}, - "unread": {} - } + "type": "text", + "placeholders": { + "appname": {}, + "unread": {} + } }, "messageAnalytics": "Análisis de mensajes", "words": "Palabras", @@ -4635,4 +4635,4 @@ "noPaymentInfo": "No se necesitan datos de pago.", "updatePhoneOS": "Puede que necesites actualizar la versión del sistema operativo de tu dispositivo.", "wordsPerMinute": "Palabras por minuto" -} +} \ No newline at end of file From fa0639b5d63b1610996180d4dfd1ccdfd2609bdd Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Wed, 15 May 2024 15:17:30 -0400 Subject: [PATCH 15/48] converting actually to words per hour so its an int --- lib/pangea/models/speech_to_text_models.dart | 11 ++-- macos/Flutter/GeneratedPluginRegistrant.swift | 4 +- pubspec.lock | 52 +++++++++++-------- 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/lib/pangea/models/speech_to_text_models.dart b/lib/pangea/models/speech_to_text_models.dart index dd07f3fc6..3bcb4711c 100644 --- a/lib/pangea/models/speech_to_text_models.dart +++ b/lib/pangea/models/speech_to_text_models.dart @@ -148,18 +148,21 @@ class STTToken { class Transcript { final String text; final int confidence; - final int wordsPerMinute; final List sttTokens; final String langCode; + final int? wordsPerHr; Transcript({ required this.text, required this.confidence, required this.sttTokens, required this.langCode, - required this.wordsPerMinute, + required this.wordsPerHr, }); + /// Returns the number of words per minute rounded to one decimal place. + double? get wordsPerMinute => wordsPerHr != null ? wordsPerHr! / 60 : null; + factory Transcript.fromJson(Map json) => Transcript( text: json['transcript'], confidence: json['confidence'] <= 100 @@ -169,7 +172,7 @@ class Transcript { .map((e) => STTToken.fromJson(e)) .toList(), langCode: json['lang_code'], - wordsPerMinute: json['words_per_minute'].round(), + wordsPerHr: json['words_per_hr'], ); Map toJson() => { @@ -177,7 +180,7 @@ class Transcript { "confidence": confidence, "stt_tokens": sttTokens.map((e) => e.toJson()).toList(), "lang_code": langCode, - "words_per_minute": wordsPerMinute, + "words_per_hr": wordsPerHr, }; Color color(BuildContext context) { diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index ce6ae4707..09c588b29 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -28,7 +28,7 @@ import package_info_plus import pasteboard import path_provider_foundation import purchases_flutter -import record_macos +import record_darwin import sentry_flutter import share_plus import shared_preferences_foundation @@ -64,7 +64,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PurchasesFlutterPlugin.register(with: registry.registrar(forPlugin: "PurchasesFlutterPlugin")) - RecordMacosPlugin.register(with: registry.registrar(forPlugin: "RecordMacosPlugin")) + RecordPlugin.register(with: registry.registrar(forPlugin: "RecordPlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 54e1ac6a7..8932ea070 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1841,50 +1841,58 @@ packages: dependency: "direct main" description: name: record - sha256: f703397f5a60d9b2b655b3acc94ba079b2d9a67dc0725bdb90ef2fee2441ebf7 + sha256: "113b368168c49c78902ab37c2b354dea30a0aec5bdeca434073826b6ea73eca1" url: "https://pub.dev" source: hosted - version: "4.4.4" + version: "5.0.5" + record_android: + dependency: transitive + description: + name: record_android + sha256: "0df98e05873b22b443309e289bf1eb3b5b9a60e7779134334e2073eb0763a992" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + record_darwin: + dependency: transitive + description: + name: record_darwin + sha256: ee8cb1bb1712d7ce38140ecabe70e5c286c02f05296d66043bee865ace7eb1b9 + url: "https://pub.dev" + source: hosted + version: "1.0.1" record_linux: dependency: transitive description: name: record_linux - sha256: "348db92c4ec1b67b1b85d791381c8c99d7c6908de141e7c9edc20dad399b15ce" + sha256: "7d0e70cd51635128fe9d37d89bafd6011d7cbba9af8dc323079ae60f23546aef" url: "https://pub.dev" source: hosted - version: "0.4.1" - record_macos: - dependency: transitive - description: - name: record_macos - sha256: d1d0199d1395f05e218207e8cacd03eb9dc9e256ddfe2cfcbbb90e8edea06057 - url: "https://pub.dev" - source: hosted - version: "0.2.2" + version: "0.7.1" record_platform_interface: dependency: transitive description: name: record_platform_interface - sha256: "7a2d4ce7ac3752505157e416e4e0d666a54b1d5d8601701b7e7e5e30bec181b4" + sha256: "3a4b56e94ecd2a0b2b43eb1fa6f94c5b8484334f5d38ef43959c4bf97fb374cf" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "1.0.2" record_web: dependency: transitive description: name: record_web - sha256: "219ffb4ca59b4338117857db56d3ffadbde3169bcaf1136f5f4d4656f4a2372d" + sha256: "24847cdbcf999f7a5762170792f622ac844858766becd0f2370ec8ae22f7526e" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "1.0.5" record_windows: dependency: transitive description: name: record_windows - sha256: "42d545155a26b20d74f5107648dbb3382dbbc84dc3f1adc767040359e57a1345" + sha256: "39998b3ea7d8d28b04159d82220e6e5e32a7c357c6fb2794f5736beea272f6c3" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "1.0.2" remove_emoji: dependency: transitive description: @@ -1945,18 +1953,18 @@ packages: dependency: transitive description: name: sentry - sha256: e572d33a3ff1d69549f33ee828a8ff514047d43ca8eea4ab093d72461205aa3e + sha256: fd1fbfe860c05f5c52820ec4dbf2b6473789e83ead26cfc18bca4fe80bf3f008 url: "https://pub.dev" source: hosted - version: "7.20.1" + version: "8.2.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: ac8cf6bb849f3560353ae33672e17b2713809a4e8de0d3cf372e9e9c42013757 + sha256: c64f0aec5332bec87083b61514d1b6b29e435b9045d03ce1575861192b9a5680 url: "https://pub.dev" source: hosted - version: "7.20.1" + version: "8.2.0" share_plus: dependency: "direct main" description: From 25daaad305761d1b5fd939587382ccfb53536549 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Wed, 15 May 2024 16:49:47 -0400 Subject: [PATCH 16/48] Change default value of autoplay option to false --- lib/pangea/pages/settings_learning/settings_learning_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 125b2ac99..d878dcb42 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -65,7 +65,7 @@ class SettingsLearningView extends StatelessWidget { defaultValue: controller.pangeaController.pStoreService.read( PLocalKey.autoPlayMessages, ) ?? - true, + false, title: L10n.of(context)!.autoPlayTitle, subtitle: L10n.of(context)!.autoPlayDesc, pStoreKey: PLocalKey.autoPlayMessages, From 00c90ec9a0d575033522c0987a7775169a1ff71d Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 16 May 2024 09:24:20 -0400 Subject: [PATCH 17/48] Checks that message is not redacted before opening toolbar --- lib/pages/chat/events/message.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 373689043..472ef4eb4 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -167,9 +167,10 @@ class Message extends StatelessWidget { // #Pangea ToolbarDisplayController? toolbarController; if (event.type == EventTypes.Message && - event.messageType == MessageTypes.Text || - event.messageType == MessageTypes.Notice || - event.messageType == MessageTypes.Audio) { + !event.redacted && + (event.messageType == MessageTypes.Text || + event.messageType == MessageTypes.Notice || + event.messageType == MessageTypes.Audio)) { toolbarController = controller.getToolbarDisplayController( event.eventId, nextEvent: nextEvent, From 46d6f9f11564c29b76516791318f463474c03f54 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 16 May 2024 10:03:54 -0400 Subject: [PATCH 18/48] Updated My Learning Settings icon in chat settings menu --- lib/widgets/chat_settings_popup_menu.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index 6c1078167..f10f08ff7 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -55,7 +55,7 @@ class ChatSettingsPopupMenuState extends State { value: 'learning_settings', child: Row( children: [ - const Icon(Icons.settings), + const Icon(Icons.psychology_outlined), const SizedBox(width: 12), Text(L10n.of(context)!.learningSettings), ], From ed1a483184b8ad77e3612e26ef5a58fcafe2cdbc Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 16 May 2024 10:08:17 -0400 Subject: [PATCH 19/48] load accuracte number for num chats in space --- lib/pages/chat_list/space_view.dart | 175 ++++++++++++++++++++-------- 1 file changed, 129 insertions(+), 46 deletions(-) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index fae04dcff..856bed7d1 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -8,6 +8,7 @@ import 'package:fluffychat/pages/chat_list/chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/utils/on_chat_tap.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; +import 'package:fluffychat/pangea/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/extensions/sync_update_extension.dart'; import 'package:fluffychat/pangea/utils/archive_space.dart'; @@ -47,11 +48,23 @@ class _SpaceViewState extends State { // #Pangea StreamSubscription? _roomSubscription; bool refreshing = false; + + final String _chatCountsKey = 'chatCounts'; + Map get chatCounts => Map.from( + widget.controller.pangeaController.pStoreService.read( + _chatCountsKey, + local: true, + ) ?? + {}, + ); // Pangea# @override void initState() { loadHierarchy(); + // #Pangea + loadChatCounts(); + // Pangea# super.initState(); } @@ -74,9 +87,15 @@ class _SpaceViewState extends State { // Pangea# } - Future loadHierarchy([String? prevBatch]) async { + Future loadHierarchy([ + String? prevBatch, // #Pangea - if (widget.controller.activeSpaceId == null || loading) { + String? spaceId, + // Pangea# + ]) async { + // #Pangea + if ((widget.controller.activeSpaceId == null && spaceId == null) || + loading) { return GetSpaceHierarchyResponse( rooms: [], nextBatch: null, @@ -89,7 +108,10 @@ class _SpaceViewState extends State { }); // Pangea# - final activeSpaceId = widget.controller.activeSpaceId!; + // #Pangea + // final activeSpaceId = widget.controller.activeSpaceId!; + final activeSpaceId = (widget.controller.activeSpaceId ?? spaceId)!; + // Pangea# final client = Matrix.of(context).client; final activeSpace = client.getRoomById(activeSpaceId); @@ -122,6 +144,14 @@ class _SpaceViewState extends State { }); rethrow; } finally { + // #Pangea + if (activeSpace != null) { + await setChatCount( + activeSpace, + _lastResponse[activeSpaceId], + ); + } + // Pangea# setState(() { loading = false; }); @@ -387,6 +417,14 @@ class _SpaceViewState extends State { } // #Pangea + Future loadChatCounts() async { + for (final Room room in Matrix.of(context).client.rooms) { + if (room.isSpace && !chatCounts.containsKey(room.id)) { + await loadHierarchy(null, room.id); + } + } + } + Future refreshOnUpdate(SyncUpdate event) async { /* refresh on leave, invite, and space child update not join events, because there's already a listener on @@ -412,6 +450,90 @@ class _SpaceViewState extends State { } setState(() => refreshing = false); } + + bool includeSpaceChild(sc, matchingSpaceChildren) { + final bool isAnalyticsRoom = sc.roomType == PangeaRoomTypes.analytics; + final bool isMember = [Membership.join, Membership.invite] + .contains(Matrix.of(context).client.getRoomById(sc.roomId)?.membership); + final bool isSuggested = matchingSpaceChildren.any( + (matchingSpaceChild) => + matchingSpaceChild.roomId == sc.roomId && + matchingSpaceChild.suggested == true, + ); + return !isAnalyticsRoom && (isMember || isSuggested); + } + + List filterSpaceChildren( + Room space, + List spaceChildren, + ) { + final childIds = + spaceChildren.map((hierarchyMember) => hierarchyMember.roomId); + + final matchingSpaceChildren = space.spaceChildren + .where((spaceChild) => childIds.contains(spaceChild.roomId)) + .toList(); + + final filteredSpaceChildren = spaceChildren + .where( + (sc) => includeSpaceChild( + sc, + matchingSpaceChildren, + ), + ) + .toList(); + return filteredSpaceChildren; + } + + int sortSpaceChildren( + SpaceRoomsChunk a, + SpaceRoomsChunk b, + ) { + final bool aIsSpace = a.roomType == 'm.space'; + final bool bIsSpace = b.roomType == 'm.space'; + + if (aIsSpace && !bIsSpace) { + return -1; + } else if (!aIsSpace && bIsSpace) { + return 1; + } + return 0; + } + + Future setChatCount( + Room space, + GetSpaceHierarchyResponse? response, + ) async { + final Map updatedChatCounts = Map.from(chatCounts); + final List spaceChildren = response?.rooms ?? []; + final filteredChildren = filterSpaceChildren(space, spaceChildren) + .where((sc) => sc.roomId != space.id) + .toList(); + updatedChatCounts[space.id] = filteredChildren.length; + + await widget.controller.pangeaController.pStoreService.save( + _chatCountsKey, + updatedChatCounts, + local: true, + ); + } + + bool roomCountLoading(Room space) => + space.membership == Membership.join && !chatCounts.containsKey(space.id); + + Widget spaceSubtitle(Room space) { + if (roomCountLoading(space)) { + return const CircularProgressIndicator.adaptive(); + } + + return Text( + space.membership == Membership.join + ? L10n.of(context)!.numChats( + chatCounts[space.id].toString(), + ) + : L10n.of(context)!.youreInvited, + ); + } // Pangea# @override @@ -473,14 +595,8 @@ class _SpaceViewState extends State { // #Pangea subtitle: Row( children: [ - Text( - rootSpace.membership == Membership.join - ? L10n.of(context)!.numChats( - rootSpace.spaceChildren.length.toString(), - ) - : L10n.of(context)!.youreInvited, - ), - if (rootSpace.locked ?? false) + spaceSubtitle(rootSpace), + if (rootSpace.locked) const Padding( padding: EdgeInsets.only(left: 4.0), child: Icon( @@ -625,42 +741,9 @@ class _SpaceViewState extends State { final space = Matrix.of(context).client.getRoomById(activeSpaceId); if (space != null) { - final matchingSpaceChildren = space.spaceChildren - .where( - (spaceChild) => spaceChildren - .map((hierarchyMember) => hierarchyMember.roomId) - .contains(spaceChild.roomId), - ) - .toList(); - spaceChildren = spaceChildren - .where( - (spaceChild) => - matchingSpaceChildren.any( - (matchingSpaceChild) => - matchingSpaceChild.roomId == - spaceChild.roomId && - matchingSpaceChild.suggested == true, - ) || - [Membership.join, Membership.invite].contains( - Matrix.of(context) - .client - .getRoomById(spaceChild.roomId) - ?.membership, - ), - ) - .toList(); + spaceChildren = filterSpaceChildren(space, spaceChildren); } - spaceChildren.sort((a, b) { - final bool aIsSpace = a.roomType == 'm.space'; - final bool bIsSpace = b.roomType == 'm.space'; - - if (aIsSpace && !bIsSpace) { - return -1; - } else if (!aIsSpace && bIsSpace) { - return 1; - } - return 0; - }); + spaceChildren.sort(sortSpaceChildren); // Pangea# final canLoadMore = response.nextBatch != null; return SliverList( From d5fc7b5e7804f41e94f2d5ce4a54a6760426c440 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 16 May 2024 14:46:08 -0400 Subject: [PATCH 20/48] fix for error with msg events with multiple of the same type of pangea match only showing the first instance --- .../pangea_message_event.dart | 17 +++-- .../class_analytics/class_analytics_view.dart | 8 ++- .../pages/analytics/construct_list.dart | 66 ++++++++++++++++--- 3 files changed, 75 insertions(+), 16 deletions(-) diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index 3f2f23616..cb7cba52a 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -564,17 +564,20 @@ class PangeaMessageEvent { return langCode ?? LanguageKeys.unknownLanguage; } - PangeaMatch? firstErrorStep(String lemma) { + List? errorSteps(String lemma) { final RepresentationEvent? repEvent = originalSent ?? originalWritten; if (repEvent?.choreo == null) return null; - final PangeaMatch? step = repEvent!.choreo!.choreoSteps - .firstWhereOrNull( - (element) => - element.acceptedOrIgnoredMatch?.match.shortMessage == lemma, + final List steps = repEvent!.choreo!.choreoSteps + .where( + (choreoStep) => + choreoStep.acceptedOrIgnoredMatch != null && + choreoStep.acceptedOrIgnoredMatch?.match.shortMessage == lemma, ) - ?.acceptedOrIgnoredMatch; - return step; + .map((element) => element.acceptedOrIgnoredMatch) + .cast() + .toList(); + return steps; } // List get activities => diff --git a/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart b/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart index dfb44e106..88c15bdf5 100644 --- a/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart +++ b/lib/pangea/pages/analytics/class_analytics/class_analytics_view.dart @@ -1,3 +1,4 @@ +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -20,7 +21,12 @@ class ClassAnalyticsView extends StatelessWidget { .map( (room) => TabItem( avatar: room.avatarUrl, - displayName: room.name ?? "", + displayName: room.name ?? + Matrix.of(context) + .client + .getRoomById(room.roomId) + ?.getLocalizedDisplayname() ?? + "", id: room.roomId, ), ) diff --git a/lib/pangea/pages/analytics/construct_list.dart b/lib/pangea/pages/analytics/construct_list.dart index 838f766a3..ffcf0e79a 100644 --- a/lib/pangea/pages/analytics/construct_list.dart +++ b/lib/pangea/pages/analytics/construct_list.dart @@ -248,6 +248,38 @@ class ConstructListViewState extends State { (element) => element.content.lemma == widget.controller.currentLemma, ); + // given the current lemma and list of message events, return a list of + // MessageEventMatch objects, which contain one PangeaMessageEvent to one PangeaMatch + // this is because some message events may have has more than one PangeaMatch of a + // given lemma type. + List getMessageEventMatches() { + if (widget.controller.currentLemma == null) return []; + final List allMsgErrorSteps = []; + + for (final msgEvent in _msgEvents) { + if (allMsgErrorSteps.any( + (element) => element.msgEvent.eventId == msgEvent.eventId, + )) { + continue; + } + // get all the pangea matches in that message which have that lemma + final List? msgErrorSteps = msgEvent.errorSteps( + widget.controller.currentLemma!, + ); + if (msgErrorSteps == null) continue; + + allMsgErrorSteps.addAll( + msgErrorSteps.map( + (errorStep) => MessageEventMatch( + msgEvent: msgEvent, + lemmaMatch: errorStep, + ), + ), + ); + } + return allMsgErrorSteps; + } + @override Widget build(BuildContext context) { if (!widget.init || fetchingUses) { @@ -262,6 +294,8 @@ class ConstructListViewState extends State { ); } + final msgEventMatches = getMessageEventMatches(); + return widget.controller.currentLemma == null ? Expanded( child: ListView.builder( @@ -299,11 +333,12 @@ class ConstructListViewState extends State { child: ListView.separated( separatorBuilder: (context, index) => const Divider(height: 1), - itemCount: _msgEvents.length, + itemCount: msgEventMatches.length, itemBuilder: (context, index) { return ConstructMessage( - msgEvent: _msgEvents[index], + msgEvent: msgEventMatches[index].msgEvent, lemma: widget.controller.currentLemma!, + errorMessage: msgEventMatches[index].lemmaMatch, ); }, ), @@ -316,21 +351,18 @@ class ConstructListViewState extends State { class ConstructMessage extends StatelessWidget { final PangeaMessageEvent msgEvent; + final PangeaMatch errorMessage; final String lemma; const ConstructMessage({ super.key, required this.msgEvent, + required this.errorMessage, required this.lemma, }); @override Widget build(BuildContext context) { - final PangeaMatch? errorMessage = msgEvent.firstErrorStep(lemma); - if (errorMessage == null) { - return const SizedBox.shrink(); - } - final String? chosen = errorMessage.match.choices ?.firstWhereOrNull( (element) => element.selected == true, @@ -488,6 +520,14 @@ class ConstructMessageMetadata extends StatelessWidget { @override Widget build(BuildContext context) { + final String roomName = msgEvent.event.room.name.isEmpty + ? Matrix.of(context) + .client + .getRoomById(msgEvent.event.room.id) + ?.getLocalizedDisplayname() ?? + "" + : msgEvent.event.room.name; + return Padding( padding: const EdgeInsets.fromLTRB(10, 0, 30, 0), child: Column( @@ -496,9 +536,19 @@ class ConstructMessageMetadata extends StatelessWidget { msgEvent.event.originServerTs.localizedTime(context), style: TextStyle(fontSize: 13 * AppConfig.fontSizeFactor), ), - Text(msgEvent.event.room.name), + Text(roomName), ], ), ); } } + +class MessageEventMatch { + final PangeaMessageEvent msgEvent; + final PangeaMatch lemmaMatch; + + MessageEventMatch({ + required this.msgEvent, + required this.lemmaMatch, + }); +} From 0b0d40c13ef3a39f02c8bc25e3844b3f549deefb Mon Sep 17 00:00:00 2001 From: Kelrap Date: Thu, 16 May 2024 16:24:09 -0400 Subject: [PATCH 21/48] Fixed bug where redacted messages sometimes showed in chat subtitle --- lib/pangea/utils/get_chat_list_item_subtitle.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pangea/utils/get_chat_list_item_subtitle.dart b/lib/pangea/utils/get_chat_list_item_subtitle.dart index 6d41a9605..2fee578bf 100644 --- a/lib/pangea/utils/get_chat_list_item_subtitle.dart +++ b/lib/pangea/utils/get_chat_list_item_subtitle.dart @@ -43,6 +43,7 @@ class GetChatListItemSubtitle { } if (!pangeaController.languageController.languagesSet || + event.redacted || event.type != EventTypes.Message || event.messageType != MessageTypes.Text || !pangeaController.permissionsController From 19809a6f2deff8ab206b0d4f40b36616b099ab20 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 17 May 2024 12:10:36 -0400 Subject: [PATCH 22/48] When a user creates a new group from inside a class, the space ID will be stored via query --- lib/config/routes.dart | 30 +++++++++++++++---------- lib/pages/chat_list/start_chat_fab.dart | 6 +++-- lib/pages/new_group/new_group.dart | 9 +++++++- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 8d4d4f181..e6ab37967 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -232,20 +232,26 @@ abstract class AppRoutes { pageBuilder: (context, state) => defaultPageBuilder( context, state, - const NewGroup(), + NewGroup( + // #Pangea + spaceId: state.uri.queryParameters['spaceId'], + // Pangea# + ), ), redirect: loggedOutRedirect, - routes: [ - GoRoute( - path: ':spaceid', - pageBuilder: (context, state) => defaultPageBuilder( - context, - state, - const NewGroup(), - ), - redirect: loggedOutRedirect, - ), - ], + // #Pangea + // routes: [ + // GoRoute( + // path: ':spaceid', + // pageBuilder: (context, state) => defaultPageBuilder( + // context, + // state, + // const NewGroup(), + // ), + // redirect: loggedOutRedirect, + // ), + // ], + // Pangea# ), GoRoute( path: 'newspace', diff --git a/lib/pages/chat_list/start_chat_fab.dart b/lib/pages/chat_list/start_chat_fab.dart index 2bea8537e..c9175b8d6 100644 --- a/lib/pages/chat_list/start_chat_fab.dart +++ b/lib/pages/chat_list/start_chat_fab.dart @@ -30,7 +30,8 @@ class StartChatFloatingActionButton extends StatelessWidget { void _onPressed(BuildContext context) async { //#Pangea if (controller.activeSpaceId != null) { - context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}'); + // context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}'); + context.go('/rooms/newgroup?spaceId=${controller.activeSpaceId ?? ''}'); return; } //Pangea# @@ -44,7 +45,8 @@ class StartChatFloatingActionButton extends StatelessWidget { case ActiveFilter.groups: // #Pangea // context.go('/rooms/newgroup'); - context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}'); + // context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}'); + context.go('/rooms/newgroup?spaceId=${controller.activeSpaceId ?? ''}'); // Pangea# break; case ActiveFilter.spaces: diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 16d4e2cdb..90c658a45 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -17,7 +17,14 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart' as sdk; class NewGroup extends StatefulWidget { - const NewGroup({super.key}); + // #Pangea + final String? spaceId; + + const NewGroup({ + super.key, + this.spaceId, + }); + // Pangea# @override NewGroupController createState() => NewGroupController(); From f27c5981e86e5c53a5430322f959d2e93023d8c4 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 17 May 2024 14:06:22 -0400 Subject: [PATCH 23/48] Checks the user is member of room and admin before showing option to remove room from space --- lib/pages/chat_list/space_view.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 856bed7d1..348550c65 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -237,6 +237,10 @@ class _SpaceViewState extends State { icon: Icons.send_outlined, ), if (spaceChild != null && + // #Pangea + room != null && + room.ownPowerLevel >= ClassDefaultValues.powerLevelOfAdmin && + // Pangea# (activeSpace?.canChangeStateEvent(EventTypes.spaceChild) ?? false)) SheetAction( key: SpaceChildContextAction.removeFromSpace, From c74b2c290a7ec6318f76a404e85075a813b20755 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 17 May 2024 14:35:21 -0400 Subject: [PATCH 24/48] Non-text messages cannot be edited --- lib/pages/chat/chat.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 0b9078ebb..460de198c 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1078,6 +1078,9 @@ class ChatController extends State bool get canEditSelectedEvents { if (isArchived || selectedEvents.length != 1 || + // #Pangea + selectedEvents.single.messageType != MessageTypes.Text || + // Pangea# !selectedEvents.first.status.isSent) { return false; } From c03ca24efe8ebfd71dfd84e7446bd6b600c8bf4e Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Fri, 17 May 2024 15:42:37 -0400 Subject: [PATCH 25/48] limit new group name length to 32 --- lib/pages/new_group/new_group_view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/new_group/new_group_view.dart b/lib/pages/new_group/new_group_view.dart index ac3d9dacc..523f3002e 100644 --- a/lib/pages/new_group/new_group_view.dart +++ b/lib/pages/new_group/new_group_view.dart @@ -57,6 +57,9 @@ class NewGroupView extends StatelessWidget { const SizedBox(width: 16), Expanded( child: TextField( + // #Pangea + maxLength: 32, + // Pangea# controller: controller.nameController, autocorrect: false, readOnly: controller.loading, From f84e92d9d09c6c30613634faa6d20460e9882de7 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Fri, 17 May 2024 15:43:36 -0400 Subject: [PATCH 26/48] limit new space name length to 32 --- lib/pages/new_space/new_space_view.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/new_space/new_space_view.dart b/lib/pages/new_space/new_space_view.dart index e550d0019..23f81b7ab 100644 --- a/lib/pages/new_space/new_space_view.dart +++ b/lib/pages/new_space/new_space_view.dart @@ -95,6 +95,9 @@ class NewSpaceView extends StatelessWidget { const SizedBox(width: 16), Expanded( child: TextField( + // #Pangea + maxLength: 32, + // Pangea# controller: controller.nameController, autocorrect: false, readOnly: controller.loading, From 09f26fb91faa3f9f3cfe7ab1abf8d69e47a28042 Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Fri, 17 May 2024 15:45:14 -0400 Subject: [PATCH 27/48] Limit display name length to 32 --- lib/pages/settings/settings.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 988ec3ff4..0e32fe478 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -42,6 +42,9 @@ class SettingsController extends State { cancelLabel: L10n.of(context)!.cancel, textFields: [ DialogTextField( + // #Pangea + maxLength: 32, + // Pangea# initialText: profile?.displayName ?? Matrix.of(context).client.userID!.localpart, ), From d247abb320c0a425e9098ff3139652620c8674cc Mon Sep 17 00:00:00 2001 From: Kelrap <99418823+Kelrap@users.noreply.github.com> Date: Fri, 17 May 2024 15:46:27 -0400 Subject: [PATCH 28/48] Limited edited class name length to 32 --- lib/pangea/utils/set_class_name.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/pangea/utils/set_class_name.dart b/lib/pangea/utils/set_class_name.dart index 92909034a..1fd2ab66d 100644 --- a/lib/pangea/utils/set_class_name.dart +++ b/lib/pangea/utils/set_class_name.dart @@ -27,6 +27,7 @@ void setClassDisplayname(BuildContext context, String? roomId) async { : L10n.of(context)!.changeTheNameOfTheChat, ), content: TextField( + maxLength: 32, controller: textFieldController, ), actions: [ From b2166906c37be52a963005345ca79b96a2d11041 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Fri, 17 May 2024 16:26:04 -0400 Subject: [PATCH 29/48] Use query parameter to autoselect class --- lib/pages/new_group/new_group.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/new_group/new_group.dart b/lib/pages/new_group/new_group.dart index 90c658a45..2ada80dfa 100644 --- a/lib/pages/new_group/new_group.dart +++ b/lib/pages/new_group/new_group.dart @@ -57,7 +57,7 @@ class NewGroupController extends State { void setVocab(List vocab) => setState(() => chatTopic.vocab = vocab); String? get activeSpaceId => - GoRouterState.of(context).pathParameters['spaceid']; + GoRouterState.of(context).uri.queryParameters['spaceId']; // Pangea# void setPublicGroup(bool b) => setState(() => publicGroup = b); From c88970d8598c4a686bbba6646e314a835f9da9b9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Fri, 17 May 2024 16:37:39 -0400 Subject: [PATCH 30/48] removed unused comments --- lib/pages/chat_list/start_chat_fab.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pages/chat_list/start_chat_fab.dart b/lib/pages/chat_list/start_chat_fab.dart index c9175b8d6..117833ff0 100644 --- a/lib/pages/chat_list/start_chat_fab.dart +++ b/lib/pages/chat_list/start_chat_fab.dart @@ -30,7 +30,6 @@ class StartChatFloatingActionButton extends StatelessWidget { void _onPressed(BuildContext context) async { //#Pangea if (controller.activeSpaceId != null) { - // context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}'); context.go('/rooms/newgroup?spaceId=${controller.activeSpaceId ?? ''}'); return; } @@ -45,7 +44,6 @@ class StartChatFloatingActionButton extends StatelessWidget { case ActiveFilter.groups: // #Pangea // context.go('/rooms/newgroup'); - // context.go('/rooms/newgroup/${controller.activeSpaceId ?? ''}'); context.go('/rooms/newgroup?spaceId=${controller.activeSpaceId ?? ''}'); // Pangea# break; From f797cdbb4afadb211a0b83efe1420ba2eb314831 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 20 May 2024 12:34:36 -0400 Subject: [PATCH 31/48] ensure speech_to_text endpoint is only called once, round words for per minute string, always auto-speech-to-text for audio message toolbar --- lib/pangea/enum/message_mode_enum.dart | 14 +++++++++++ .../pangea_message_event.dart | 13 ++++++++++ .../pangea_representation_event.dart | 5 ---- .../chat/message_speech_to_text_card.dart | 2 +- lib/pangea/widgets/chat/message_toolbar.dart | 25 +++++++++++++++---- 5 files changed, 48 insertions(+), 11 deletions(-) diff --git a/lib/pangea/enum/message_mode_enum.dart b/lib/pangea/enum/message_mode_enum.dart index c64861dc0..25948d23b 100644 --- a/lib/pangea/enum/message_mode_enum.dart +++ b/lib/pangea/enum/message_mode_enum.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:material_symbols_icons/symbols.dart'; +import 'package:matrix/matrix.dart'; enum MessageMode { translation, definition, speechToText, textToSpeech } @@ -52,4 +53,17 @@ extension MessageModeExtension on MessageMode { .oopsSomethingWentWrong; // Title to indicate an error or unsupported mode } } + + bool isValidMode(Event event) { + switch (this) { + case MessageMode.translation: + case MessageMode.textToSpeech: + case MessageMode.definition: + return event.messageType == MessageTypes.Text; + case MessageMode.speechToText: + return event.messageType == MessageTypes.Audio; + default: + return true; + } + } } diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index cb7cba52a..db5c7902a 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -346,6 +346,19 @@ class PangeaMessageEvent { ), ); + _representations?.add( + RepresentationEvent( + timeline: timeline, + content: PangeaRepresentation( + langCode: response.langCode, + text: response.transcript.text, + originalSent: false, + originalWritten: false, + speechToText: response, + ), + ), + ); + return response; } diff --git a/lib/pangea/matrix_event_wrappers/pangea_representation_event.dart b/lib/pangea/matrix_event_wrappers/pangea_representation_event.dart index 0796f7f5d..e774dc4f2 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_representation_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_representation_event.dart @@ -3,7 +3,6 @@ import 'dart:developer'; import 'package:fluffychat/pangea/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_choreo_event.dart'; import 'package:fluffychat/pangea/models/pangea_token_model.dart'; -import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; import 'package:fluffychat/pangea/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/repo/tokens_repo.dart'; import 'package:flutter/foundation.dart'; @@ -27,15 +26,12 @@ class RepresentationEvent { ChoreoRecord? _choreo; Timeline timeline; - SpeechToTextModel? _speechToTextResponse; - RepresentationEvent({ required this.timeline, Event? event, PangeaRepresentation? content, PangeaMessageTokens? tokens, ChoreoRecord? choreo, - SpeechToTextModel? speechToTextResponse, }) { if (event != null && event.type != PangeaEventTypes.representation) { throw Exception( @@ -46,7 +42,6 @@ class RepresentationEvent { _content = content; _tokens = tokens; _choreo = choreo; - _speechToTextResponse = speechToTextResponse; } Event? get event => _event; diff --git a/lib/pangea/widgets/chat/message_speech_to_text_card.dart b/lib/pangea/widgets/chat/message_speech_to_text_card.dart index bc2cf3327..37b013b53 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -138,7 +138,7 @@ class MessageSpeechToTextCardState extends State { } String? get wordsPerMinuteString => - speechToTextResponse?.transcript.wordsPerMinute?.toString(); + speechToTextResponse?.transcript.wordsPerMinute?.toStringAsFixed(2); @override Widget build(BuildContext context) { diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index b2c61a354..81884ffca 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -172,6 +172,19 @@ class MessageToolbarState extends State { debugPrint("updating toolbar mode"); final bool subscribed = MatrixState.pangeaController.subscriptionController.isSubscribed; + + if (!newMode.isValidMode(widget.pangeaMessageEvent.event)) { + ErrorHandler.logError( + e: "Invalid mode for event", + s: StackTrace.current, + data: { + "newMode": newMode, + "event": widget.pangeaMessageEvent.event, + }, + ); + return; + } + setState(() { currentMode = newMode; updatingMode = true; @@ -274,12 +287,14 @@ class MessageToolbarState extends State { PLocalKey.autoPlayMessages, ) ?? true; + + if (widget.pangeaMessageEvent.isAudioMessage) { + updateMode(MessageMode.speechToText); + return; + } + autoplay - ? updateMode( - widget.pangeaMessageEvent.isAudioMessage - ? MessageMode.speechToText - : MessageMode.textToSpeech, - ) + ? updateMode(MessageMode.textToSpeech) : updateMode(MessageMode.translation); }); From 39c8012137740463d1c30c725092ac1b8919f50a Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 20 May 2024 13:52:14 -0400 Subject: [PATCH 32/48] Archive list does not show spaces/analytics rooms --- lib/pages/archive/archive.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/pages/archive/archive.dart b/lib/pages/archive/archive.dart index a3c35c347..06d80b7df 100644 --- a/lib/pages/archive/archive.dart +++ b/lib/pages/archive/archive.dart @@ -1,13 +1,12 @@ -import 'package:flutter/material.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/pages/archive/archive_view.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension.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:matrix/matrix.dart'; -import 'package:fluffychat/pages/archive/archive_view.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - class Archive extends StatefulWidget { const Archive({super.key}); @@ -20,7 +19,11 @@ class ArchiveController extends State { Future> getArchive(BuildContext context) async { if (archive.isNotEmpty) return archive; - return archive = await Matrix.of(context).client.loadArchive(); + // #Pangea + return archive = (await Matrix.of(context).client.loadArchive()) + .where((e) => (!e.isSpace && !e.isAnalyticsRoom)) + .toList(); + // Pangea# } void forgetRoomAction(int i) async { From 5f839555440df8c33ef5af771457263fbc7ef37d Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 20 May 2024 14:26:18 -0400 Subject: [PATCH 33/48] Archived chats don't show Chat Details or Leave --- lib/pages/archive/archive.dart | 1 + lib/pages/chat/chat_view.dart | 3 +- lib/widgets/chat_settings_popup_menu.dart | 39 ++++++++++++++++------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/pages/archive/archive.dart b/lib/pages/archive/archive.dart index 06d80b7df..78e735d05 100644 --- a/lib/pages/archive/archive.dart +++ b/lib/pages/archive/archive.dart @@ -20,6 +20,7 @@ class ArchiveController extends State { Future> getArchive(BuildContext context) async { if (archive.isNotEmpty) return archive; // #Pangea + //return archive = await Matrix.of(context).client.loadArchive(); return archive = (await Matrix.of(context).client.loadArchive()) .where((e) => (!e.isSpace && !e.isAnalyticsRoom)) .toList(); diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 57255ffc9..21789f527 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -116,7 +116,8 @@ class ChatView extends StatelessWidget { // #Pangea } else { return [ - ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat), + ChatSettingsPopupMenu(controller.room, + (!controller.room.isDirectChat && !controller.room.isArchived)), ]; } diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index f10f08ff7..1feadc459 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -83,19 +83,34 @@ class ChatSettingsPopupMenuState extends State { ], ), ), - PopupMenuItem( - value: 'leave', - child: Row( - children: [ - // #Pangea - // const Icon(Icons.delete_outlined), - const Icon(Icons.arrow_forward), - // Pangea# - const SizedBox(width: 12), - Text(L10n.of(context)!.leave), - ], + // #Pangea + // PopupMenuItem( + // value: 'leave', + // child: Row( + // children: [ + // // #Pangea + // // const Icon(Icons.delete_outlined), + // const Icon(Icons.arrow_forward), + // // Pangea# + // const SizedBox(width: 12), + // Text(L10n.of(context)!.leave), + // ], + // ), + // ), + if (!widget.room.isArchived) + PopupMenuItem( + value: 'leave', + child: Row( + children: [ + // #Pangea + // const Icon(Icons.delete_outlined), + const Icon(Icons.arrow_forward), + // Pangea# + const SizedBox(width: 12), + Text(L10n.of(context)!.leave), + ], + ), ), - ), // #Pangea if (classSettings != null) PopupMenuItem( From 8c421d80cc82427e4d1579366d0c115c97bfc3b9 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 20 May 2024 14:38:17 -0400 Subject: [PATCH 34/48] adjusted pangea comments --- lib/widgets/chat_settings_popup_menu.dart | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/lib/widgets/chat_settings_popup_menu.dart b/lib/widgets/chat_settings_popup_menu.dart index 1feadc459..781b8ab61 100644 --- a/lib/widgets/chat_settings_popup_menu.dart +++ b/lib/widgets/chat_settings_popup_menu.dart @@ -84,20 +84,8 @@ class ChatSettingsPopupMenuState extends State { ), ), // #Pangea - // PopupMenuItem( - // value: 'leave', - // child: Row( - // children: [ - // // #Pangea - // // const Icon(Icons.delete_outlined), - // const Icon(Icons.arrow_forward), - // // Pangea# - // const SizedBox(width: 12), - // Text(L10n.of(context)!.leave), - // ], - // ), - // ), if (!widget.room.isArchived) + // Pangea# PopupMenuItem( value: 'leave', child: Row( From 9a6a58e53cb6ea54daffaff01382ddc1fe609149 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Mon, 20 May 2024 16:33:33 -0400 Subject: [PATCH 35/48] Prevent non-admins of room from toggling class membership --- lib/pangea/extensions/pangea_room_extension.dart | 3 +++ lib/pangea/widgets/class/add_space_toggles.dart | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/pangea/extensions/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension.dart index bf152012a..febd17fa8 100644 --- a/lib/pangea/extensions/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension.dart @@ -815,6 +815,9 @@ extension PangeaRoom on Room { ); return false; } + if (room != null && !room.isRoomAdmin) { + return false; + } if (!pangeaCanSendEvent(EventTypes.spaceChild) && !isRoomAdmin) { return false; } diff --git a/lib/pangea/widgets/class/add_space_toggles.dart b/lib/pangea/widgets/class/add_space_toggles.dart index cd875cd95..eb06cd26a 100644 --- a/lib/pangea/widgets/class/add_space_toggles.dart +++ b/lib/pangea/widgets/class/add_space_toggles.dart @@ -235,8 +235,13 @@ class AddToSpaceState extends State { ), activeColor: AppConfig.activeToggleColor, value: isSuggestedInSpace(possibleParent), - onChanged: (bool suggest) => - setSuggested(suggest, possibleParent), + onChanged: (bool suggest) => canAdd + ? setSuggested(suggest, possibleParent) + : ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(L10n.of(context)!.noPermission), + ), + ), ) : Container(), ), From 0aacd6407cb3d5f926aa1a4308a77c9bdf9be4bd Mon Sep 17 00:00:00 2001 From: ggurdin Date: Mon, 20 May 2024 16:52:56 -0400 Subject: [PATCH 36/48] don't add spaceId query parameter if activeSpaceId is null --- lib/pages/chat_list/start_chat_fab.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pages/chat_list/start_chat_fab.dart b/lib/pages/chat_list/start_chat_fab.dart index 117833ff0..f25374f4e 100644 --- a/lib/pages/chat_list/start_chat_fab.dart +++ b/lib/pages/chat_list/start_chat_fab.dart @@ -30,7 +30,9 @@ class StartChatFloatingActionButton extends StatelessWidget { void _onPressed(BuildContext context) async { //#Pangea if (controller.activeSpaceId != null) { - context.go('/rooms/newgroup?spaceId=${controller.activeSpaceId ?? ''}'); + context.go( + '/rooms/newgroup${controller.activeSpaceId != null ? '?spaceId=${controller.activeSpaceId}' : ''}', + ); return; } //Pangea# @@ -44,7 +46,9 @@ class StartChatFloatingActionButton extends StatelessWidget { case ActiveFilter.groups: // #Pangea // context.go('/rooms/newgroup'); - context.go('/rooms/newgroup?spaceId=${controller.activeSpaceId ?? ''}'); + context.go( + '/rooms/newgroup${controller.activeSpaceId != null ? '?spaceId=${controller.activeSpaceId}' : ''}', + ); // Pangea# break; case ActiveFilter.spaces: From 733f37717cffcdf83da6a3ae61848656a2fd6134 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 21 May 2024 11:11:55 -0400 Subject: [PATCH 37/48] When user changes languages, updates learning settings --- .../settings_learning/settings_learning.dart | 7 +++++-- .../settings_learning_view.dart | 2 +- .../widgets/user_settings/language_tile.dart | 16 ++++++++++------ .../widgets/user_settings/p_language_dialog.dart | 2 +- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/pangea/pages/settings_learning/settings_learning.dart b/lib/pangea/pages/settings_learning/settings_learning.dart index f699fc7b5..0785f3407 100644 --- a/lib/pangea/pages/settings_learning/settings_learning.dart +++ b/lib/pangea/pages/settings_learning/settings_learning.dart @@ -1,10 +1,9 @@ import 'dart:async'; -import 'package:flutter/material.dart'; - import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning_view.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; class SettingsLearning extends StatefulWidget { const SettingsLearning({super.key}); @@ -32,6 +31,10 @@ class SettingsLearningController extends State { }); } + Future refresh() async { + setState(() {}); + } + @override void dispose() { super.dispose(); diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index d878dcb42..6c3a87f00 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -31,7 +31,7 @@ class SettingsLearningView extends StatelessWidget { withScrolling: true, child: Column( children: [ - LanguageTile(), + LanguageTile(controller), CountryPickerTile(), const SizedBox(height: 8), const Divider(height: 1), diff --git a/lib/pangea/widgets/user_settings/language_tile.dart b/lib/pangea/widgets/user_settings/language_tile.dart index 085779781..95ff42f86 100644 --- a/lib/pangea/widgets/user_settings/language_tile.dart +++ b/lib/pangea/widgets/user_settings/language_tile.dart @@ -1,19 +1,20 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; +import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + import '../flag.dart'; import 'p_language_dialog.dart'; //PTODO - move this to settings_learning_view.dart and make callback a setState class LanguageTile extends StatelessWidget { + final SettingsLearningController learningController; final PangeaController pangeaController = MatrixState.pangeaController; - LanguageTile({super.key}); + LanguageTile(this.learningController, {super.key}); @override Widget build(BuildContext context) { @@ -81,7 +82,10 @@ class LanguageTile extends StatelessWidget { ], ), trailing: const Icon(Icons.edit_outlined), - onTap: () => pLanguageDialog(context, () {}), + onTap: () async { + await pLanguageDialog(context, () {}); + learningController.refresh(); + }, ); } } diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index 077959ead..4d09b506e 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -15,7 +15,7 @@ import '../../../widgets/matrix.dart'; import 'p_language_dropdown.dart'; import 'p_question_container.dart'; -pLanguageDialog(BuildContext parentContext, Function callback) { +pLanguageDialog(BuildContext parentContext, Function callback) async { final PangeaController pangeaController = MatrixState.pangeaController; //PTODO: if source language not set by user, default to languge from device settings final LanguageModel? userL1 = pangeaController.languageController.userL1; From 8b0104db06da84a2ea3a184030b1485cacad0467 Mon Sep 17 00:00:00 2001 From: Kelrap Date: Tue, 21 May 2024 11:51:30 -0400 Subject: [PATCH 38/48] Moved state-related functionality to controller --- lib/pangea/pages/settings_learning/settings_learning.dart | 4 +++- lib/pangea/widgets/user_settings/language_tile.dart | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pangea/pages/settings_learning/settings_learning.dart b/lib/pangea/pages/settings_learning/settings_learning.dart index 0785f3407..c560fef57 100644 --- a/lib/pangea/pages/settings_learning/settings_learning.dart +++ b/lib/pangea/pages/settings_learning/settings_learning.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning_view.dart'; +import 'package:fluffychat/pangea/widgets/user_settings/p_language_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; @@ -31,7 +32,8 @@ class SettingsLearningController extends State { }); } - Future refresh() async { + Future changeLanguage() async { + await pLanguageDialog(context, () {}); setState(() {}); } diff --git a/lib/pangea/widgets/user_settings/language_tile.dart b/lib/pangea/widgets/user_settings/language_tile.dart index 95ff42f86..d66335300 100644 --- a/lib/pangea/widgets/user_settings/language_tile.dart +++ b/lib/pangea/widgets/user_settings/language_tile.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import '../flag.dart'; -import 'p_language_dialog.dart'; //PTODO - move this to settings_learning_view.dart and make callback a setState @@ -83,8 +82,7 @@ class LanguageTile extends StatelessWidget { ), trailing: const Icon(Icons.edit_outlined), onTap: () async { - await pLanguageDialog(context, () {}); - learningController.refresh(); + learningController.changeLanguage(); }, ); } From aea0c9ccecd9cb53e4e4b5c67732a8a29a3ea7eb Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Tue, 21 May 2024 17:44:09 -0400 Subject: [PATCH 39/48] added language detection controller, edited README, and removed some old code --- README.md | 66 ++-------- .../controllers/analytics_sender.dart | 24 ---- .../language_detection_controller.dart | 122 ++++++++++++++++++ lib/pangea/network/urls.dart | 7 +- lib/pangea/repo/message_service.repo.dart | 55 -------- needed-translations.txt | 99 ++++++++++++++ 6 files changed, 233 insertions(+), 140 deletions(-) delete mode 100644 lib/pangea/choreographer/controllers/analytics_sender.dart create mode 100644 lib/pangea/controllers/language_detection_controller.dart delete mode 100644 lib/pangea/repo/message_service.repo.dart diff --git a/README.md b/README.md index 7c4970e00..7c27b6e2e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ -Pangea Chat Client Setup: +# Overview -* Download VSCode if you do not already have it installed +[Pangea Chat](https://pangea.chat) is a web and mobile platform which lets students ‘learn a language while texting their friends.’ Addressing the gap in communicative language teaching, especially for beginners lacking skill and confidence, Pangea Chat provides a low-stress, high-support environment for language learning through authentic conversations. By integrating human and artificial intelligence, the app enhances communicative abilities and supports educators. Pangea Chat has been grant funded by the National Science Foundation and Virginia Innovation Partnership Corporation based on its technical innovation and potential for broad social impact. Our mission is to build a global, decentralized learning network supporting intercultural learning and exchange. + +# Pangea Chat Client Setup + +* Download VSCode if you do not already have it installed. This is the preferred IDE for development with Pangea Chat. * Download flutter on your device using this guide: https://docs.flutter.dev/get-started/install * Test to make sure that flutter is properly installed by running “flutter –version” * You may need to add flutter to your path manually. Instructions can be found here: https://docs.flutter.dev/get-started/install/macos/mobile-ios?tab=download#add-flutter-to-your-path @@ -14,7 +18,7 @@ Pangea Chat Client Setup: * Run “brew install cocoapods” to install cocoapods * Run “flutter doctor” and for any missing components, follow the instructions from the print out to install / setup * Clone the client repo -* Copy the .env file (and the .env.prod file, if you want to run production builds), into the root folder of the client and the assets/ folder +* Copy the .env file (and the .env.prod file, if you want to run production builds), into the root folder of the client and the assets/ folder. Contact Gabby for a copy of this file. * Uncomment the lines in the pubspec.yaml file in the assets section with paths to .env file * To run on iOS: * Run “flutter precache --ios” @@ -25,62 +29,10 @@ Pangea Chat Client Setup: * On web, run `flutter run -d chrome –hot` * On mobile device or simulator, run `flutter run –hot -d ` -![Screenshot](https://github.com/krille-chan/fluffychat/blob/main/assets/banner_transparent.png?raw=true) - -[FluffyChat](https://fluffychat.im) is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of the app is to create an easy to use instant messenger which is open source and accessible for everyone. - -### Links: - -- 🌐 [[Weblate] Translate FluffyChat into your language](https://hosted.weblate.org/projects/fluffychat/) -- 🌍 [[m] Join the community](https://matrix.to/#/#fluffychat:matrix.org) -- 📰 [[Mastodon] Get updates on social media](https://mastodon.art/@krille) -- 🖥️ [[Famedly] Server hosting and professional support](https://famedly.com/kontakt) -- 💝 [[Liberapay] Support FluffyChat development](https://de.liberapay.com/KrilleChritzelius) - -Buy Me a Coffee at ko-fi.com - -### Screenshots: - -![Screenshot](https://github.com/krille-chan/fluffychat/blob/main/docs/screenshots/product.jpeg?raw=true) - -# Features - -- 📩 Send all kinds of messages, images and files -- 🎙️ Voice messages -- 📍 Location sharing -- 🔔 Push notifications -- 💬 Unlimited private and public group chats -- 📣 Public channels with thousands of participants -- 🛠️ Feature rich group moderation including all matrix features -- 🔍 Discover and join public groups -- 🌙 Dark mode -- 🎨 Material You design -- 📟 Hides complexity of Matrix IDs behind simple QR codes -- 😄 Custom emotes and stickers -- 🌌 Spaces -- 🔄 Compatible with Element, Nheko, NeoChat and all other Matrix apps -- 🔐 End to end encryption -- 🔒 Encrypted chat backup -- 😀 Emoji verification & cross signing - -... and much more. - - -# Installation - -Please visit the website for installation instructions: - -- https://fluffychat.im - -# How to build - -Please visit the [Wiki](https://github.com/krille-chan/fluffychat/wiki) for build instructions: - -- https://github.com/krille-chan/fluffychat/wiki/How-To-Build - - # Special thanks +* Pangea Chat is a fork of [FluffyChat](https://fluffychat.im), is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of FluffyChat is to create an easy to use instant messenger which is open source and accessible for everyone. You can [support the primary maker of FluffyChat directly here.](https://ko-fi.com/C1C86VN53) + * Fabiyamada is a graphics designer and has made the fluffychat logo and the banner. Big thanks for her great designs. * Advocatux has made the Spanish translation with great love and care. He always stands by my side and supports my work with great commitment. diff --git a/lib/pangea/choreographer/controllers/analytics_sender.dart b/lib/pangea/choreographer/controllers/analytics_sender.dart deleted file mode 100644 index fe4575f38..000000000 --- a/lib/pangea/choreographer/controllers/analytics_sender.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart'; - -class MlController { - final ITController controller; - MlController(this.controller); - - // sendPayloads(String message, String messageId) async { - // final MessageServiceModel serviceModel = MessageServiceModel( - // classId: controller.state!.classId, - // roomId: controller.state!.roomId, - // message: message.toString(), - // messageId: messageId.toString(), - // payloadIds: controller.state!.payLoadIds, - // userId: controller.state!.userId!, - // l1Lang: controller.state!.sourceLangCode, - // l2Lang: controller.state!.targetLangCode!, - // ); - // try { - // await MessageServiceRepo.sendPayloads(serviceModel); - // } catch (err) { - // debugPrint('$err in sendPayloads'); - // } - // } -} diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart new file mode 100644 index 000000000..400cd4419 --- /dev/null +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -0,0 +1,122 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:fluffychat/pangea/config/environment.dart'; +import 'package:fluffychat/pangea/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/network/urls.dart'; +import 'package:http/http.dart' as http; + +import '../network/requests.dart'; + +class LanguageDetectionRequest { + String fullText; + String userL1; + String userL2; + + LanguageDetectionRequest({ + required this.fullText, + this.userL1 = "", + required this.userL2, + }); + + Map toJson() => { + 'full_text': fullText, + 'user_l1': userL1, + 'user_l2': userL2, + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is LanguageDetectionRequest && + other.fullText == fullText && + other.userL1 == userL1 && + other.userL2 == userL2; + } + + @override + int get hashCode => fullText.hashCode ^ userL1.hashCode ^ userL2.hashCode; +} + +class LanguageDetectionResponse { + List> detections; + String fullText; + + LanguageDetectionResponse({ + required this.detections, + required this.fullText, + }); + + factory LanguageDetectionResponse.fromJson(Map json) { + return LanguageDetectionResponse( + detections: List>.from(json['detections']), + fullText: json['full_text'], + ); + } +} + +class _LanguageDetectionCacheItem { + Future data; + + _LanguageDetectionCacheItem({ + required this.data, + }); +} + +class LanguageDetectionController { + static final Map + _cache = {}; + late final PangeaController _pangeaController; + Timer? _cacheClearTimer; + + LanguageDetectionController(PangeaController pangeaController) { + _pangeaController = pangeaController; + _initializeCacheClearing(); + } + + void _initializeCacheClearing() { + const duration = Duration(minutes: 15); // Adjust the duration as needed + _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache()); + } + + void _clearCache() { + _cache.clear(); + } + + void dispose() { + _cacheClearTimer?.cancel(); + } + + Future get( + LanguageDetectionRequest params, + ) async { + if (_cache.containsKey(params)) { + return _cache[params]!.data; + } else { + final Future response = _fetchResponse( + await _pangeaController.userController.accessToken, + params, + ); + _cache[params] = _LanguageDetectionCacheItem(data: response); + return response; + } + } + + static Future _fetchResponse( + String accessToken, + LanguageDetectionRequest params, + ) async { + final Requests request = Requests( + choreoApiKey: Environment.choreoApi, + accessToken: accessToken, + ); + + final http.Response res = await request.post( + url: PApiUrls.languageDetection, + body: params.toJson(), + ); + + final Map json = jsonDecode(res.body); + return LanguageDetectionResponse.fromJson(json); + } +} diff --git a/lib/pangea/network/urls.dart b/lib/pangea/network/urls.dart index 16d8bcd23..ff0404947 100644 --- a/lib/pangea/network/urls.dart +++ b/lib/pangea/network/urls.dart @@ -24,13 +24,12 @@ class PApiUrls { /// ---------------------- Conversation Partner ------------------------- static String searchUserProfiles = "/account/search"; - ///-------------------------------- Deprecated analytics -------------------- - static String classAnalytics = "${Environment.choreoApi}/class_analytics"; - static String messageService = "/message_service"; - ///-------------------------------- choreo -------------------------- static String igc = "${Environment.choreoApi}/grammar"; + static String languageDetection = + "${Environment.choreoApi}/language_detection"; + static String igcLite = "${Environment.choreoApi}/grammar_lite"; static String spanDetails = "${Environment.choreoApi}/span_details"; diff --git a/lib/pangea/repo/message_service.repo.dart b/lib/pangea/repo/message_service.repo.dart deleted file mode 100644 index ce51a3802..000000000 --- a/lib/pangea/repo/message_service.repo.dart +++ /dev/null @@ -1,55 +0,0 @@ -import '../config/environment.dart'; -import '../network/requests.dart'; -import '../network/urls.dart'; - -class MessageServiceRepo { - static Future sendPayloads( - MessageServiceModel serviceModel, - String messageId, - ) async { - final Requests req = Requests( - baseUrl: Environment.choreoApi, - choreoApiKey: Environment.choreoApiKey, - ); - - final json = serviceModel.toJson(); - json["msg_id"] = messageId; - - await req.post(url: PApiUrls.messageService, body: json); - } -} - -class MessageServiceModel { - List payloadIds; - String? messageId; - String message; - String userId; - String roomId; - String? classId; - String? l1Lang; - String l2Lang; - - MessageServiceModel({ - required this.payloadIds, - required this.messageId, - required this.message, - required this.userId, - required this.roomId, - required this.classId, - required this.l1Lang, - required this.l2Lang, - }); - - toJson() { - return { - 'payload_ids': payloadIds, - 'msg_id': messageId, - 'message': message, - 'user_id': userId, - 'room_id': roomId, - 'class_id': classId, - 'l1_lang': l1Lang, - 'l2_lang': l2Lang, - }; - } -} diff --git a/needed-translations.txt b/needed-translations.txt index 7cbcc8a05..120bd4255 100644 --- a/needed-translations.txt +++ b/needed-translations.txt @@ -821,6 +821,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -2242,6 +2244,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -3125,6 +3129,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -4008,6 +4014,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -4891,6 +4899,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -5774,6 +5784,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -6604,6 +6616,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -7487,6 +7501,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -8370,10 +8386,17 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], + "es": [ + "studentAnalyticsNotAvailable", + "roomDataMissing" + ], + "et": [ "accountInformation", "addGroupDescription", @@ -9196,6 +9219,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -10022,6 +10047,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -10905,6 +10932,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -11788,6 +11817,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -12671,6 +12702,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -13554,6 +13587,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -14380,6 +14415,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -15263,6 +15300,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -16146,6 +16185,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -17016,6 +17057,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -17899,6 +17942,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -19306,6 +19351,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -20189,6 +20236,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -21072,6 +21121,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -21940,6 +21991,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -22823,6 +22876,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -23706,6 +23761,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -24589,6 +24646,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -25472,6 +25531,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -26355,6 +26416,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -27238,6 +27301,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -28121,6 +28186,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -29004,6 +29071,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -29856,6 +29925,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -30739,6 +30810,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -31622,6 +31695,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -32448,6 +32523,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -33331,6 +33408,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -34214,6 +34293,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -35097,6 +35178,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -35945,6 +36028,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -36828,6 +36913,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -37711,6 +37798,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -38579,6 +38668,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -39405,6 +39496,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -40288,6 +40381,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -41114,6 +41209,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ], @@ -41997,6 +42094,8 @@ "accuracy", "points", "noPaymentInfo", + "studentAnalyticsNotAvailable", + "roomDataMissing", "updatePhoneOS", "wordsPerMinute" ] From 757e9be21222e518d6e051c4197f32b30ea4401d Mon Sep 17 00:00:00 2001 From: William Jordan-Cooley Date: Tue, 21 May 2024 17:53:37 -0400 Subject: [PATCH 40/48] allowing user_l1 and user_l2 to be nullable in request --- .../controllers/language_detection_controller.dart | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index 400cd4419..0a233a12d 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -9,9 +9,18 @@ import 'package:http/http.dart' as http; import '../network/requests.dart'; class LanguageDetectionRequest { + /// The full text from which to detect the language. String fullText; - String userL1; - String userL2; + + /// The base language of the user, if known. Including this is much preferred + /// and should return better results; however, it is not absolutely necessary. + /// This property is nullable to allow for situations where the languages are not set + /// at the time of the request. + String? userL1; + + /// The target language of the user. This is expected to be set for the request + /// but is nullable to handle edge cases where it might not be. + String? userL2; LanguageDetectionRequest({ required this.fullText, From 392aad50bbf234f09582ffff18989fcbb6e1ef07 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 22 May 2024 09:43:56 -0400 Subject: [PATCH 41/48] added language detection controller to pangea controller --- .../controllers/it_controller.dart | 27 +++++++++---------- .../language_detection_controller.dart | 7 +++++ lib/pangea/controllers/pangea_controller.dart | 3 +++ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 74d0b0159..83d678e98 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -15,7 +15,6 @@ import '../../models/it_response_model.dart'; import '../../models/it_step.dart'; import '../../models/system_choice_translation_model.dart'; import '../../repo/interactive_translation_repo.dart'; -import '../../repo/message_service.repo.dart'; import 'choreographer.dart'; class ITController { @@ -247,19 +246,19 @@ class ITController { ), ); - MessageServiceModel? messageServiceModelWithMessageId() => - usedInteractiveTranslation - ? MessageServiceModel( - classId: choreographer.classId, - roomId: choreographer.roomId, - message: choreographer.currentText, - messageId: null, - payloadIds: payLoadIds, - userId: choreographer.userId!, - l1Lang: sourceLangCode, - l2Lang: targetLangCode, - ) - : null; + // MessageServiceModel? messageServiceModelWithMessageId() => + // usedInteractiveTranslation + // ? MessageServiceModel( + // classId: choreographer.classId, + // roomId: choreographer.roomId, + // message: choreographer.currentText, + // messageId: null, + // payloadIds: payLoadIds, + // userId: choreographer.userId!, + // l1Lang: sourceLangCode, + // l2Lang: targetLangCode, + // ) + // : null; //maybe we store IT data in the same format? make a specific kind of match? void selectTranslation(int chosenIndex) { diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index 0a233a12d..7b4571c5d 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -62,6 +62,13 @@ class LanguageDetectionResponse { fullText: json['full_text'], ); } + + Map toJson() { + return { + 'detections': detections, + 'full_text': fullText, + }; + } } class _LanguageDetectionCacheItem { diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 204ef9307..753a8c9e6 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -6,6 +6,7 @@ import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/controllers/class_controller.dart'; import 'package:fluffychat/pangea/controllers/contextual_definition_controller.dart'; import 'package:fluffychat/pangea/controllers/language_controller.dart'; +import 'package:fluffychat/pangea/controllers/language_detection_controller.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/controllers/local_settings.dart'; import 'package:fluffychat/pangea/controllers/message_data_controller.dart'; @@ -51,6 +52,7 @@ class PangeaController { late SubscriptionController subscriptionController; late TextToSpeechController textToSpeech; late SpeechToTextController speechToText; + late LanguageDetectionController languageDetection; ///store Services late PLocalStore pStoreService; @@ -98,6 +100,7 @@ class PangeaController { itFeedback = ITFeedbackController(this); textToSpeech = TextToSpeechController(this); speechToText = SpeechToTextController(this); + languageDetection = LanguageDetectionController(this); PAuthGaurd.pController = this; } From 38634b8dc7351846bad1fa2bb8cc1ebadae70697 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 22 May 2024 10:05:33 -0400 Subject: [PATCH 42/48] fix for empty data returned from wordnet --- lib/pangea/models/word_data_model.dart | 6 +- lib/pangea/widgets/igc/word_data_card.dart | 89 ++++++++++++---------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/lib/pangea/models/word_data_model.dart b/lib/pangea/models/word_data_model.dart index 838240725..c4c059241 100644 --- a/lib/pangea/models/word_data_model.dart +++ b/lib/pangea/models/word_data_model.dart @@ -1,9 +1,8 @@ import 'dart:developer'; -import 'package:flutter/foundation.dart'; - import 'package:fluffychat/pangea/constants/model_keys.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; +import 'package:flutter/foundation.dart'; class WordData { final String word; @@ -102,10 +101,11 @@ class WordData { }) => word == w && userL1 == l1 && userL2 == l2 && fullText == f; - String formattedPartOfSpeech(LanguageType languageType) { + String? formattedPartOfSpeech(LanguageType languageType) { final String pos = languageType == LanguageType.base ? basePartOfSpeech : targetPartOfSpeech; + if (pos.isEmpty) return null; return pos[0].toUpperCase() + pos.substring(1); } diff --git a/lib/pangea/widgets/igc/word_data_card.dart b/lib/pangea/widgets/igc/word_data_card.dart index a6ef7b511..4b8445417 100644 --- a/lib/pangea/widgets/igc/word_data_card.dart +++ b/lib/pangea/widgets/igc/word_data_card.dart @@ -314,6 +314,23 @@ class PartOfSpeechBlock extends StatelessWidget { required this.languageType, }); + String get exampleSentence => languageType == LanguageType.target + ? wordData.targetExampleSentence + : wordData.baseExampleSentence; + + String get definition => languageType == LanguageType.target + ? wordData.targetDefinition + : wordData.baseDefinition; + + String formattedTitle(BuildContext context) { + final String word = languageType == LanguageType.target + ? wordData.targetWord + : wordData.baseWord; + String? pos = wordData.formattedPartOfSpeech(languageType); + if (pos == null || pos.isEmpty) pos = L10n.of(context)!.unkDisplayName; + return "$word (${wordData.formattedPartOfSpeech(languageType)})"; + } + @override Widget build(BuildContext context) { return Padding( @@ -324,9 +341,7 @@ class PartOfSpeechBlock extends StatelessWidget { Align( alignment: Alignment.centerLeft, child: Text( - languageType == LanguageType.target - ? "${wordData.targetWord} (${wordData.formattedPartOfSpeech(languageType)})" - : "${wordData.baseWord} (${wordData.formattedPartOfSpeech(languageType)})", + formattedTitle(context), style: BotStyle.text(context, italics: true, bold: false), ), ), @@ -337,47 +352,43 @@ class PartOfSpeechBlock extends StatelessWidget { alignment: Alignment.centerLeft, child: Column( children: [ - RichText( - text: TextSpan( - style: BotStyle.text( - context, - italics: false, - bold: false, + if (definition.isNotEmpty) + RichText( + text: TextSpan( + style: BotStyle.text( + context, + italics: false, + bold: false, + ), + children: [ + TextSpan( + text: "${L10n.of(context)!.definition}: ", + style: const TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: definition), + ], ), - children: [ - TextSpan( - text: "${L10n.of(context)!.definition}: ", - style: const TextStyle(fontWeight: FontWeight.bold), - ), - TextSpan( - text: languageType == LanguageType.target - ? wordData.targetDefinition - : wordData.baseDefinition, - ), - ], ), - ), const SizedBox(height: 10), - RichText( - text: TextSpan( - style: BotStyle.text( - context, - italics: false, - bold: false, + if (exampleSentence.isNotEmpty) + RichText( + text: TextSpan( + style: BotStyle.text( + context, + italics: false, + bold: false, + ), + children: [ + TextSpan( + text: "${L10n.of(context)!.exampleSentence}: ", + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + TextSpan(text: exampleSentence), + ], ), - children: [ - TextSpan( - text: "${L10n.of(context)!.exampleSentence}: ", - style: const TextStyle(fontWeight: FontWeight.bold), - ), - TextSpan( - text: languageType == LanguageType.target - ? wordData.targetExampleSentence - : wordData.baseExampleSentence, - ), - ], ), - ), ], ), ), From ab33ec2b3f0ed9356d40777abc16f47351142bee Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 22 May 2024 12:58:35 -0400 Subject: [PATCH 43/48] don't show rich text for own messages if they're not overlay --- lib/pages/chat/events/message_content.dart | 8 ++- .../pangea_message_event.dart | 11 ++++- lib/pangea/widgets/chat/message_toolbar.dart | 7 ++- lib/pangea/widgets/chat/overlay_message.dart | 1 + lib/pangea/widgets/igc/pangea_rich_text.dart | 49 ++++++++++--------- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 9866ddb2d..f04296d38 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -38,6 +38,7 @@ class MessageContent extends StatelessWidget { //further down in the chain is also using pangeaController so its not constant final bool immersionMode; final ToolbarDisplayController? toolbarController; + final bool isOverlay; // Pangea# const MessageContent( @@ -50,6 +51,7 @@ class MessageContent extends StatelessWidget { this.pangeaMessageEvent, required this.immersionMode, required this.toolbarController, + this.isOverlay = false, // Pangea# required this.borderRadius, }); @@ -203,7 +205,8 @@ class MessageContent extends StatelessWidget { && !(pangeaMessageEvent?.showRichText( selected, - toolbarController?.highlighted ?? false, + isOverlay: isOverlay, + highlighted: toolbarController?.highlighted ?? false, ) ?? false) // Pangea# @@ -305,7 +308,8 @@ class MessageContent extends StatelessWidget { ); if (pangeaMessageEvent?.showRichText( selected, - toolbarController?.highlighted ?? false, + isOverlay: isOverlay, + highlighted: toolbarController?.highlighted ?? false, ) ?? false) { return PangeaRichText( diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index db5c7902a..fd669f6ec 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -80,7 +80,11 @@ class PangeaMessageEvent { return _latestEdit; } - bool showRichText(bool selected, bool highlighted) { + bool showRichText( + bool selected, { + bool highlighted = false, + bool isOverlay = false, + }) { if (!_isValidPangeaMessageEvent) { return false; } @@ -90,8 +94,11 @@ class PangeaMessageEvent { if ([EventStatus.error, EventStatus.sending].contains(_event.status)) { return false; } - if (ownMessage && !selected && !highlighted) return false; + if (isOverlay) return true; + if (ownMessage && !selected && !highlighted) { + return false; + } return true; } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 81884ffca..2f6d82ef1 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -134,8 +134,11 @@ class ToolbarDisplayController { }); } - bool get highlighted => - MatrixState.pAnyState.overlay.hashCode.toString() == overlayId; + bool get highlighted { + if (overlayId == null) return false; + if (MatrixState.pAnyState.overlay == null) overlayId = null; + return MatrixState.pAnyState.overlay.hashCode.toString() == overlayId; + } } class MessageToolbar extends StatefulWidget { diff --git a/lib/pangea/widgets/chat/overlay_message.dart b/lib/pangea/widgets/chat/overlay_message.dart index 5c7ccdef0..e8143b376 100644 --- a/lib/pangea/widgets/chat/overlay_message.dart +++ b/lib/pangea/widgets/chat/overlay_message.dart @@ -141,6 +141,7 @@ class OverlayMessage extends StatelessWidget { pangeaMessageEvent: pangeaMessageEvent, immersionMode: immersionMode, toolbarController: toolbarController, + isOverlay: true, ), if (event.hasAggregatedEvents( timeline, diff --git a/lib/pangea/widgets/igc/pangea_rich_text.dart b/lib/pangea/widgets/igc/pangea_rich_text.dart index 779955f82..43416e006 100644 --- a/lib/pangea/widgets/igc/pangea_rich_text.dart +++ b/lib/pangea/widgets/igc/pangea_rich_text.dart @@ -48,28 +48,33 @@ class PangeaRichTextState extends State { @override void initState() { super.initState(); - updateTextSpan(); - } - - void updateTextSpan() { - setState(() { - textSpan = getTextSpan(); - }); + setTextSpan(); } @override void didUpdateWidget(PangeaRichText oldWidget) { super.didUpdateWidget(oldWidget); - updateTextSpan(); + setTextSpan(); } - String getTextSpan() { + void _setTextSpan(String newTextSpan) { + widget.toolbarController?.toolbar?.textSelection.setMessageText( + newTextSpan, + ); + setState(() { + textSpan = newTextSpan; + }); + } + + void setTextSpan() { if (_fetchingRepresentation == true) { - return widget.pangeaMessageEvent.body; + _setTextSpan(textSpan = widget.pangeaMessageEvent.body); + return; } if (repEvent != null) { - return repEvent!.text; + _setTextSpan(repEvent!.text); + return; } if (widget.pangeaMessageEvent.eventId.contains("webdebug")) { @@ -84,7 +89,6 @@ class PangeaRichTextState extends State { if (repEvent == null) { setState(() => _fetchingRepresentation = true); - widget.pangeaMessageEvent .representationByLanguageGlobal( langCode: widget.pangeaMessageEvent.messageDisplayLangCode, @@ -95,23 +99,17 @@ class PangeaRichTextState extends State { ) .then((event) { repEvent = event; - widget.toolbarController?.toolbar?.textSelection.setMessageText( - repEvent?.text ?? widget.pangeaMessageEvent.body, - ); + _setTextSpan(repEvent?.text ?? widget.pangeaMessageEvent.body); }).whenComplete(() { if (mounted) { setState(() => _fetchingRepresentation = false); } }); - return widget.pangeaMessageEvent.body; - } else { - widget.toolbarController?.toolbar?.textSelection.setMessageText( - repEvent!.text, - ); - setState(() {}); - } - return repEvent!.text; + _setTextSpan(widget.pangeaMessageEvent.body); + } else { + _setTextSpan(repEvent!.text); + } } @override @@ -190,7 +188,10 @@ class PangeaRichTextState extends State { return blur > 0 ? ImageFiltered( - imageFilter: ImageFilter.blur(sigmaX: blur, sigmaY: blur), + imageFilter: ImageFilter.blur( + sigmaX: blur, + sigmaY: blur, + ), child: richText, ) : richText; From 087c0d05e9004b12c47c8b05aaa18a40008c73c1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 22 May 2024 13:04:23 -0400 Subject: [PATCH 44/48] fix for showRichText function --- .../matrix_event_wrappers/pangea_message_event.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index fd669f6ec..91f19f6e7 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -88,15 +88,16 @@ class PangeaMessageEvent { if (!_isValidPangeaMessageEvent) { return false; } - // if (URLFinder.getMatches(event.body).isNotEmpty) { - // return false; - // } + if ([EventStatus.error, EventStatus.sending].contains(_event.status)) { return false; } if (isOverlay) return true; - if (ownMessage && !selected && !highlighted) { + + // if ownMessage, don't show rich text if not selected or highlighted + // and don't show is the message is not an overlay + if (ownMessage && ((!selected && !highlighted) || !isOverlay)) { return false; } return true; From 20733e6645a95c20ed9ee4e55ea753845735d4f1 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 22 May 2024 13:21:08 -0400 Subject: [PATCH 45/48] fix for API key --- lib/pangea/controllers/language_detection_controller.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pangea/controllers/language_detection_controller.dart b/lib/pangea/controllers/language_detection_controller.dart index 7b4571c5d..0ff18b556 100644 --- a/lib/pangea/controllers/language_detection_controller.dart +++ b/lib/pangea/controllers/language_detection_controller.dart @@ -123,7 +123,7 @@ class LanguageDetectionController { LanguageDetectionRequest params, ) async { final Requests request = Requests( - choreoApiKey: Environment.choreoApi, + choreoApiKey: Environment.choreoApiKey, accessToken: accessToken, ); From f79203a47ebc4842b53fe5e57109fa4df731754a Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 22 May 2024 16:12:10 -0400 Subject: [PATCH 46/48] Mark chats as read before leaving them --- lib/pages/chat_list/chat_list.dart | 3 +++ lib/pages/chat_list/chat_list_item.dart | 3 +++ lib/pages/chat_list/space_view.dart | 5 ++++- lib/pages/chat_list/utils/on_chat_tap.dart | 13 +++++++------ lib/pangea/utils/archive_space.dart | 6 ++++-- lib/pangea/utils/delete_room.dart | 5 ++++- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index f96baa040..9a6345c6a 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -728,6 +728,9 @@ class ChatListController extends State while (selectedRoomIds.isNotEmpty) { final roomId = selectedRoomIds.first; try { + if (client.getRoomById(roomId)!.isUnread) { + await client.getRoomById(roomId)!.markUnread(false); + } await client.getRoomById(roomId)!.leave(); } finally { toggleSelection(roomId); diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 447b5f6c3..71d5d558b 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -53,6 +53,9 @@ class ChatListItem extends StatelessWidget { message: L10n.of(context)!.archiveRoomDescription, ); if (confirmed == OkCancelResult.cancel) return; + if (room.isUnread) { + await room.markUnread(false); + } await showFutureLoadingDialog( context: context, future: () => room.leave(), diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 348550c65..48be9eb04 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -288,7 +288,10 @@ class _SpaceViewState extends State { // #Pangea // future: room!.leave, future: () async { - await room!.leave(); + if (room!.isUnread) { + await room.markUnread(false); + } + await room.leave(); if (Matrix.of(context).activeRoomId == room.id) { context.go('/rooms'); } diff --git a/lib/pages/chat_list/utils/on_chat_tap.dart b/lib/pages/chat_list/utils/on_chat_tap.dart index d24af1fb4..3869cd60d 100644 --- a/lib/pages/chat_list/utils/on_chat_tap.dart +++ b/lib/pages/chat_list/utils/on_chat_tap.dart @@ -1,15 +1,13 @@ -import 'package:flutter/material.dart'; - import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:fluffychat/pages/chat/send_file_dialog.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.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:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pages/chat/send_file_dialog.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - void onChatTap(Room room, BuildContext context) async { if (room.membership == Membership.invite) { final inviterId = @@ -47,6 +45,9 @@ void onChatTap(Room room, BuildContext context) async { return; } if (inviteAction == InviteActions.decline) { + if (room.isUnread) { + await room.markUnread(false); + } await showFutureLoadingDialog( context: context, future: room.leave, diff --git a/lib/pangea/utils/archive_space.dart b/lib/pangea/utils/archive_space.dart index 72f10fae4..ac83980fb 100644 --- a/lib/pangea/utils/archive_space.dart +++ b/lib/pangea/utils/archive_space.dart @@ -1,7 +1,6 @@ -import 'package:matrix/matrix.dart'; - import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:matrix/matrix.dart'; Future archiveSpace(Room? space, Client client) async { if (space == null) { @@ -14,6 +13,9 @@ Future archiveSpace(Room? space, Client client) async { final List children = await space.getChildRooms(); for (final Room child in children) { + if (child.isUnread) { + await child.markUnread(false); + } await child.leave(); } await space.leave(); diff --git a/lib/pangea/utils/delete_room.dart b/lib/pangea/utils/delete_room.dart index f8e168779..d2a0acae9 100644 --- a/lib/pangea/utils/delete_room.dart +++ b/lib/pangea/utils/delete_room.dart @@ -1,6 +1,6 @@ +import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'error_handler.dart'; Future deleteRoom(String? roomID, Client client) async { @@ -79,6 +79,9 @@ Future deleteRoom(String? roomID, Client client) async { } try { + if (room.isUnread) { + await room.markUnread(false); + } await room.leave(); } catch (err) { ErrorHandler.logError( From ceb7fdb24e5a719b6ef4c1455e49bdc58178bdfc Mon Sep 17 00:00:00 2001 From: Kelrap Date: Wed, 22 May 2024 16:21:25 -0400 Subject: [PATCH 47/48] Fixed code in Fluffchat files to match convention --- lib/pages/chat_list/chat_list.dart | 2 ++ lib/pages/chat_list/chat_list_item.dart | 2 ++ lib/pages/chat_list/utils/on_chat_tap.dart | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 9a6345c6a..c0791c936 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -728,9 +728,11 @@ class ChatListController extends State while (selectedRoomIds.isNotEmpty) { final roomId = selectedRoomIds.first; try { + // #Pangea if (client.getRoomById(roomId)!.isUnread) { await client.getRoomById(roomId)!.markUnread(false); } + // Pangea# await client.getRoomById(roomId)!.leave(); } finally { toggleSelection(roomId); diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 71d5d558b..9775ec624 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -53,9 +53,11 @@ class ChatListItem extends StatelessWidget { message: L10n.of(context)!.archiveRoomDescription, ); if (confirmed == OkCancelResult.cancel) return; + // #Pangea if (room.isUnread) { await room.markUnread(false); } + // Pangea# await showFutureLoadingDialog( context: context, future: () => room.leave(), diff --git a/lib/pages/chat_list/utils/on_chat_tap.dart b/lib/pages/chat_list/utils/on_chat_tap.dart index 3869cd60d..d9f1c8191 100644 --- a/lib/pages/chat_list/utils/on_chat_tap.dart +++ b/lib/pages/chat_list/utils/on_chat_tap.dart @@ -45,9 +45,11 @@ void onChatTap(Room room, BuildContext context) async { return; } if (inviteAction == InviteActions.decline) { + // #Pangea if (room.isUnread) { await room.markUnread(false); } + // Pangea# await showFutureLoadingDialog( context: context, future: room.leave, From c203071a7db3ba6f9937a06f69621f53d075d608 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 22 May 2024 16:39:32 -0400 Subject: [PATCH 48/48] removed unused delete room files --- .../p_class_widgets/delete_class_tile.dart | 149 ------------------ lib/pangea/utils/delete_room.dart | 92 ----------- 2 files changed, 241 deletions(-) delete mode 100644 lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart delete mode 100644 lib/pangea/utils/delete_room.dart diff --git a/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart b/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart deleted file mode 100644 index 7e3fc3af6..000000000 --- a/lib/pangea/pages/class_settings/p_class_widgets/delete_class_tile.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:future_loading_dialog/future_loading_dialog.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart'; - -import 'package:fluffychat/pangea/utils/delete_room.dart'; -import 'package:fluffychat/widgets/matrix.dart'; - -class DeleteSpaceTile extends StatelessWidget { - final Room room; - - const DeleteSpaceTile({ - super.key, - required this.room, - }); - - @override - Widget build(BuildContext context) { - bool classNameMatch = true; - final textController = TextEditingController(); - Future deleteSpace() async { - final Client client = Matrix.of(context).client; - final GetSpaceHierarchyResponse spaceHierarchy = - await client.getSpaceHierarchy(room.id); - - if (spaceHierarchy.rooms.isNotEmpty) { - final List spaceChats = spaceHierarchy.rooms - .where((c) => c.roomId != room.id) - .map((e) => Matrix.of(context).client.getRoomById(e.roomId)) - .where((c) => c != null && !c.isSpace && !c.isDirectChat) - .cast() - .toList(); - - await Future.wait( - spaceChats.map((c) => deleteRoom(c.id, client)), - ); - } - deleteRoom(room.id, client); - context.go('/rooms'); - return; - } - - Future deleteChat() { - context.go('/rooms'); - return deleteRoom(room.id, Matrix.of(context).client); - } - - Future deleteChatAction() async { - showDialog( - context: context, - useRootNavigator: false, - builder: (context) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - title: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - room.isSpace - ? L10n.of(context)!.areYouSureDeleteClass - : L10n.of(context)!.areYouSureDeleteGroup, - style: const TextStyle( - fontSize: 20, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 5), - Text( - L10n.of(context)!.cannotBeReversed, - style: const TextStyle( - fontSize: 16, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 10), - if (room.isSpace) - Text( - L10n.of(context)!.enterDeletedClassName, - style: const TextStyle( - fontSize: 14, - ), - textAlign: TextAlign.center, - ), - ], - ), - content: room.isSpace - ? TextField( - autofocus: true, - controller: textController, - decoration: InputDecoration( - hintText: room.name, - errorText: !classNameMatch - ? L10n.of(context)!.incorrectClassName - : null, - ), - ) - : null, - actions: [ - TextButton( - child: Text(L10n.of(context)!.ok), - onPressed: () async { - if (room.isSpace) { - setState(() { - classNameMatch = textController.text == room.name; - }); - if (classNameMatch) { - Navigator.of(context).pop(); - await showFutureLoadingDialog( - context: context, - future: () => deleteSpace(), - ); - } - } else { - await showFutureLoadingDialog( - context: context, - future: () => deleteChat(), - ); - } - }, - ), - TextButton( - child: Text(L10n.of(context)!.cancel), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ], - ); - }, - ); - }, - ); - } - - return ListTile( - trailing: const Icon(Icons.delete_outlined), - title: Text( - room.isSpace - ? L10n.of(context)!.deleteSpace - : L10n.of(context)!.deleteGroup, - style: const TextStyle(color: Colors.red), - ), - onTap: () => deleteChatAction(), - ); - } -} diff --git a/lib/pangea/utils/delete_room.dart b/lib/pangea/utils/delete_room.dart deleted file mode 100644 index d2a0acae9..000000000 --- a/lib/pangea/utils/delete_room.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:fluffychat/pangea/constants/class_default_values.dart'; -import 'package:matrix/matrix.dart'; - -import 'error_handler.dart'; - -Future deleteRoom(String? roomID, Client client) async { - if (roomID == null) { - ErrorHandler.logError( - m: "in deleteRoomAction with null pangeaClassRoomID", - s: StackTrace.current, - ); - return; - } - - final Room? room = client.getRoomById(roomID); - if (room == null) { - ErrorHandler.logError( - m: "failed to fetch room with roomID $roomID", - s: StackTrace.current, - ); - return; - } - - try { - await room.join(); - } catch (err) { - ErrorHandler.logError( - m: "failed to join room with roomID $roomID", - s: StackTrace.current, - ); - return; - } - - List members; - try { - members = await room.requestParticipants(); - } catch (err) { - ErrorHandler.logError( - m: "failed to fetch members for room with roomID $roomID", - s: StackTrace.current, - ); - return; - } - - final List otherAdmins = []; - for (final User member in members) { - final String memberID = member.id; - final int memberPowerLevel = room.getPowerLevelByUserId(memberID); - if (memberID == client.userID) continue; - if (memberPowerLevel >= ClassDefaultValues.powerLevelOfAdmin) { - otherAdmins.add(member); - continue; - } - try { - await room.kick(memberID); - } catch (err) { - ErrorHandler.logError( - m: "Failed to kick user $memberID from room with id $roomID. Error: $err", - s: StackTrace.current, - ); - continue; - } - } - - if (otherAdmins.isNotEmpty && room.canSendEvent(EventTypes.RoomJoinRules)) { - try { - await client.setRoomStateWithKey( - roomID, - EventTypes.RoomJoinRules, - "", - {"join_rules": "invite"}, - ); - } catch (err) { - ErrorHandler.logError( - m: "Failed to update student create room permissions. error: $err, roomId: $roomID", - s: StackTrace.current, - ); - } - } - - try { - if (room.isUnread) { - await room.markUnread(false); - } - await room.leave(); - } catch (err) { - ErrorHandler.logError( - m: "Failed to leave room with id $roomID. Error: $err", - s: StackTrace.current, - ); - } -}