Merge pull request #3335 from pangeachat/3223-marking-new-forms-and-simple-satisfying-collection-mechanic

3223 marking new forms and simple satisfying collection mechanic
This commit is contained in:
ggurdin 2025-07-03 15:45:58 -04:00 committed by GitHub
commit f71121ae2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 546 additions and 196 deletions

View file

@ -4589,6 +4589,7 @@
"constructUseIncMDesc": "Incorrect in grammar activity",
"constructUseIgnMDesc": "Ignored in grammar activity",
"constructUseEmojiDesc": "Correct in emoji activity",
"constructUseCollected": "Collected in chat",
"constructUseNanDesc": "Not applicable",
"xpIntoLevel": "{currentXP} / {maxXP} XP",
"@xpIntoLevel": {

View file

@ -388,6 +388,10 @@ class HtmlMessage extends StatelessWidget {
? isSelected!.call(token)
: false;
final isNew = token != null &&
overlayController != null &&
overlayController!.isNewToken(token);
final tokenWidth = renderer.tokenTextWidthForContainer(
context,
node.text,
@ -419,6 +423,7 @@ class HtmlMessage extends StatelessWidget {
color: renderer.backgroundColor(
context,
selected,
isNew,
),
),
width: tokenWidth,
@ -442,6 +447,7 @@ class HtmlMessage extends StatelessWidget {
color: renderer.backgroundColor(
context,
selected,
isNew,
),
),
linkStyle: linkStyle,
@ -578,6 +584,7 @@ class HtmlMessage extends StatelessWidget {
color: renderer.backgroundColor(
context,
false,
false,
),
),
),
@ -593,6 +600,7 @@ class HtmlMessage extends StatelessWidget {
color: renderer.backgroundColor(
context,
false,
false,
),
),
// Pangea#

View file

@ -66,6 +66,9 @@ enum ConstructUseTypeEnum {
incMM,
ignMM,
/// lemma collected by clicking on it
click,
/// not defined, likely a new construct introduced by choreo and not yet classified by an old version of the client
nan
}
@ -135,6 +138,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
return L10n.of(context).constructUseIncMmDesc;
case ConstructUseTypeEnum.ignMM:
return L10n.of(context).constructUseIgnMmDesc;
case ConstructUseTypeEnum.click:
return L10n.of(context).constructUseCollected;
case ConstructUseTypeEnum.nan:
return L10n.of(context).constructUseNanDesc;
}
@ -185,6 +190,8 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
case ConstructUseTypeEnum.unk:
case ConstructUseTypeEnum.nan:
return Icons.help;
case ConstructUseTypeEnum.click:
return Icons.format_color_text;
}
}
@ -211,6 +218,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
case ConstructUseTypeEnum.corIGC:
case ConstructUseTypeEnum.corL:
case ConstructUseTypeEnum.click:
return 2;
case ConstructUseTypeEnum.corIt:
@ -279,6 +287,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
case ConstructUseTypeEnum.incMM:
case ConstructUseTypeEnum.ignMM:
case ConstructUseTypeEnum.em:
case ConstructUseTypeEnum.click:
case ConstructUseTypeEnum.nan:
return false;
}
@ -318,6 +327,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
case ConstructUseTypeEnum.incMM:
case ConstructUseTypeEnum.ignMM:
case ConstructUseTypeEnum.em:
case ConstructUseTypeEnum.click:
return LearningSkillsEnum.reading;
case ConstructUseTypeEnum.pvm:
return LearningSkillsEnum.speaking;
@ -364,6 +374,7 @@ extension ConstructUseTypeExtension on ConstructUseTypeEnum {
case ConstructUseTypeEnum.ignL:
case ConstructUseTypeEnum.ignM:
case ConstructUseTypeEnum.ignMM:
case ConstructUseTypeEnum.click:
case ConstructUseTypeEnum.nan:
return null;
}

View file

@ -1,5 +1,9 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_list_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
@ -9,8 +13,6 @@ import 'package:fluffychat/pangea/analytics_summary/progress_indicator.dart';
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
/// A summary of "My Analytics" shown at the top of the chat list
/// It shows a variety of progress indicators such as

View file

@ -30,13 +30,9 @@ class ProgressIndicatorBadge extends StatelessWidget {
),
const SizedBox(width: 6.0),
!loading
? Text(
points.toString(),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: indicator.color(context),
),
? _AnimatedFloatingNumber(
number: points,
indicator: indicator,
)
: const SizedBox(
height: 8,
@ -50,3 +46,88 @@ class ProgressIndicatorBadge extends StatelessWidget {
);
}
}
class _AnimatedFloatingNumber extends StatefulWidget {
final int number;
final ProgressIndicatorEnum indicator;
const _AnimatedFloatingNumber({
required this.number,
required this.indicator,
});
@override
State<_AnimatedFloatingNumber> createState() =>
_AnimatedFloatingNumberState();
}
class _AnimatedFloatingNumberState extends State<_AnimatedFloatingNumber>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnim;
late Animation<Offset> _offsetAnim;
int? _lastNumber;
int? _floatingNumber;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this, duration: const Duration(milliseconds: 900));
_fadeAnim = CurvedAnimation(parent: _controller, curve: Curves.easeOut);
_offsetAnim = Tween<Offset>(
begin: const Offset(0, 0),
end: const Offset(0, -0.7),
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_lastNumber = widget.number;
}
@override
void didUpdateWidget(covariant _AnimatedFloatingNumber oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.number > _lastNumber!) {
_floatingNumber = widget.number;
_controller.forward(from: 0.0).then((_) {
setState(() {
_lastNumber = widget.number;
_floatingNumber = null;
});
});
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final TextStyle indicatorStyle = TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: widget.indicator.color(context),
);
return Stack(
alignment: Alignment.center,
children: [
if (_floatingNumber != null)
SlideTransition(
position: _offsetAnim,
child: FadeTransition(
opacity: ReverseAnimation(_fadeAnim),
child: Text(
"$_floatingNumber",
style: indicatorStyle,
),
),
),
Text(
widget.number.toString(),
style: indicatorStyle,
),
],
);
}
}

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
@ -85,7 +86,10 @@ class TokenRenderingUtil {
}
}
Color backgroundColor(BuildContext context, bool selected) {
Color backgroundColor(BuildContext context, bool selected, bool isNew) {
if (isNew) {
return AppConfig.success;
}
return selected
? Theme.of(context).colorScheme.primary
: Colors.white.withAlpha(0);

View file

@ -11,6 +11,10 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.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';
@ -104,6 +108,8 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
double maxWidth = AppConfig.toolbarMinWidth;
List<PangeaToken> newTokens = [];
/////////////////////////////////////
/// Lifecycle
/////////////////////////////////////
@ -115,6 +121,13 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
WidgetsBinding.instance.addPostFrameCallback(
(_) => widget.chatController.setSelectedEvent(event),
);
newTokens = pangeaMessageEvent?.messageDisplayRepresentation?.tokens
?.where((token) {
return token.lemma.saveVocab == true &&
token.vocabConstruct.uses.isEmpty &&
messageInUserL2;
}).toList() ??
[];
}
@override
@ -557,6 +570,44 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
}
updateSelectedSpan(token.text);
if (isNewToken(token)) {
Future.delayed(const Duration(milliseconds: 1700), () {
MatrixState.pangeaController.putAnalytics.setState(
AnalyticsStream(
eventId: event.eventId,
roomId: event.room.id,
constructs: [
OneConstructUse(
useType: ConstructUseTypeEnum.click,
lemma: token.lemma.text,
constructType: ConstructTypeEnum.vocab,
metadata: ConstructUseMetaData(
roomId: event.room.id,
timeStamp: DateTime.now(),
eventId: event.eventId,
),
category: token.pos,
form: token.text.content,
xp: ConstructUseTypeEnum.click.pointValue,
),
],
targetID: token.text.uniqueKey,
),
);
if (mounted) {
setState(() {
newTokens.removeWhere(
(t) =>
t.text.offset == token.text.offset &&
t.text.length == token.text.length &&
t.lemma.text.equals(token.lemma.text),
);
});
}
});
}
}
PracticeTarget? practiceTargetForToken(PangeaToken token) {
@ -573,6 +624,15 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
return isSelected;
}
bool isNewToken(PangeaToken token) {
if (newTokens.isEmpty) return false;
return newTokens.any(
(t) =>
t.text.offset == token.text.offset &&
t.text.length == token.text.length,
);
}
bool isTokenHighlighted(PangeaToken token) {
if (_highlightedTokens == null) return false;
return _highlightedTokens!.any(

View file

@ -120,6 +120,8 @@ class ReadingAssistanceContentState extends State<ReadingAssistanceContent> {
token: widget.overlayController.selectedToken!,
messageEvent: widget.overlayController.pangeaMessageEvent!,
overlayController: widget.overlayController,
wordIsNew: widget.overlayController
.isNewToken(widget.overlayController.selectedToken!),
);
}
}

View file

@ -1,5 +1,11 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
@ -20,10 +26,6 @@ import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/morph_sel
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';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_symbols_icons/symbols.dart';
class MorphologicalListItem extends StatelessWidget {
final MorphFeaturesEnum morphFeature;

View file

@ -0,0 +1,158 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
class NewWordOverlay extends StatefulWidget {
final Color overlayColor;
final GlobalKey cardKey;
const NewWordOverlay({
super.key,
required this.overlayColor,
required this.cardKey,
});
@override
State<NewWordOverlay> createState() => _NewWordOverlayState();
}
class _NewWordOverlayState extends State<NewWordOverlay>
with TickerProviderStateMixin {
AnimationController? _controller;
Animation<double>? _xpScaleAnim;
Animation<double>? _fadeAnim;
Size cardSize = const Size(0, 0);
Offset cardPosition = const Offset(0, 0);
OverlayEntry? _overlayEntry;
bool columnMode = false;
Widget? get svg => ConstructLevelEnum.seeds.icon();
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1700),
);
_xpScaleAnim = CurvedAnimation(
parent: _controller!,
curve: const Interval(0.0, 0.6, curve: Curves.easeInOut),
);
_fadeAnim = CurvedAnimation(
parent: _controller!,
curve: const Interval(0.7, 1.0, curve: Curves.easeOut),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
columnMode = FluffyThemes.isColumnMode(context);
calculateSizeAndPosition();
_showFlyingWidget();
_controller?.forward();
});
}
@override
void dispose() {
_overlayEntry?.remove();
_controller?.dispose();
super.dispose();
}
void calculateSizeAndPosition() {
//find position of word card and overlaybox(chat view) to figure out where seed should start
final RenderBox? cardBox =
widget.cardKey.currentContext?.findRenderObject() as RenderBox?;
final RenderBox? overlayBox =
Overlay.of(context).context.findRenderObject() as RenderBox?;
if (cardBox != null && overlayBox != null) {
final cardGlobal = cardBox.localToGlobal(Offset.zero);
final overlayGlobal = overlayBox.localToGlobal(Offset.zero);
setState(() {
cardPosition = cardGlobal - overlayGlobal;
cardSize = cardBox.size;
});
}
}
void _showFlyingWidget() {
_overlayEntry?.remove(); // Remove any existing overlay
if (_controller == null || _xpScaleAnim == null || _fadeAnim == null) {
return;
}
_overlayEntry = OverlayEntry(
builder: (context) => AnimatedBuilder(
animation: _controller!,
builder: (context, child) {
final scale = _xpScaleAnim!.value;
final fade = 1.0 - (_fadeAnim!.value);
double t = 0.0;
if ((_controller!.value) >= 0.7) {
t = ((_controller!.value) - 0.7) / 0.3;
t = t.clamp(0.0, 1.0);
}
//move starting position as seed grows so it stays centered
final seedSize = 75 * scale * ((!columnMode) ? fade : 1);
final startX = cardPosition.dx + cardSize.width / 2 - seedSize;
final startY = cardPosition.dy + cardSize.height / 2 + 20 - seedSize;
//end is top left if column mode (going towards vocab stats) or top right of card otherwise
final endX = (columnMode) ? 0.0 : cardPosition.dx + cardSize.width;
final endY = (columnMode) ? 0.0 : cardPosition.dy + 30;
final currentX = startX * (1 - t) + endX * t;
final currentY = startY * (1 - t) + endY * t;
//Grows into frame, and then shrinks if going to top right so it matches card seed size
return Positioned(
left: currentX,
top: currentY,
child: Opacity(
opacity: fade,
child: Transform.rotate(
angle: scale * 2 * pi,
child: SizedBox(
//if going to card top right, shrinks as it moves to match word card seed size
width: seedSize,
height: seedSize,
child: svg ?? const SizedBox(),
),
),
),
);
},
),
);
Overlay.of(context).insert(_overlayEntry!);
_controller?.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_overlayEntry?.remove();
_overlayEntry = null;
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: cardSize.height,
width: cardSize.width,
color: Colors.transparent,
),
Positioned(
left: 5,
right: 5,
top: 50,
bottom: 5,
child: Container(
height: cardSize.height,
width: cardSize.width,
color: widget.overlayColor,
),
),
],
);
}
}

