diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index a5ba7527d..92591e449 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5310,5 +5310,6 @@ "tokenInfoFeedbackDialogDesc": "AI makes mistakes. Please describe any issues you found with the information above.", "noPublicCoursesFound": "No public courses found. Would you like to create one?", "noCourseTemplatesFound": "We couldn't find any courses for your target language. You can chat with Pangea Bot in the meantime, and check back later for more courses.", - "botActivityJoinFailMessage": "Pangea Bot is taking a while to respond. Please try again later, or invite a friend." + "botActivityJoinFailMessage": "Pangea Bot is taking a while to respond. Please try again later, or invite a friend.", + "unsubscribedResponseError": "This feature requires a subscription" } diff --git a/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart b/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart index 0fbb72422..ddb06ba48 100644 --- a/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart +++ b/lib/pangea/activity_sessions/activity_session_chat/activity_stats_menu.dart @@ -308,38 +308,23 @@ class VocabTile extends StatelessWidget { OverlayUtil.showPositionedCard( overlayKey: "activity-vocab-${vocab.lemma}", context: context, - cardToShow: Material( - type: MaterialType.transparency, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: 4.0, - ), - borderRadius: const BorderRadius.all( - Radius.circular(AppConfig.borderRadius), - ), - ), - child: WordZoomWidget( - token: PangeaTokenText( - content: vocab.lemma, - length: vocab.lemma.characters.length, - offset: 0, - ), - construct: ConstructIdentifier( - lemma: vocab.lemma, - type: ConstructTypeEnum.vocab, - category: vocab.pos, - ), - langCode: langCode, - onClose: () { - MatrixState.pAnyState.closeOverlay( - "activity-vocab-${vocab.lemma}", - ); - }, - ), + cardToShow: WordZoomWidget( + token: PangeaTokenText( + content: vocab.lemma, + length: vocab.lemma.characters.length, + offset: 0, ), + construct: ConstructIdentifier( + lemma: vocab.lemma, + type: ConstructTypeEnum.vocab, + category: vocab.pos, + ), + langCode: langCode, + onClose: () { + MatrixState.pAnyState.closeOverlay( + "activity-vocab-${vocab.lemma}", + ); + }, ), transformTargetId: "activity-vocab-${vocab.lemma}", closePrevOverlay: false, diff --git a/lib/pangea/activity_sessions/activity_summary_widget.dart b/lib/pangea/activity_sessions/activity_summary_widget.dart index 614c76057..dfea580f2 100644 --- a/lib/pangea/activity_sessions/activity_summary_widget.dart +++ b/lib/pangea/activity_sessions/activity_summary_widget.dart @@ -202,42 +202,23 @@ class ActivitySummary extends StatelessWidget { overlayKey: "activity-summary-vocab-${vocab.lemma}", context: context, - cardToShow: Material( - type: MaterialType.transparency, - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - border: Border.all( - color: Theme.of(context) - .colorScheme - .primary, - width: 4.0, - ), - borderRadius: const BorderRadius.all( - Radius.circular( - AppConfig.borderRadius, - ), - ), - ), - child: WordZoomWidget( - token: PangeaTokenText( - content: vocab.lemma, - length: vocab.lemma.characters.length, - offset: 0, - ), - construct: ConstructIdentifier( - lemma: vocab.lemma, - type: ConstructTypeEnum.vocab, - category: vocab.pos, - ), - langCode: activity.req.targetLanguage, - onClose: () { - MatrixState.pAnyState.closeOverlay( - "activity-summary-vocab-${vocab.lemma}", - ); - }, - ), + cardToShow: WordZoomWidget( + token: PangeaTokenText( + content: vocab.lemma, + length: vocab.lemma.characters.length, + offset: 0, ), + construct: ConstructIdentifier( + lemma: vocab.lemma, + type: ConstructTypeEnum.vocab, + category: vocab.pos, + ), + langCode: activity.req.targetLanguage, + onClose: () { + MatrixState.pAnyState.closeOverlay( + "activity-summary-vocab-${vocab.lemma}", + ); + }, ), transformTargetId: "activity-summary-vocab-${vocab.lemma}", diff --git a/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart b/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart index 65d8df8e5..bc242d64d 100644 --- a/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart +++ b/lib/pangea/token_info_feedback/token_info_feedback_dialog.dart @@ -121,6 +121,17 @@ class _TokenInfoFeedbackDialogState extends State { return response.userFriendlyMessage; } + Future _submit() async { + final resp = await showFutureLoadingDialog( + context: context, + future: () => _submitFeedback(), + ); + + if (!resp.isError) { + Navigator.of(context).pop(resp.result!); + } + } + Future _updateLemmaInfo( PangeaToken token, LemmaInfoResponse response, @@ -164,112 +175,97 @@ class _TokenInfoFeedbackDialogState extends State { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20.0), ), - child: SizedBox( + child: Container( width: 325.0, + constraints: const BoxConstraints( + maxHeight: 600.0, + ), + padding: const EdgeInsets.all(12.0), child: Column( spacing: 20.0, mainAxisSize: MainAxisSize.min, children: [ // Header with title and close button - Padding( - padding: const EdgeInsets.all(12.0), - child: Row( - children: [ - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(), - ), - Expanded( - child: Text( - L10n.of(context).tokenInfoFeedbackDialogTitle, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - textAlign: TextAlign.center, + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + Expanded( + child: Text( + L10n.of(context).tokenInfoFeedbackDialogTitle, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, ), + textAlign: TextAlign.center, ), - const SizedBox( - width: 40.0, - height: 40.0, - child: Center( - child: Icon(Icons.flag_outlined), - ), + ), + const SizedBox( + width: 40.0, + height: 40.0, + child: Center( + child: Icon(Icons.flag_outlined), ), - ], - ), + ), + ], ), + // Content - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20.0, - ), - child: Column( - spacing: 20.0, - mainAxisSize: MainAxisSize.min, - children: [ - // Placeholder for word card - Container( - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).colorScheme.outline, - ), - borderRadius: BorderRadius.circular(8.0), - ), - child: Center( + Expanded( + child: SingleChildScrollView( + child: Column( + spacing: 20.0, + mainAxisSize: MainAxisSize.min, + children: [ + // Placeholder for word card + Center( child: WordZoomWidget( token: selectedToken.text, construct: selectedToken.vocabConstructID, langCode: widget.langCode, ), ), - ), - // Description text - Text( - L10n.of(context).tokenInfoFeedbackDialogDesc, - textAlign: TextAlign.center, - ), - // Feedback text field - TextField( - controller: _feedbackController, - decoration: InputDecoration( - hintText: L10n.of(context).feedbackHint, + // Description text + Text( + L10n.of(context).tokenInfoFeedbackDialogDesc, + textAlign: TextAlign.center, ), - keyboardType: TextInputType.multiline, - minLines: 1, - maxLines: 5, - ), - // Submit button - ValueListenableBuilder( - valueListenable: _feedbackController, - builder: (context, value, _) { - final isNotEmpty = value.text.isNotEmpty; - return ElevatedButton( - onPressed: isNotEmpty - ? () async { - final resp = await showFutureLoadingDialog( - context: context, - future: () => _submitFeedback(), - ); - - if (!resp.isError) { - Navigator.of(context).pop(resp.result!); - } - } - : null, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(L10n.of(context).feedbackButton), - ], - ), - ); - }, - ), - const SizedBox.shrink(), - ], + // Feedback text field + TextFormField( + controller: _feedbackController, + decoration: InputDecoration( + hintText: L10n.of(context).feedbackHint, + ), + keyboardType: TextInputType.multiline, + minLines: 1, + maxLines: 5, + onFieldSubmitted: _feedbackController.text.isNotEmpty + ? (_) => _submit() + : null, + ), + ], + ), ), ), + + ValueListenableBuilder( + valueListenable: _feedbackController, + builder: (context, value, _) { + final isNotEmpty = value.text.isNotEmpty; + return ElevatedButton( + onPressed: isNotEmpty ? _submit : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(L10n.of(context).feedbackButton), + ], + ), + ); + }, + ), ], ), ), diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index f0e40632f..fe928f2a9 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -35,7 +35,6 @@ import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dar import 'package:fluffychat/pangea/toolbar/models/speech_to_text_models.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/reading_assistance_content.dart'; import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; @@ -91,8 +90,6 @@ class MessageOverlayController extends State List? _highlightedTokens; bool initialized = false; - final GlobalKey wordZoomKey = GlobalKey(); - ReadingAssistanceMode? readingAssistanceMode; // default mode SpeechToTextModel? transcription; @@ -234,8 +231,6 @@ class MessageOverlayController extends State phase == SchedulerPhase.postFrameCallbacks)) { // It's safe to call setState immediately try { - wordZoomKey.currentState?.setState(() {}); - super.setState(fn); } catch (e, s) { ErrorHandler.logError( diff --git a/lib/pangea/toolbar/widgets/reading_assistance_content.dart b/lib/pangea/toolbar/widgets/reading_assistance_content.dart index 539ee968b..1ae64b5a3 100644 --- a/lib/pangea/toolbar/widgets/reading_assistance_content.dart +++ b/lib/pangea/toolbar/widgets/reading_assistance_content.dart @@ -1,25 +1,16 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; import 'package:matrix/matrix_api_lite/model/message_types.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; -import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_unsubscribed_card.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/toolbar_content_loading_indicator.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; const double minCardHeight = 70; -class ReadingAssistanceContent extends StatefulWidget { +class ReadingAssistanceContent extends StatelessWidget { final MessageOverlayController overlayController; final Duration animationDuration; @@ -30,51 +21,15 @@ class ReadingAssistanceContent extends StatefulWidget { }); @override - ReadingAssistanceContentState createState() => - ReadingAssistanceContentState(); -} - -class ReadingAssistanceContentState extends State { - Widget? toolbarContent(BuildContext context) { - final bool? subscribed = - MatrixState.pangeaController.subscriptionController.isSubscribed; - - if (subscribed != null && !subscribed) { - return const MessageUnsubscribedCard(); + Widget build(BuildContext context) { + if (![MessageTypes.Text, MessageTypes.Audio].contains( + overlayController.pangeaMessageEvent.event.messageType, + )) { + return const SizedBox(); } - if (widget.overlayController.practiceSelection?.hasHiddenWordActivity ?? - false) { - return PracticeActivityCard( - overlayController: widget.overlayController, - targetTokensAndActivityType: widget.overlayController.practiceSelection! - .nextActivity(ActivityTypeEnum.hiddenWordListening)!, - ); - } - - if (widget.overlayController.practiceSelection?.hasMessageMeaningActivity ?? - false) { - return PracticeActivityCard( - overlayController: widget.overlayController, - targetTokensAndActivityType: widget.overlayController.practiceSelection! - .nextActivity(ActivityTypeEnum.messageMeaning)!, - ); - } - - if (!widget.overlayController.initialized) { - return const ToolbarContentLoadingIndicator(); - } - - // final unlocked = widget.overlayController.toolbarMode - // .isUnlocked(widget.overlayController); - - // if (!unlocked) { - // return MessageModeLockedCard(controller: widget.overlayController); - // } - - final tokens = - widget.overlayController.pangeaMessageEvent.originalSent?.tokens; - final selectedToken = widget.overlayController.selectedToken; + final tokens = overlayController.pangeaMessageEvent.originalSent?.tokens; + final selectedToken = overlayController.selectedToken; final selectedTokenIndex = selectedToken != null ? tokens?.indexWhere( (t) => t.text == selectedToken.text, @@ -82,121 +37,33 @@ class ReadingAssistanceContentState extends State { -1 : -1; - switch (widget.overlayController.toolbarMode) { - case MessageMode.messageTranslation: - // return MessageTranslationCard( - // messageEvent: widget.pangeaMessageEvent, - // ); - case MessageMode.messageSpeechToText: - // return MessageSpeechToTextCard( - // messageEvent: widget.pangeaMessageEvent, - // ); - case MessageMode.noneSelected: - // return Padding( - // padding: const EdgeInsets.all(8), - // child: Text( - // L10n.of(context).clickWordsInstructions, - // textAlign: TextAlign.center, - // ), - // ); - case MessageMode.messageMeaning: - // return MessageMeaningCard(controller: widget.overlayController); - case MessageMode.listening: - // return MessageAudioCard( - // messageEvent: widget.overlayController.pangeaMessageEvent!, - // overlayController: widget.overlayController, - // setIsPlayingAudio: widget.overlayController.setIsPlayingAudio); - case MessageMode.practiceActivity: - case MessageMode.wordZoom: - case MessageMode.wordEmoji: - case MessageMode.wordMorph: - case MessageMode.wordMeaning: - if (widget.overlayController.selectedToken == null) { - return Padding( - padding: const EdgeInsets.all(16), - child: Text( - L10n.of(context).clickWordsInstructions, - textAlign: TextAlign.center, - ), - ); - } - return WordZoomWidget( - key: MatrixState.pAnyState - .layerLinkAndKey( - "word-zoom-card-${widget.overlayController.selectedToken!.text.uniqueKey}", - ) - .key, - token: widget.overlayController.selectedToken!.text, - construct: widget.overlayController.selectedToken!.vocabConstructID, - event: widget.overlayController.event, - wordIsNew: widget.overlayController - .isNewToken(widget.overlayController.selectedToken!), - onClose: () => widget.overlayController.updateSelectedSpan(null), - langCode: widget - .overlayController.pangeaMessageEvent.messageDisplayLangCode, - onDismissNewWordOverlay: () => - widget.overlayController.setState(() {}), - requestData: selectedTokenIndex >= 0 - ? TokenInfoFeedbackRequestData( - userId: Matrix.of(context).client.userID!, - roomId: widget.overlayController.event.room.id, - fullText: widget - .overlayController.pangeaMessageEvent.messageDisplayText, - detectedLanguage: widget.overlayController.pangeaMessageEvent - .messageDisplayLangCode, - tokens: tokens ?? [], - selectedToken: selectedTokenIndex, - wordCardL1: MatrixState.pangeaController.languageController - .activeL1Code()!, - ) - : null, - pangeaMessageEvent: widget.overlayController.pangeaMessageEvent, - ); - } - } - - @override - Widget build(BuildContext context) { - if (![MessageTypes.Text, MessageTypes.Audio].contains( - widget.overlayController.pangeaMessageEvent.event.messageType, - )) { - return const SizedBox(); - } - - return Material( - type: MaterialType.transparency, - child: SelectionArea( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - border: Border.all( - color: Theme.of(context).colorScheme.primary, - width: 4.0, - ), - borderRadius: const BorderRadius.all( - Radius.circular(AppConfig.borderRadius), - ), - ), - constraints: BoxConstraints( - minWidth: min( - AppConfig.toolbarMinWidth, - widget.overlayController.maxWidth, - ), - maxWidth: widget.overlayController.maxWidth, - ), - height: AppConfig.toolbarMaxHeight, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AnimatedSize( - duration: widget.animationDuration, - child: toolbarContent(context), - ), - ], - ), - ), - ), + return WordZoomWidget( + key: MatrixState.pAnyState + .layerLinkAndKey( + "word-zoom-card-${overlayController.selectedToken!.text.uniqueKey}", + ) + .key, + token: overlayController.selectedToken!.text, + construct: overlayController.selectedToken!.vocabConstructID, + event: overlayController.event, + wordIsNew: overlayController.isNewToken(overlayController.selectedToken!), + onClose: () => overlayController.updateSelectedSpan(null), + langCode: overlayController.pangeaMessageEvent.messageDisplayLangCode, + onDismissNewWordOverlay: () => overlayController.setState(() {}), + requestData: selectedTokenIndex >= 0 + ? TokenInfoFeedbackRequestData( + userId: Matrix.of(context).client.userID!, + roomId: overlayController.event.room.id, + fullText: overlayController.pangeaMessageEvent.messageDisplayText, + detectedLanguage: + overlayController.pangeaMessageEvent.messageDisplayLangCode, + tokens: tokens ?? [], + selectedToken: selectedTokenIndex, + wordCardL1: MatrixState.pangeaController.languageController + .activeL1Code()!, + ) + : null, + pangeaMessageEvent: overlayController.pangeaMessageEvent, ); } } diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index dfb8197b7..060f776ea 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/lemmas/lemma_reaction_picker.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_widget.dart'; import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_button.dart'; import 'package:fluffychat/pangea/token_info_feedback/token_info_feedback_request.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/message_unsubscribed_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/new_word_overlay.dart'; @@ -53,239 +54,280 @@ class WordZoomWidget extends StatelessWidget { @override Widget build(BuildContext context) { + final bool? subscribed = + MatrixState.pangeaController.subscriptionController.isSubscribed; final overlayColor = Theme.of(context).scaffoldBackgroundColor; - return Stack( - children: [ - Container( - height: AppConfig.toolbarMaxHeight - 8, - padding: const EdgeInsets.all(12.0), - constraints: const BoxConstraints( - maxWidth: AppConfig.toolbarMinWidth, - ), - child: CompositedTransformTarget( - link: layerLink, - child: SingleChildScrollView( - child: Column( - spacing: 12.0, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - onClose != null - ? SizedBox( - width: 24.0, - height: 24.0, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onClose, - child: const Icon( - Icons.close, - size: 16.0, - ), - ), - ), - ) - : const SizedBox( - width: 24.0, - height: 24.0, - ), - Flexible( - child: Text( - token.content, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 28.0, - fontWeight: FontWeight.w600, - height: 1.2, - color: - Theme.of(context).brightness == Brightness.light - ? AppConfig.yellowDark - : AppConfig.yellowLight, - ), - ), - ), - requestData != null && pangeaMessageEvent != null - ? TokenInfoFeedbackButton( - requestData: requestData!, - langCode: langCode, - event: pangeaMessageEvent!, - onUpdate: () { - // close the zoom when updating - if (onClose != null) { - onClose!(); - } - }, - ) - : const SizedBox( - width: 24.0, - height: 24.0, - ), - ], - ), - LemmaMeaningBuilder( - langCode: langCode, - constructId: construct, - builder: (context, controller) { - if (controller.editMode) { - return Column( - children: [ - Text( - "${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning( - construct.lemma, - construct.category, - )}", - textAlign: TextAlign.center, - style: const TextStyle( - fontStyle: FontStyle.italic, - ), - ), - const SizedBox(height: 10), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - ), - child: TextField( - minLines: 1, - maxLines: 3, - controller: controller.controller, - decoration: InputDecoration( - hintText: controller.lemmaInfo?.meaning, - ), - ), - ), - const SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => - controller.toggleEditMode(false), - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - ), - child: Text(L10n.of(context).cancel), - ), - const SizedBox(width: 10), - ElevatedButton( - onPressed: () => controller.controller.text != - controller.lemmaInfo?.meaning && - controller.controller.text.isNotEmpty - ? controller.editLemmaMeaning( - controller.controller.text, - ) - : null, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - padding: const EdgeInsets.symmetric( - horizontal: 10, - ), - ), - child: Text(L10n.of(context).saveChanges), - ), - ], - ), - ], - ); - } - return Column( - spacing: 12.0, - mainAxisSize: MainAxisSize.min, - children: [ - if (MatrixState.pangeaController.languageController - .showTranscription) - PhoneticTranscriptionWidget( - text: token.content, - textLanguage: PLanguageStore.byLangCode( - langCode, - ) ?? - LanguageModel.unknown, - style: const TextStyle(fontSize: 14.0), - iconSize: 24.0, - ) - else - WordAudioButton( - text: token.content, - uniqueID: "lemma-content-${token.content}", - langCode: langCode, - iconSize: 24.0, - ), - LemmaReactionPicker( - emojis: controller.lemmaInfo?.emoji ?? [], - loading: controller.isLoading, - event: event, - ), - if (controller.error != null) - ErrorIndicator( - message: L10n.of(context).errorFetchingDefinition, - style: const TextStyle(fontSize: 14.0), - ) - else if (controller.isLoading || - controller.lemmaInfo == null) - const CircularProgressIndicator.adaptive() - else - GestureDetector( - onLongPress: () => - controller.toggleEditMode(true), - onDoubleTap: () => - controller.toggleEditMode(true), - child: construct.lemma.toLowerCase() == - token.content.toLowerCase() - ? Text( - controller.lemmaInfo!.meaning, - style: const TextStyle(fontSize: 14.0), - textAlign: TextAlign.center, - ) - : RichText( - text: TextSpan( - style: DefaultTextStyle.of(context) - .style - .copyWith( - fontSize: 14.0, - ), - children: [ - TextSpan(text: construct.lemma), - const WidgetSpan( - child: SizedBox(width: 8.0), - ), - const TextSpan(text: ":"), - const WidgetSpan( - child: SizedBox(width: 8.0), - ), - TextSpan( - text: controller.lemmaInfo!.meaning, - ), - ], + final Widget content = subscribed != null && !subscribed + ? const MessageUnsubscribedCard() + : Stack( + children: [ + Container( + height: AppConfig.toolbarMaxHeight - 8, + padding: const EdgeInsets.all(12.0), + constraints: const BoxConstraints( + maxWidth: AppConfig.toolbarMinWidth, + ), + child: CompositedTransformTarget( + link: layerLink, + child: SingleChildScrollView( + child: Column( + spacing: 12.0, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + onClose != null + ? SizedBox( + width: 24.0, + height: 24.0, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onClose, + child: const Icon( + Icons.close, + size: 16.0, + ), ), ), + ) + : const SizedBox( + width: 24.0, + height: 24.0, + ), + Flexible( + child: Text( + token.content, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 28.0, + fontWeight: FontWeight.w600, + height: 1.2, + color: Theme.of(context).brightness == + Brightness.light + ? AppConfig.yellowDark + : AppConfig.yellowLight, + ), + ), ), - ], - ); - }, + requestData != null && pangeaMessageEvent != null + ? TokenInfoFeedbackButton( + requestData: requestData!, + langCode: langCode, + event: pangeaMessageEvent!, + onUpdate: () { + // close the zoom when updating + if (onClose != null) { + onClose!(); + } + }, + ) + : const SizedBox( + width: 24.0, + height: 24.0, + ), + ], + ), + LemmaMeaningBuilder( + langCode: langCode, + constructId: construct, + builder: (context, controller) { + if (controller.editMode) { + return Column( + children: [ + Text( + "${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning( + construct.lemma, + construct.category, + )}", + textAlign: TextAlign.center, + style: const TextStyle( + fontStyle: FontStyle.italic, + ), + ), + const SizedBox(height: 10), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + ), + child: TextField( + minLines: 1, + maxLines: 3, + controller: controller.controller, + decoration: InputDecoration( + hintText: controller.lemmaInfo?.meaning, + ), + ), + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => + controller.toggleEditMode(false), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(10.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + ), + child: Text(L10n.of(context).cancel), + ), + const SizedBox(width: 10), + ElevatedButton( + onPressed: () => + controller.controller.text != + controller.lemmaInfo + ?.meaning && + controller.controller.text + .isNotEmpty + ? controller.editLemmaMeaning( + controller.controller.text, + ) + : null, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(10.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + ), + child: + Text(L10n.of(context).saveChanges), + ), + ], + ), + ], + ); + } + + return Column( + spacing: 12.0, + mainAxisSize: MainAxisSize.min, + children: [ + if (MatrixState.pangeaController + .languageController.showTranscription) + PhoneticTranscriptionWidget( + text: token.content, + textLanguage: PLanguageStore.byLangCode( + langCode, + ) ?? + LanguageModel.unknown, + style: const TextStyle(fontSize: 14.0), + iconSize: 24.0, + ) + else + WordAudioButton( + text: token.content, + uniqueID: "lemma-content-${token.content}", + langCode: langCode, + iconSize: 24.0, + ), + LemmaReactionPicker( + emojis: controller.lemmaInfo?.emoji ?? [], + loading: controller.isLoading, + event: event, + ), + if (controller.error != null) + ErrorIndicator( + message: L10n.of(context) + .errorFetchingDefinition, + style: const TextStyle(fontSize: 14.0), + ) + else if (controller.isLoading || + controller.lemmaInfo == null) + const CircularProgressIndicator.adaptive() + else + GestureDetector( + onLongPress: () => + controller.toggleEditMode(true), + onDoubleTap: () => + controller.toggleEditMode(true), + child: construct.lemma.toLowerCase() == + token.content.toLowerCase() + ? Text( + controller.lemmaInfo!.meaning, + style: + const TextStyle(fontSize: 14.0), + textAlign: TextAlign.center, + ) + : RichText( + text: TextSpan( + style: + DefaultTextStyle.of(context) + .style + .copyWith( + fontSize: 14.0, + ), + children: [ + TextSpan(text: construct.lemma), + const WidgetSpan( + child: SizedBox(width: 8.0), + ), + const TextSpan(text: ":"), + const WidgetSpan( + child: SizedBox(width: 8.0), + ), + TextSpan( + text: controller + .lemmaInfo!.meaning, + ), + ], + ), + ), + ), + ], + ); + }, + ), + ], + ), ), - ], + ), ), + wordIsNew + ? NewWordOverlay( + key: ValueKey(transformTargetId), + overlayColor: overlayColor, + transformTargetId: transformTargetId, + onDismiss: onDismissNewWordOverlay, + ) + : const SizedBox.shrink(), + ], + ); + + return Material( + type: MaterialType.transparency, + child: SelectionArea( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 4.0, + ), + borderRadius: const BorderRadius.all( + Radius.circular(AppConfig.borderRadius), ), ), + height: AppConfig.toolbarMaxHeight, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + content, + ], + ), ), - wordIsNew - ? NewWordOverlay( - key: ValueKey(transformTargetId), - overlayColor: overlayColor, - transformTargetId: transformTargetId, - onDismiss: onDismissNewWordOverlay, - ) - : const SizedBox.shrink(), - ], + ), ); } } diff --git a/lib/utils/localized_exception_extension.dart b/lib/utils/localized_exception_extension.dart index 838d7ed73..d50dbbbfa 100644 --- a/lib/utils/localized_exception_extension.dart +++ b/lib/utils/localized_exception_extension.dart @@ -8,6 +8,7 @@ import 'package:matrix/encryption.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; import 'package:fluffychat/utils/other_party_can_receive.dart'; import 'uia_request_manager.dart'; @@ -29,6 +30,11 @@ extension LocalizedExceptionExtension on Object { BuildContext context, [ ExceptionContext? exceptionContext, ]) { + // #Pangea + if (this is UnsubscribedException) { + return L10n.of(context).unsubscribedResponseError; + } + // Pangea# if (this is FileTooBigMatrixException) { final exception = this as FileTooBigMatrixException; return L10n.of(context).fileIsTooBigForServer(