Adjust translation animation (#2556)
* chore: some edits to message toolbar * chore: some updates to reading assistance
This commit is contained in:
parent
22f46caf80
commit
7f11a5dba9
17 changed files with 334 additions and 311 deletions
|
|
@ -138,6 +138,16 @@ class MessageContent extends StatelessWidget {
|
|||
if (overlayController != null) {
|
||||
overlayController?.onClickOverlayMessageToken(token);
|
||||
return;
|
||||
} else {
|
||||
Future.delayed(
|
||||
const Duration(
|
||||
milliseconds: AppConfig.overlayAnimationDuration,
|
||||
), () {
|
||||
controller.choreographer.tts.tryToSpeak(
|
||||
token.text.content,
|
||||
langCode: pangeaMessageEvent!.messageDisplayLangCode,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
controller.showToolbar(
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ class ChoicesArrayState extends State<ChoicesArray> {
|
|||
widget.langCode != null) {
|
||||
widget.tts?.tryToSpeak(
|
||||
value,
|
||||
context,
|
||||
targetID: null,
|
||||
langCode: widget.langCode!,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -149,9 +149,9 @@ class OverlayUtil {
|
|||
transformTargetOffset.dy + (transformTargetSize.height / 2);
|
||||
|
||||
final halfMaxWidth = maxWidth / 2;
|
||||
final hasLeftOverflow = (horizontalMidpoint - halfMaxWidth) < 0;
|
||||
final hasLeftOverflow = (horizontalMidpoint - halfMaxWidth) < 10;
|
||||
final hasRightOverflow = (horizontalMidpoint + halfMaxWidth) >
|
||||
(MediaQuery.of(context).size.width - columnWidth);
|
||||
(MediaQuery.of(context).size.width - columnWidth - 10);
|
||||
hasTopOverflow = (verticalMidpoint - maxHeight) < 0;
|
||||
|
||||
double xOffset = 0;
|
||||
|
|
|
|||
|
|
@ -159,11 +159,11 @@ class TtsController {
|
|||
/// A safer version of speak, that handles the case of
|
||||
/// the language not being supported by the TTS engine
|
||||
Future<void> tryToSpeak(
|
||||
String text,
|
||||
BuildContext context, {
|
||||
String text, {
|
||||
required String langCode,
|
||||
// Target ID for where to show warning popup
|
||||
String? targetID,
|
||||
BuildContext? context,
|
||||
}) async {
|
||||
chatController?.stopAudioStream.add(null);
|
||||
await _setSpeakingLanguage(langCode);
|
||||
|
|
@ -180,7 +180,7 @@ class TtsController {
|
|||
text,
|
||||
langCode,
|
||||
));
|
||||
} else if (targetID != null) {
|
||||
} else if (targetID != null && context != null) {
|
||||
await _showTTSDisabledPopup(context, targetID);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class PracticeMatchItemState extends State<PracticeMatchItem> {
|
|||
if (l2 != null) {
|
||||
await tts.tryToSpeak(
|
||||
widget.audioContent!,
|
||||
context,
|
||||
context: context,
|
||||
targetID: 'word-audio-button',
|
||||
langCode: l2,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/reactions_picker.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_mode_locked_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_speech_to_text_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_translation_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/practice_activity_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart';
|
||||
|
||||
const double minContentHeight = 120;
|
||||
|
||||
|
|
@ -26,6 +26,11 @@ class ReadingAssistanceInputBar extends StatelessWidget {
|
|||
});
|
||||
|
||||
Widget barContent(BuildContext context) {
|
||||
if (overlayController.readingAssistanceMode !=
|
||||
ReadingAssistanceMode.practiceMode) {
|
||||
return ReactionsPicker(controller);
|
||||
}
|
||||
|
||||
Widget? content;
|
||||
final target =
|
||||
overlayController.toolbarMode.associatedActivityType != null &&
|
||||
|
|
@ -84,12 +89,6 @@ class ReadingAssistanceInputBar extends StatelessWidget {
|
|||
targetTokensAndActivityType: target,
|
||||
overlayController: overlayController,
|
||||
);
|
||||
} else if (overlayController.selectedMorph != null) {
|
||||
content = MorphFocusWidget(
|
||||
morphFeature: overlayController.selectedMorph!.morph,
|
||||
pangeaMessageEvent: overlayController.pangeaMessageEvent!,
|
||||
overlayController: overlayController,
|
||||
);
|
||||
} else {
|
||||
content = Center(
|
||||
child: Text(
|
||||
|
|
|
|||
|
|
@ -534,7 +534,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
void onClickOverlayMessageToken(
|
||||
PangeaToken token,
|
||||
) {
|
||||
if (practiceSelection?.hasHiddenWordActivity == true) {
|
||||
if (practiceSelection?.hasHiddenWordActivity == true ||
|
||||
readingAssistanceMode == ReadingAssistanceMode.practiceMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -548,7 +549,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
|
|||
!hideWordCardContent) {
|
||||
widget.chatController.choreographer.tts.tryToSpeak(
|
||||
token.text.content,
|
||||
context,
|
||||
targetID: null,
|
||||
langCode: pangeaMessageEvent!.messageDisplayLangCode,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -430,7 +430,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
} else {
|
||||
return Offset(
|
||||
_ownMessage ? _messageRightOffset : _messageLeftOffset,
|
||||
_footerHeight + AppConfig.toolbarSpacing,
|
||||
_footerHeight + (AppConfig.toolbarSpacing * 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/events/message_reactions.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
|
|
@ -55,8 +53,6 @@ class OverlayCenterContent extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showTranslation = overlayController.showTranslation &&
|
||||
overlayController.translationText != null;
|
||||
return IgnorePointer(
|
||||
ignoring: !isTransitionAnimation &&
|
||||
readingAssistanceMode != ReadingAssistanceMode.practiceMode,
|
||||
|
|
@ -70,80 +66,31 @@ class OverlayCenterContent extends StatelessWidget {
|
|||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
if (overlayController.readingAssistanceMode ==
|
||||
ReadingAssistanceMode.selectMode)
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
),
|
||||
padding: EdgeInsets.all(
|
||||
showTranslation ? 8.0 : 0.0,
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: messageWidth ?? maxWidth,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
height: showTranslation ? messageHeight : 0,
|
||||
width: showTranslation ? messageWidth : 0,
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: SizedBox(
|
||||
width: messageWidth,
|
||||
child: showTranslation
|
||||
? Text(
|
||||
overlayController.translationText!,
|
||||
style: AppConfig.messageTextStyle(
|
||||
event,
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
MeasureRenderBox(
|
||||
onChange: onChangeMessageSize,
|
||||
child: OverlayMessage(
|
||||
event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: chatController.choreographer.immersionMode,
|
||||
controller: chatController,
|
||||
overlayController: overlayController,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
timeline: chatController.timeline!,
|
||||
sizeAnimation: sizeAnimation,
|
||||
// there's a split seconds between when the transition animation starts and
|
||||
// when the sizeAnimation is set when the original dimensions need to be enforced
|
||||
messageWidth:
|
||||
(sizeAnimation == null && isTransitionAnimation)
|
||||
? messageWidth
|
||||
: null,
|
||||
messageHeight:
|
||||
(sizeAnimation == null && isTransitionAnimation)
|
||||
? messageHeight
|
||||
: null,
|
||||
maxHeight: maxHeight,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
),
|
||||
),
|
||||
],
|
||||
MeasureRenderBox(
|
||||
onChange: onChangeMessageSize,
|
||||
child: OverlayMessage(
|
||||
event,
|
||||
pangeaMessageEvent: pangeaMessageEvent,
|
||||
immersionMode: chatController.choreographer.immersionMode,
|
||||
controller: chatController,
|
||||
overlayController: overlayController,
|
||||
nextEvent: nextEvent,
|
||||
prevEvent: prevEvent,
|
||||
timeline: chatController.timeline!,
|
||||
sizeAnimation: sizeAnimation,
|
||||
// there's a split seconds between when the transition animation starts and
|
||||
// when the sizeAnimation is set when the original dimensions need to be enforced
|
||||
messageWidth: (sizeAnimation == null && isTransitionAnimation)
|
||||
? messageWidth
|
||||
: null,
|
||||
messageHeight:
|
||||
(sizeAnimation == null && isTransitionAnimation)
|
||||
? messageHeight
|
||||
: null,
|
||||
maxHeight: maxHeight,
|
||||
isTransitionAnimation: isTransitionAnimation,
|
||||
readingAssistanceMode: readingAssistanceMode,
|
||||
),
|
||||
),
|
||||
if (hasReactions)
|
||||
Padding(
|
||||
|
|
|
|||
|
|
@ -124,6 +124,9 @@ class OverlayMessage extends StatelessWidget {
|
|||
? ThemeData.dark().colorScheme.onPrimary
|
||||
: theme.colorScheme.onSurface;
|
||||
|
||||
final showTranslation = overlayController.showTranslation &&
|
||||
overlayController.translationText != null;
|
||||
|
||||
final content = Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(
|
||||
|
|
@ -253,18 +256,48 @@ class OverlayMessage extends StatelessWidget {
|
|||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: sizeAnimation != null
|
||||
? AnimatedBuilder(
|
||||
animation: sizeAnimation!,
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: sizeAnimation!.value.height,
|
||||
width: sizeAnimation!.value.width,
|
||||
child: content,
|
||||
);
|
||||
},
|
||||
)
|
||||
: content,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: color,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
sizeAnimation != null
|
||||
? AnimatedBuilder(
|
||||
animation: sizeAnimation!,
|
||||
builder: (context, child) {
|
||||
return SizedBox(
|
||||
height: sizeAnimation!.value.height,
|
||||
width: sizeAnimation!.value.width,
|
||||
child: content,
|
||||
);
|
||||
},
|
||||
)
|
||||
: content,
|
||||
if (showTranslation)
|
||||
SizedBox(
|
||||
width: messageWidth,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
12.0,
|
||||
20.0,
|
||||
12.0,
|
||||
12.0,
|
||||
),
|
||||
child: Text(
|
||||
overlayController.translationText!,
|
||||
style:
|
||||
AppConfig.messageTextStyle(event, textColor).copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class WordAudioButtonState extends State<WordAudioButton> {
|
|||
if (widget.langCode != null) {
|
||||
await tts.tryToSpeak(
|
||||
widget.text,
|
||||
context,
|
||||
context: context,
|
||||
targetID: 'word-audio-button-${widget.uniqueID}',
|
||||
langCode: widget.langCode!,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class WordAudioButtonState extends State<WordTextWithAudioButton> {
|
|||
if (l2 != null) {
|
||||
await tts.tryToSpeak(
|
||||
widget.text,
|
||||
context,
|
||||
context: context,
|
||||
targetID: 'text-audio-button-${widget.uniqueID}',
|
||||
langCode: l2,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/events/audio_player.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/pressable_button.dart';
|
||||
|
|
@ -113,7 +112,6 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
|
||||
if (_selectedMode == SelectMode.translate) {
|
||||
widget.overlayController.setShowTranslation(false, null);
|
||||
await Future.delayed(FluffyThemes.animationDuration);
|
||||
}
|
||||
|
||||
setState(
|
||||
|
|
@ -245,7 +243,7 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
return Icon(
|
||||
_audioPlayer.playerState.playing == true
|
||||
? Icons.pause_outlined
|
||||
: Icons.play_arrow,
|
||||
: Icons.volume_up,
|
||||
size: 20,
|
||||
color: mode == _selectedMode ? Colors.white : null,
|
||||
);
|
||||
|
|
@ -294,6 +292,9 @@ class SelectModeButtonsState extends State<SelectModeButtons> {
|
|||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
onPressed: () => _updateMode(mode),
|
||||
playSound: true,
|
||||
colorFactor: Theme.of(context).brightness == Brightness.light
|
||||
? 0.55
|
||||
: 0.3,
|
||||
child: Container(
|
||||
height: buttonSize,
|
||||
width: buttonSize,
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ class ToolbarButton extends StatelessWidget {
|
|||
color: color(context),
|
||||
onPressed: () => onPressed(mode),
|
||||
playSound: true,
|
||||
colorFactor:
|
||||
Theme.of(context).brightness == Brightness.light ? 0.55 : 0.3,
|
||||
child: AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
height: buttonSize,
|
||||
|
|
|
|||
|
|
@ -1,165 +0,0 @@
|
|||
// stateful widget that displays morphological label and a shimmer effect while the text is loading
|
||||
// takes a token and morphological feature as input
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart';
|
||||
import 'package:fluffychat/pangea/morphs/edit_morph_widget.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_feature_display.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_tag_display.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
|
||||
class MorphFocusWidget extends StatefulWidget {
|
||||
final MorphFeaturesEnum morphFeature;
|
||||
final PangeaMessageEvent pangeaMessageEvent;
|
||||
final MessageOverlayController overlayController;
|
||||
|
||||
const MorphFocusWidget({
|
||||
required this.morphFeature,
|
||||
required this.pangeaMessageEvent,
|
||||
required this.overlayController,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
MorphFocusWidgetState createState() => MorphFocusWidgetState();
|
||||
}
|
||||
|
||||
class MorphFocusWidgetState extends State<MorphFocusWidget> {
|
||||
PangeaToken get token => widget.overlayController.selectedToken!;
|
||||
|
||||
bool _editMode = false;
|
||||
|
||||
/// the morphological tag that the user has selected in edit mode
|
||||
String _selectedMorphTag = "";
|
||||
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
void _resetMorphTag() {
|
||||
setState(
|
||||
() => _selectedMorphTag = token.getMorphTag(widget.morphFeature) ?? "X",
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_resetMorphTag();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(MorphFocusWidget oldWidget) {
|
||||
if (widget.morphFeature != oldWidget.morphFeature) {
|
||||
_resetMorphTag();
|
||||
setState(() => _editMode = false);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _enterEditMode() {
|
||||
setState(() {
|
||||
_editMode = true;
|
||||
});
|
||||
}
|
||||
|
||||
ConstructIdentifier get _id {
|
||||
return ConstructIdentifier(
|
||||
lemma: _selectedMorphTag,
|
||||
type: ConstructTypeEnum.morph,
|
||||
category: widget.morphFeature.name,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_editMode) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
MorphFeatureDisplay(
|
||||
morphFeature: widget.morphFeature,
|
||||
),
|
||||
if (token.getMorphTag(widget.morphFeature) != null) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Tooltip(
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
message: L10n.of(context).doubleClickToEdit,
|
||||
child: GestureDetector(
|
||||
onLongPress: _enterEditMode,
|
||||
onDoubleTap: _enterEditMode,
|
||||
child: MorphTagDisplay(
|
||||
morphFeature: widget.morphFeature,
|
||||
morphTag: token.getMorphTag(widget.morphFeature) ??
|
||||
L10n.of(context).nan,
|
||||
textColor: Theme.of(context).brightness ==
|
||||
Brightness.light
|
||||
? _id.constructUses.lemmaCategory.darkColor(context)
|
||||
: _id.constructUses.lemmaCategory.color(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
ConstructXpWidget(
|
||||
id: _id,
|
||||
onTap: () => showDialog<AnalyticsPopupWrapper>(
|
||||
context: context,
|
||||
builder: (context) => AnalyticsPopupWrapper(
|
||||
constructZoom: _id,
|
||||
view: ConstructTypeEnum.morph,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
MorphMeaningWidget(
|
||||
feature: widget.morphFeature,
|
||||
tag: token.getMorphTag(widget.morphFeature)!,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
] else
|
||||
Text(L10n.of(context).nan),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: EditMorphWidget(
|
||||
token: token,
|
||||
pangeaMessageEvent: widget.pangeaMessageEvent,
|
||||
morphFeature: widget.morphFeature,
|
||||
onClose: () {
|
||||
setState(() => _editMode = false);
|
||||
widget.overlayController.onMorphActivitySelect(
|
||||
MorphSelection(
|
||||
token,
|
||||
widget.morphFeature,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,29 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart';
|
||||
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_features_enum.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_icon.dart';
|
||||
import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_repo.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_selection.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class MorphologicalListItem extends StatelessWidget {
|
||||
final MorphFeaturesEnum morphFeature;
|
||||
|
|
@ -41,40 +54,223 @@ class MorphologicalListItem extends StatelessWidget {
|
|||
|
||||
String get morphTag => token.getMorphTag(morphFeature) ?? "X";
|
||||
|
||||
ConstructIdentifier get cId =>
|
||||
token.morphIdByFeature(morphFeature) ??
|
||||
ConstructIdentifier(
|
||||
type: ConstructTypeEnum.morph,
|
||||
category: morphFeature.name,
|
||||
lemma: morphTag,
|
||||
);
|
||||
|
||||
void _openDefintionPopup(BuildContext context) async {
|
||||
const width = 300.0;
|
||||
const height = 150.0;
|
||||
|
||||
try {
|
||||
OverlayUtil.showPositionedCard(
|
||||
context: context,
|
||||
cardToShow: MorphMeaningPopup(
|
||||
cId: cId,
|
||||
width: width,
|
||||
height: height,
|
||||
),
|
||||
transformTargetId: cId.string,
|
||||
backDropToDismiss: true,
|
||||
borderColor: Theme.of(context).colorScheme.primary,
|
||||
closePrevOverlay: false,
|
||||
addBorder: false,
|
||||
maxHeight: height,
|
||||
maxWidth: width,
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
data: cId.toJson(),
|
||||
e: e,
|
||||
s: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: WordZoomActivityButton(
|
||||
icon: shouldDoActivity
|
||||
? const Icon(Symbols.toys_and_games)
|
||||
: MorphIcon(
|
||||
morphFeature: morphFeature,
|
||||
morphTag: token.getMorphTag(morphFeature),
|
||||
size: const Size(24, 24),
|
||||
),
|
||||
isSelected: isSelected,
|
||||
onPressed: () => overlayController
|
||||
.onMorphActivitySelect(MorphSelection(token, morphFeature)),
|
||||
onLongPress: () {
|
||||
overlayController
|
||||
.onMorphActivitySelect(MorphSelection(token, morphFeature));
|
||||
editMorph();
|
||||
},
|
||||
onDoubleTap: () {
|
||||
overlayController
|
||||
.onMorphActivitySelect(MorphSelection(token, morphFeature));
|
||||
editMorph();
|
||||
},
|
||||
tooltip: shouldDoActivity
|
||||
? morphFeature.getDisplayCopy(context)
|
||||
: getGrammarCopy(
|
||||
category: morphFeature.name,
|
||||
lemma: morphTag,
|
||||
context: context,
|
||||
),
|
||||
opacity: isSelected ? 1 : 0.7,
|
||||
return CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState.layerLinkAndKey(cId.string).link,
|
||||
child: SizedBox(
|
||||
key: MatrixState.pAnyState.layerLinkAndKey(cId.string).key,
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: WordZoomActivityButton(
|
||||
icon: shouldDoActivity
|
||||
? const Icon(Symbols.toys_and_games)
|
||||
: MorphIcon(
|
||||
morphFeature: morphFeature,
|
||||
morphTag: token.getMorphTag(morphFeature),
|
||||
size: const Size(24, 24),
|
||||
),
|
||||
isSelected: isSelected,
|
||||
onPressed: () {
|
||||
overlayController
|
||||
.onMorphActivitySelect(MorphSelection(token, morphFeature));
|
||||
_openDefintionPopup(context);
|
||||
},
|
||||
onLongPress: () {
|
||||
overlayController
|
||||
.onMorphActivitySelect(MorphSelection(token, morphFeature));
|
||||
editMorph();
|
||||
},
|
||||
tooltip: shouldDoActivity
|
||||
? morphFeature.getDisplayCopy(context)
|
||||
: getGrammarCopy(
|
||||
category: morphFeature.name,
|
||||
lemma: morphTag,
|
||||
context: context,
|
||||
),
|
||||
opacity: isSelected ? 1 : 0.7,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MorphMeaningPopup extends StatefulWidget {
|
||||
final ConstructIdentifier cId;
|
||||
final double width;
|
||||
final double height;
|
||||
|
||||
const MorphMeaningPopup({
|
||||
super.key,
|
||||
required this.cId,
|
||||
required this.width,
|
||||
required this.height,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MorphMeaningPopup> createState() => MorphMeaningPopupState();
|
||||
}
|
||||
|
||||
class MorphMeaningPopupState extends State<MorphMeaningPopup> {
|
||||
MorphFeaturesEnum get _morphFeature =>
|
||||
MorphFeaturesEnumExtension.fromString(widget.cId.category);
|
||||
|
||||
String get _morphTag => widget.cId.lemma;
|
||||
|
||||
String? _defintion;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fetchDefinition();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant MorphMeaningPopup oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.cId != widget.cId) {
|
||||
_fetchDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchDefinition() async {
|
||||
try {
|
||||
final response = await MorphInfoRepo.get(
|
||||
feature: _morphFeature,
|
||||
tag: _morphTag,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
setState(
|
||||
() => _defintion = response ?? L10n.of(context).meaningNotFound,
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(
|
||||
data: widget.cId.toJson(),
|
||||
e: e,
|
||||
s: s,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
height: widget.height,
|
||||
width: widget.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.onSurface.withAlpha(50),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
spacing: 16.0,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
child: MorphIcon(
|
||||
morphFeature: _morphFeature,
|
||||
morphTag: _morphTag,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
getGrammarCopy(
|
||||
category: _morphFeature.name,
|
||||
lemma: _morphTag,
|
||||
context: context,
|
||||
) ??
|
||||
_morphTag,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
SizedBox(
|
||||
width: 24.0,
|
||||
height: 24.0,
|
||||
child: ConstructXpWidget(
|
||||
id: widget.cId,
|
||||
onTap: () => showDialog<AnalyticsPopupWrapper>(
|
||||
context: context,
|
||||
builder: (context) => AnalyticsPopupWrapper(
|
||||
constructZoom: widget.cId,
|
||||
view: ConstructTypeEnum.morph,
|
||||
backButtonOverride: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: _defintion != null
|
||||
? Text(
|
||||
_defintion!,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
)
|
||||
: const LinearProgressIndicator(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,8 +76,9 @@ class WordZoomWidget extends StatelessWidget {
|
|||
children: [
|
||||
//@ggurdin - might need to play with size to properly center
|
||||
IconButton(
|
||||
onPressed: () =>
|
||||
overlayController.onClickOverlayMessageToken(token),
|
||||
onPressed: () => overlayController.updateSelectedSpan(
|
||||
token.text,
|
||||
),
|
||||
icon: const Icon(Icons.close),
|
||||
),
|
||||
LemmaWidget(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue