diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index a9f2f1db0..c54d4e7b0 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4365,5 +4365,6 @@ "chooseVoice": "Choose a voice", "enterLanguageLevel": "Please enter a language level", "enterDiscussionTopic": "Please enter a discussion topic", - "selectBotChatMode": "Select chat mode" + "selectBotChatMode": "Select chat mode", + "messageNotInTargetLang": "Message not in target language" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 6be6edc91..9f5e656bd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/config/environment.dart'; import 'package:fluffychat/pangea/controllers/language_list_controller.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/pangea/utils/firebase_analytics.dart'; @@ -100,6 +101,19 @@ Future startGui(List clients, SharedPreferences store) async { await firstClient?.accountDataLoading; ErrorWidget.builder = (details) => FluffyChatErrorWidget(details); + + // #Pangea + // errors seems to happen a lot when users switch better production / staging + // while testing by accident. If the account is a production account but server is + // staging or vice versa, logout. + if (firstClient?.userID?.domain != null) { + final isStagingUser = firstClient!.userID!.domain!.contains("staging"); + final isStagingServer = Environment.isStaging; + if (isStagingServer != isStagingUser) { + await firstClient.logout(); + } + } + // Pangea# runApp(FluffyChatApp(clients: clients, pincode: pin, store: store)); } diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index cb1c86f5e..ebfd384bc 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -1419,7 +1419,7 @@ class ChatController extends State void onSelectMessage(Event event) { // #Pangea - if (choreographer.itController.isOpen) { + if (choreographer.itController.willOpen) { return; } // Pangea# diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index cc931ef12..57360d25d 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -321,6 +321,7 @@ class ChatInputRow extends StatelessWidget { // #Pangea // hintText: L10n.of(context)!.writeAMessage, hintText: hintText(), + disabledBorder: InputBorder.none, // Pangea# hintMaxLines: 1, border: InputBorder.none, diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index c0939f278..347d68786 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -478,6 +478,8 @@ class InputBar extends StatelessWidget { // builder: (context, controller, focusNode) => TextField( builder: (context, _, focusNode) => TextField( enableSuggestions: false, + readOnly: + controller != null && controller!.choreographer.isRunningIT, // Pangea# controller: controller, focusNode: focusNode, diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index df254d49c..6f65ea836 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -68,7 +68,7 @@ class Choreographer { } void send(BuildContext context) { - if (isFetching) return; + if (!canSendMessage) return; if (pangeaController.subscriptionController.subscriptionStatus == SubscriptionStatus.showPaywall) { @@ -92,7 +92,7 @@ class Choreographer { } Future _sendWithIGC(BuildContext context) async { - if (!igc.canSendMessage) { + if (!canSendMessage) { igc.showFirstMatch(context); return; } @@ -255,13 +255,16 @@ class Choreographer { } startLoading(); + + // if getting language assistance after finishing IT, + // reset the itController if (choreoMode == ChoreoMode.it && itController.isTranslationDone && !onlyTokensAndLanguageDetection) { - // debugger(when: kDebugMode); + itController.clear(); } - await (choreoMode == ChoreoMode.it && !itController.isTranslationDone + await (isRunningIT ? itController.getTranslationData(_useCustomInput) : igc.getIGCTextData( onlyTokensAndLanguageDetection: onlyTokensAndLanguageDetection, @@ -418,7 +421,7 @@ class Choreographer { setState(); } - giveInputFocus() { + void giveInputFocus() { Future.delayed(Duration.zero, () { chatController.inputFocus.requestFocus(); }); @@ -478,6 +481,9 @@ class Choreographer { bool get _noChange => _lastChecked != null && _lastChecked == _textController.text; + bool get isRunningIT => + choreoMode == ChoreoMode.it && !itController.isTranslationDone; + void startLoading() { _lastChecked = _textController.text; isFetching = true; @@ -505,8 +511,6 @@ class Choreographer { } } - bool get showIsError => !itController.isOpen && errorService.isError; - LayerLinkAndKey get itBarLinkAndKey => MatrixState.pAnyState.layerLinkAndKey(itBarTransformTargetKey); @@ -570,7 +574,7 @@ class Choreographer { return AssistanceState.noMessage; } - if (igc.igcTextData?.matches.isNotEmpty ?? false) { + if ((igc.igcTextData?.matches.isNotEmpty ?? false) || isRunningIT) { return AssistanceState.fetched; } @@ -584,4 +588,33 @@ class Choreographer { return AssistanceState.complete; } + + bool get canSendMessage { + // if there's an error, let them send. we don't want to block them from sending in this case + if (errorService.isError) return true; + + // if they're in IT mode, don't let them send + if (itEnabled && isRunningIT) return false; + + // if they've turned off IGC then let them send the message when they want + if (!isAutoIGCEnabled) return true; + + // if we're in the middle of fetching results, don't let them send + if (isFetching) return false; + + // they're supposed to run IGC but haven't yet, don't let them send + if (isAutoIGCEnabled && igc.igcTextData == null) return false; + + // if they have relevant matches, don't let them send + final hasITMatches = + igc.igcTextData!.matches.any((match) => match.isITStart); + final hasIGCMatches = + igc.igcTextData!.matches.any((match) => !match.isITStart); + if ((itEnabled && hasITMatches) || (igcEnabled && hasIGCMatches)) { + return false; + } + + // otherwise, let them send + return true; + } } diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index 1acb37332..ed770cca4 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -192,18 +192,4 @@ class IgcController { // Not sure why this is here // MatrixState.pAnyState.closeOverlay(); } - - bool get canSendMessage { - if (choreographer.isFetching) return false; - if (igcTextData == null || - choreographer.errorService.isError || - igcTextData!.matches.isEmpty) { - return true; - } - - return !((choreographer.itEnabled && - igcTextData!.matches.any((match) => match.isOutOfTargetMatch)) || - (choreographer.igcEnabled && - igcTextData!.matches.any((match) => !match.isOutOfTargetMatch))); - } } diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index b618386f8..e30f30b3d 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -68,9 +68,10 @@ class ITController { } void closeIT() { - //if they close it before completing, just put their text back - //PTODO - explore using last itStep - choreographer.textController.text = sourceText ?? ""; + // if the user hasn't gone through any IT steps, reset the text + if (completedITSteps.isEmpty && sourceText != null) { + choreographer.textController.text = sourceText!; + } clear(); } diff --git a/lib/pangea/choreographer/widgets/has_error_button.dart b/lib/pangea/choreographer/widgets/has_error_button.dart index 007820139..77a7170d9 100644 --- a/lib/pangea/choreographer/widgets/has_error_button.dart +++ b/lib/pangea/choreographer/widgets/has_error_button.dart @@ -1,15 +1,15 @@ +import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:flutter/material.dart'; -import '../../controllers/pangea_controller.dart'; import '../controllers/error_service.dart'; class ChoreographerHasErrorButton extends StatelessWidget { final ChoreoError error; - final PangeaController pangeaController; + final Choreographer choreographer; const ChoreographerHasErrorButton( - this.pangeaController, - this.error, { + this.error, + this.choreographer, { super.key, }); @@ -26,6 +26,7 @@ class ChoreographerHasErrorButton extends StatelessWidget { ), ), ); + choreographer.errorService.resetError(); } }, mini: true, diff --git a/lib/pangea/choreographer/widgets/it_feedback_card.dart b/lib/pangea/choreographer/widgets/it_feedback_card.dart index dd72f2457..f006e2552 100644 --- a/lib/pangea/choreographer/widgets/it_feedback_card.dart +++ b/lib/pangea/choreographer/widgets/it_feedback_card.dart @@ -103,7 +103,7 @@ class ITFeedbackCardController extends State { @override Widget build(BuildContext context) => error == null ? ITFeedbackCardView(controller: this) - : CardErrorWidget(error: error); + : CardErrorWidget(error: error!); } class ITFeedbackCardView extends StatelessWidget { diff --git a/lib/pangea/choreographer/widgets/send_button.dart b/lib/pangea/choreographer/widgets/send_button.dart index f5e358a31..6fba75395 100644 --- a/lib/pangea/choreographer/widgets/send_button.dart +++ b/lib/pangea/choreographer/widgets/send_button.dart @@ -56,7 +56,10 @@ class ChoreographerSendButtonState extends State { color: widget.controller.choreographer.assistanceState .stateColor(context), onPressed: () { - widget.controller.choreographer.send(context); + widget.controller.choreographer.canSendMessage + ? widget.controller.choreographer.send(context) + : widget.controller.choreographer.igc + .showFirstMatch(context); }, tooltip: L10n.of(context)!.send, ), diff --git a/lib/pangea/enum/message_mode_enum.dart b/lib/pangea/enum/message_mode_enum.dart index c8659f0fc..cfc42f63b 100644 --- a/lib/pangea/enum/message_mode_enum.dart +++ b/lib/pangea/enum/message_mode_enum.dart @@ -66,10 +66,12 @@ extension MessageModeExtension on MessageMode { } } - bool isValidMode(Event event) { + bool shouldShowAsToolbarButton(Event event) { switch (this) { case MessageMode.translation: + return event.messageType == MessageTypes.Text; case MessageMode.textToSpeech: + return event.messageType == MessageTypes.Text; case MessageMode.definition: return event.messageType == MessageTypes.Text; case MessageMode.speechToText: diff --git a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart index 73371b080..b0224f929 100644 --- a/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart +++ b/lib/pangea/extensions/pangea_room_extension/room_analytics_extension.dart @@ -10,7 +10,7 @@ extension AnalyticsRoomExtension on Room { return; } - if (!isRoomAdmin) return; + if (client.userID == null || !isRoomAdmin) return; final spaceHierarchy = await client.getSpaceHierarchy( id, maxDepth: 1, diff --git a/lib/pangea/widgets/chat/chat_floating_action_button.dart b/lib/pangea/widgets/chat/chat_floating_action_button.dart index 35ea1c3eb..ce4128700 100644 --- a/lib/pangea/widgets/chat/chat_floating_action_button.dart +++ b/lib/pangea/widgets/chat/chat_floating_action_button.dart @@ -76,8 +76,8 @@ class ChatFloatingActionButtonState extends State { } if (widget.controller.choreographer.errorService.error != null) { return ChoreographerHasErrorButton( - widget.controller.pangeaController, widget.controller.choreographer.errorService.error!, + widget.controller.choreographer, ); } diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index bd5b3da00..4c5cf86da 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -63,6 +63,11 @@ class MessageOverlayController extends State /// If we don't have any good activities for them, we'll decrease this number static const int neededActivities = 3; int activitiesLeftToComplete = neededActivities; + + bool get messageInUserL2 => + pangeaMessageEvent.messageDisplayLangCode == + MatrixState.pangeaController.languageController.userL2?.langCode; + PangeaMessageEvent get pangeaMessageEvent => widget._pangeaMessageEvent; final TtsController tts = TtsController(); @@ -160,6 +165,11 @@ class MessageOverlayController extends State toolbarMode = MessageMode.speechToText; return; } + // if (!messageInUserL2) { + // activitiesLeftToComplete = 0; + // toolbarMode = MessageMode.nullMode; + // return; + // } if (activitiesLeftToComplete > 0) { toolbarMode = MessageMode.practiceActivity; @@ -385,6 +395,10 @@ class MessageOverlayController extends State } Size? get messageSize { + if (messageRenderBox == null || !messageRenderBox!.hasSize) { + return null; + } + try { return messageRenderBox?.size; } catch (e, s) { @@ -394,6 +408,10 @@ class MessageOverlayController extends State } Offset? get messageOffset { + if (messageRenderBox == null || !messageRenderBox!.hasSize) { + return null; + } + try { return messageRenderBox?.localToGlobal(Offset.zero); } catch (e, s) { 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 aae3b3eeb..cf61c3d49 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -152,7 +152,7 @@ class MessageSpeechToTextCardState extends State { // done fetchig but not results means some kind of error if (speechToTextResponse == null) { return CardErrorWidget( - error: error, + error: error ?? "Failed to fetch speech to text", maxWidth: AppConfig.toolbarMinWidth, ); } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 071a99f69..bc6ed54bc 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -12,11 +12,12 @@ 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/tts_controller.dart'; import 'package:fluffychat/pangea/widgets/igc/word_data_card.dart'; +import 'package:fluffychat/pangea/widgets/message_display_card.dart'; import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart'; -import 'package:fluffychat/pangea/widgets/select_to_define.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'; const double minCardHeight = 70; @@ -32,7 +33,7 @@ class MessageToolbar extends StatelessWidget { required this.tts, }); - Widget get toolbarContent { + Widget toolbarContent(BuildContext context) { final bool subscribed = MatrixState.pangeaController.subscriptionController.isSubscribed; @@ -42,6 +43,18 @@ class MessageToolbar extends StatelessWidget { ); } + // Check if the message is in the user's second language + final bool messageInUserL2 = pangeaMessageEvent.messageDisplayLangCode == + MatrixState.pangeaController.languageController.userL2?.langCode; + + // If not in the target language show specific messsage + if (!messageInUserL2) { + return MessageDisplayCard( + displayText: + L10n.of(context)!.messageNotInTargetLang, // Pass the display text, + ); + } + switch (overLayController.toolbarMode) { case MessageMode.translation: return MessageTranslationCard( @@ -62,7 +75,9 @@ class MessageToolbar extends StatelessWidget { ); case MessageMode.definition: if (!overLayController.isSelection) { - return const SelectToDefine(); + return MessageDisplayCard( + displayText: L10n.of(context)!.selectToDefine, + ); } else { try { final selectedText = overLayController.targetText; @@ -127,7 +142,7 @@ class MessageToolbar extends StatelessWidget { child: SingleChildScrollView( child: AnimatedSize( duration: FluffyThemes.animationDuration, - child: toolbarContent, + child: toolbarContent(context), ), ), ); diff --git a/lib/pangea/widgets/chat/message_toolbar_buttons.dart b/lib/pangea/widgets/chat/message_toolbar_buttons.dart index 41cd47c6a..190a0fdff 100644 --- a/lib/pangea/widgets/chat/message_toolbar_buttons.dart +++ b/lib/pangea/widgets/chat/message_toolbar_buttons.dart @@ -22,7 +22,7 @@ class ToolbarButtons extends StatelessWidget { overlayController.pangeaMessageEvent; List get modes => MessageMode.values - .where((mode) => mode.isValidMode(pangeaMessageEvent.event)) + .where((mode) => mode.shouldShowAsToolbarButton(pangeaMessageEvent.event)) .toList(); static const double iconWidth = 36.0; diff --git a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart index 833ba8924..044fcc214 100644 --- a/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart +++ b/lib/pangea/widgets/conversation_bot/conversation_bot_settings.dart @@ -241,6 +241,8 @@ class ConversationBotSettingsDialogState updateFromTextControllers(); + Navigator.of(context).pop(botOptions); + final bool isBotRoomMember = await widget.room.botIsInRoom; if (addBot && !isBotRoomMember) { @@ -248,8 +250,6 @@ class ConversationBotSettingsDialogState } else if (!addBot && isBotRoomMember) { await widget.room.kick(BotName.byEnvironment); } - - Navigator.of(context).pop(botOptions); }, child: Text(L10n.of(context)!.confirm), ), diff --git a/lib/pangea/widgets/igc/card_error_widget.dart b/lib/pangea/widgets/igc/card_error_widget.dart index 8c810391b..bbe1bc63b 100644 --- a/lib/pangea/widgets/igc/card_error_widget.dart +++ b/lib/pangea/widgets/igc/card_error_widget.dart @@ -6,14 +6,14 @@ import 'package:fluffychat/pangea/widgets/igc/card_header.dart'; import 'package:flutter/material.dart'; class CardErrorWidget extends StatelessWidget { - final Object? error; + final Object error; final Choreographer? choreographer; final int? offset; final double? maxWidth; const CardErrorWidget({ super.key, - this.error, + required this.error, this.choreographer, this.offset, this.maxWidth, diff --git a/lib/pangea/widgets/igc/word_data_card.dart b/lib/pangea/widgets/igc/word_data_card.dart index ff494bb38..06e7a1ed7 100644 --- a/lib/pangea/widgets/igc/word_data_card.dart +++ b/lib/pangea/widgets/igc/word_data_card.dart @@ -167,7 +167,7 @@ class WordDataCardView extends StatelessWidget { Widget build(BuildContext context) { if (controller.wordNetError != null) { return CardErrorWidget( - error: controller.wordNetError, + error: controller.wordNetError!, maxWidth: AppConfig.toolbarMinWidth, ); } diff --git a/lib/pangea/widgets/select_to_define.dart b/lib/pangea/widgets/message_display_card.dart similarity index 69% rename from lib/pangea/widgets/select_to_define.dart rename to lib/pangea/widgets/message_display_card.dart index 968eaa147..8536f2c99 100644 --- a/lib/pangea/widgets/select_to_define.dart +++ b/lib/pangea/widgets/message_display_card.dart @@ -1,10 +1,12 @@ import 'package:fluffychat/pangea/utils/bot_style.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -class SelectToDefine extends StatelessWidget { - const SelectToDefine({ +class MessageDisplayCard extends StatelessWidget { + final String displayText; + + const MessageDisplayCard({ super.key, + required this.displayText, }); @override @@ -12,7 +14,7 @@ class SelectToDefine extends StatelessWidget { return Padding( padding: const EdgeInsets.fromLTRB(16, 20, 16, 16), child: Text( - L10n.of(context)!.selectToDefine, + displayText, style: BotStyle.text(context), textAlign: TextAlign.center, ), diff --git a/pubspec.yaml b/pubspec.yaml index 80906d77c..c4662cdbd 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.22.6+3556 +version: 1.22.9+3559 environment: sdk: ">=3.0.0 <4.0.0"