diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 385009ff6..981a36238 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3879,7 +3879,7 @@ "define": "Define", "listen": "Listen", "addConversationBot": "Enable Conversation Bot", - "addConversationBotDesc": "Add a bot to this group chat that will ask questions on a specific topic", + "addConversationBotDesc": "Add a bot to this group chat", "convoBotSettingsTitle": "Conversation Bot Settings", "convoBotSettingsDescription": "Edit conversation topic and difficulty", "enterAConversationTopic": "Enter a conversation topic", @@ -4004,12 +4004,15 @@ "conversationBotCustomZone_customSystemPromptLabel": "System prompt", "conversationBotCustomZone_customSystemPromptPlaceholder": "Set custom system prompt", "conversationBotCustomZone_customTriggerReactionEnabledLabel": "Responds on ⏩ reaction", + "botConfig": "Conversation Bot Settings", "addConversationBotDialogTitleInvite": "Confirm inviting conversation bot", "addConversationBotButtonInvite": "Invite", "addConversationBotDialogInviteConfirmation": "Invite", "addConversationBotButtonTitleRemove": "Confirm removing conversation bot", "addConversationBotButtonRemove": "Remove", "addConversationBotDialogRemoveConfirmation": "Remove", + "conversationBotConfigConfirmChange": "Confirm", + "conversationBotStatus": "Bot Status", "studentAnalyticsNotAvailable": "Student data not currently available", "roomDataMissing": "Some data may be missing from rooms in which you are not a member.", "updatePhoneOS": "You may need to update your device's OS version.", diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index aa2b78d4d..3e0413230 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -328,7 +328,6 @@ class ChatController extends State ); } await Matrix.of(context).client.roomsLoading; - choreographer.setRoomId(roomId); }); // Pangea# _tryLoadTimeline(); @@ -486,12 +485,6 @@ class ChatController extends State final timeline = this.timeline; if (timeline == null || timeline.events.isEmpty) { - // #Pangea - ErrorHandler.logError( - e: PangeaWarningError("Timeline is null or empty"), - s: StackTrace.current, - ); - // Pangea# return; } @@ -1114,8 +1107,14 @@ class ChatController extends State inputFocus.requestFocus(); } - void scrollToEventId(String eventId) async { - final eventIndex = timeline!.events.indexWhere((e) => e.eventId == eventId); + void scrollToEventId( + String eventId, { + bool highlightEvent = true, + }) async { + final eventIndex = timeline!.events + .where((event) => event.isVisibleInGui) + .toList() + .indexWhere((e) => e.eventId == eventId); if (eventIndex == -1) { setState(() { timeline = null; @@ -1131,11 +1130,14 @@ class ChatController extends State }); return; } - setState(() { - scrollToEventIdMarker = eventId; - }); + if (highlightEvent) { + setState(() { + scrollToEventIdMarker = eventId; + }); + } await scrollController.scrollToIndex( - eventIndex, + eventIndex + 1, + duration: FluffyThemes.animationDuration, preferPosition: AutoScrollPosition.middle, ); _updateScrollController(); diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 9b12f9904..405e16418 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -14,7 +14,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/widgets/chat/chat_floating_action_button.dart'; import 'package:fluffychat/utils/account_config.dart'; -import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/chat_settings_popup_menu.dart'; import 'package:fluffychat/widgets/connection_status_header.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -299,216 +298,232 @@ class ChatView extends StatelessWidget { ), ), SafeArea( - child: Column( - children: [ - Expanded( - child: GestureDetector( - onTap: controller.clearSingleSelectedEvent, - child: Builder( - builder: (context) { - if (controller.timeline == null) { - return const Center( - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ); - } - return ChatEventList( - controller: controller, - ); - }, - ), - ), - ), - if (controller.room.canSendDefaultMessages && - controller.room.membership == Membership.join) - Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2.5, - ), - alignment: Alignment.center, - child: Material( - clipBehavior: Clip.hardEdge, - color: Theme.of(context) - .colorScheme - // ignore: deprecated_member_use - .surfaceVariant, - borderRadius: const BorderRadius.all( - Radius.circular(24), - ), - child: controller.room.isAbandonedDMRoom == true - ? Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - children: [ - // #Pangea - if (controller.room.isRoomAdmin) - TextButton.icon( - style: TextButton.styleFrom( - padding: const EdgeInsets.all( - 16, - ), - foregroundColor: Theme.of(context) - .colorScheme - .error, - ), - icon: const Icon( - Icons.archive_outlined, - ), - onPressed: controller.archiveChat, - label: Text( - L10n.of(context)!.archive, - ), - ), - // Pangea# - TextButton.icon( - style: TextButton.styleFrom( - padding: const EdgeInsets.all( - 16, - ), - foregroundColor: Theme.of(context) - .colorScheme - .error, - ), - icon: const Icon( - // #Pangea - // Icons.archive_outlined, - Icons.arrow_forward, - // Pangea# - ), - onPressed: controller.leaveChat, - label: Text( - L10n.of(context)!.leave, - ), - ), - TextButton.icon( - style: TextButton.styleFrom( - padding: const EdgeInsets.all( - 16, - ), - ), - icon: const Icon( - Icons.forum_outlined, - ), - onPressed: controller.recreateChat, - label: Text( - L10n.of(context)!.reopenChat, - ), - ), - ], - ) - : - // #Pangea - null, - // Column( - // mainAxisSize: MainAxisSize.min, - // children: [ - // const ConnectionStatusHeader(), - // ITBar( - // choreographer: - // controller.choreographer, - // ), - // ReactionsPicker(controller), - // ReplyDisplay(controller), - // ChatInputRow(controller), - // ChatEmojiPicker(controller), - // ], - // ), - // Pangea# - ), - ), + child: // #Pangea - // Keep messages above minimum input bar height - SizedBox( - height: (PlatformInfos.isMobile ? 30 : 60), + Stack( + children: [ + // Pangea# + Column( + children: [ + Expanded( + child: GestureDetector( + onTap: controller.clearSingleSelectedEvent, + child: Builder( + builder: (context) { + if (controller.timeline == null) { + return const Center( + child: + CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ); + } + return ChatEventList( + controller: controller, + ); + }, + ), + ), + ), + if (controller.room.canSendDefaultMessages && + controller.room.membership == Membership.join) + Container( + margin: EdgeInsets.only( + bottom: bottomSheetPadding, + left: bottomSheetPadding, + right: bottomSheetPadding, + ), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + alignment: Alignment.center, + child: Material( + clipBehavior: Clip.hardEdge, + color: Theme.of(context) + .colorScheme + // ignore: deprecated_member_use + .surfaceVariant, + borderRadius: const BorderRadius.all( + Radius.circular(24), + ), + child: controller.room.isAbandonedDMRoom == + true + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + // #Pangea + if (controller.room.isRoomAdmin) + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.all( + 16, + ), + foregroundColor: + Theme.of(context) + .colorScheme + .error, + ), + icon: const Icon( + Icons.archive_outlined, + ), + onPressed: + controller.archiveChat, + label: Text( + L10n.of(context)!.archive, + ), + ), + // Pangea# + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.all( + 16, + ), + foregroundColor: + Theme.of(context) + .colorScheme + .error, + ), + icon: const Icon( + // #Pangea + // Icons.archive_outlined, + Icons.arrow_forward, + // Pangea# + ), + onPressed: controller.leaveChat, + label: Text( + L10n.of(context)!.leave, + ), + ), + TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.all( + 16, + ), + ), + icon: const Icon( + Icons.forum_outlined, + ), + onPressed: + controller.recreateChat, + label: Text( + L10n.of(context)!.reopenChat, + ), + ), + ], + ) + : + // #Pangea + null, + // Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // const ConnectionStatusHeader(), + // ITBar( + // choreographer: + // controller.choreographer, + // ), + // ReactionsPicker(controller), + // ReplyDisplay(controller), + // ChatInputRow(controller), + // ChatEmojiPicker(controller), + // ], + // ), + // Pangea# + ), + ), + // #Pangea + // Keep messages above minimum input bar height + const SizedBox( + height: 60, + ), + // Pangea# + ], + ), + // #Pangea + Positioned( + left: 0, + right: 0, + bottom: 16, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (!controller.selectMode) + Container( + margin: EdgeInsets.only( + bottom: 10, + left: bottomSheetPadding, + right: bottomSheetPadding, + ), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.4, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + StartIGCButton( + controller: controller, + ), + ChatFloatingActionButton( + controller: controller, + ), + ], + ), + ), + Container( + margin: EdgeInsets.only( + bottom: bottomSheetPadding, + left: bottomSheetPadding, + right: bottomSheetPadding, + ), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + alignment: Alignment.center, + child: Material( + clipBehavior: Clip.hardEdge, + color: Theme.of(context) + .colorScheme + .surfaceContainerHighest, + borderRadius: const BorderRadius.all( + Radius.circular(24), + ), + child: Column( + children: [ + const ConnectionStatusHeader(), + ITBar( + choreographer: controller.choreographer, + ), + ReactionsPicker(controller), + ReplyDisplay(controller), + ChatInputRow(controller), + ChatEmojiPicker(controller), + ], + ), + ), + ), + ], + ), ), // Pangea# ], ), + // #Pangea + // if (controller.dragging) + // Container( + // color: Theme.of(context) + // .scaffoldBackgroundColor + // .withOpacity(0.9), + // alignment: Alignment.center, + // child: const Icon( + // Icons.upload_outlined, + // size: 100, + // ), + // ), + // Pangea# ), - // #Pangea - // if (controller.dragging) - // Container( - // color: Theme.of(context) - // .scaffoldBackgroundColor - // .withOpacity(0.9), - // alignment: Alignment.center, - // child: const Icon( - // Icons.upload_outlined, - // size: 100, - // ), - // ), - Positioned( - left: 0, - right: 0, - bottom: 16, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (!controller.selectMode) - Container( - margin: EdgeInsets.only( - bottom: 10, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2.4, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - StartIGCButton( - controller: controller, - ), - ChatFloatingActionButton( - controller: controller, - ), - ], - ), - ), - Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2.5, - ), - alignment: Alignment.center, - child: Material( - clipBehavior: Clip.hardEdge, - color: Theme.of(context) - .colorScheme - .surfaceContainerHighest, - borderRadius: const BorderRadius.all( - Radius.circular(24), - ), - child: Column( - children: [ - const ConnectionStatusHeader(), - ITBar( - choreographer: controller.choreographer, - ), - ReactionsPicker(controller), - ReplyDisplay(controller), - ChatInputRow(controller), - ChatEmojiPicker(controller), - ], - ), - ), - ), - ], - ), - ), - // Pangea# ], ), ); diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index 84a802e54..61ce641fc 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -463,7 +463,7 @@ class InputBar extends StatelessWidget { debounceDuration: const Duration(milliseconds: 50), // show suggestions after 50ms idle time (default is 300) // #Pangea - key: controller!.choreographer.inputLayerLinkAndKey.key, + key: controller?.choreographer.inputLayerLinkAndKey.key, // builder: (context, controller, focusNode) => TextField( builder: (context, _, focusNode) => TextField( // Pangea# @@ -504,11 +504,11 @@ class InputBar extends StatelessWidget { onSubmitted!(text); }, // #Pangea - style: controller?.isMaxLength ?? false + style: controller?.exceededMaxLength ?? false ? const TextStyle(color: Colors.red) : null, onTap: () { - controller!.onInputTap( + controller?.onInputTap( context, fNode: focusNode, ); diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 7b7bbfd1c..050a1b272 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -6,7 +6,6 @@ import 'package:fluffychat/pages/settings/settings.dart'; import 'package:fluffychat/pangea/pages/class_settings/p_class_widgets/class_description_button.dart'; import 'package:fluffychat/pangea/utils/set_class_name.dart'; import 'package:fluffychat/pangea/widgets/class/add_space_toggles.dart'; -import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/widgets/app_lock.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -43,8 +42,9 @@ class ChatDetailsController extends State { // #Pangea final GlobalKey addToSpaceKey = GlobalKey(); - final GlobalKey addConversationBotKey = - GlobalKey(); + final GlobalKey + addConversationBotKey = + GlobalKey(); bool displayAddStudentOptions = false; void toggleAddStudentOptions() => diff --git a/lib/pages/chat_details/chat_details_view.dart b/lib/pages/chat_details/chat_details_view.dart index 24db0f582..0afa56086 100644 --- a/lib/pages/chat_details/chat_details_view.dart +++ b/lib/pages/chat_details/chat_details_view.dart @@ -138,17 +138,20 @@ class ChatDetailsView extends StatelessWidget { Avatar.defaultSize * 2.5, ), ), - child: Hero( - tag: controller.widget - .embeddedCloseButton != - null - ? 'embedded_content_banner' - : 'content_banner', - child: Avatar( - mxContent: room.avatar, - name: displayname, - size: Avatar.defaultSize * 2.5, - ), + // #Pangea + // Hero animation is causing weird visual glitch + // Probably not worth keeping + // child: Hero( + // tag: controller.widget + // .embeddedCloseButton != + // null + // ? 'embedded_content_banner' + // : 'content_banner', + // Pangea# + child: Avatar( + mxContent: room.avatar, + name: displayname, + size: Avatar.defaultSize * 2.5, ), ), if (!room.isDirectChat && diff --git a/lib/pages/chat_list/chat_list.dart b/lib/pages/chat_list/chat_list.dart index 0897a28eb..ec34a5b81 100644 --- a/lib/pages/chat_list/chat_list.dart +++ b/lib/pages/chat_list/chat_list.dart @@ -863,7 +863,7 @@ class ChatListController extends State if (space.canSendDefaultStates) { for (final roomId in selectedRoomIds) { - await space.pangeaSetSpaceChild(roomId); + await space.pangeaSetSpaceChild(roomId, suggested: true); } } // Pangea# @@ -911,11 +911,12 @@ class ChatListController extends State await client.roomsLoading; await client.accountDataLoading; await client.userDeviceKeysLoading; - if (client.prevBatch == null) { + // #Pangea + // See here for explanation of this change: https://github.com/pangeachat/client/pull/539 + // if (client.prevBatch == null) { + if (client.onSync.value?.nextBatch == null) { + // Pangea# await client.onSync.stream.first; - // #Pangea - pangeaController.startChatWithBotIfNotPresent(); - //Pangea# // Display first login bootstrap if enabled // #Pangea @@ -930,9 +931,19 @@ class ChatListController extends State } // #Pangea + await _initPangeaControllers(client); + // Pangea# + if (!mounted) return; + setState(() { + waitForFirstSync = true; + }); + } + + // #Pangea + Future _initPangeaControllers(Client client) async { if (mounted) { - // TODO try not to await so much GoogleAnalytics.analyticsUserUpdate(client.userID); + pangeaController.startChatWithBotIfNotPresent(); await pangeaController.subscriptionController.initialize(); await pangeaController.myAnalytics.initialize(); pangeaController.afterSyncAndFirstLoginInitialization(context); @@ -943,14 +954,9 @@ class ChatListController extends State ErrorHandler.logError( m: "didn't run afterSyncAndFirstLoginInitialization because not mounted", ); - // debugger(when: kDebugMode); } - // Pangea# - if (!mounted) return; - setState(() { - waitForFirstSync = true; - }); } + // Pangea# void cancelAction() { if (selectMode == SelectMode.share) { diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index ccc9aa9c4..a90c0d094 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -579,7 +579,12 @@ class _SpaceViewState extends State { : null, ); } - await activeSpace.setSpaceChild(roomId); + await activeSpace.setSpaceChild( + roomId, + // #Pangea + suggested: true, + // Pangea# + ); }, ); if (result.error != null) return; diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart index 19e4a524d..ef79c0e31 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet_view.dart @@ -370,15 +370,17 @@ class UserBottomSheetView extends StatelessWidget { onTap: () => controller .participantAction(UserBottomSheetAction.unban), ), - if (user != null && user.id != client.userID) - ListTile( - textColor: Theme.of(context).colorScheme.onErrorContainer, - iconColor: Theme.of(context).colorScheme.onErrorContainer, - title: Text(L10n.of(context)!.reportUser), - leading: const Icon(Icons.report_outlined), - onTap: () => controller - .participantAction(UserBottomSheetAction.report), - ), + // #Pangea + // if (user != null && user.id != client.userID) + // ListTile( + // textColor: Theme.of(context).colorScheme.onErrorContainer, + // iconColor: Theme.of(context).colorScheme.onErrorContainer, + // title: Text(L10n.of(context)!.reportUser), + // leading: const Icon(Icons.report_outlined), + // onTap: () => controller + // .participantAction(UserBottomSheetAction.report), + // ), + // Pangea# if (profileSearchError != null) ListTile( leading: const Icon( diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index dd2078fa2..c3cd0ab09 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -42,7 +42,6 @@ class Choreographer { bool isFetching = false; Timer? debounceTimer; - String? _roomId; ChoreoRecord choreoRecord = ChoreoRecord.newRecord; // last checked by IGC or translation String? _lastChecked; @@ -464,10 +463,7 @@ class Choreographer { setState(); } - get roomId => _roomId; - void setRoomId(String? roomId) { - _roomId = roomId ?? ''; - } + get roomId => chatController.roomId; bool get _useCustomInput => [ EditType.keyboard, diff --git a/lib/pangea/controllers/my_analytics_controller.dart b/lib/pangea/controllers/my_analytics_controller.dart index 5960fab11..199d640ab 100644 --- a/lib/pangea/controllers/my_analytics_controller.dart +++ b/lib/pangea/controllers/my_analytics_controller.dart @@ -233,11 +233,11 @@ class MyAnalyticsController { if (userL2 == null || _client.userID == null) return; // analytics room for the user and current target language - final Room analyticsRoom = await _client.getMyAnalyticsRoom(userL2!); + final Room? analyticsRoom = await _client.getMyAnalyticsRoom(userL2!); // get the last time analytics were updated for this room final DateTime? l2AnalyticsLastUpdated = - await analyticsRoom.analyticsLastUpdated( + await analyticsRoom?.analyticsLastUpdated( PangeaEventTypes.summaryAnalytics, _client.userID!, ); @@ -311,7 +311,7 @@ class MyAnalyticsController { // if there's new content to be sent, or if lastUpdated hasn't been // set yet for this room, send the analytics events if (summaryContent.isNotEmpty || l2AnalyticsLastUpdated == null) { - await analyticsRoom.sendSummaryAnalyticsEvent( + await analyticsRoom?.sendSummaryAnalyticsEvent( summaryContent, ); } @@ -351,7 +351,7 @@ class MyAnalyticsController { // ); if (recentConstructUses.isNotEmpty) { - await analyticsRoom.sendConstructsEvent( + await analyticsRoom?.sendConstructsEvent( recentConstructUses, ); } diff --git a/lib/pangea/controllers/user_controller.dart b/lib/pangea/controllers/user_controller.dart index 2ef0227e5..bf8ce3c11 100644 --- a/lib/pangea/controllers/user_controller.dart +++ b/lib/pangea/controllers/user_controller.dart @@ -83,6 +83,15 @@ class UserController extends BaseController { createdAt: DateTime.now(), ); final newProfile = Profile(userSettings: userSettings); + + // we don't use the pangea profile anymore, but we still need + // it to get access token for the choreographer, so create one + await PUserRepo.repoCreatePangeaUser( + userID: userId!, + dob: dob.toIso8601String(), + fullName: fullname!, + matrixAccessToken: _matrixAccessToken!, + ); await newProfile.saveProfileData(waitForDataInSync: true); } @@ -155,13 +164,25 @@ class UserController extends BaseController { _pangeaController.pStoreService.read(PLocalKey.access); if (localAccessToken == null || needNewJWT(localAccessToken)) { - final PangeaProfileResponse? userModel = - await PUserRepo.fetchPangeaUserInfo( + PangeaProfileResponse? userModel = await PUserRepo.fetchPangeaUserInfo( userID: userId!, matrixAccessToken: _matrixAccessToken!, ); + // Oops, some accounts were made without creating pangea profiles, so they + // don't have access to an access token yet. In that case, create a pangea profile. if (userModel?.access == null) { - throw ("Trying to get accessToken with null userModel"); + final dob = profile.userSettings.dateOfBirth; + if (dob != null) { + userModel = await PUserRepo.repoCreatePangeaUser( + userID: userId!, + dob: dob.toIso8601String(), + fullName: fullname!, + matrixAccessToken: _matrixAccessToken!, + ); + if (userModel?.access == null) { + throw ("Trying to get accessToken with null userModel"); + } + } } _pangeaController.pStoreService.save( PLocalKey.access, diff --git a/lib/pangea/enum/message_mode_enum.dart b/lib/pangea/enum/message_mode_enum.dart index 58753e5b5..11c96e10e 100644 --- a/lib/pangea/enum/message_mode_enum.dart +++ b/lib/pangea/enum/message_mode_enum.dart @@ -71,12 +71,11 @@ extension MessageModeExtension on MessageMode { switch (this) { case MessageMode.translation: case MessageMode.textToSpeech: - case MessageMode.practiceActivity: case MessageMode.definition: return event.messageType == MessageTypes.Text; case MessageMode.speechToText: return event.messageType == MessageTypes.Audio; - default: + case MessageMode.practiceActivity: return true; } } diff --git a/lib/pangea/extensions/client_extension/client_analytics_extension.dart b/lib/pangea/extensions/client_extension/client_analytics_extension.dart index 396e09f8d..381bbba4c 100644 --- a/lib/pangea/extensions/client_extension/client_analytics_extension.dart +++ b/lib/pangea/extensions/client_extension/client_analytics_extension.dart @@ -3,7 +3,7 @@ part of "client_extension.dart"; extension AnalyticsClientExtension on Client { /// Get the logged in user's analytics room matching /// a given langCode. If not present, create it. - Future _getMyAnalyticsRoom(String langCode) async { + Future _getMyAnalyticsRoom(String langCode) async { final Room? analyticsRoom = _analyticsRoomLocal(langCode); if (analyticsRoom != null) return analyticsRoom; return _makeAnalyticsRoom(langCode); @@ -35,7 +35,11 @@ extension AnalyticsClientExtension on Client { /// /// If the room does not appear immediately after creation, this method waits for it to appear in sync. /// Returns the created [Room] object. - Future _makeAnalyticsRoom(String langCode) async { + Future _makeAnalyticsRoom(String langCode) async { + if (userID == null || userID == BotName.byEnvironment) { + return null; + } + final String roomID = await createRoom( creationContent: { 'type': PangeaRoomTypes.analytics, @@ -74,6 +78,7 @@ extension AnalyticsClientExtension on Client { // migration function to change analytics rooms' vsibility to public // so they will appear in the space hierarchy Future _updateAnalyticsRoomVisibility() async { + if (userID == null || userID == BotName.byEnvironment) return; await Future.wait( allMyAnalyticsRooms.map((room) async { final visability = await getRoomVisibilityOnDirectory(room.id); @@ -91,6 +96,7 @@ extension AnalyticsClientExtension on Client { /// so teachers can join them via space hierarchy. /// Allows teachers to join analytics rooms without being invited. void _addAnalyticsRoomsToAllSpaces() { + if (userID == null || userID == BotName.byEnvironment) return; for (final Room room in allMyAnalyticsRooms) { room.addAnalyticsRoomToSpaces(); } @@ -100,6 +106,7 @@ extension AnalyticsClientExtension on Client { /// Handles case when students cannot add analytics room to space(s) /// so teacher is still able to get analytics data for this student void _inviteAllTeachersToAllAnalyticsRooms() { + if (userID == null || userID == BotName.byEnvironment) return; for (final Room room in allMyAnalyticsRooms) { room.inviteTeachersToAnalyticsRoom(); } diff --git a/lib/pangea/extensions/client_extension/client_extension.dart b/lib/pangea/extensions/client_extension/client_extension.dart index af66c7d1f..7af50d501 100644 --- a/lib/pangea/extensions/client_extension/client_extension.dart +++ b/lib/pangea/extensions/client_extension/client_extension.dart @@ -21,7 +21,7 @@ extension PangeaClient on Client { /// Get the logged in user's analytics room matching /// a given langCode. If not present, create it. - Future getMyAnalyticsRoom(String langCode) async => + Future getMyAnalyticsRoom(String langCode) async => await _getMyAnalyticsRoom(langCode); /// Get local analytics room for a given langCode and diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index fe052a5ec..5f32f92d1 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -140,8 +140,6 @@ class IGCTextData { matches.removeAt(matchIndex); for (final match in matches) { - final matchOffset = match.match.offset; - final matchLength = match.match.length; match.match.fullText = originalInput; if (match.match.offset > pangeaMatch.match.offset) { match.match.offset += replacement.length - pangeaMatch.match.length; @@ -305,7 +303,7 @@ class IGCTextData { // create a pointer to the current index in the original input // and iterate until the pointer has reached the end of the input int currentIndex = 0; - while (currentIndex < originalInput.characters.length - 1) { + while (currentIndex < originalInput.characters.length) { // check if the pointer is at a match, and if so, get the index of the match final int matchIndex = matchRanges.indexWhere( (range) => currentIndex >= range[0] && currentIndex < range[1], diff --git a/lib/pangea/pages/settings_learning/settings_learning_view.dart b/lib/pangea/pages/settings_learning/settings_learning_view.dart index 4182ab2d6..1a6576770 100644 --- a/lib/pangea/pages/settings_learning/settings_learning_view.dart +++ b/lib/pangea/pages/settings_learning/settings_learning_view.dart @@ -1,4 +1,3 @@ -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/models/space_model.dart'; import 'package:fluffychat/pangea/pages/settings_learning/settings_learning.dart'; import 'package:fluffychat/pangea/widgets/user_settings/country_picker_tile.dart'; @@ -32,15 +31,15 @@ class SettingsLearningView extends StatelessWidget { const SizedBox(height: 8), const Divider(height: 1), const SizedBox(height: 8), - if (controller.pangeaController.permissionsController.isUser18()) - SwitchListTile.adaptive( - activeColor: AppConfig.activeToggleColor, - title: Text(L10n.of(context)!.publicProfileTitle), - subtitle: Text(L10n.of(context)!.publicProfileDesc), - value: controller.pangeaController.userController.isPublic, - onChanged: (bool isPublicProfile) => - controller.setPublicProfile(isPublicProfile), - ), + // if (controller.pangeaController.permissionsController.isUser18()) + // SwitchListTile.adaptive( + // activeColor: AppConfig.activeToggleColor, + // title: Text(L10n.of(context)!.publicProfileTitle), + // subtitle: Text(L10n.of(context)!.publicProfileDesc), + // value: controller.pangeaController.userController.isPublic, + // onChanged: (bool isPublicProfile) => + // controller.setPublicProfile(isPublicProfile), + // ), ListTile( subtitle: Text(L10n.of(context)!.toggleToolSettingsDescription), ), diff --git a/lib/pangea/repo/user_repo.dart b/lib/pangea/repo/user_repo.dart index 47caaab0b..244e21c54 100644 --- a/lib/pangea/repo/user_repo.dart +++ b/lib/pangea/repo/user_repo.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:fluffychat/pangea/constants/model_keys.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:http/http.dart'; import '../models/user_model.dart'; @@ -10,6 +11,34 @@ import '../network/requests.dart'; import '../network/urls.dart'; class PUserRepo { + static Future repoCreatePangeaUser({ + required String userID, + required String dob, + required fullName, + required String matrixAccessToken, + }) async { + try { + final Requests req = Requests( + baseUrl: PApiUrls.baseAPI, + matrixAccessToken: matrixAccessToken, + ); + + final Map body = { + ModelKey.userFullName: fullName, + ModelKey.userPangeaUserId: userID, + ModelKey.userDateOfBirth: dob, + }; + final resp = await req.post( + url: PApiUrls.createUser, + body: body, + ); + return PangeaProfileResponse.fromJson(jsonDecode(resp.body)); + } catch (err, s) { + ErrorHandler.logError(e: err, s: s); + return null; + } + } + static Future fetchPangeaUserInfo({ required String userID, required String matrixAccessToken, diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index a638640e7..3c63c81fc 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -435,7 +435,11 @@ class MessageToolbarState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Flexible( + Container( + constraints: const BoxConstraints( + minWidth: 300, + maxHeight: 228, + ), child: SingleChildScrollView( child: AnimatedSize( duration: FluffyThemes.animationDuration, diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart index 553de7182..d5002ce2f 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_custom_zone.dart @@ -16,7 +16,6 @@ class ConversationBotCustomZone extends StatelessWidget { @override Widget build(BuildContext context) { - print(initialBotOptions.toJson()); return Column( children: [ const SizedBox(height: 12), diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart index 1c78fd030..e4054f4e5 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -1,22 +1,19 @@ import 'dart:developer'; +import 'package:fluffychat/pangea/constants/pangea_event_types.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/models/bot_options_model.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/widgets/common/bot_face_svg.dart'; -import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart'; -import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart'; -import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart'; +import 'package:fluffychat/widgets/matrix.dart'; 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:matrix/matrix.dart'; -import '../../../widgets/matrix.dart'; -import '../../constants/pangea_event_types.dart'; -import '../../extensions/pangea_room_extension/pangea_room_extension.dart'; -import '../../utils/error_handler.dart'; - class ConversationBotSettings extends StatefulWidget { final Room? room; final bool startOpen; @@ -36,6 +33,7 @@ class ConversationBotSettings extends StatefulWidget { class ConversationBotSettingsState extends State { late BotOptionsModel botOptions; late bool isOpen; + late bool isCreating; bool addBot = false; Room? parentSpace; @@ -56,6 +54,22 @@ class ConversationBotSettingsState extends State { parentSpace = widget.activeSpaceId != null ? Matrix.of(context).client.getRoomById(widget.activeSpaceId!) : null; + isCreating = widget.room == null; + } + + Future setBotOption() async { + if (widget.room == null) return; + try { + await Matrix.of(context).client.setRoomStateWithKey( + widget.room!.id, + PangeaEventTypes.botOptions, + '', + botOptions.toJson(), + ); + } catch (err, stack) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: stack); + } } Future updateBotOption(void Function() makeLocalChange) async { @@ -74,196 +88,191 @@ class ConversationBotSettingsState extends State { ); } - Future setBotOption() async { - if (widget.room == null) return; - try { - await Matrix.of(context).client.setRoomStateWithKey( - widget.room!.id, - PangeaEventTypes.botOptions, - '', - botOptions.toJson(), - ); - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: stack); - } - } - @override - Widget build(BuildContext context) => Column( + Widget build(BuildContext context) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( title: Text( - L10n.of(context)!.convoBotSettingsTitle, + isCreating + ? L10n.of(context)!.addConversationBot + : L10n.of(context)!.botConfig, style: TextStyle( color: Theme.of(context).colorScheme.secondary, fontWeight: FontWeight.bold, ), ), - subtitle: Text(L10n.of(context)!.convoBotSettingsDescription), + subtitle: isCreating + ? Text(L10n.of(context)!.addConversationBotDesc) + : null, leading: CircleAvatar( backgroundColor: Theme.of(context).scaffoldBackgroundColor, foregroundColor: Theme.of(context).textTheme.bodyLarge!.color, - child: const Icon(Icons.psychology_outlined), + child: const BotFace( + width: 30.0, + expression: BotExpression.idle, + ), ), - trailing: Icon( - isOpen - ? Icons.keyboard_arrow_down_outlined - : Icons.keyboard_arrow_right_outlined, - ), - onTap: () => setState(() => isOpen = !isOpen), - ), - if (isOpen) - AnimatedContainer( - duration: const Duration(milliseconds: 300), - height: isOpen ? null : 0, - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16), - child: ListTile( - title: Text( - L10n.of(context)!.addConversationBot, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - ), - ), - subtitle: Text(L10n.of(context)!.addConversationBotDesc), - leading: CircleAvatar( - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - foregroundColor: - Theme.of(context).textTheme.bodyLarge!.color, - child: const BotFace( - width: 30.0, - expression: BotExpression.idle, - ), - ), - trailing: ElevatedButton( - onPressed: () async { - final bool? confirm = await showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: addBot + trailing: isCreating + ? ElevatedButton( + onPressed: () async { + final bool? confirm = await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: addBot + ? Text( + L10n.of(context)! + .addConversationBotButtonTitleRemove, + ) + : Text( + L10n.of(context)! + .addConversationBotDialogTitleInvite, + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(L10n.of(context)!.cancel), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(!addBot); + }, + child: addBot ? Text( L10n.of(context)! - .addConversationBotButtonTitleRemove, + .addConversationBotDialogRemoveConfirmation, ) : Text( L10n.of(context)! - .addConversationBotDialogTitleInvite, + .addConversationBotDialogInviteConfirmation, ), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text(L10n.of(context)!.cancel), - ), - TextButton( - onPressed: () { - Navigator.of(context).pop(!addBot); - }, - child: addBot - ? Text( - L10n.of(context)! - .addConversationBotDialogRemoveConfirmation, - ) - : Text( - L10n.of(context)! - .addConversationBotDialogInviteConfirmation, - ), - ), - ], - ); - }, - ); - - if (confirm == true) { - setState(() => addBot = true); - widget.room?.invite(BotName.byEnvironment); - } else { - setState(() => addBot = false); - widget.room?.kick(BotName.byEnvironment); - } - }, - child: addBot - ? Text( - L10n.of(context)! - .addConversationBotButtonRemove, - ) - : Text( - L10n.of(context)! - .addConversationBotButtonInvite, ), - ), - ), - ), - if (addBot) ...[ - Padding( - padding: const EdgeInsets.fromLTRB(32, 16, 0, 0), - child: Text( - L10n.of(context)!.conversationLanguageLevel, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16), - child: LanguageLevelDropdown( - initialLevel: botOptions.languageLevel, - onChanged: (int? newValue) => updateBotOption(() { - botOptions.languageLevel = newValue!; - }), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(32, 16, 0, 0), - child: Text( - L10n.of(context)!.conversationBotModeSelectDescription, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16), - child: ConversationBotModeSelect( - initialMode: botOptions.mode, - onChanged: (String? mode) => updateBotOption( - () { - botOptions.mode = mode ?? "discussion"; - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(28, 0, 12, 0), - child: ConversationBotModeDynamicZone( - initialBotOptions: botOptions, - onChanged: (BotOptionsModel? newOptions) { - updateBotOption(() { - if (newOptions != null) { - botOptions = newOptions; - } - }); + ], + ); }, - ), - ), - const SizedBox(height: 16), - ], - ], - ), + ); + + if (confirm == true) { + setState(() => addBot = true); + widget.room?.invite(BotName.byEnvironment); + } else { + setState(() => addBot = false); + widget.room?.kick(BotName.byEnvironment); + } + }, + child: addBot + ? Text( + L10n.of(context)!.addConversationBotButtonRemove, + ) + : Text( + L10n.of(context)!.addConversationBotButtonInvite, + ), + ) + : const Icon(Icons.settings), + onTap: isCreating + ? null + : () async { + final bool? confirm = await showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, setState) => AlertDialog( + title: Text( + L10n.of(context)!.botConfig, + ), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: + const EdgeInsets.fromLTRB(0, 0, 0, 12), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + L10n.of(context)!.conversationBotStatus, + ), + Switch( + value: addBot, + onChanged: (value) { + setState( + () => addBot = value, + ); + }, + ), + ], + ), + ), + if (addBot) + Flexible( + child: SingleChildScrollView( + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context) + .colorScheme + .secondary, + width: 0.5, + ), + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), + ), + child: ConversationBotSettingsForm( + botOptions: botOptions, + ), + ), + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(L10n.of(context)!.cancel), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text( + L10n.of(context)! + .conversationBotConfigConfirmChange, + ), + ), + ], + ), + ); + }, + ); + if (confirm == true) { + if (addBot) { + await widget.room?.invite(BotName.byEnvironment); + } else { + await widget.room?.kick(BotName.byEnvironment); + } + updateBotOption(() { + botOptions = botOptions; + }); + } + }, + ), + if (isCreating && addBot) + ConversationBotSettingsForm( + botOptions: botOptions, ), ], - ); + ), + ); + } } diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart new file mode 100644 index 000000000..519245303 --- /dev/null +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings_form.dart @@ -0,0 +1,88 @@ +import 'package:fluffychat/pangea/models/bot_options_model.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_dynamic_zone.dart'; +import 'package:fluffychat/pangea/widgets/conversation_bot/conversation_bot_mode_select.dart'; +import 'package:fluffychat/pangea/widgets/space/language_level_dropdown.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +class ConversationBotSettingsForm extends StatefulWidget { + final BotOptionsModel botOptions; + + const ConversationBotSettingsForm({ + super.key, + required this.botOptions, + }); + + @override + ConversationBotSettingsFormState createState() => + ConversationBotSettingsFormState(); +} + +class ConversationBotSettingsFormState + extends State { + final formKey = GlobalKey(); + + late BotOptionsModel botOptions; + + @override + void initState() { + super.initState(); + botOptions = widget.botOptions; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: Text( + L10n.of(context)!.conversationLanguageLevel, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ), + LanguageLevelDropdown( + initialLevel: botOptions.languageLevel, + onChanged: (int? newValue) => { + setState(() { + botOptions.languageLevel = newValue!; + }), + }, + ), + Text( + L10n.of(context)!.conversationBotModeSelectDescription, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + ConversationBotModeSelect( + initialMode: botOptions.mode, + onChanged: (String? mode) => { + setState(() { + botOptions.mode = mode ?? "discussion"; + }), + }, + ), + Padding( + padding: const EdgeInsets.all(12), + child: ConversationBotModeDynamicZone( + initialBotOptions: botOptions, + onChanged: (BotOptionsModel? newOptions) { + if (newOptions != null) { + setState(() { + botOptions = newOptions; + }); + } + }, + ), + ), + ], + ); + } +} diff --git a/lib/pangea/widgets/igc/pangea_text_controller.dart b/lib/pangea/widgets/igc/pangea_text_controller.dart index 8fc136edd..63c5b3f94 100644 --- a/lib/pangea/widgets/igc/pangea_text_controller.dart +++ b/lib/pangea/widgets/igc/pangea_text_controller.dart @@ -27,7 +27,7 @@ class PangeaTextController extends TextEditingController { } static const int maxLength = 1000; - bool get isMaxLength => text.length == 1000; + bool get exceededMaxLength => text.length >= maxLength; bool forceKeepOpen = false; diff --git a/pubspec.yaml b/pubspec.yaml index 1e75fc4b5..2f2d47699 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ description: Learn a language while texting your friends. # Pangea# publish_to: none # On version bump also increase the build number for F-Droid -version: 1.21.1+3533 +version: 1.21.2+3534 environment: sdk: ">=3.0.0 <4.0.0"