feat: show word card in vocab details page

This commit is contained in:
ggurdin 2025-12-17 11:31:52 -05:00
parent d8685841ed
commit 602020cb40
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
5 changed files with 175 additions and 247 deletions

View file

@ -1,82 +0,0 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics_details_popup/lemma_usage_dots.dart';
import 'package:fluffychat/pangea/analytics_details_popup/lemma_use_example_messages.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
class AnalyticsDetailsViewContent extends StatelessWidget {
final Widget title;
final Widget subtitle;
final Widget headerContent;
final Widget xpIcon;
final ConstructIdentifier constructId;
const AnalyticsDetailsViewContent({
required this.title,
required this.subtitle,
required this.xpIcon,
required this.headerContent,
required this.constructId,
super.key,
});
ConstructUses get construct => constructId.constructUses;
@override
Widget build(BuildContext context) {
final Color textColor = (Theme.of(context).brightness != Brightness.light
? construct.lemmaCategory.color(context)
: construct.lemmaCategory.darkColor(context));
return SingleChildScrollView(
child: Column(
children: [
title,
const SizedBox(height: 16.0),
subtitle,
const SizedBox(height: 16.0),
headerContent,
const Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Divider(),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
xpIcon,
const SizedBox(width: 16.0),
Text(
"${construct.points} XP",
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: textColor,
),
),
],
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
LemmaUseExampleMessages(construct: construct),
...LearningSkillsEnum.values
.where((v) => v.isVisible)
.map((skill) {
return LemmaUsageDots(
construct: construct,
category: skill,
tooltip: skill.tooltip(context),
icon: skill.icon,
);
}),
],
),
),
],
),
);
}
}

View file

@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics_details_popup/lemma_usage_dots.dart';
import 'package:fluffychat/pangea/analytics_details_popup/lemma_use_example_messages.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/learning_skills_enum.dart';
class AnalyticsDetailsUsageContent extends StatelessWidget {
final ConstructUses construct;
const AnalyticsDetailsUsageContent({
required this.construct,
super.key,
});
@override
Widget build(BuildContext context) {
return Column(
children: [
LemmaUseExampleMessages(construct: construct),
...LearningSkillsEnum.values.where((v) => v.isVisible).map((skill) {
return LemmaUsageDots(
construct: construct,
category: skill,
tooltip: skill.tooltip(context),
icon: skill.icon,
);
}),
],
);
}
}

View file

@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_usage_content.dart';
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
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/construct_xp_widget.dart';
@ -18,31 +17,54 @@ class MorphDetailsView extends StatelessWidget {
super.key,
});
ConstructUses get _construct => constructId.constructUses;
MorphFeaturesEnum get _morphFeature =>
MorphFeaturesEnumExtension.fromString(constructId.category);
String get _morphTag => constructId.lemma;
@override
Widget build(BuildContext context) {
final construct = constructId.constructUses;
final Color textColor = Theme.of(context).brightness != Brightness.light
? _construct.lemmaCategory.color(context)
: _construct.lemmaCategory.darkColor(context);
? construct.lemmaCategory.color(context)
: construct.lemmaCategory.darkColor(context);
return AnalyticsDetailsViewContent(
subtitle: MorphFeatureDisplay(morphFeature: _morphFeature),
title: MorphTagDisplay(
morphFeature: _morphFeature,
morphTag: _morphTag,
textColor: textColor,
return SingleChildScrollView(
child: Column(
spacing: 16.0,
children: [
MorphTagDisplay(
morphFeature: _morphFeature,
morphTag: _morphTag,
textColor: textColor,
),
MorphFeatureDisplay(morphFeature: _morphFeature),
MorphMeaningWidget(
feature: _morphFeature,
tag: _morphTag,
style: Theme.of(context).textTheme.bodyLarge,
),
const Divider(),
Row(
spacing: 16.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
ConstructXpWidget(id: constructId),
Text(
"${construct.points} XP",
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: textColor,
),
),
],
),
Padding(
padding: const EdgeInsets.all(20.0),
child: AnalyticsDetailsUsageContent(
construct: construct,
),
),
],
),
headerContent: MorphMeaningWidget(
feature: _morphFeature,
tag: _morphTag,
style: Theme.of(context).textTheme.bodyLarge,
),
xpIcon: ConstructXpWidget(id: constructId),
constructId: constructId,
);
}
}

View file

@ -3,18 +3,12 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_details_emoji_selector.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_usage_content.dart';
import 'package:fluffychat/pangea/analytics_details_popup/word_text_with_audio_button.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/common/widgets/shrinkable_text.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/pangea/lemmas/lemma_meaning_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/phonetic_transcription/phonetic_transcription_widget.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/toolbar/word_card/word_zoom_widget.dart';
import 'package:fluffychat/widgets/matrix.dart';
/// Displays information about selected lemma, and its usage
@ -26,15 +20,9 @@ class VocabDetailsView extends StatelessWidget {
required this.constructId,
});
ConstructUses get _construct => constructId.constructUses;
/// Get the language code for the current lemma
String? get _userL2 =>
MatrixState.pangeaController.userController.userL2?.langCode;
List<String> get forms =>
MatrixState.pangeaController.getAnalytics.constructListModel
.getConstructUsesByLemma(_construct.lemma)
.getConstructUsesByLemma(constructId.lemma)
.map((e) => e.uses)
.expand((element) => element)
.map((e) => e.form?.toLowerCase())
@ -46,140 +34,110 @@ class VocabDetailsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final construct = constructId.constructUses;
final Color textColor = (Theme.of(context).brightness != Brightness.light
? _construct.lemmaCategory.color(context)
: _construct.lemmaCategory.darkColor(context));
? construct.lemmaCategory.color(context)
: construct.lemmaCategory.darkColor(context));
return AnalyticsDetailsViewContent(
title: Column(
return SingleChildScrollView(
child: Column(
spacing: 16.0,
children: [
LayoutBuilder(
builder: (context, constraints) {
return ShrinkableText(
text: _construct.lemma,
maxWidth: constraints.maxWidth - 40.0,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
color: textColor,
),
);
},
WordZoomWidget(
token: PangeaTokenText.fromString(constructId.lemma),
langCode: MatrixState.pangeaController.userController.userL2Code!,
construct: constructId,
),
if (MatrixState.pangeaController.userController.showTranscription)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: PhoneticTranscriptionWidget(
text: _construct.lemma,
textLanguage:
MatrixState.pangeaController.userController.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,
Column(
children: [
Text(
getGrammarCopy(
category: "POS",
lemma: _construct.category,
context: context,
) ??
_construct.lemma,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: textColor,
Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
spacing: 16.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
construct.lemmaCategory.icon(_iconSize + 6.0),
Text(
"${construct.points} XP",
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: textColor,
),
),
],
),
),
SizedBox(
width: _iconSize,
height: _iconSize,
child: MorphIcon(
morphFeature: MorphFeaturesEnum.Pos,
morphTag: _construct.category,
Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: _VocabForms(
lemma: constructId.lemma,
forms: forms,
textColor: textColor,
),
),
AnalyticsDetailsUsageContent(
construct: construct,
),
],
),
),
],
),
const SizedBox(height: 16.0),
Text(
L10n.of(context).vocabEmoji,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
fontStyle: FontStyle.italic,
color: textColor,
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: VocabDetailsEmojiSelector(constructId),
),
],
),
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(
constructId: constructId,
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,
);
}
}
class _VocabForms extends StatelessWidget {
final String lemma;
final List<String> forms;
final Color textColor;
const _VocabForms({
required this.lemma,
required this.forms,
required this.textColor,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
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-$lemma-$i",
langCode:
MatrixState.pangeaController.userController.userL2Code!,
),
if (i != forms.length - 1) const Text(", "),
],
),
),
],
),
);
}
}

View file

@ -5,6 +5,7 @@ import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart';
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/lemmas/lemma_meaning_builder.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
class LemmaMeaningDisplay extends StatelessWidget {
final String langCode;
@ -38,15 +39,12 @@ class LemmaMeaningDisplay extends StatelessWidget {
);
}
if (constructId.lemma.toLowerCase() == text.toLowerCase()) {
return Text(
controller.lemmaInfo!.meaning,
style: const TextStyle(
fontSize: 14.0,
),
textAlign: TextAlign.center,
);
}
final pos = getGrammarCopy(
category: "POS",
lemma: constructId.category,
context: context,
) ??
L10n.of(context).other;
return RichText(
text: TextSpan(
@ -55,7 +53,7 @@ class LemmaMeaningDisplay extends StatelessWidget {
),
children: [
TextSpan(
text: constructId.lemma,
text: "${constructId.lemma} ($pos)",
),
const WidgetSpan(
child: SizedBox(width: 8.0),