View file

@ -15,18 +15,21 @@ import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.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';
import 'package:fluffychat/widgets/matrix.dart';
class WordZoomWidget extends StatelessWidget {
final PangeaToken token;
final PangeaMessageEvent messageEvent;
final MessageOverlayController overlayController;
final bool wordIsNew;
const WordZoomWidget({
super.key,
required this.token,
required this.messageEvent,
required this.overlayController,
required this.wordIsNew,
});
PangeaToken get _selectedToken => overlayController.selectedToken!;
@ -41,202 +44,220 @@ class WordZoomWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12.0),
constraints: const BoxConstraints(
minHeight: AppConfig.toolbarMinHeight - 8,
maxHeight: AppConfig.toolbarMaxHeight - 8,
maxWidth: AppConfig.toolbarMinWidth,
),
child: SingleChildScrollView(
child: Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
final GlobalKey cardKey = GlobalKey();
final overlayColor = Theme.of(context).scaffoldBackgroundColor;
return Stack(
children: [
Container(
key: cardKey,
padding: const EdgeInsets.all(12.0),
constraints: const BoxConstraints(
minHeight: AppConfig.toolbarMinHeight - 8,
maxHeight: AppConfig.toolbarMaxHeight - 8,
maxWidth: AppConfig.toolbarMinWidth,
),
child: SingleChildScrollView(
child: Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 24.0,
height: 24.0,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => overlayController.updateSelectedSpan(
token.text,
),
child: const Icon(
Icons.close,
size: 16.0,
),
),
),
),
Flexible(
child: Text(
token.text.content,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 32.0,
fontWeight: FontWeight.w600,
height: 1.2,
color: Theme.of(context).brightness == Brightness.light
? AppConfig.yellowDark
: AppConfig.yellowLight,
),
),
),
ConstructXpWidget(
id: token.vocabConstructID,
onTap: () => context.go(
"/rooms/analytics?mode=vocab",
extra: token.vocabConstructID,
),
),
],
),
LemmaMeaningBuilder(
langCode: messageEvent.messageDisplayLangCode,
constructId: token.vocabConstructID,
builder: (context, controller) {
if (controller.editMode) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
token.vocabConstructID.lemma,
token.vocabConstructID.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,
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 24.0,
height: 24.0,
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => overlayController.updateSelectedSpan(
token.text,
),
child: const Icon(
Icons.close,
size: 16.0,
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
),
Flexible(
child: Text(
token.text.content,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 32.0,
fontWeight: FontWeight.w600,
height: 1.2,
color:
Theme.of(context).brightness == Brightness.light
? AppConfig.yellowDark
: AppConfig.yellowLight,
),
),
),
ConstructXpWidget(
id: token.vocabConstructID,
onTap: () => context.go(
"/rooms/analytics?mode=vocab",
extra: token.vocabConstructID,
),
),
],
),
LemmaMeaningBuilder(
langCode: messageEvent.messageDisplayLangCode,
constructId: token.vocabConstructID,
builder: (context, controller) {
if (controller.editMode) {
return Column(
mainAxisSize: MainAxisSize.min,
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),
Text(
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
token.vocabConstructID.lemma,
token.vocabConstructID.category,
)}",
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
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),
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,
),
padding:
const EdgeInsets.symmetric(horizontal: 10),
),
child: Text(L10n.of(context).saveChanges),
),
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.showTrancription)
PhoneticTranscriptionWidget(
text: token.text.content,
textLanguage: PLanguageStore.byLangCode(
messageEvent.messageDisplayLangCode,
) ??
LanguageModel.unknown,
style: const TextStyle(fontSize: 14.0),
iconSize: 24.0,
)
else
WordAudioButton(
text: token.text.content,
uniqueID: "lemma-content-${token.text.content}",
langCode: messageEvent.messageDisplayLangCode,
iconSize: 24.0,
),
LemmaReactionPicker(
cId: _selectedToken.vocabConstructID,
controller: overlayController.widget.chatController,
),
if (controller.error != null)
Text(
L10n.of(context).oopsSomethingWentWrong,
textAlign: TextAlign.center,
)
else if (controller.isLoading ||
controller.lemmaInfo == null)
const CircularProgressIndicator.adaptive()
else
GestureDetector(
onLongPress: () => controller.toggleEditMode(true),
onDoubleTap: () => controller.toggleEditMode(true),
child: token.lemma.text == token.text.content
? 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: token.lemma.text),
const WidgetSpan(
child: SizedBox(width: 8.0),
return Column(
spacing: 12.0,
mainAxisSize: MainAxisSize.min,
children: [
if (MatrixState.pangeaController.languageController
.showTrancription)
PhoneticTranscriptionWidget(
text: token.text.content,
textLanguage: PLanguageStore.byLangCode(
messageEvent.messageDisplayLangCode,
) ??
LanguageModel.unknown,
style: const TextStyle(fontSize: 14.0),
iconSize: 24.0,
)
else
WordAudioButton(
text: token.text.content,
uniqueID: "lemma-content-${token.text.content}",
langCode: messageEvent.messageDisplayLangCode,
iconSize: 24.0,
),
LemmaReactionPicker(
cId: _selectedToken.vocabConstructID,
controller: overlayController.widget.chatController,
),
if (controller.error != null)
Text(
L10n.of(context).oopsSomethingWentWrong,
textAlign: TextAlign.center,
)
else if (controller.isLoading ||
controller.lemmaInfo == null)
const CircularProgressIndicator.adaptive()
else
GestureDetector(
onLongPress: () => controller.toggleEditMode(true),
onDoubleTap: () => controller.toggleEditMode(true),
child: token.lemma.text == token.text.content
? 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: token.lemma.text),
const WidgetSpan(
child: SizedBox(width: 8.0),
),
const TextSpan(text: ":"),
const WidgetSpan(
child: SizedBox(width: 8.0),
),
TextSpan(
text: controller.lemmaInfo!.meaning,
),
],
),
const TextSpan(text: ":"),
const WidgetSpan(
child: SizedBox(width: 8.0),
),
TextSpan(
text: controller.lemmaInfo!.meaning,
),
],
),
),
),
],
);
},
),
),
],
);
},
),
],
),
],
),
),
),
wordIsNew
? NewWordOverlay(
overlayColor: overlayColor,
cardKey: cardKey,
)
: const SizedBox.shrink(),
],
);
}
}