diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index de1b2f8e7..e8e2aa585 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -3149,7 +3149,7 @@ "generateVocabulary": "Generate vocabulary from title and description", "generatePrompts": "Generate prompts", "subscribe": "Subscribe", - "getAccess": "Unlock learning tools", + "getAccess": "Subscribe now!", "subscriptionDesc": "Messaging is free! Subscribe to unlock interactive translation, grammar checking and learning analytics.", "subscriptionManagement": "Subscription Management", "currentSubscription": "Current Subscription", @@ -3788,7 +3788,7 @@ } }, "freeTrialDesc": "New users recieve a one week free trial of Pangea Chat", - "activateTrial": "Activate Free Trial", + "activateTrial": "Free 7-Day Trial", "inNoSpaces": "You are not a member of any spaces", "successfullySubscribed": "You have successfully subscribed!", "clickToManageSubscription": "Click here to manage your subscription.", @@ -3968,11 +3968,11 @@ "seeOptions": "See options", "continuedWithoutSubscription": "Continue without subscribing", "trialPeriodExpired": "Your trial period has expired", - "selectToDefine": "Click a word to see its definition!", + "selectToDefine": "Click any word to see its definition!", "translations": "translations", "messageAudio": "message audio", "definitions": "definitions", - "subscribedToUnlockTools": "Subscribe to unlock language tools, including", + "subscribedToUnlockTools": "Subscribe to unlock interactive translation and grammar checking, audio playback, personalized practice activities, and learning analytics!", "more": "More", "translationTooltip": "Translate", "audioTooltip": "Play Audio", @@ -4162,7 +4162,7 @@ "placeholders": {} }, "changeAnalyticsView": "Change Analytics View", - "l1TranslationBody": "Oops! It looks like this message wasn't sent in your target language. Messages not sent in your target language will not be translated.", + "l1TranslationBody": "Messages in your base language will not be translated.", "continueText": "Continue", "deleteSubscriptionWarningTitle": "You have an active subscription", "deleteSubscriptionWarningBody": "Deleting your account will not automatically cancel your subscription.", @@ -4232,5 +4232,6 @@ "chatName": "Chat name", "reportContentIssueTitle": "Report content issue", "feedback": "Your feedback (optional)", - "reportContentIssueDescription": "Sorry! AI can make personalized experiences but also may have issues. Please provide any feedback you have and we'll generate a new activity." + "reportContentIssueDescription": "Sorry! AI can make personalized experiences but also may have issues. Please provide any feedback you have and we'll generate a new activity.", + "clickTheWordAgainToDeselect": "Click the selected word to deselect it." } \ No newline at end of file diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 632ff11ee..2d374f381 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -4329,7 +4329,6 @@ "pleaseTryAgainLaterOrChooseDifferentServer": "", "@pleaseTryAgainLaterOrChooseDifferentServer": {}, "createGroup": "", - "@createGroup": {}, "@noBackupWarning": {}, "kickUserDescription": "", "@kickUserDescription": {}, @@ -4490,7 +4489,6 @@ "translations": "traducciónes", "messageAudio": "mensaje de audio", "definitions": "definiciones", - "subscribedToUnlockTools": "Suscríbase para desbloquear herramientas lingüísticas, como", "clickMessageTitle": "¿Necesitas ayuda?", "clickMessageBody": "¡Lame un mensaje para obtener ayuda con el idioma! Haz clic y mantén presionado para reaccionar 😀", "more": "Más", @@ -4686,7 +4684,6 @@ "fetchingVersion": "Obteniendo versión...", "versionFetchError": "Error al obtener la versión", "connectedToStaging": "Conectado al entorno de pruebas", - "connectedToStaging": "Conectado al entorno de pruebas", "versionText": "Versión: {version}+{buildNumber}", "@versionText": { "description": "Texto que muestra la versión y el número de compilación de la aplicación.", @@ -4710,8 +4707,6 @@ }, "emojis": "Emojis", "@emojis": {}, - "createGroup": "Crear grupo", - "@createGroup": {}, "hydrateTorLong": "¿Exportó su sesión la última vez que estuvo en TOR? Impórtela rápidamente y continúe chateando.", "@hydrateTorLong": {}, "custom": "Personalizado", @@ -4737,6 +4732,5 @@ } }, "commandHint_googly": "Enviar unos ojos saltones", - "@commandHint_googly": {}, "reportContentIssue": "Problema de contenido" } \ No newline at end of file diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 47998523c..2dd0ae476 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -552,6 +552,7 @@ class ChatController extends State //#Pangea choreographer.stateListener.close(); choreographer.dispose(); + MatrixState.pAnyState.closeOverlay(); //Pangea# super.dispose(); } diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 389c5fad5..9053d3353 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -109,9 +109,14 @@ class Choreographer { // if not, let's get the tokens again and log an error if (igc.igcTextData?.tokens != null && PangeaToken.reconstructText(igc.igcTextData!.tokens) != currentText) { - debugger(when: kDebugMode); + if (kDebugMode) { + PangeaToken.reconstructText( + igc.igcTextData!.tokens, + debugWalkThrough: true, + ); + } ErrorHandler.logError( - m: "reconstructed text does not match current text", + m: "reconstructed text not working", s: StackTrace.current, data: { "igcTextData": igc.igcTextData?.toJson(), diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index c3ce49575..636415b8e 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -56,23 +56,10 @@ class ITController { choreographer.setState(); } - bool _closingHint = false; - Duration get animationSpeed => (_closingHint || !_willOpen) + Duration get animationSpeed => (!_willOpen) ? const Duration(milliseconds: 500) : const Duration(milliseconds: 2000); - void closeHint() { - _closingHint = true; - final String hintKey = InlineInstructions.translationChoices.toString(); - final instructionsController = choreographer.pangeaController.instructions; - instructionsController.turnOffInstruction(hintKey); - instructionsController.updateEnableInstructions(hintKey, true); - choreographer.setState(); - Future.delayed(const Duration(milliseconds: 500), () { - _closingHint = false; - }); - } - Future initializeIT(ITStartData itStartData) async { _willOpen = true; Future.delayed(const Duration(microseconds: 100), () { diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 768783dd0..2fc20531f 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -49,12 +49,6 @@ class ITBarState extends State { super.dispose(); } - bool get instructionsTurnedOff => - widget.choreographer.pangeaController.instructions - .wereInstructionsTurnedOff( - InlineInstructions.translationChoices.toString(), - ); - @override Widget build(BuildContext context) { return AnimatedSize( @@ -120,11 +114,12 @@ class ITBarState extends State { // const SizedBox(height: 40.0), OriginalText(controller: itController), const SizedBox(height: 7.0), - if (!instructionsTurnedOff) + if (!InstructionsEnum.translationChoices + .toggledOff(context)) InlineTooltip( - body: InlineInstructions.translationChoices - .body(context), - onClose: itController.closeHint, + instructionsEnum: + InstructionsEnum.translationChoices, + onClose: () => setState(() {}), ), IntrinsicHeight( child: Container( diff --git a/lib/pangea/enum/instructions_enum.dart b/lib/pangea/enum/instructions_enum.dart index 48544925e..e1a403526 100644 --- a/lib/pangea/enum/instructions_enum.dart +++ b/lib/pangea/enum/instructions_enum.dart @@ -1,5 +1,9 @@ +import 'dart:developer'; + +import 'package:fluffychat/pangea/utils/error_handler.dart'; import 'package:fluffychat/utils/platform_infos.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'; @@ -8,10 +12,22 @@ enum InstructionsEnum { clickMessage, blurMeansTranslate, tooltipInstructions, + speechToText, + l1Translation, + translationChoices, + clickAgainToDeselect, } extension InstructionsEnumExtension on InstructionsEnum { String title(BuildContext context) { + if (!context.mounted) { + ErrorHandler.logError( + e: Exception("Context not mounted"), + m: 'InstructionsEnumExtension.title for $this', + ); + debugger(when: kDebugMode); + return ''; + } switch (this) { case InstructionsEnum.itInstructions: return L10n.of(context)!.itInstructionsTitle; @@ -21,10 +37,31 @@ extension InstructionsEnumExtension on InstructionsEnum { return L10n.of(context)!.blurMeansTranslateTitle; case InstructionsEnum.tooltipInstructions: return L10n.of(context)!.tooltipInstructionsTitle; + case InstructionsEnum.clickAgainToDeselect: + case InstructionsEnum.speechToText: + case InstructionsEnum.l1Translation: + case InstructionsEnum.translationChoices: + ErrorHandler.logError( + e: Exception("No title for this instruction"), + m: 'InstructionsEnumExtension.title', + data: { + 'this': this, + }, + ); + debugger(when: kDebugMode); + return ""; } } String body(BuildContext context) { + if (!context.mounted) { + ErrorHandler.logError( + e: Exception("Context not mounted"), + m: 'InstructionsEnumExtension.body for $this', + ); + debugger(when: kDebugMode); + return ""; + } switch (this) { case InstructionsEnum.itInstructions: return L10n.of(context)!.itInstructionsBody; @@ -32,6 +69,14 @@ extension InstructionsEnumExtension on InstructionsEnum { return L10n.of(context)!.clickMessageBody; case InstructionsEnum.blurMeansTranslate: return L10n.of(context)!.blurMeansTranslateBody; + case InstructionsEnum.speechToText: + return L10n.of(context)!.speechToTextBody; + case InstructionsEnum.l1Translation: + return L10n.of(context)!.l1TranslationBody; + case InstructionsEnum.translationChoices: + return L10n.of(context)!.translationChoicesBody; + case InstructionsEnum.clickAgainToDeselect: + return L10n.of(context)!.clickTheWordAgainToDeselect; case InstructionsEnum.tooltipInstructions: return PlatformInfos.isMobile ? L10n.of(context)!.tooltipInstructionsMobileBody @@ -39,7 +84,15 @@ extension InstructionsEnumExtension on InstructionsEnum { } } - bool get toggledOff { + bool toggledOff(BuildContext context) { + if (!context.mounted) { + ErrorHandler.logError( + e: Exception("Context not mounted"), + m: 'InstructionsEnumExtension.toggledOff for $this', + ); + debugger(when: kDebugMode); + return false; + } final instructionSettings = MatrixState.pangeaController.userController.profile.instructionSettings; switch (this) { @@ -51,38 +104,14 @@ extension InstructionsEnumExtension on InstructionsEnum { return instructionSettings.showedBlurMeansTranslate; case InstructionsEnum.tooltipInstructions: return instructionSettings.showedTooltipInstructions; - } - } -} - -enum InlineInstructions { - speechToText, - l1Translation, - translationChoices, -} - -extension InlineInstructionsExtension on InlineInstructions { - String body(BuildContext context) { - switch (this) { - case InlineInstructions.speechToText: - return L10n.of(context)!.speechToTextBody; - case InlineInstructions.l1Translation: - return L10n.of(context)!.l1TranslationBody; - case InlineInstructions.translationChoices: - return L10n.of(context)!.translationChoicesBody; - } - } - - bool get toggledOff { - final instructionSettings = - MatrixState.pangeaController.userController.profile.instructionSettings; - switch (this) { - case InlineInstructions.speechToText: + case InstructionsEnum.speechToText: return instructionSettings.showedSpeechToTextTooltip; - case InlineInstructions.l1Translation: + case InstructionsEnum.l1Translation: return instructionSettings.showedL1TranslationTooltip; - case InlineInstructions.translationChoices: + case InstructionsEnum.translationChoices: return instructionSettings.showedTranslationChoicesTooltip; + case InstructionsEnum.clickAgainToDeselect: + return instructionSettings.showedClickAgainToDeselect; } } } diff --git a/lib/pangea/models/igc_text_data_model.dart b/lib/pangea/models/igc_text_data_model.dart index 180273e30..60497955b 100644 --- a/lib/pangea/models/igc_text_data_model.dart +++ b/lib/pangea/models/igc_text_data_model.dart @@ -117,6 +117,7 @@ class IGCTextData { ) async { //should be already added to choreoRecord //TODO - that should be done in the same function to avoid error potential + final PangeaMatch pangeaMatch = matches[matchIndex]; if (pangeaMatch.match.choices == null) { @@ -147,8 +148,8 @@ class IGCTextData { // for all tokens after the replacement, update their offsets for (int i = endIndex; i < tokens.length; i++) { - final PangeaToken token = tokens[i]; - token.text.offset += replacement.value.length - pangeaMatch.match.length; + tokens[i].text.offset += + replacement.value.length - pangeaMatch.match.length; } // clone the list for debugging purposes @@ -159,10 +160,16 @@ class IGCTextData { final String newFullText = PangeaToken.reconstructText(newTokens); - if (newFullText != originalInput) { - debugger(when: kDebugMode); + if (newFullText != originalInput && kDebugMode) { + PangeaToken.reconstructText(newTokens, debugWalkThrough: true); ErrorHandler.logError( - m: "reconstructed text does not match original input", + m: "reconstructed text not working", + s: StackTrace.current, + data: { + "originalInput": originalInput, + "newFullText": newFullText, + "match": pangeaMatch.match.toJson(), + }, ); } diff --git a/lib/pangea/models/pangea_token_model.dart b/lib/pangea/models/pangea_token_model.dart index b8a73b65a..e6b577c20 100644 --- a/lib/pangea/models/pangea_token_model.dart +++ b/lib/pangea/models/pangea_token_model.dart @@ -28,11 +28,17 @@ class PangeaToken { required this.morph, }); + /// reconstructs the text from the tokens + /// [tokens] - the tokens to reconstruct + /// [debugWalkThrough] - if true, will start the debugger static String reconstructText( - List tokens, [ + List tokens, { + bool debugWalkThrough = false, int startTokenIndex = 0, int endTokenIndex = -1, - ]) { + }) { + debugger(when: kDebugMode && debugWalkThrough); + if (endTokenIndex == -1) { endTokenIndex = tokens.length; } @@ -55,7 +61,6 @@ class PangeaToken { (i > 0 ? (subset[i - 1].text.offset + subset[i - 1].text.length) : 0); if (whitespace < 0) { - debugger(when: kDebugMode); whitespace = 0; } reconstruction += ' ' * whitespace + subset[i].text.content; diff --git a/lib/pangea/models/user_model.dart b/lib/pangea/models/user_model.dart index 34518f645..7ef85fbd5 100644 --- a/lib/pangea/models/user_model.dart +++ b/lib/pangea/models/user_model.dart @@ -189,6 +189,7 @@ class UserInstructions { bool showedSpeechToTextTooltip; bool showedL1TranslationTooltip; bool showedTranslationChoicesTooltip; + bool showedClickAgainToDeselect; UserInstructions({ this.showedItInstructions = false, @@ -198,12 +199,12 @@ class UserInstructions { this.showedSpeechToTextTooltip = false, this.showedL1TranslationTooltip = false, this.showedTranslationChoicesTooltip = false, + this.showedClickAgainToDeselect = false, }); factory UserInstructions.fromJson(Map json) => UserInstructions( - showedItInstructions: - json[InstructionsEnum.itInstructions.toString()] ?? false, + showedItInstructions: json[InstructionsEnum.itInstructions.toString()], showedClickMessage: json[InstructionsEnum.clickMessage.toString()] ?? false, showedBlurMeansTranslate: @@ -211,11 +212,13 @@ class UserInstructions { showedTooltipInstructions: json[InstructionsEnum.tooltipInstructions.toString()] ?? false, showedL1TranslationTooltip: - json[InlineInstructions.l1Translation.toString()] ?? false, + json[InstructionsEnum.l1Translation.toString()] ?? false, showedTranslationChoicesTooltip: - json[InlineInstructions.translationChoices.toString()] ?? false, + json[InstructionsEnum.translationChoices.toString()] ?? false, showedSpeechToTextTooltip: - json[InlineInstructions.speechToText.toString()] ?? false, + json[InstructionsEnum.speechToText.toString()] ?? false, + showedClickAgainToDeselect: + json[InstructionsEnum.clickAgainToDeselect.toString()] ?? false, ); Map toJson() { @@ -226,12 +229,13 @@ class UserInstructions { showedBlurMeansTranslate; data[InstructionsEnum.tooltipInstructions.toString()] = showedTooltipInstructions; - data[InlineInstructions.l1Translation.toString()] = + data[InstructionsEnum.l1Translation.toString()] = showedL1TranslationTooltip; - data[InlineInstructions.translationChoices.toString()] = + data[InstructionsEnum.translationChoices.toString()] = showedTranslationChoicesTooltip; - data[InlineInstructions.speechToText.toString()] = - showedSpeechToTextTooltip; + data[InstructionsEnum.speechToText.toString()] = showedSpeechToTextTooltip; + data[InstructionsEnum.clickAgainToDeselect.toString()] = + showedClickAgainToDeselect; return data; } @@ -258,20 +262,25 @@ class UserInstructions { as bool?) ?? false, showedL1TranslationTooltip: - (accountData[InlineInstructions.l1Translation.toString()] - ?.content[InlineInstructions.l1Translation.toString()] + (accountData[InstructionsEnum.l1Translation.toString()] + ?.content[InstructionsEnum.l1Translation.toString()] as bool?) ?? false, - showedTranslationChoicesTooltip: (accountData[ - InlineInstructions.translationChoices.toString()] - ?.content[InlineInstructions.translationChoices.toString()] + showedTranslationChoicesTooltip: + (accountData[InstructionsEnum.translationChoices.toString()] + ?.content[InstructionsEnum.translationChoices.toString()] + as bool?) ?? + false, + showedSpeechToTextTooltip: + (accountData[InstructionsEnum.speechToText.toString()] + ?.content[InstructionsEnum.speechToText.toString()] + as bool?) ?? + false, + showedClickAgainToDeselect: (accountData[ + InstructionsEnum.clickAgainToDeselect.toString()] + ?.content[InstructionsEnum.clickAgainToDeselect.toString()] as bool?) ?? false, - showedSpeechToTextTooltip: - (accountData[InlineInstructions.speechToText.toString()] - ?.content[InlineInstructions.speechToText.toString()] - as bool?) ?? - false, ); } } diff --git a/lib/pangea/utils/inline_tooltip.dart b/lib/pangea/utils/inline_tooltip.dart index f82a11682..f0d95c6f7 100644 --- a/lib/pangea/utils/inline_tooltip.dart +++ b/lib/pangea/utils/inline_tooltip.dart @@ -1,60 +1,71 @@ +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/enum/instructions_enum.dart'; +import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; class InlineTooltip extends StatelessWidget { - final String body; + final InstructionsEnum instructionsEnum; final VoidCallback onClose; const InlineTooltip({ super.key, - required this.body, + required this.instructionsEnum, required this.onClose, }); @override Widget build(BuildContext context) { - return Badge( - offset: const Offset(0, -7), - backgroundColor: Colors.transparent, - label: CircleAvatar( - radius: 10, - child: IconButton( - padding: EdgeInsets.zero, - icon: const Icon( - Icons.close_outlined, - size: 15, - ), - onPressed: onClose, - ), - ), + if (instructionsEnum.toggledOff(context)) { + return const SizedBox(); + } + + return Padding( + padding: const EdgeInsets.all(8.0), child: DecoratedBox( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(AppConfig.borderRadius), color: Theme.of(context).colorScheme.primary.withAlpha(20), ), child: Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - children: [ - const WidgetSpan( - child: Icon( - Icons.lightbulb, - size: 16, - ), - ), - const WidgetSpan( - child: SizedBox(width: 5), - ), - TextSpan( - text: body, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Lightbulb icon on the left + Icon( + Icons.lightbulb, + size: 20, + color: Theme.of(context).colorScheme.onSurface, + ), + 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, ), + textAlign: TextAlign.left, ), - ], - ), + ), + // Close button on the right + IconButton( + constraints: const BoxConstraints(), + icon: Icon( + Icons.close_outlined, + size: 20, + color: Theme.of(context).colorScheme.onSurface, + ), + onPressed: () { + MatrixState.pangeaController.instructions.setToggledOff( + instructionsEnum, + true, + ); + onClose(); + }, + ), + ], ), ), ), diff --git a/lib/pangea/utils/instructions.dart b/lib/pangea/utils/instructions.dart index 78dab6f6c..ca76b0629 100644 --- a/lib/pangea/utils/instructions.dart +++ b/lib/pangea/utils/instructions.dart @@ -1,6 +1,4 @@ -import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/enum/instructions_enum.dart'; -import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -24,54 +22,40 @@ class InstructionsController { /// Instruction popup has already been shown this session final Map _instructionsShown = {}; - /// Returns true if the user requested this popup not be shown again - bool? toggledOff(String key) { - final bool? instruction = InstructionsEnum.values - .firstWhereOrNull((value) => value.toString() == key) - ?.toggledOff; - final bool? tooltip = InlineInstructions.values - .firstWhereOrNull((value) => value.toString() == key) - ?.toggledOff; - return instruction ?? tooltip; - } - InstructionsController(PangeaController pangeaController) { _pangeaController = pangeaController; } - /// Returns true if the instructions were closed - /// or turned off by the user via the toggle switch - bool wereInstructionsTurnedOff(String key) { - return toggledOff(key) ?? _instructionsClosed[key] ?? false; - } - - void turnOffInstruction(String key) => _instructionsClosed[key] = true; - - void updateEnableInstructions( - String key, + void setToggledOff( + InstructionsEnum key, bool value, ) { _pangeaController.userController.updateProfile((profile) { - if (key == InstructionsEnum.itInstructions.toString()) { - profile.instructionSettings.showedItInstructions = value; - } - if (key == InstructionsEnum.clickMessage.toString()) { - profile.instructionSettings.showedClickMessage = value; - } - if (key == InstructionsEnum.blurMeansTranslate.toString()) { - profile.instructionSettings.showedBlurMeansTranslate = value; - } - if (key == InstructionsEnum.tooltipInstructions.toString()) { - profile.instructionSettings.showedTooltipInstructions = value; - } - if (key == InlineInstructions.speechToText.toString()) { - profile.instructionSettings.showedSpeechToTextTooltip = value; - } - if (key == InlineInstructions.l1Translation.toString()) { - profile.instructionSettings.showedL1TranslationTooltip = value; - } - if (key == InlineInstructions.translationChoices.toString()) { - profile.instructionSettings.showedTranslationChoicesTooltip = value; + switch (key) { + case InstructionsEnum.speechToText: + profile.instructionSettings.showedSpeechToTextTooltip = value; + break; + case InstructionsEnum.l1Translation: + profile.instructionSettings.showedL1TranslationTooltip = value; + break; + case InstructionsEnum.translationChoices: + profile.instructionSettings.showedTranslationChoicesTooltip = value; + break; + case InstructionsEnum.tooltipInstructions: + profile.instructionSettings.showedTooltipInstructions = value; + break; + case InstructionsEnum.itInstructions: + profile.instructionSettings.showedItInstructions = value; + break; + case InstructionsEnum.clickMessage: + profile.instructionSettings.showedClickMessage = value; + break; + case InstructionsEnum.blurMeansTranslate: + profile.instructionSettings.showedBlurMeansTranslate = value; + break; + case InstructionsEnum.clickAgainToDeselect: + profile.instructionSettings.showedClickAgainToDeselect = value; + break; } return profile; }); @@ -90,7 +74,7 @@ class InstructionsController { } _instructionsShown[key.toString()] = true; - if (wereInstructionsTurnedOff(key.toString())) { + if (key.toggledOff(context)) { return; } if (L10n.of(context) == null) { @@ -142,31 +126,6 @@ class InstructionsController { ), ); } - - /// Returns a widget that will be added to existing widget - /// which displays hint text defined in the enum extension - Widget getInstructionInlineTooltip( - BuildContext context, - InlineInstructions key, - VoidCallback onClose, - ) { - if (wereInstructionsTurnedOff(key.toString())) { - return const SizedBox(); - } - - if (L10n.of(context) == null) { - ErrorHandler.logError( - m: "null context in ITBotButton.showCard", - s: StackTrace.current, - ); - return const SizedBox(); - } - - return InlineTooltip( - body: InlineInstructions.speechToText.body(context), - onClose: onClose, - ); - } } /// User can toggle on to prevent Instruction Card @@ -196,12 +155,10 @@ class InstructionsToggleState extends State { return SwitchListTile.adaptive( activeColor: AppConfig.activeToggleColor, title: Text(L10n.of(context)!.doNotShowAgain), - value: pangeaController.instructions.wereInstructionsTurnedOff( - widget.instructionsKey.toString(), - ), + value: widget.instructionsKey.toggledOff(context), onChanged: ((value) async { - pangeaController.instructions.updateEnableInstructions( - widget.instructionsKey.toString(), + pangeaController.instructions.setToggledOff( + widget.instructionsKey, value, ); setState(() {}); diff --git a/lib/pangea/utils/match_copy.dart b/lib/pangea/utils/match_copy.dart index 86d784356..97d89bba4 100644 --- a/lib/pangea/utils/match_copy.dart +++ b/lib/pangea/utils/match_copy.dart @@ -91,7 +91,7 @@ class MatchCopy { } final String afterColon = splits.join(); - print("grammar rule ${match.match.rule!.id}"); + debugPrint("grammar rule ${match.match.rule!.id}"); switch (afterColon) { case MatchRuleIds.interactiveTranslation: diff --git a/lib/pangea/widgets/chat/message_audio_card.dart b/lib/pangea/widgets/chat/message_audio_card.dart index 02b637ee1..e9cf67dd7 100644 --- a/lib/pangea/widgets/chat/message_audio_card.dart +++ b/lib/pangea/widgets/chat/message_audio_card.dart @@ -1,6 +1,7 @@ import 'package:fluffychat/pages/chat/events/audio_player.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; import 'package:flutter/material.dart'; @@ -9,10 +10,12 @@ import 'package:matrix/matrix.dart'; class MessageAudioCard extends StatefulWidget { final PangeaMessageEvent messageEvent; + final MessageOverlayController overlayController; const MessageAudioCard({ super.key, required this.messageEvent, + required this.overlayController, }); @override @@ -70,6 +73,12 @@ class MessageAudioCardState extends State { @override void initState() { super.initState(); + + //once we have audio for words, we'll play that + if (widget.overlayController.isSelection) { + widget.overlayController.clearSelection(); + } + fetchAudio(); } @@ -80,20 +89,15 @@ class MessageAudioCardState extends State { child: _isLoading ? const ToolbarContentLoadingIndicator() : localAudioEvent != null || audioFile != null - ? Container( - constraints: const BoxConstraints( - maxWidth: 250, - ), - child: Column( - children: [ - AudioPlayerWidget( - localAudioEvent, - color: Theme.of(context).colorScheme.onPrimaryContainer, - matrixFile: audioFile, - autoplay: true, - ), - ], - ), + ? Column( + children: [ + AudioPlayerWidget( + localAudioEvent, + color: Theme.of(context).colorScheme.onPrimaryContainer, + matrixFile: audioFile, + autoplay: true, + ), + ], ) : const CardErrorWidget(), ); diff --git a/lib/pangea/widgets/chat/message_selection_overlay.dart b/lib/pangea/widgets/chat/message_selection_overlay.dart index 34ea42ffc..d94790925 100644 --- a/lib/pangea/widgets/chat/message_selection_overlay.dart +++ b/lib/pangea/widgets/chat/message_selection_overlay.dart @@ -87,7 +87,6 @@ class MessageOverlayController extends State /// In some cases, we need to exit the practice flow and let the user /// interact with the toolbar without completing activities void exitPracticeFlow() { - debugPrint('Exiting practice flow'); clearSelection(); activitiesLeftToComplete = 0; setState(() {}); 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 3c06a45f3..b80007897 100644 --- a/lib/pangea/widgets/chat/message_speech_to_text_card.dart +++ b/lib/pangea/widgets/chat/message_speech_to_text_card.dart @@ -4,6 +4,7 @@ import 'package:fluffychat/pangea/enum/instructions_enum.dart'; import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/speech_to_text_models.dart'; import 'package:fluffychat/pangea/utils/error_handler.dart'; +import 'package:fluffychat/pangea/utils/inline_tooltip.dart'; import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/widgets/common/icon_number_widget.dart'; import 'package:fluffychat/pangea/widgets/igc/card_error_widget.dart'; @@ -66,13 +67,6 @@ class MessageSpeechToTextCardState extends State { } void closeHint() { - MatrixState.pangeaController.instructions.turnOffInstruction( - InlineInstructions.speechToText.toString(), - ); - MatrixState.pangeaController.instructions.updateEnableInstructions( - InlineInstructions.speechToText.toString(), - true, - ); setState(() {}); } @@ -183,33 +177,18 @@ class MessageSpeechToTextCardState extends State { number: "${selectedToken?.confidence ?? speechToTextResponse!.transcript.confidence}%", toolTip: L10n.of(context)!.accuracy, - onPressed: () => MatrixState.pangeaController.instructions - .showInstructionsPopup( - context, - InstructionsEnum.tooltipInstructions, - widget.messageEvent.eventId, - true, - ), ), IconNumberWidget( icon: Icons.speed, number: wordsPerMinuteString != null ? "$wordsPerMinuteString" : "??", toolTip: L10n.of(context)!.wordsPerMinute, - onPressed: () => MatrixState.pangeaController.instructions - .showInstructionsPopup( - context, - InstructionsEnum.tooltipInstructions, - widget.messageEvent.eventId, - true, - ), ), ], ), - MatrixState.pangeaController.instructions.getInstructionInlineTooltip( - context, - InlineInstructions.speechToText, - closeHint, + InlineTooltip( + instructionsEnum: InstructionsEnum.speechToText, + onClose: () => setState(() => {}), ), ], ); diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index f204aec47..63a202f8e 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -43,9 +43,7 @@ class MessageToolbarState extends State { if (!subscribed) { return MessageUnsubscribedCard( - languageTool: widget.overLayController.toolbarMode.title(context), - mode: widget.overLayController.toolbarMode, - controller: this, + controller: widget.overLayController, ); } @@ -58,6 +56,7 @@ class MessageToolbarState extends State { case MessageMode.textToSpeech: return MessageAudioCard( messageEvent: widget.pangeaMessageEvent, + overlayController: widget.overLayController, ); case MessageMode.speechToText: return MessageSpeechToTextCard( diff --git a/lib/pangea/widgets/chat/message_translation_card.dart b/lib/pangea/widgets/chat/message_translation_card.dart index 081163558..9c648fa5b 100644 --- a/lib/pangea/widgets/chat/message_translation_card.dart +++ b/lib/pangea/widgets/chat/message_translation_card.dart @@ -32,14 +32,13 @@ class MessageTranslationCardState extends State { @override void initState() { - print('MessageTranslationCard initState'); + debugPrint('MessageTranslationCard initState'); super.initState(); loadTranslation(); } @override void didUpdateWidget(covariant MessageTranslationCard oldWidget) { - // debugger(when: kDebugMode); if (oldWidget.selection != widget.selection) { debugPrint('selection changed'); loadTranslation(); @@ -48,7 +47,6 @@ class MessageTranslationCardState extends State { } Future fetchRepresentationText() async { - // debugger(when: kDebugMode); if (l1Code == null) return; repEvent = widget.messageEvent @@ -114,36 +112,21 @@ class MessageTranslationCardState extends State { String? get l2Code => MatrixState.pangeaController.languageController.activeL2Code(); - void closeHint() { - MatrixState.pangeaController.instructions.turnOffInstruction( - InlineInstructions.l1Translation.toString(), - ); - MatrixState.pangeaController.instructions.updateEnableInstructions( - InlineInstructions.l1Translation.toString(), - true, - ); - setState(() {}); - } - /// Show warning if message's language code is user's L1 /// or if translated text is same as original text. /// Warning does not show if was previously closed - bool get showWarning { - if (MatrixState.pangeaController.instructions.wereInstructionsTurnedOff( - InlineInstructions.l1Translation.toString(), - )) return false; - + bool get notGoingToTranslate { final bool isWrittenInL1 = l1Code != null && widget.messageEvent.originalSent?.langCode == l1Code; final bool isTextIdentical = selectionTranslation != null && widget.messageEvent.originalSent?.text == selectionTranslation; - return (isWrittenInL1 || isTextIdentical) && widget.messageEvent.ownMessage; + return (isWrittenInL1 || isTextIdentical); } @override Widget build(BuildContext context) { - print('MessageTranslationCard build'); + debugPrint('MessageTranslationCard build'); if (!_fetchingTranslation && repEvent == null && selectionTranslation == null) { @@ -165,11 +148,15 @@ class MessageTranslationCardState extends State { repEvent!.text, style: BotStyle.text(context), ), - const SizedBox(height: 10), - if (showWarning) + if (notGoingToTranslate && widget.selection == null) InlineTooltip( - body: InlineInstructions.l1Translation.body(context), - onClose: closeHint, + instructionsEnum: InstructionsEnum.l1Translation, + onClose: () => setState(() {}), + ), + if (widget.selection != null) + InlineTooltip( + instructionsEnum: InstructionsEnum.clickAgainToDeselect, + onClose: () => setState(() {}), ), // if (widget.selection != null) ], diff --git a/lib/pangea/widgets/chat/message_unsubscribed_card.dart b/lib/pangea/widgets/chat/message_unsubscribed_card.dart index 00b9c352d..5363915c3 100644 --- a/lib/pangea/widgets/chat/message_unsubscribed_card.dart +++ b/lib/pangea/widgets/chat/message_unsubscribed_card.dart @@ -1,21 +1,15 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/utils/bot_style.dart'; -import 'package:fluffychat/pangea/widgets/chat/message_toolbar.dart'; +import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; -import '../../enum/message_mode_enum.dart'; - class MessageUnsubscribedCard extends StatelessWidget { - final String languageTool; - final MessageMode mode; - final MessageToolbarState controller; + final MessageOverlayController controller; const MessageUnsubscribedCard({ super.key, - required this.languageTool, - required this.mode, required this.controller, }); @@ -24,42 +18,52 @@ class MessageUnsubscribedCard extends StatelessWidget { final bool inTrialWindow = MatrixState.pangeaController.userController.inTrialWindow; - void onButtonPress() { - if (inTrialWindow) { - MatrixState.pangeaController.subscriptionController - .activateNewUserTrial(); - controller.widget.overLayController.updateToolbarMode(mode); - } else { - MatrixState.pangeaController.subscriptionController - .showPaywall(context); - } - } - - return Column( - children: [ - Text( - style: BotStyle.text(context), - "${L10n.of(context)!.subscribedToUnlockTools} $languageTool", - textAlign: TextAlign.center, - ), - const SizedBox(height: 10), - SizedBox( - width: double.infinity, - child: TextButton( - onPressed: onButtonPress, - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - (AppConfig.primaryColor).withOpacity(0.1), + return Container( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Text( + style: BotStyle.text(context), + L10n.of(context)!.subscribedToUnlockTools, + textAlign: TextAlign.center, + ), + if (inTrialWindow) ...[ + const SizedBox(height: 10), + SizedBox( + width: double.infinity, + child: TextButton( + onPressed: () { + MatrixState.pangeaController.subscriptionController + .activateNewUserTrial(); + controller.updateToolbarMode(controller.toolbarMode); + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + (AppConfig.primaryColor).withOpacity(0.1), + ), + ), + child: Text(L10n.of(context)!.activateTrial), ), ), - child: Text( - inTrialWindow - ? L10n.of(context)!.activateTrial - : L10n.of(context)!.getAccess, + ], + const SizedBox(height: 10), + SizedBox( + width: double.infinity, + child: TextButton( + onPressed: () { + MatrixState.pangeaController.subscriptionController + .showPaywall(context); + }, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + (AppConfig.primaryColor).withOpacity(0.1), + ), + ), + child: Text(L10n.of(context)!.getAccess), ), ), - ), - ], + ], + ), ); } } diff --git a/lib/pangea/widgets/chat/overlay_message_text.dart b/lib/pangea/widgets/chat/overlay_message_text.dart index b99028037..4d91ed359 100644 --- a/lib/pangea/widgets/chat/overlay_message_text.dart +++ b/lib/pangea/widgets/chat/overlay_message_text.dart @@ -98,7 +98,7 @@ class OverlayMessageTextState extends State { return TextSpan( recognizer: TapGestureRecognizer() ..onTap = () { - print( + debugPrint( 'tokenPosition.tokenIndex: ${tokenPosition.tokenIndex}', ); widget.overlayController.onClickOverlayMessageToken( diff --git a/lib/pangea/widgets/common/icon_number_widget.dart b/lib/pangea/widgets/common/icon_number_widget.dart index f677ea579..24307112c 100644 --- a/lib/pangea/widgets/common/icon_number_widget.dart +++ b/lib/pangea/widgets/common/icon_number_widget.dart @@ -6,7 +6,7 @@ class IconNumberWidget extends StatelessWidget { final Color? iconColor; final double? iconSize; final String? toolTip; - final VoidCallback onPressed; + final VoidCallback? onPressed; const IconNumberWidget({ super.key, @@ -15,7 +15,7 @@ class IconNumberWidget extends StatelessWidget { this.toolTip, this.iconColor, this.iconSize, - required this.onPressed, + this.onPressed, }); Widget _content(BuildContext context) { diff --git a/lib/pangea/widgets/igc/word_data_card.dart b/lib/pangea/widgets/igc/word_data_card.dart index 54ff49544..c35829264 100644 --- a/lib/pangea/widgets/igc/word_data_card.dart +++ b/lib/pangea/widgets/igc/word_data_card.dart @@ -411,7 +411,6 @@ class SelectToDefine extends StatelessWidget { return Center( child: Container( height: 80, - width: 200, padding: const EdgeInsets.all(8), child: Center( child: Text( diff --git a/lib/pangea/widgets/practice_activity/practice_activity_card.dart b/lib/pangea/widgets/practice_activity/practice_activity_card.dart index 067288157..5ebb4ec38 100644 --- a/lib/pangea/widgets/practice_activity/practice_activity_card.dart +++ b/lib/pangea/widgets/practice_activity/practice_activity_card.dart @@ -413,10 +413,11 @@ class MessagePracticeActivityCardState extends State { top: 0, right: 0, child: Opacity( - opacity: 0.8, // Slight opacity + opacity: 0.65, // Slight opacity child: Tooltip( message: L10n.of(context)!.reportContentIssueTitle, child: IconButton( + padding: const EdgeInsets.all(2), icon: const Icon(Icons.flag), iconSize: 16, onPressed: () => diff --git a/pubspec.yaml b/pubspec.yaml index 1c8287d72..d9b688f24 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+3537 +version: 1.21.4+3538 environment: sdk: ">=3.0.0 <4.0.0"