3680 emoji population in vocab page (#3754)
* more consistent emojis and emoji selection in vocab page - Makes emoji row always visible in vocab page and highlights selection - selects one by default so more emojis show on the page - Saves spot in vocab page on navigation - Doesn't override emoji choice from emoji activity * code and import formatting * reduce calls to lemma_definition, remove unused widget file, prevent copy-related errors, don't show emoji activities for messages with less-than 2 relevant tokens --------- Co-authored-by: ggurdin <ggurdin@gmail.com> Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com>
This commit is contained in:
parent
598820295f
commit
bae5765a97
12 changed files with 431 additions and 576 deletions
|
|
@ -4843,7 +4843,7 @@
|
|||
"autocorrectNotAvailable": "Unfortunately your platform is not currently supported for this feature. Stay tuned for further development!",
|
||||
"pleaseUpdateApp": "Please update the app to continue.",
|
||||
"chooseEmojiInstructionsBody": "Match emojis with the words they best represent. Don't worry! No points off for disagreeing. 😅",
|
||||
"pickAnEmojiFor": "Pick an emoji for ${lemma}",
|
||||
"pickAnEmojiFor": "Pick an emoji for {lemma}",
|
||||
"@pickAnEmojiFor": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
|
|||
|
|
@ -7,13 +7,14 @@ import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popu
|
|||
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_emoji_row.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_highlight_emoji_row.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/phonetic_transcription/phonetic_transcription_widget.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_text_with_audio_button.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -50,136 +51,143 @@ class VocabDetailsView extends StatelessWidget {
|
|||
? _construct.lemmaCategory.color(context)
|
||||
: _construct.lemmaCategory.darkColor(context));
|
||||
|
||||
return AnalyticsDetailsViewContent(
|
||||
title: Column(
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ShrinkableText(
|
||||
text: _construct.lemma,
|
||||
maxWidth: constraints.maxWidth - 40.0,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (MatrixState.pangeaController.languageController.showTrancription)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: PhoneticTranscriptionWidget(
|
||||
text: _construct.lemma,
|
||||
textLanguage:
|
||||
MatrixState.pangeaController.languageController.userL2!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: textColor.withAlpha((0.7 * 255).toInt()),
|
||||
fontSize: 18,
|
||||
),
|
||||
iconSize: _iconSize * 0.8,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8.0,
|
||||
return LemmaMeaningBuilder(
|
||||
langCode: _userL2!,
|
||||
constructId: _construct.id,
|
||||
builder: (context, controller) {
|
||||
return AnalyticsDetailsViewContent(
|
||||
title: Column(
|
||||
children: [
|
||||
Text(
|
||||
getGrammarCopy(
|
||||
category: "POS",
|
||||
lemma: _construct.category,
|
||||
context: context,
|
||||
) ??
|
||||
_construct.lemma,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ShrinkableText(
|
||||
text: _construct.lemma,
|
||||
maxWidth: constraints.maxWidth - 40.0,
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
width: _iconSize,
|
||||
height: _iconSize,
|
||||
child: MorphIcon(
|
||||
morphFeature: MorphFeaturesEnum.Pos,
|
||||
morphTag: _construct.category,
|
||||
if (MatrixState
|
||||
.pangeaController.languageController.showTrancription)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: PhoneticTranscriptionWidget(
|
||||
text: _construct.lemma,
|
||||
textLanguage:
|
||||
MatrixState.pangeaController.languageController.userL2!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: textColor.withAlpha((0.7 * 255).toInt()),
|
||||
fontSize: 18,
|
||||
),
|
||||
iconSize: _iconSize * 0.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
LemmaEmojiRow(
|
||||
isSelected: false,
|
||||
shouldShowEmojis: true,
|
||||
cId: constructId,
|
||||
onTapOverride: null,
|
||||
emojiSetCallback: () {
|
||||
debugPrint('Emoji set callback');
|
||||
},
|
||||
iconSize: _iconSize,
|
||||
),
|
||||
],
|
||||
),
|
||||
headerContent: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: _userL2 == null
|
||||
? Text(L10n.of(context).meaningNotFound)
|
||||
: LemmaMeaningWidget(
|
||||
constructUse: _construct,
|
||||
langCode: _userL2!,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
leading: TextSpan(
|
||||
text: L10n.of(context).meaningSectionHeader,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
subtitle: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).formSectionHeader,
|
||||
getGrammarCopy(
|
||||
category: "POS",
|
||||
lemma: _construct.category,
|
||||
context: context,
|
||||
) ??
|
||||
_construct.lemma,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6.0),
|
||||
...forms.mapIndexed(
|
||||
(i, form) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
WordTextWithAudioButton(
|
||||
text: form,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
uniqueID: "$form-${_construct.lemma}-$i",
|
||||
langCode: _userL2!,
|
||||
),
|
||||
if (i != forms.length - 1) const Text(", "),
|
||||
],
|
||||
SizedBox(
|
||||
width: _iconSize,
|
||||
height: _iconSize,
|
||||
child: MorphIcon(
|
||||
morphFeature: MorphFeaturesEnum.Pos,
|
||||
morphTag: _construct.category,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
LemmmaHighlightEmojiRow(
|
||||
controller: controller,
|
||||
isSelected: false,
|
||||
cId: constructId,
|
||||
onTapOverride: null,
|
||||
iconSize: _iconSize,
|
||||
),
|
||||
],
|
||||
),
|
||||
headerContent: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
|
||||
child: Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: _userL2 == null
|
||||
? Text(L10n.of(context).meaningNotFound)
|
||||
: LemmaMeaningWidget(
|
||||
controller: controller,
|
||||
constructUse: _construct,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
leading: TextSpan(
|
||||
text: L10n.of(context).meaningSectionHeader,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
runAlignment: WrapAlignment.start,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
L10n.of(context).formSectionHeader,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6.0),
|
||||
...forms.mapIndexed(
|
||||
(i, form) => Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
WordTextWithAudioButton(
|
||||
text: form,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
color: textColor,
|
||||
),
|
||||
uniqueID: "$form-${_construct.lemma}-$i",
|
||||
langCode: _userL2!,
|
||||
),
|
||||
if (i != forms.length - 1) const Text(", "),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
xpIcon: _construct.lemmaCategory.icon(_iconSize + 6.0),
|
||||
constructId: constructId,
|
||||
),
|
||||
xpIcon: _construct.lemmaCategory.icon(_iconSize + 6.0),
|
||||
constructId: constructId,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ class VocabAnalyticsListView extends StatelessWidget {
|
|||
),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
key: const PageStorageKey("vocab-analytics-list-view-page-key"),
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 100.0,
|
||||
mainAxisExtent: 100.0,
|
||||
|
|
|
|||
|
|
@ -1,276 +0,0 @@
|
|||
import 'dart:developer';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/app_emojis.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/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LemmaEmojiRow extends StatefulWidget {
|
||||
final ConstructIdentifier cId;
|
||||
final VoidCallback? onTapOverride;
|
||||
final bool isSelected;
|
||||
final bool shouldShowEmojis;
|
||||
final double? iconSize;
|
||||
|
||||
/// if a setState is defined then we're in a context where
|
||||
/// we allow removing an emoji
|
||||
/// later we'll probably want to allow this everywhere
|
||||
final void Function()? emojiSetCallback;
|
||||
|
||||
const LemmaEmojiRow({
|
||||
super.key,
|
||||
required this.cId,
|
||||
required this.onTapOverride,
|
||||
required this.isSelected,
|
||||
required this.shouldShowEmojis,
|
||||
this.emojiSetCallback,
|
||||
this.iconSize,
|
||||
});
|
||||
|
||||
@override
|
||||
LemmaEmojiRowState createState() => LemmaEmojiRowState();
|
||||
}
|
||||
|
||||
class LemmaEmojiRowState extends State<LemmaEmojiRow> {
|
||||
String? displayEmoji;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
displayEmoji = widget.cId.userSetEmoji.firstOrNull;
|
||||
}
|
||||
|
||||
@override
|
||||
didUpdateWidget(LemmaEmojiRow oldWidget) {
|
||||
if (oldWidget.isSelected != widget.isSelected ||
|
||||
widget.cId.userSetEmoji != oldWidget.cId.userSetEmoji) {
|
||||
setState(() => displayEmoji = widget.cId.userSetEmoji.firstOrNull);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
MatrixState.pAnyState.closeOverlay(widget.cId.string);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void openEmojiSetOverlay() async {
|
||||
List<String> emojiChoices = [];
|
||||
try {
|
||||
final info = await widget.cId.getLemmaInfo();
|
||||
emojiChoices = info.emoji;
|
||||
} catch (e, s) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
emojiChoices
|
||||
.add(AppEmojis.emojis[Random().nextInt(AppEmojis.emojis.length)]);
|
||||
}
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s);
|
||||
}
|
||||
|
||||
try {
|
||||
OverlayUtil.showOverlay(
|
||||
context: context,
|
||||
child: EmojiEditOverlay(
|
||||
cId: widget.cId,
|
||||
onSelectEmoji: setEmoji,
|
||||
emojis: emojiChoices,
|
||||
),
|
||||
transformTargetId: widget.cId.string,
|
||||
backDropToDismiss: true,
|
||||
blurBackground: false,
|
||||
borderColor: Theme.of(context).colorScheme.primary,
|
||||
closePrevOverlay: false,
|
||||
followerAnchor: Alignment.topCenter,
|
||||
targetAnchor: Alignment.bottomCenter,
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setEmoji(String emoji) async {
|
||||
try {
|
||||
displayEmoji = emoji;
|
||||
|
||||
await widget.cId.setUserLemmaInfo(
|
||||
UserSetLemmaInfo(
|
||||
emojis: [emoji],
|
||||
),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
widget.emojiSetCallback?.call();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
widget.emojiSetCallback?.call();
|
||||
setState(() {});
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: CompositedTransformTarget(
|
||||
link: MatrixState.pAnyState
|
||||
.layerLinkAndKey(
|
||||
widget.cId.string,
|
||||
)
|
||||
.link,
|
||||
child: Container(
|
||||
key: MatrixState.pAnyState
|
||||
.layerLinkAndKey(
|
||||
widget.cId.string,
|
||||
)
|
||||
.key,
|
||||
height: 50,
|
||||
width: 50,
|
||||
alignment: Alignment.center,
|
||||
child: displayEmoji != null && widget.shouldShowEmojis
|
||||
? InkWell(
|
||||
hoverColor:
|
||||
Theme.of(context).colorScheme.primary.withAlpha(50),
|
||||
onTap: widget.onTapOverride ?? openEmojiSetOverlay,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
displayEmoji!,
|
||||
style: TextStyle(fontSize: widget.iconSize ?? 20),
|
||||
),
|
||||
),
|
||||
)
|
||||
: WordZoomActivityButton(
|
||||
icon: Icon(
|
||||
Icons.add_reaction_outlined,
|
||||
color: widget.isSelected
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
isSelected: widget.isSelected,
|
||||
onPressed: widget.onTapOverride ?? openEmojiSetOverlay,
|
||||
opacity: widget.isSelected ? 1 : 0.4,
|
||||
tooltip: MessageMode.wordEmoji.title(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiEditOverlay extends StatelessWidget {
|
||||
final Function(String) onSelectEmoji;
|
||||
final ConstructIdentifier cId;
|
||||
final List<String> emojis;
|
||||
|
||||
const EmojiEditOverlay({
|
||||
super.key,
|
||||
required this.onSelectEmoji,
|
||||
required this.cId,
|
||||
required this.emojis,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
height: 70,
|
||||
width: 200,
|
||||
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(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: emojis
|
||||
.map(
|
||||
(emoji) => EmojiChoiceItem(
|
||||
emoji: emoji,
|
||||
onSelectEmoji: onSelectEmoji,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiChoiceItem extends StatefulWidget {
|
||||
final String emoji;
|
||||
final Function(String) onSelectEmoji;
|
||||
|
||||
const EmojiChoiceItem({
|
||||
super.key,
|
||||
required this.emoji,
|
||||
required this.onSelectEmoji,
|
||||
});
|
||||
|
||||
@override
|
||||
EmojiChoiceItemState createState() => EmojiChoiceItemState();
|
||||
}
|
||||
|
||||
class EmojiChoiceItemState extends State<EmojiChoiceItem> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
debugPrint('Selected emoji: ${widget.emoji}');
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
widget.onSelectEmoji(widget.emoji);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? Theme.of(context).colorScheme.primary.withAlpha(50)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
child: Text(
|
||||
widget.emoji,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
161
lib/pangea/lemmas/lemma_highlight_emoji_row.dart
Normal file
161
lib/pangea/lemmas/lemma_highlight_emoji_row.dart
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
|
||||
|
||||
class LemmmaHighlightEmojiRow extends StatefulWidget {
|
||||
final LemmaMeaningBuilderState controller;
|
||||
final ConstructIdentifier cId;
|
||||
final VoidCallback? onTapOverride;
|
||||
final bool isSelected;
|
||||
final double? iconSize;
|
||||
|
||||
const LemmmaHighlightEmojiRow({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.cId,
|
||||
required this.onTapOverride,
|
||||
required this.isSelected,
|
||||
this.iconSize,
|
||||
});
|
||||
|
||||
@override
|
||||
LemmmaHighlightEmojiRowState createState() => LemmmaHighlightEmojiRowState();
|
||||
}
|
||||
|
||||
class LemmmaHighlightEmojiRowState extends State<LemmmaHighlightEmojiRow> {
|
||||
String? displayEmoji;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
displayEmoji = widget.cId.userSetEmoji.firstOrNull;
|
||||
}
|
||||
|
||||
@override
|
||||
didUpdateWidget(LemmmaHighlightEmojiRow oldWidget) {
|
||||
if (oldWidget.isSelected != widget.isSelected ||
|
||||
widget.cId.userSetEmoji != oldWidget.cId.userSetEmoji) {
|
||||
setState(() => displayEmoji = widget.cId.userSetEmoji.firstOrNull);
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> setEmoji(String emoji) async {
|
||||
try {
|
||||
setState(() => displayEmoji = emoji);
|
||||
await widget.cId.setUserLemmaInfo(
|
||||
UserSetLemmaInfo(
|
||||
emojis: [emoji],
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
debugger(when: kDebugMode);
|
||||
ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.controller.isLoading) {
|
||||
return const CircularProgressIndicator.adaptive();
|
||||
}
|
||||
|
||||
final emojis = widget.controller.lemmaInfo?.emoji;
|
||||
if (widget.controller.error != null || emojis == null || emojis.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Material(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
height: 80,
|
||||
alignment: Alignment.center,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: emojis
|
||||
.map(
|
||||
(emoji) => EmojiChoiceItem(
|
||||
emoji: emoji,
|
||||
onSelectEmoji: () => setEmoji(emoji),
|
||||
// will highlight selected emoji, or the first emoji if none are selected
|
||||
isDisplay: (displayEmoji == emoji ||
|
||||
(displayEmoji == null && emoji == emojis.first)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiChoiceItem extends StatefulWidget {
|
||||
final String emoji;
|
||||
final VoidCallback onSelectEmoji;
|
||||
final bool isDisplay;
|
||||
|
||||
const EmojiChoiceItem({
|
||||
super.key,
|
||||
required this.emoji,
|
||||
required this.isDisplay,
|
||||
required this.onSelectEmoji,
|
||||
});
|
||||
|
||||
@override
|
||||
EmojiChoiceItemState createState() => EmojiChoiceItemState();
|
||||
}
|
||||
|
||||
class EmojiChoiceItemState extends State<EmojiChoiceItem> {
|
||||
bool _isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _isHovered = true),
|
||||
onExit: (_) => setState(() => _isHovered = false),
|
||||
child: GestureDetector(
|
||||
onTap: widget.onSelectEmoji,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: _isHovered
|
||||
? Theme.of(context).colorScheme.primary.withAlpha(50)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
border: widget.isDisplay
|
||||
? Border.all(
|
||||
color: AppConfig.goldLight,
|
||||
width: 4,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Text(
|
||||
widget.emoji,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -198,9 +198,9 @@ extension ActivityTypeExtension on ActivityTypeEnum {
|
|||
case ActivityTypeEnum.wordMeaning:
|
||||
case ActivityTypeEnum.lemmaId:
|
||||
case ActivityTypeEnum.wordFocusListening:
|
||||
case ActivityTypeEnum.emoji:
|
||||
return 2;
|
||||
case ActivityTypeEnum.hiddenWordListening:
|
||||
case ActivityTypeEnum.emoji:
|
||||
case ActivityTypeEnum.morphId:
|
||||
case ActivityTypeEnum.messageMeaning:
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -1,65 +1,56 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_form.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_match.dart';
|
||||
|
||||
class EmojiActivityGenerator {
|
||||
Future<MessageActivityResponse> get(
|
||||
MessageActivityRequest req,
|
||||
BuildContext context,
|
||||
) async {
|
||||
if (req.targetTokens.length == 1) {
|
||||
return _favorite(req, context);
|
||||
} else {
|
||||
return _matchActivity(req, context);
|
||||
if (req.targetTokens.length <= 1) {
|
||||
throw Exception("Emoji activity requires at least 2 tokens");
|
||||
}
|
||||
}
|
||||
|
||||
Future<MessageActivityResponse> _favorite(
|
||||
MessageActivityRequest req,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final PangeaToken token = req.targetTokens.first;
|
||||
|
||||
final List<String> emojis = await token.getEmojiChoices();
|
||||
|
||||
return MessageActivityResponse(
|
||||
activity: PracticeActivityModel(
|
||||
activityType: ActivityTypeEnum.emoji,
|
||||
targetTokens: [token],
|
||||
langCode: req.userL2,
|
||||
multipleChoiceContent: MultipleChoiceActivity(
|
||||
question: L10n.of(context).pickAnEmojiFor(token.lemma.text),
|
||||
choices: emojis,
|
||||
answers: emojis,
|
||||
spanDisplayDetails: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
return _matchActivity(req);
|
||||
}
|
||||
|
||||
Future<MessageActivityResponse> _matchActivity(
|
||||
MessageActivityRequest req,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final List<Future<LemmaInfoResponse>> lemmaInfoFutures = req.targetTokens
|
||||
.map((token) => token.vocabConstructID.getLemmaInfo())
|
||||
.toList();
|
||||
final Map<ConstructForm, List<String>> matchInfo = {};
|
||||
final List<MapEntry<PangeaToken, List<String>>> tokensWithUserEmojis = [];
|
||||
final List<PangeaToken> tokensNeedingServerEmojis = [];
|
||||
//if user saved emojis, use those, otherwise generate.
|
||||
for (final token in req.targetTokens) {
|
||||
final List<String> userSavedEmojis = token.vocabConstructID.userSetEmoji;
|
||||
|
||||
final List<LemmaInfoResponse> lemmaInfos =
|
||||
await Future.wait(lemmaInfoFutures);
|
||||
if (userSavedEmojis.isNotEmpty) {
|
||||
tokensWithUserEmojis.add(MapEntry(token, userSavedEmojis));
|
||||
} else {
|
||||
tokensNeedingServerEmojis.add(token);
|
||||
}
|
||||
}
|
||||
|
||||
final Map<ConstructForm, List<String>> matchInfo = Map.fromIterables(
|
||||
req.targetTokens.map((token) => token.vocabForm),
|
||||
lemmaInfos.map((e) => e.emoji),
|
||||
);
|
||||
for (final entry in tokensWithUserEmojis) {
|
||||
matchInfo[entry.key.vocabForm] = entry.value;
|
||||
}
|
||||
|
||||
if (tokensNeedingServerEmojis.isNotEmpty) {
|
||||
final List<Future<LemmaInfoResponse>> lemmaInfoFutures =
|
||||
tokensNeedingServerEmojis
|
||||
.map((token) => token.vocabConstructID.getLemmaInfo())
|
||||
.toList();
|
||||
|
||||
final List<LemmaInfoResponse> lemmaInfos =
|
||||
await Future.wait(lemmaInfoFutures);
|
||||
|
||||
for (int i = 0; i < tokensNeedingServerEmojis.length; i++) {
|
||||
matchInfo[tokensNeedingServerEmojis[i].vocabForm] = lemmaInfos[i].emoji;
|
||||
}
|
||||
}
|
||||
|
||||
return MessageActivityResponse(
|
||||
activity: PracticeActivityModel(
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class PracticeRepo {
|
|||
final _morph = MorphActivityGenerator();
|
||||
final _emoji = EmojiActivityGenerator();
|
||||
final _lemma = LemmaActivityGenerator();
|
||||
final _wordFoocusListening = WordFocusListeningGenerator();
|
||||
final _wordFocusListening = WordFocusListeningGenerator();
|
||||
final _wordMeaning = LemmaMeaningActivityGenerator();
|
||||
|
||||
PracticeRepo() {
|
||||
|
|
@ -129,7 +129,7 @@ class PracticeRepo {
|
|||
// some activities we'll get from the server and others we'll generate locally
|
||||
switch (req.targetType) {
|
||||
case ActivityTypeEnum.emoji:
|
||||
return _emoji.get(req, context);
|
||||
return _emoji.get(req);
|
||||
case ActivityTypeEnum.lemmaId:
|
||||
return _lemma.get(req, context);
|
||||
case ActivityTypeEnum.morphId:
|
||||
|
|
@ -139,7 +139,7 @@ class PracticeRepo {
|
|||
return _wordMeaning.get(req);
|
||||
case ActivityTypeEnum.messageMeaning:
|
||||
case ActivityTypeEnum.wordFocusListening:
|
||||
return _wordFoocusListening.get(req, context);
|
||||
return _wordFocusListening.get(req);
|
||||
case ActivityTypeEnum.hiddenWordListening:
|
||||
return _fetchFromServer(
|
||||
accessToken: accessToken,
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class PracticeSelection {
|
|||
}
|
||||
}
|
||||
|
||||
tokens.sorted(
|
||||
tokens.sort(
|
||||
(a, b) {
|
||||
final bScore = b.activityPriorityScore(activityType, null) *
|
||||
(tokenIsIncludedInActivityOfAnyType(b) ? 1.1 : 1);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_form.dart';
|
||||
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/message_activity_request.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/multiple_choice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart';
|
||||
import 'package:fluffychat/pangea/practice_activities/practice_match.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -15,40 +12,18 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
class WordFocusListeningGenerator {
|
||||
Future<MessageActivityResponse> get(
|
||||
MessageActivityRequest req,
|
||||
BuildContext context,
|
||||
) async {
|
||||
if (req.targetTokens.length == 1) {
|
||||
return _multipleChoiceActivity(req, context);
|
||||
} else {
|
||||
return _matchActivity(req, context);
|
||||
if (req.targetTokens.length <= 1) {
|
||||
throw Exception(
|
||||
"Word focus listening activity requires at least 2 tokens",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<MessageActivityResponse> _multipleChoiceActivity(
|
||||
MessageActivityRequest req,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final token = req.targetTokens.first;
|
||||
final List<String> choices = await lemmaActivityDistractors(token);
|
||||
|
||||
return MessageActivityResponse(
|
||||
activity: PracticeActivityModel(
|
||||
activityType: ActivityTypeEnum.wordFocusListening,
|
||||
targetTokens: [token],
|
||||
langCode: req.userL2,
|
||||
multipleChoiceContent: MultipleChoiceActivity(
|
||||
question: L10n.of(context).wordFocusListeningMultipleChoice,
|
||||
choices: choices,
|
||||
answers: [token.lemma.text],
|
||||
spanDisplayDetails: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
return _matchActivity(req);
|
||||
}
|
||||
|
||||
Future<MessageActivityResponse> _matchActivity(
|
||||
MessageActivityRequest req,
|
||||
BuildContext context,
|
||||
) async {
|
||||
return MessageActivityResponse(
|
||||
activity: PracticeActivityModel(
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class PracticeActivityCard extends StatefulWidget {
|
|||
}
|
||||
|
||||
class PracticeActivityCardState extends State<PracticeActivityCard> {
|
||||
bool fetchingActivity = false;
|
||||
bool fetchingActivity = true;
|
||||
bool savoringTheJoy = false;
|
||||
|
||||
Completer<PracticeActivityEvent?>? currentActivityCompleter;
|
||||
|
|
@ -62,8 +62,10 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
|
|||
|
||||
@override
|
||||
void initState() {
|
||||
_fetchActivity();
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(
|
||||
(_) => _fetchActivity(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -10,132 +10,125 @@ import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
|||
import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_builder.dart';
|
||||
|
||||
class LemmaMeaningWidget extends StatelessWidget {
|
||||
final LemmaMeaningBuilderState controller;
|
||||
|
||||
final ConstructUses constructUse;
|
||||
final String langCode;
|
||||
final TextStyle? style;
|
||||
final InlineSpan? leading;
|
||||
|
||||
const LemmaMeaningWidget({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.constructUse,
|
||||
required this.langCode,
|
||||
this.style,
|
||||
this.leading,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LemmaMeaningBuilder(
|
||||
langCode: langCode,
|
||||
constructId: constructUse.id,
|
||||
builder: (context, controller) {
|
||||
if (controller.isLoading) {
|
||||
return const TextLoadingShimmer();
|
||||
}
|
||||
if (controller.isLoading) {
|
||||
return const TextLoadingShimmer();
|
||||
}
|
||||
|
||||
if (controller.error != null) {
|
||||
debugger(when: kDebugMode);
|
||||
return ErrorIndicator(
|
||||
message: L10n.of(context).errorFetchingDefinition,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
if (controller.error != null) {
|
||||
debugger(when: kDebugMode);
|
||||
return ErrorIndicator(
|
||||
message: L10n.of(context).errorFetchingDefinition,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
|
||||
if (controller.editMode) {
|
||||
controller.controller.text = controller.lemmaInfo?.meaning ?? "";
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
|
||||
constructUse.lemma,
|
||||
constructUse.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 Row(
|
||||
if (controller.editMode) {
|
||||
controller.controller.text = controller.lemmaInfo?.meaning ?? "";
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Tooltip(
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
message: L10n.of(context).doubleClickToEdit,
|
||||
child: GestureDetector(
|
||||
onLongPress: () => controller.toggleEditMode(true),
|
||||
onDoubleTap: () => controller.toggleEditMode(true),
|
||||
child: RichText(
|
||||
textAlign:
|
||||
leading == null ? TextAlign.center : TextAlign.start,
|
||||
text: TextSpan(
|
||||
style: style,
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
if (leading != null)
|
||||
const WidgetSpan(child: SizedBox(width: 6.0)),
|
||||
TextSpan(
|
||||
text: controller.lemmaInfo?.meaning,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).whatIsMeaning(
|
||||
constructUse.lemma,
|
||||
constructUse.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 Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Tooltip(
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
message: L10n.of(context).doubleClickToEdit,
|
||||
child: GestureDetector(
|
||||
onLongPress: () => controller.toggleEditMode(true),
|
||||
onDoubleTap: () => controller.toggleEditMode(true),
|
||||
child: RichText(
|
||||
textAlign: leading == null ? TextAlign.center : TextAlign.start,
|
||||
text: TextSpan(
|
||||
style: style,
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
if (leading != null)
|
||||
const WidgetSpan(child: SizedBox(width: 6.0)),
|
||||
TextSpan(
|
||||
text: controller.lemmaInfo?.meaning,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue