diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 437b6d075..282acab40 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4233,5 +4233,9 @@ "reportContentIssueTitle": "Report content issue", "feedback": "Optional feedback", "reportContentIssueDescription": "Uh oh! AI can faciliate personalized learning experiences but... also hallucinates. Please provide any feedback you have and we'll try again.", - "clickTheWordAgainToDeselect": "Click the selected word to deselect it." + "clickTheWordAgainToDeselect": "Click the selected word to deselect it.", + "l2SupportNa": "Not Available", + "l2SupportAlpha": "Alpha", + "l2SupportBeta": "Beta", + "l2SupportFull": "Full" } \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index ed223b061..a51a86b37 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -470,6 +470,14 @@ class ChatController extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { + // #Pangea + // On iOS, if the toolbar is open and the app is closed, then the user goes + // back to do more toolbar activities, the toolbar buttons / selection don't + // update properly. So, when the user closes the app, close the toolbar overlay. + if (state == AppLifecycleState.paused) { + clearSelectedEvents(); + } + // Pangea# if (state != AppLifecycleState.resumed) return; setReadMarker(); } diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index d848cb6cc..3213d085f 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -391,18 +391,19 @@ class AudioPlayerState extends State { // #Pangea // const SizedBox(width: 8), const SizedBox(width: 5), + // SizedBox( + // width: 36, + // child: // Pangea# - SizedBox( - width: 36, - child: Text( - statusText, - style: TextStyle( - color: widget.color, - fontSize: 12, - ), + Text( + statusText, + style: TextStyle( + color: widget.color, + fontSize: 12, ), ), // #Pangea + // ), // const SizedBox(width: 8), // Badge( // isLabelVisible: audioPlayer != null, diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 589c0341c..12d38b1fc 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -384,9 +384,12 @@ class _SpaceViewState extends State { } else { roomId = await client.createGroupChat( groupName: names.first, - preset: activeSpace.joinRules == JoinRules.public - ? CreateRoomPreset.publicChat - : CreateRoomPreset.privateChat, + // #Pangea + // preset: activeSpace.joinRules == JoinRules.public + // ? CreateRoomPreset.publicChat + // : CreateRoomPreset.privateChat, + preset: CreateRoomPreset.publicChat, + // Pangea# visibility: activeSpace.joinRules == JoinRules.public ? sdk.Visibility.public : sdk.Visibility.private, diff --git a/lib/pages/new_space/new_space.dart b/lib/pages/new_space/new_space.dart index a9a051063..0b1faf674 100644 --- a/lib/pages/new_space/new_space.dart +++ b/lib/pages/new_space/new_space.dart @@ -152,9 +152,12 @@ class NewSpaceController extends State { avatarUrl ??= avatar == null ? null : await client.uploadContent(avatar); final spaceId = await client.createRoom( - preset: publicGroup - ? sdk.CreateRoomPreset.publicChat - : sdk.CreateRoomPreset.privateChat, + // #Pangea + // preset: publicGroup + // ? sdk.CreateRoomPreset.publicChat + // : sdk.CreateRoomPreset.privateChat, + preset: sdk.CreateRoomPreset.publicChat, + // Pangea# creationContent: {'type': RoomCreationTypes.mSpace}, visibility: publicGroup ? sdk.Visibility.public : null, // #Pangea diff --git a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart index 7a19043e8..786727cf8 100644 --- a/lib/pages/user_bottom_sheet/user_bottom_sheet.dart +++ b/lib/pages/user_bottom_sheet/user_bottom_sheet.dart @@ -255,7 +255,12 @@ class UserBottomSheetController extends State { sendError = null; }); try { - final roomId = await client.startDirectChat(userId); + final roomId = await client.startDirectChat( + userId, + // #Pangea + enableEncryption: false, + // Pangea# + ); if (!mounted) return; final room = client.getRoomById(roomId); if (room == null) { diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index e835359e6..1acb37332 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -105,6 +105,7 @@ class IgcController { return; } + choreographer.chatController.inputFocus.unfocus(); OverlayUtil.showPositionedCard( context: context, cardToShow: SpanCard( @@ -124,8 +125,8 @@ class IgcController { ), roomId: choreographer.roomId, ), - maxHeight: match.isITStart ? 260 : 400, - maxWidth: match.isITStart ? 350 : 400, + maxHeight: match.isITStart ? 260 : 350, + maxWidth: 350, transformTargetId: choreographer.inputTransformTargetKey, ); } diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 718e28beb..9b81eb960 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -308,6 +308,8 @@ class ITChoices extends StatelessWidget { ); return; } + + controller.choreographer.chatController.inputFocus.unfocus(); OverlayUtil.showPositionedCard( context: context, cardToShow: choiceFeedback == null diff --git a/lib/pangea/constants/bot_mode.dart b/lib/pangea/constants/bot_mode.dart index 96aa51e72..fbdd24b6f 100644 --- a/lib/pangea/constants/bot_mode.dart +++ b/lib/pangea/constants/bot_mode.dart @@ -1,4 +1,5 @@ class BotMode { + static const direct = "direct"; static const discussion = "discussion"; static const custom = "custom"; static const storyGame = "story_game"; diff --git a/lib/pangea/controllers/language_list_controller.dart b/lib/pangea/controllers/language_list_controller.dart index 77f4ae9e2..7380f3e3f 100644 --- a/lib/pangea/controllers/language_list_controller.dart +++ b/lib/pangea/controllers/language_list_controller.dart @@ -27,7 +27,9 @@ class PangeaLanguage { static Future initialize() async { try { _langList = await _getCachedFlags(); - if (await _shouldFetch || _langList.isEmpty) { + if (await _shouldFetch || + _langList.isEmpty || + _langList.every((lang) => !lang.l2)) { _langList = await LanguageRepo.fetchLanguages(); await _saveFlags(_langList); diff --git a/lib/pangea/controllers/pangea_controller.dart b/lib/pangea/controllers/pangea_controller.dart index 87552955f..fbb23845c 100644 --- a/lib/pangea/controllers/pangea_controller.dart +++ b/lib/pangea/controllers/pangea_controller.dart @@ -20,7 +20,6 @@ import 'package:fluffychat/pangea/controllers/subscription_controller.dart'; 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/client_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart'; import 'package:fluffychat/pangea/guard/p_vguard.dart'; import 'package:fluffychat/pangea/utils/bot_name.dart'; @@ -193,19 +192,53 @@ class PangeaController { void startChatWithBotIfNotPresent() { Future.delayed(const Duration(milliseconds: 10000), () async { // check if user is logged in - if (!matrixState.client.isLogged() || - (await matrixState.client.hasBotDM)) { + if (!matrixState.client.isLogged()) { return; } - try { - await matrixState.client.startDirectChat( - BotName.byEnvironment, - enableEncryption: false, - ); - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError(e: err, s: stack); + const List botDMs = []; + for (final room in matrixState.client.rooms) { + if (await room.isBotDM) { + botDMs.add(room); + } + } + + if (botDMs.isEmpty) { + try { + await matrixState.client.startDirectChat( + BotName.byEnvironment, + enableEncryption: false, + ); + } catch (err, stack) { + debugger(when: kDebugMode); + ErrorHandler.logError(e: err, s: stack); + } + return; + } + + final Room botDMWithLatestActivity = botDMs.reduce((a, b) { + if (a.timeline == null || b.timeline == null) { + return a; + } + final aLastEvent = a.timeline!.events.last; + final bLastEvent = b.timeline!.events.last; + return aLastEvent.originServerTs.isAfter(bLastEvent.originServerTs) + ? a + : b; + }); + + for (final room in botDMs) { + if (room.id != botDMWithLatestActivity.id) { + await room.leave(); + continue; + } + } + + final participants = await botDMWithLatestActivity.requestParticipants(); + final joinedParticipants = + participants.where((e) => e.membership == Membership.join).toList(); + if (joinedParticipants.length < 2) { + await botDMWithLatestActivity.invite(BotName.byEnvironment); } }); } diff --git a/lib/pangea/controllers/practice_activity_generation_controller.dart b/lib/pangea/controllers/practice_activity_generation_controller.dart index 0e021f205..1d3c7f7ae 100644 --- a/lib/pangea/controllers/practice_activity_generation_controller.dart +++ b/lib/pangea/controllers/practice_activity_generation_controller.dart @@ -118,6 +118,11 @@ class PracticeGenerationController { requestModel: req, ); + if (res.finished) { + debugPrint('Activity generation finished'); + return null; + } + // if the server points to an existing event, return that event if (res.existingActivityEventId != null) { final Event? existingEvent = diff --git a/lib/pangea/enum/l2_support_enum.dart b/lib/pangea/enum/l2_support_enum.dart new file mode 100644 index 000000000..c75534ea7 --- /dev/null +++ b/lib/pangea/enum/l2_support_enum.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +enum L2SupportEnum { + na, + alpha, + beta, + full, +} + +extension L2SupportEnumExtension on L2SupportEnum { + String get storageString { + switch (this) { + case L2SupportEnum.na: + return 'na'; + case L2SupportEnum.alpha: + return 'alpha'; + case L2SupportEnum.beta: + return 'beta'; + case L2SupportEnum.full: + return 'full'; + } + } + + L2SupportEnum fromStorageString(String storageString) { + switch (storageString) { + case 'na': + case 'L2SupportEnum.na': + return L2SupportEnum.na; + case 'alpha': + case 'L2SupportEnum.alpha': + return L2SupportEnum.alpha; + case 'beta': + case 'L2SupportEnum.beta': + return L2SupportEnum.beta; + case 'full': + case 'L2SupportEnum.full': + return L2SupportEnum.full; + default: + throw Exception('Unknown L2SupportEnum storage string: $storageString'); + } + } + + String toLocalizedString(BuildContext context) { + final l10n = L10n.of(context)!; + + switch (this) { + case L2SupportEnum.na: + return l10n.l2SupportNa; + case L2SupportEnum.alpha: + return l10n.l2SupportAlpha; + case L2SupportEnum.beta: + return l10n.l2SupportBeta; + case L2SupportEnum.full: + return l10n.l2SupportFull; + } + } + + Badge toBadge(BuildContext context) { + final theme = Theme.of(context); + Color color; + String label; + + switch (this) { + case L2SupportEnum.na: + color = theme.colorScheme.onSurface.withOpacity(0.4); // Muted grey + label = toLocalizedString(context); + break; + case L2SupportEnum.alpha: + color = theme.colorScheme.primary.withOpacity(0.4); // Subtle primary + label = toLocalizedString(context); + break; + case L2SupportEnum.beta: + color = + theme.colorScheme.secondary.withOpacity(0.4); // Subtle secondary + label = toLocalizedString(context); + break; + case L2SupportEnum.full: + color = theme.colorScheme.tertiary.withOpacity(0.4); // Subtle tertiary + label = toLocalizedString(context); + break; + } + + return Badge( + label: Text( + label, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurface.withOpacity(0.8), // Dimmed text + fontWeight: FontWeight.w500, + ), + ), + backgroundColor: color, + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + smallSize: 20, // A smaller badge for subtlety + ); + } +} diff --git a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart index bf9e10953..813fb7f1c 100644 --- a/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/pangea_room_extension.dart @@ -4,6 +4,7 @@ import 'dart:developer'; import 'package:adaptive_dialog/adaptive_dialog.dart'; import 'package:collection/collection.dart'; +import 'package:fluffychat/pangea/constants/bot_mode.dart'; import 'package:fluffychat/pangea/constants/class_default_values.dart'; import 'package:fluffychat/pangea/constants/language_constants.dart'; import 'package:fluffychat/pangea/constants/model_keys.dart'; @@ -253,7 +254,7 @@ extension PangeaRoom on Room { // bool isMadeForLang(String langCode) => _isMadeForLang(langCode); - Future get isBotRoom async => await _isBotRoom; + Future get botIsInRoom async => await _botIsInRoom; Future get isBotDM async => await _isBotDM; diff --git a/lib/pangea/extensions/pangea_room_extension/room_information_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_information_extension.dart index 857ac26e0..a88551336 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_information_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_information_extension.dart @@ -49,15 +49,14 @@ extension RoomInformationRoomExtension on Room { // creationContent?.tryGet(ModelKey.oldLangCode) == langCode; // } - Future get _isBotRoom async { + Future get _botIsInRoom async { final List participants = await requestParticipants(); return participants.any( (User user) => user.id == BotName.byEnvironment, ); } - Future get _isBotDM async => - (await isBotRoom) && getParticipants().length == 2; + Future get _isBotDM async => botOptions?.mode == BotMode.direct; bool get _isLocked { if (isDirectChat) return false; diff --git a/lib/pangea/models/language_model.dart b/lib/pangea/models/language_model.dart index be3cc71ba..37c93f504 100644 --- a/lib/pangea/models/language_model.dart +++ b/lib/pangea/models/language_model.dart @@ -1,6 +1,7 @@ import 'dart:developer'; import 'package:fluffychat/pangea/constants/language_constants.dart'; +import 'package:fluffychat/pangea/enum/l2_support_enum.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -12,15 +13,15 @@ class LanguageModel { final String languageFlag; final String displayName; final String? languageEmoji; - final bool l2; final bool l1; + final L2SupportEnum l2Support; LanguageModel({ required this.langCode, required this.languageFlag, required this.displayName, - required this.l2, required this.l1, + this.l2Support = L2SupportEnum.na, this.languageEmoji, }); @@ -37,9 +38,11 @@ class LanguageModel { displayName: _LanguageLocal.getDisplayName( code != LanguageKeys.unknownLanguage ? code : json['language_name'], ), - l2: json["l2"] ?? code.contains("es") || code.contains("en"), l1: json["l1"] ?? !code.contains("es") && !code.contains("en"), languageEmoji: json['language_emoji'], + l2Support: json['l2_support'] != null + ? L2SupportEnum.na.fromStorageString(json['l2_support']) + : L2SupportEnum.na, ); } @@ -47,11 +50,13 @@ class LanguageModel { 'language_code': langCode, 'language_name': displayName, 'language_flag': languageFlag, - 'l2': l2, 'l1': l1, 'language_emoji': languageEmoji, + 'l2_support': l2Support.storageString, }; + bool get l2 => l2Support != L2SupportEnum.na; + // Discuss with Jordan - adding langCode field to language objects as separate from displayName static String codeFromNameOrCode(String codeOrName, [String? url]) { if (codeOrName.isEmpty) return LanguageKeys.unknownLanguage; @@ -73,7 +78,6 @@ class LanguageModel { langCode: LanguageKeys.unknownLanguage, languageFlag: "", displayName: "Unknown", - l2: false, l1: false, ); @@ -81,16 +85,12 @@ class LanguageModel { displayName: context != null ? L10n.of(context)!.multiLingualSpace : "Multilingual Space", - l2: false, l1: false, langCode: LanguageKeys.multiLanguage, languageFlag: 'assets/colors.png', languageEmoji: "🌎", ); - // Discuss with Jordan - bool get hasContextualDefinitionSupport => l2; - String? getDisplayName(BuildContext context) { switch (langCode) { case 'ab': diff --git a/lib/pangea/models/practice_activities.dart/message_activity_request.dart b/lib/pangea/models/practice_activities.dart/message_activity_request.dart index db57d01aa..458619d20 100644 --- a/lib/pangea/models/practice_activities.dart/message_activity_request.dart +++ b/lib/pangea/models/practice_activities.dart/message_activity_request.dart @@ -156,6 +156,20 @@ class ActivityQualityFeedback { 'bad_activity': badActivity.toJson(), }; } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ActivityQualityFeedback && + other.feedbackText == feedbackText && + other.badActivity == badActivity; + } + + @override + int get hashCode { + return feedbackText.hashCode ^ badActivity.hashCode; + } } class MessageActivityRequest { @@ -231,7 +245,9 @@ class MessageActivityRequest { @override int get hashCode { - return messageId.hashCode ^ const ListEquality().hash(tokensWithXP); + return messageId.hashCode ^ + const ListEquality().hash(tokensWithXP) ^ + activityQualityFeedback.hashCode; } } diff --git a/lib/pangea/pages/find_partner/find_partner_view.dart b/lib/pangea/pages/find_partner/find_partner_view.dart index 032ed7daf..092c6eee1 100644 --- a/lib/pangea/pages/find_partner/find_partner_view.dart +++ b/lib/pangea/pages/find_partner/find_partner_view.dart @@ -234,6 +234,7 @@ class LanguageSelectionRow extends StatelessWidget { targetLanguage: isSource ? null : language, ); }, + isL2List: !isSource, initialLanguage: isSource ? controller.sourceLanguageSearch : controller.targetLanguageSearch, diff --git a/lib/pangea/utils/inline_tooltip.dart b/lib/pangea/utils/inline_tooltip.dart index f0d95c6f7..bef617b98 100644 --- a/lib/pangea/utils/inline_tooltip.dart +++ b/lib/pangea/utils/inline_tooltip.dart @@ -40,13 +40,15 @@ class InlineTooltip extends StatelessWidget { const SizedBox(width: 8), // Text in the middle Expanded( - child: Text( - instructionsEnum.body(context), - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface, - height: 1.5, + child: Center( + child: Text( + instructionsEnum.body(context), + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + height: 1.5, + ), + textAlign: TextAlign.left, ), - textAlign: TextAlign.left, ), ), // Close button on the right diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index b420ddcaa..2f7de2cf1 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; const double minCardHeight = 70; -class MessageToolbar extends StatefulWidget { +class MessageToolbar extends StatelessWidget { final PangeaMessageEvent pangeaMessageEvent; final MessageOverlayController overLayController; @@ -29,55 +29,45 @@ class MessageToolbar extends StatefulWidget { required this.overLayController, }); - @override - MessageToolbarState createState() => MessageToolbarState(); -} - -class MessageToolbarState extends State { - @override - void initState() { - super.initState(); - } - Widget get toolbarContent { final bool subscribed = MatrixState.pangeaController.subscriptionController.isSubscribed; if (!subscribed) { return MessageUnsubscribedCard( - controller: widget.overLayController, + controller: overLayController, ); } - switch (widget.overLayController.toolbarMode) { + switch (overLayController.toolbarMode) { case MessageMode.translation: return MessageTranslationCard( - messageEvent: widget.pangeaMessageEvent, - selection: widget.overLayController.selectedSpan, + messageEvent: pangeaMessageEvent, + selection: overLayController.selectedSpan, ); case MessageMode.textToSpeech: return MessageAudioCard( - messageEvent: widget.pangeaMessageEvent, - overlayController: widget.overLayController, + messageEvent: pangeaMessageEvent, + overlayController: overLayController, ); case MessageMode.speechToText: return MessageSpeechToTextCard( - messageEvent: widget.pangeaMessageEvent, + messageEvent: pangeaMessageEvent, ); case MessageMode.definition: - if (!widget.overLayController.isSelection) { + if (!overLayController.isSelection) { return const SelectToDefine(); } else { try { - final selectedText = widget.overLayController.targetText; + final selectedText = overLayController.targetText; return WordDataCard( word: selectedText, - wordLang: widget.pangeaMessageEvent.messageDisplayLangCode, - fullText: widget.pangeaMessageEvent.messageDisplayText, - fullTextLang: widget.pangeaMessageEvent.messageDisplayLangCode, + wordLang: pangeaMessageEvent.messageDisplayLangCode, + fullText: pangeaMessageEvent.messageDisplayText, + fullTextLang: pangeaMessageEvent.messageDisplayLangCode, hasInfo: true, - room: widget.overLayController.widget.chatController.room, + room: overLayController.widget.chatController.room, ); } catch (e, s) { debugger(when: kDebugMode); @@ -85,8 +75,8 @@ class MessageToolbarState extends State { e: "Error in WordDataCard", s: s, data: { - "word": widget.overLayController.targetText, - "fullText": widget.pangeaMessageEvent.messageDisplayText, + "word": overLayController.targetText, + "fullText": pangeaMessageEvent.messageDisplayText, }, ); return const SizedBox(); @@ -94,30 +84,25 @@ class MessageToolbarState extends State { } case MessageMode.practiceActivity: return PracticeActivityCard( - pangeaMessageEvent: widget.pangeaMessageEvent, - overlayController: widget.overLayController, + pangeaMessageEvent: pangeaMessageEvent, + overlayController: overLayController, ); default: debugger(when: kDebugMode); ErrorHandler.logError( e: "Invalid toolbar mode", s: StackTrace.current, - data: {"newMode": widget.overLayController.toolbarMode}, + data: {"newMode": overLayController.toolbarMode}, ); return const SizedBox(); } } - @override - void dispose() { - super.dispose(); - } - @override Widget build(BuildContext context) { return Material( key: MatrixState.pAnyState - .layerLinkAndKey('${widget.pangeaMessageEvent.eventId}-toolbar') + .layerLinkAndKey('${pangeaMessageEvent.eventId}-toolbar') .key, type: MaterialType.transparency, child: Row( diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart index 288149d76..fe85b48c7 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -46,7 +46,7 @@ class ConversationBotSettingsState extends State { botOptions = widget.room?.botOptions != null ? BotOptionsModel.fromJson(widget.room?.botOptions?.toJson()) : BotOptionsModel(); - widget.room?.isBotRoom.then((bool isBotRoom) { + widget.room?.botIsInRoom.then((bool isBotRoom) { setState(() { addBot = isBotRoom; }); @@ -260,7 +260,7 @@ class ConversationBotSettingsState extends State { botOptions = botOptions; }); final bool isBotRoomMember = - await widget.room?.isBotRoom ?? false; + await widget.room?.botIsInRoom ?? false; if (addBot && !isBotRoomMember) { await widget.room?.invite(BotName.byEnvironment); } else if (!addBot && isBotRoomMember) { diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index ab4f59451..442b1cf64 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -231,6 +231,10 @@ class MessagePracticeActivityCardState extends State { return; } + // clear the current activity and record + currentActivity = null; + currentCompletionRecord = null; + _fetchNewActivity( ActivityQualityFeedback( feedbackText: feedback, @@ -248,12 +252,13 @@ class MessagePracticeActivityCardState extends State { 'record': currentCompletionRecord, }, ); + + // clear the current activity and record + currentActivity = null; + currentCompletionRecord = null; + widget.overlayController.exitPracticeFlow(); }); - - // clear the current activity and record - currentActivity = null; - currentCompletionRecord = null; } RepresentationEvent? get representation => diff --git a/lib/pangea/widgets/user_settings/p_language_dialog.dart b/lib/pangea/widgets/user_settings/p_language_dialog.dart index b04b0e435..80dd7fa9d 100644 --- a/lib/pangea/widgets/user_settings/p_language_dialog.dart +++ b/lib/pangea/widgets/user_settings/p_language_dialog.dart @@ -63,6 +63,7 @@ Future pLanguageDialog( setState(() => selectedSourceLanguage = p0), initialLanguage: selectedSourceLanguage, languages: pangeaController.pLanguageStore.baseOptions, + isL2List: false, ), PQuestionContainer( title: L10n.of(parentContext)!.whatLanguageYouWantToLearn, @@ -72,6 +73,7 @@ Future pLanguageDialog( setState(() => selectedTargetLanguage = p0), initialLanguage: selectedTargetLanguage, languages: pangeaController.pLanguageStore.targetOptions, + isL2List: true, ), ], ), diff --git a/lib/pangea/widgets/user_settings/p_language_dropdown.dart b/lib/pangea/widgets/user_settings/p_language_dropdown.dart index 0793efdc2..19c2e71a5 100644 --- a/lib/pangea/widgets/user_settings/p_language_dropdown.dart +++ b/lib/pangea/widgets/user_settings/p_language_dropdown.dart @@ -1,5 +1,6 @@ // Flutter imports: +import 'package:fluffychat/pangea/enum/l2_support_enum.dart'; import 'package:fluffychat/pangea/models/language_model.dart'; import 'package:flutter/material.dart'; @@ -10,6 +11,7 @@ class PLanguageDropdown extends StatefulWidget { final LanguageModel initialLanguage; final Function(LanguageModel) onChange; final bool showMultilingual; + final bool isL2List; const PLanguageDropdown({ super.key, @@ -17,6 +19,7 @@ class PLanguageDropdown extends StatefulWidget { required this.onChange, required this.initialLanguage, this.showMultilingual = false, + required this.isL2List, }); @override @@ -98,6 +101,7 @@ class _PLanguageDropdownState extends State { value: LanguageModel.multiLingual(context), child: LanguageDropDownEntry( languageModel: LanguageModel.multiLingual(context), + isL2List: widget.isL2List, ), ), ...sortedLanguages.map( @@ -105,6 +109,7 @@ class _PLanguageDropdownState extends State { value: languageModel, child: LanguageDropDownEntry( languageModel: languageModel, + isL2List: widget.isL2List, ), ), ), @@ -118,9 +123,11 @@ class _PLanguageDropdownState extends State { class LanguageDropDownEntry extends StatelessWidget { final LanguageModel languageModel; + final bool isL2List; const LanguageDropDownEntry({ super.key, required this.languageModel, + required this.isL2List, }); @override @@ -144,6 +151,9 @@ class LanguageDropDownEntry extends StatelessWidget { overflow: TextOverflow.clip, textAlign: TextAlign.center, ), + const SizedBox(width: 10), + if (isL2List && languageModel.l2Support != L2SupportEnum.full) + languageModel.l2Support.toBadge(context), ], ), ); diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index 612f18f57..a98209ce0 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -21,6 +21,11 @@ extension IsStateExtension on Event { (isState || !AppConfig.hideAllStateEvents) && // #Pangea content.tryGet(ModelKey.transcription) == null && + // if sending of transcription fails, + // don't show it as a errored audio event in timeline. + ((unsigned?['extra_content'] + as Map?)?[ModelKey.transcription] == + null) && // hide unimportant state events (!AppConfig.hideUnimportantStateEvents || !isState || diff --git a/pubspec.yaml b/pubspec.yaml index d15833a5a..173fef435 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.4+3539 +version: 1.21.4+3540 environment: sdk: ">=3.0.0 <4.0.0"