fix: prevent overflows in token info feedback dialog on mobile, add more specific unsubscribed error in future loading dialog (#4333)
This commit is contained in:
parent
5e1f4f3123
commit
c4ff6b0ac4
8 changed files with 422 additions and 549 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}",
|
||||
|
|
|
|||
|
|
@ -121,6 +121,17 @@ class _TokenInfoFeedbackDialogState extends State<TokenInfoFeedbackDialog> {
|
|||
return response.userFriendlyMessage;
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => _submitFeedback(),
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
Navigator.of(context).pop(resp.result!);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _updateLemmaInfo(
|
||||
PangeaToken token,
|
||||
LemmaInfoResponse response,
|
||||
|
|
@ -164,112 +175,97 @@ class _TokenInfoFeedbackDialogState extends State<TokenInfoFeedbackDialog> {
|
|||
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<TextEditingValue>(
|
||||
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<TextEditingValue>(
|
||||
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),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<MessageSelectionOverlay>
|
|||
List<PangeaTokenText>? _highlightedTokens;
|
||||
bool initialized = false;
|
||||
|
||||
final GlobalKey<ReadingAssistanceContentState> wordZoomKey = GlobalKey();
|
||||
|
||||
ReadingAssistanceMode? readingAssistanceMode; // default mode
|
||||
|
||||
SpeechToTextModel? transcription;
|
||||
|
|
@ -234,8 +231,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
phase == SchedulerPhase.postFrameCallbacks)) {
|
||||
// It's safe to call setState immediately
|
||||
try {
|
||||
wordZoomKey.currentState?.setState(() {});
|
||||
|
||||
super.setState(fn);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
|
|
|
|||
|
|
@ -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<ReadingAssistanceContent> {
|
||||
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<ReadingAssistanceContent> {
|
|||
-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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue