Vocab-details-v2 (#2093)

* started activity in chat creation

* starting vocab tile display

* refactor(vocab_analytics_list): made into little tiles that show the user chosen emoji

* chore: comment out unused file

* chore: remove unused variable

* chore: make eventID nullable for constructs so users can set emoji from vocab details popup

---------

Co-authored-by: ggurdin <ggurdin@gmail.com>
Co-authored-by: ggurdin <46800240+ggurdin@users.noreply.github.com>
This commit is contained in:
wcjord 2025-03-10 10:33:39 -04:00 committed by GitHub
parent 74b0cfd584
commit 0128ac42cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 544 additions and 396 deletions

View file

@ -4782,7 +4782,7 @@
},
"cancelInSubscriptionSettings": "• Cancel at any time in subscription settings",
"cancelToAvoidCharges": "• Cancel before {trialEnds} to avoid charges",
"@cancelToAvoidCharges": {
"@cancelToAvoidCharges": {
"type": "String",
"placeholders": {
"trialEnds": {
@ -4803,6 +4803,8 @@
}
}
},
"analyticsVocabListBody": "This is all your vocabulary! As you earn XP for each word, they'll go from seedling to full bloom. Click on any word to see more details.",
"morphAnalyticsListBody": "These are all the grammar concepts in the language you're learning! You'll unlock them as you encounter them while chatting. Click for details.",
"knockSpaceSuccess": "You have requested to join this space! An admin will respond to your request when they receive it 😀",
"joinByCode": "Join by code",
"createASpace": "Create a space"

View file

@ -0,0 +1,46 @@
import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
import 'package:fluffychat/pangea/activity_planner/activity_plan_response.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ActivitySearchRepo {
static final GetStorage _activityPlanStorage =
GetStorage('activity_plan_search_storage');
static void set(ActivityPlanRequest request, ActivityPlanResponse response) {
_activityPlanStorage.write(request.storageKey, response.toJson());
}
static Future<ActivityPlanResponse> get(ActivityPlanRequest request) async {
final cachedJson = _activityPlanStorage.read(request.storageKey);
if (cachedJson != null) {
final cached = ActivityPlanResponse.fromJson(cachedJson);
return cached;
}
final Requests req = Requests(
choreoApiKey: Environment.choreoApiKey,
accessToken: MatrixState.pangeaController.userController.accessToken,
);
final Response res = await req.post(
url: PApiUrls.activityPlanSearch,
body: request.toJson(),
);
final decodedBody = jsonDecode(utf8.decode(res.bodyBytes));
final response = ActivityPlanResponse.fromJson(decodedBody);
set(request, response);
return response;
}
}

View file

@ -0,0 +1,38 @@
import 'package:fluffychat/pangea/activity_planner/media_enum.dart';
import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart';
class ActivitySearchRequest {
final String targetLanguage;
final String languageOfInstructions;
final LanguageLevelTypeEnum? languageLevel;
final String? mode;
final String? learningObjective;
final String? topic;
final MediaEnum? media;
final int? numberOfParticipants;
ActivitySearchRequest({
required this.targetLanguage,
required this.languageOfInstructions,
this.mode,
this.learningObjective,
this.topic,
this.media,
this.numberOfParticipants = 2,
this.languageLevel = LanguageLevelTypeEnum.preA1,
});
Map<String, dynamic> toJson() {
return {
'target_language': targetLanguage,
'language_of_instructions': languageOfInstructions,
'language_level': languageLevel,
'mode': mode,
'objective': learningObjective,
'topic': topic,
'media': media,
'number_of_participants': numberOfParticipants,
};
}
}

View file

@ -0,0 +1,45 @@
// // shows n rows of activity suggestions vertically, where n is the number of rows
// // as the user tries to scroll horizontally to the right, the client will fetch more activity suggestions
// import 'package:flutter/material.dart';
// import 'package:fluffychat/pangea/activity_planner/activity_plan_request.dart';
// import 'package:fluffychat/pangea/activity_suggestions/activity_plan_search_repo.dart';
// class ActivitySuggestionsArea extends StatefulWidget {
// const ActivitySuggestionsArea({super.key});
// @override
// ActivitySuggestionsAreaState createState() => ActivitySuggestionsAreaState();
// }
// class ActivitySuggestionsAreaState extends State<ActivitySuggestionsArea> {
// @override
// void initState() {
// super.initState();
// }
// Future<void> fetchMoreSuggestions() async {
// ActivitySearchRepo.get(
// ActivityPlanRequest(),
// );
// }
// @override
// Widget build(BuildContext context) {
// return Container(
// child: ListView.builder(
// scrollDirection: Axis.vertical,
// itemCount: 5,
// itemBuilder: (context, index) {
// return Container(
// height: 100,
// width: 100,
// color: Colors.blue,
// margin: const EdgeInsets.all(10),
// );
// },
// ),
// );
// }
// }

View file

@ -4,15 +4,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/pangea/analytics_details_popup/morph_analytics_view.dart';
import 'package:fluffychat/pangea/analytics_details_popup/morph_analytics_list_view.dart';
import 'package:fluffychat/pangea/analytics_details_popup/morph_details_view.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_view.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_details_view.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_details_view.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_view.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/common/widgets/full_width_dialog.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart';
import 'package:fluffychat/pangea/morphs/morph_models.dart';
import 'package:fluffychat/pangea/morphs/morph_repo.dart';
@ -135,13 +135,13 @@ class AnalyticsPopupWrapperState extends State<AnalyticsPopupWrapper> {
),
body: localView == ConstructTypeEnum.morph
? localConstructZoom == null
? MorphAnalyticsView(
? MorphAnalyticsListView(
onConstructZoom: _setConstructZoom,
controller: this,
)
: MorphDetailsView(constructId: localConstructZoom!)
: localConstructZoom == null
? VocabAnalyticsView(onConstructZoom: _setConstructZoom)
? VocabAnalyticsListView(onConstructZoom: _setConstructZoom)
: VocabDetailsView(constructId: localConstructZoom!),
),
maxWidth: 600,

View file

@ -8,10 +8,10 @@ import 'package:fluffychat/config/app_config.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/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.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;

View file

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.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/learning_skills_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
class LemmaUsageDots extends StatelessWidget {
final ConstructUses construct;

View file

@ -6,11 +6,11 @@ import 'package:collection/collection.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.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/learning_skills_enum.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/widgets/matrix.dart';
@ -49,8 +49,9 @@ class LemmaUseExampleMessages extends StatelessWidget {
continue;
}
if (use.metadata.roomId == null) continue;
final Room? room = MatrixState.pangeaController.matrixState.client
.getRoomById(use.metadata.roomId);
.getRoomById(use.metadata.roomId!);
if (room == null) continue;
Timeline? timeline = room.timeline;

View file

@ -5,20 +5,22 @@ import 'package:flutter_gen/gen_l10n/l10n.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_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.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/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/pangea/morphs/morph_icon.dart';
import 'package:fluffychat/pangea/user/client_extension.dart';
import 'package:fluffychat/widgets/matrix.dart';
class MorphAnalyticsView extends StatelessWidget {
class MorphAnalyticsListView extends StatelessWidget {
final void Function(ConstructIdentifier) onConstructZoom;
final AnalyticsPopupWrapperState controller;
const MorphAnalyticsView({
const MorphAnalyticsListView({
required this.onConstructZoom,
required this.controller,
super.key,
@ -27,23 +29,35 @@ class MorphAnalyticsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: ListView.builder(
key: const PageStorageKey<String>('morph-analytics'),
itemCount: controller.features.length,
itemBuilder: (context, index) {
final feature = controller.features[index];
return feature.displayTags.isNotEmpty
? MorphFeatureBox(
morphFeature: feature.feature,
allTags: controller.morphs
.getDisplayTags(feature.feature)
.map((tag) => tag.toLowerCase())
.toSet(),
onConstructZoom: onConstructZoom,
)
: const SizedBox.shrink();
},
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// spacing: 16.0,
children: [
// Add your text widget here
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.morphAnalyticsList,
),
Expanded(
child: ListView.builder(
key: const PageStorageKey<String>('morph-analytics'),
itemCount: controller.features.length,
itemBuilder: (context, index) {
final feature = controller.features[index];
return feature.displayTags.isNotEmpty
? MorphFeatureBox(
morphFeature: feature.feature,
allTags: controller.morphs
.getDisplayTags(feature.feature)
.map((tag) => tag.toLowerCase())
.toSet(),
onConstructZoom: onConstructZoom,
)
: const SizedBox.shrink();
},
),
),
],
),
);
}

View file

@ -4,10 +4,10 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart';
import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.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';
import 'package:fluffychat/pangea/morphs/morph_feature_display.dart';
import 'package:fluffychat/pangea/morphs/morph_meaning/morph_info_repo.dart';

View file

@ -4,11 +4,10 @@ import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup_content.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/lemmas/lemma.dart';
@ -178,16 +177,7 @@ class VocabDetailsView extends StatelessWidget {
],
),
),
xpIcon: CustomizedSvg(
svgUrl: _construct.lemmaCategory.svgURL,
colorReplacements: const {},
errorIcon: Text(
_construct.lemmaCategory.emoji,
style: const TextStyle(
fontSize: 20,
),
),
),
xpIcon: _construct.lemmaCategory.icon(12),
constructId: constructId,
);
}

View file

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart';
class VocabAnalyticsListTile extends StatefulWidget {
const VocabAnalyticsListTile({
super.key,
required this.constructUse,
required this.onTap,
});
final void Function() onTap;
final ConstructUses constructUse;
@override
VocabAnalyticsListTileState createState() => VocabAnalyticsListTileState();
}
class VocabAnalyticsListTileState extends State<VocabAnalyticsListTile> {
bool _isHovered = false;
final double maxWidth = 100;
final double padding = 8.0;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: InkWell(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
onTap: widget.onTap,
child: Container(
height: maxWidth,
width: maxWidth,
padding: EdgeInsets.all(padding),
decoration: BoxDecoration(
color: _isHovered
? widget.constructUse.constructLevel.color.withAlpha(30)
: Colors.transparent,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
alignment: Alignment.center,
height: (maxWidth - padding * 2) * 0.6,
child: Opacity(
opacity:
widget.constructUse.id.userSetEmoji == null ? 0.2 : 1,
child: widget.constructUse.id.userSetEmoji != null
? Text(
widget.constructUse.id.userSetEmoji!,
style: const TextStyle(
fontSize: 22,
),
)
: widget.constructUse.constructLevel.icon(10),
),
),
Container(
alignment: Alignment.topCenter,
padding: const EdgeInsets.only(top: 4),
height: (maxWidth - padding * 2) * 0.4,
child: ShrinkableText(
text: widget.constructUse.lemma,
maxWidth: maxWidth - padding * 2,
style: TextStyle(
fontSize: 16,
color: widget.constructUse.constructLevel.color,
),
),
),
],
),
),
),
);
}
}

View file

@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics_details_popup/vocab_analytics_list_tile.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.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/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/widgets/matrix.dart';
/// Displays vocab analytics, sorted into categories
/// (flowers, greens, and seeds) by points
class VocabAnalyticsListView extends StatelessWidget {
final void Function(ConstructIdentifier) onConstructZoom;
List<ConstructUses> get vocab => MatrixState
.pangeaController.getAnalytics.constructListModel
.constructList(type: ConstructTypeEnum.vocab)
..sort((a, b) => a.lemma.toLowerCase().compareTo(b.lemma.toLowerCase()));
const VocabAnalyticsListView({
super.key,
required this.onConstructZoom,
});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
const InstructionsInlineTooltip(
instructionsEnum: InstructionsEnum.analyticsVocabList,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 50,
children: ConstructLevelEnum.values.reversed
.map((constructLevelCategory) {
final int count = vocab
.where((e) => e.lemmaCategory == constructLevelCategory)
.length;
return Badge(
label: Text(count.toString()),
child: constructLevelCategory.icon(24),
);
}).toList(),
),
),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
runAlignment: WrapAlignment.start,
children: vocab
.map(
(vocab) => VocabAnalyticsListTile(
onTap: () => onConstructZoom(vocab.id),
constructUse: vocab,
),
)
.toList(),
),
],
),
);
}
}

View file

@ -1,182 +0,0 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
import 'package:fluffychat/widgets/matrix.dart';
/// Displays vocab analytics, sorted into categories
/// (flowers, greens, and seeds) by points
class VocabAnalyticsView extends StatelessWidget {
final void Function(ConstructIdentifier) onConstructZoom;
const VocabAnalyticsView({
super.key,
required this.onConstructZoom,
});
@override
Widget build(BuildContext context) {
final lemmas = MatrixState.pangeaController.getAnalytics.constructListModel
.constructList(type: ConstructTypeEnum.vocab)
..sort((a, b) => a.lemma.toLowerCase().compareTo(b.lemma.toLowerCase()));
final flowerLemmas = <VocabChip>[];
final greenLemmas = <VocabChip>[];
final seedLemmas = <VocabChip>[];
for (int i = 0; i < lemmas.length; i++) {
final construct = lemmas[i];
if (construct.lemma.isEmpty) continue;
final int points = construct.points;
String? displayText;
// Check if previous or next entry has same lemma as this entry
if ((i > 0 && lemmas[i - 1].lemma.equals(construct.lemma)) ||
(i < lemmas.length - 1 &&
lemmas[i + 1].lemma.equals(construct.lemma))) {
final pos = getGrammarCopy(
category: "pos",
lemma: construct.category,
context: context,
) ??
construct.category;
displayText = "${construct.lemma} (${pos.toLowerCase()})";
}
final lemma = VocabChip(
construct: construct,
displayText: displayText,
);
// Sort lemmas into categories
if (points < AnalyticsConstants.xpForGreens) {
seedLemmas.add(lemma);
} else if (points >= AnalyticsConstants.xpForFlower) {
flowerLemmas.add(lemma);
} else {
greenLemmas.add(lemma);
}
}
final flowers = LemmaListSection(
type: ConstructLevelEnum.flowers,
lemmas: flowerLemmas,
onTap: onConstructZoom,
);
final greens = LemmaListSection(
type: ConstructLevelEnum.greens,
lemmas: greenLemmas,
onTap: onConstructZoom,
);
final seeds = LemmaListSection(
type: ConstructLevelEnum.seeds,
lemmas: seedLemmas,
onTap: onConstructZoom,
);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: ListView(
key: const PageStorageKey<String>('vocab-analytics'),
children: [flowers, greens, seeds],
),
);
}
}
class VocabChip {
final ConstructUses construct;
final String? displayText;
VocabChip({
required this.construct,
this.displayText,
});
}
class LemmaListSection extends StatelessWidget {
final ConstructLevelEnum type;
final List<VocabChip> lemmas;
final Function(ConstructIdentifier) onTap;
const LemmaListSection({
super.key,
required this.type,
required this.lemmas,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final fontColor =
theme.brightness == Brightness.dark ? type.color : type.darkColor;
return Container(
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.0),
border: Border.all(color: type.color, width: 2),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomizedSvg(
svgUrl: type.svgURL,
colorReplacements: const {},
errorIcon: Text(type.emoji),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: lemmas.isEmpty
? Text(
L10n.of(context).noLemmasFound(type.xpNeeded),
style: TextStyle(
color: fontColor,
fontStyle: FontStyle.italic,
),
)
: Wrap(
spacing: 0,
runSpacing: 0,
children: lemmas.mapIndexed((index, lemma) {
return MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: () => onTap(lemma.construct.id),
child: Text(
"${lemma.displayText ?? lemma.construct.lemma}${index < lemmas.length - 1 ? ', ' : ''}",
style: TextStyle(
shadows: [
Shadow(
color: fontColor,
offset: const Offset(0, -2.5),
),
],
color: Colors.transparent,
decoration: TextDecoration.underline,
decorationStyle: TextDecorationStyle.dotted,
decorationColor: fontColor,
decorationThickness: 1,
fontSize: theme.textTheme.bodyLarge?.fontSize,
),
),
),
);
}).toList(),
),
),
],
),
);
}
}

View file

@ -5,11 +5,11 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart';
/// A wrapper around a list of [OneConstructUse]s, used to simplify

View file

@ -1,9 +1,9 @@
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.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/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
/// One lemma and a list of construct uses for that lemma
class ConstructUses {

View file

@ -4,9 +4,9 @@ import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_type_enum.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart';
import 'package:fluffychat/pangea/morphs/morph_models.dart';
import 'construct_type_enum.dart';
@ -97,7 +97,7 @@ class OneConstructUse {
this.id,
}) : _category = category ?? "other";
String get chatId => metadata.roomId;
String? get chatId => metadata.roomId;
String get msgId => metadata.eventId!;
DateTime get timeStamp => metadata.timeStamp;
@ -177,7 +177,8 @@ class OneConstructUse {
}
Room? getRoom(Client client) {
return client.getRoomById(metadata.roomId);
if (metadata.roomId == null) return null;
return client.getRoomById(metadata.roomId!);
}
Future<Event?> getEvent(Client client) async {
@ -197,7 +198,7 @@ class OneConstructUse {
class ConstructUseMetaData {
String? eventId;
String roomId;
String? roomId;
DateTime timeStamp;
ConstructUseMetaData({

View file

@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.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/toolbar/enums/activity_type_enum.dart';

View file

@ -119,13 +119,16 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
/// the data locally and reset the update timer
/// Decide whether to update the analytics room
void _onNewAnalyticsData(AnalyticsStream data) {
final List<OneConstructUse> constructs = _getDraftUses(data.roomId);
final String? eventID = data.eventId;
final String? roomID = data.roomId;
List<OneConstructUse> constructs = [];
if (roomID != null) {
constructs = _getDraftUses(roomID);
}
constructs.addAll(data.constructs);
final String eventID = data.eventId;
final String roomID = data.roomId;
if (kDebugMode) {
for (final use in constructs) {
debugPrint(
@ -138,7 +141,7 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
_addLocalMessage(eventID, constructs).then(
(_) {
_clearDraftUses(roomID);
if (roomID != null) _clearDraftUses(roomID);
_decideWhetherToUpdateAnalyticsRoom(
level,
data.origin,
@ -245,7 +248,7 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
/// Add a list of construct uses for a new message to the local
/// cache of recently sent messages
Future<void> _addLocalMessage(
String cacheKey,
String? cacheKey,
List<OneConstructUse> constructs,
) async {
try {
@ -254,7 +257,7 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
// if this is not a draft message, add the eventId to the metadata
// if it's missing (it will be missing for draft constructs)
if (!cacheKey.startsWith('draft')) {
if (cacheKey != null && !cacheKey.startsWith('draft')) {
constructs = constructs.map((construct) {
if (construct.metadata.eventId != null) return construct;
construct.metadata.eventId = cacheKey;
@ -262,6 +265,7 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
}).toList();
}
cacheKey ??= Object.hashAll(constructs).toString();
currentCache[cacheKey] = constructs;
await _setMessagesSinceUpdate(currentCache);
@ -425,8 +429,8 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
}
class AnalyticsStream {
final String eventId;
final String roomId;
final String? eventId;
final String? roomId;
final AnalyticsUpdateOrigin? origin;
final List<OneConstructUse> constructs;

View file

@ -72,6 +72,8 @@ class PApiUrls {
static String activityModeList = "${PApiUrls.choreoEndpoint}/modes";
static String objectiveList = "${PApiUrls.choreoEndpoint}/objectives";
static String topicList = "${PApiUrls.choreoEndpoint}/topics";
static String activityPlanSearch =
"${PApiUrls.choreoEndpoint}/activity_plan/search";
static String morphFeaturesAndTags = "${PApiUrls.choreoEndpoint}/morphs";

View file

@ -16,12 +16,24 @@ class CustomizedSvg extends StatelessWidget {
/// Icon to show in case of error
final Widget errorIcon;
/// Width of the SVG
/// Default is 24
/// If you want to keep the aspect ratio, set only the height
final double? width;
/// Height of the SVG
/// Default is 24
/// If you want to keep the aspect ratio, set only the width
final double? height;
static final GetStorage _svgStorage = GetStorage('svg_cache');
const CustomizedSvg({
super.key,
required this.svgUrl,
required this.colorReplacements,
this.errorIcon = const Icon(Icons.error_outline),
this.width = 24,
this.height = 24,
});
Future<String?> _fetchSvg() async {
@ -110,7 +122,11 @@ class CustomizedSvg extends StatelessWidget {
} else if (snapshot.hasError || snapshot.data == null) {
return errorIcon;
} else if (snapshot.hasData) {
return SvgPicture.string(snapshot.data!);
return SvgPicture.string(
snapshot.data!,
width: width,
height: height,
);
} else {
return const SizedBox.shrink();
}

View file

@ -12,6 +12,9 @@ import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
import 'package:fluffychat/widgets/matrix.dart';
class ConstructIdentifier {
@ -126,4 +129,51 @@ class ConstructIdentifier {
}
return null;
}
/// [setEmoji] sets the emoji for the lemma
/// NOTE: assumes that the language of the lemma is the same as the user's current l2
Future<void> setEmoji(String emoji) async {
final analyticsRoom =
MatrixState.pangeaController.matrixState.client.analyticsRoomLocal();
if (analyticsRoom == null) return;
try {
final client = MatrixState.pangeaController.matrixState.client;
final syncFuture = client.onRoomState.stream.firstWhere((event) {
return event.roomId == analyticsRoom.id &&
event.state.type == PangeaEventTypes.userChosenEmoji;
});
client.setRoomStateWithKey(
analyticsRoom.id,
PangeaEventTypes.userChosenEmoji,
string,
{ModelKey.emoji: emoji},
);
await syncFuture;
} catch (err, s) {
debugger(when: kDebugMode);
ErrorHandler.logError(
e: err,
data: {
"construct": string,
"emoji": emoji,
},
s: s,
);
}
}
// [getEmojiChoices] gets the emoji choices for the lemma
// assumes that the language of the lemma is the same as the user's current l2
Future<List<String>> getEmojiChoices() => LemmaInfoRepo.get(
LemmaInfoRequest(
lemma: lemma,
partOfSpeech: category,
lemmaLang: MatrixState
.pangeaController.languageController.userL2?.langCode ??
LanguageKeys.unknownLanguage,
userL1: MatrixState
.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
),
).then((onValue) => onValue.emoji);
}

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
enum ConstructLevelEnum {
flowers,
@ -79,4 +80,17 @@ extension ConstructLevelEnumExt on ConstructLevelEnum {
return 0;
}
}
Widget icon([double? size]) => CustomizedSvg(
svgUrl: svgURL,
colorReplacements: const {},
errorIcon: Text(
emoji,
style: TextStyle(
fontSize: size ?? 24,
),
),
width: size,
height: size,
);
}

View file

@ -5,19 +5,14 @@ import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.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/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
import 'package:fluffychat/pangea/morphs/morph_repo.dart';
import 'package:fluffychat/pangea/toolbar/enums/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart';
@ -543,18 +538,7 @@ class PangeaToken {
.cast<ConstructUses>()
.toList();
Future<List<String>> getEmojiChoices() => LemmaInfoRepo.get(
LemmaInfoRequest(
lemma: lemma.text,
partOfSpeech: pos,
lemmaLang: MatrixState
.pangeaController.languageController.userL2?.langCode ??
LanguageKeys.unknownLanguage,
userL1: MatrixState
.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
),
).then((onValue) => onValue.emoji);
Future<List<String>> getEmojiChoices() => vocabConstructID.getEmojiChoices();
ConstructIdentifier get vocabConstructID => ConstructIdentifier(
lemma: lemma.text,
@ -562,37 +546,7 @@ class PangeaToken {
category: pos,
);
/// [setEmoji] sets the emoji for the lemma
/// NOTE: assumes that the language of the lemma is the same as the user's current l2
Future<void> setEmoji(String emoji) async {
final analyticsRoom =
MatrixState.pangeaController.matrixState.client.analyticsRoomLocal();
if (analyticsRoom == null) return;
try {
final client = MatrixState.pangeaController.matrixState.client;
final syncFuture = client.onRoomState.stream.firstWhere((event) {
return event.roomId == analyticsRoom.id &&
event.state.type == PangeaEventTypes.userChosenEmoji;
});
client.setRoomStateWithKey(
analyticsRoom.id,
PangeaEventTypes.userChosenEmoji,
vocabConstructID.string,
{ModelKey.emoji: emoji},
);
await syncFuture;
} catch (err, s) {
debugger(when: kDebugMode);
ErrorHandler.logError(
e: err,
data: {
"construct": vocabConstructID.string,
"emoji": emoji,
},
s: s,
);
}
}
Future<void> setEmoji(String emoji) => vocabConstructID.setEmoji(emoji);
/// [getEmoji] gets the emoji for the lemma
/// NOTE: assumes that the language of the lemma is the same as the user's current l2

View file

@ -24,6 +24,8 @@ enum InstructionsEnum {
activityPlannerOverview,
ttsDisabled,
chooseEmoji,
analyticsVocabList,
morphAnalyticsList,
}
extension InstructionsEnumExtension on InstructionsEnum {
@ -50,6 +52,8 @@ extension InstructionsEnumExtension on InstructionsEnum {
case InstructionsEnum.clickBestOption:
case InstructionsEnum.completeActivitiesToUnlock:
case InstructionsEnum.lemmaMeaning:
case InstructionsEnum.analyticsVocabList:
case InstructionsEnum.morphAnalyticsList:
ErrorHandler.logError(
e: Exception("No title for this instruction"),
m: 'InstructionsEnumExtension.title',
@ -96,6 +100,10 @@ extension InstructionsEnumExtension on InstructionsEnum {
return l10n.chooseEmojiInstructionsBody;
case InstructionsEnum.ttsDisabled:
return l10n.ttsDisabledBody;
case InstructionsEnum.analyticsVocabList:
return l10n.analyticsVocabListBody;
case InstructionsEnum.morphAnalyticsList:
return l10n.morphAnalyticsListBody;
}
}

View file

@ -69,7 +69,7 @@ class InstructionsInlineTooltipState extends State<InstructionsInlineTooltip>
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
color: Theme.of(context).colorScheme.primary.withAlpha(20),
color: Theme.of(context).colorScheme.primary.withAlpha(5),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),

View file

@ -2,11 +2,10 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/widgets/matrix.dart';
/// display the construct xp widget
@ -90,17 +89,7 @@ class ConstructXpWidgetState extends State<ConstructXpWidget>
Stream<AnalyticsStreamUpdate> get stream =>
MatrixState.pangeaController.getAnalytics.analyticsStream.stream;
Widget get svg => CustomizedSvg(
svgUrl:
constructLemmaCategory?.svgURL ?? ConstructLevelEnum.seeds.svgURL,
colorReplacements: const {},
errorIcon: Text(
constructLemmaCategory?.emoji ?? ConstructLevelEnum.seeds.svgURL,
style: const TextStyle(
fontSize: 20,
),
),
);
Widget get svg => constructLemmaCategory?.icon() ?? const SizedBox();
@override
void dispose() {

View file

@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.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/constructs/construct_identifier.dart';
enum ActivityTypeEnum {
wordMeaning,

View file

@ -7,8 +7,8 @@ import 'package:collection/collection.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/morphs/morph_categories_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/activity_display_instructions_enum.dart';

View file

@ -5,9 +5,9 @@ import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_emojis.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/common/widgets/customized_svg.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/toolbar/enums/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_emoji_choice_item.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
@ -82,13 +82,18 @@ class MessageEmojiChoice extends StatelessWidget {
isSelected: false,
onDoubleTap: () => onDoubleTapOrLongPress(context, emoji),
onLongPress: () => onDoubleTapOrLongPress(context, emoji),
token: null,
greenHighlight: false,
),
)
.toList();
List<Widget> perTokenEmoji(BuildContext context) =>
tokens!.where((token) => token.lemma.saveVocab).map((token) {
final bool greenHighlight = token.shouldDoActivity(
a: ActivityTypeEnum.wordMeaning,
feature: null,
tag: null,
);
if (!token.lemma.saveVocab) {
return MessageEmojiChoiceItem(
content: token.text.content,
@ -96,7 +101,7 @@ class MessageEmojiChoice extends StatelessWidget {
isSelected: overlayController.isTokenSelected(token),
onDoubleTap: null,
onLongPress: null,
token: token,
greenHighlight: greenHighlight,
);
}
@ -104,18 +109,14 @@ class MessageEmojiChoice extends StatelessWidget {
if (emoji == null) {
return MessageEmojiChoiceItem(
topContent: CustomizedSvg(
svgUrl: token.vocabConstruct.constructLevel.svgURL,
colorReplacements: const {},
errorIcon: Text(token.xpEmoji),
),
topContent: token.vocabConstruct.constructLevel.icon(),
content: token.text.content,
onTap: () => overlayController.onClickOverlayMessageToken(token),
onDoubleTap: null,
onLongPress: null,
isSelected: overlayController.isTokenSelected(token),
contentOpacity: 0.1,
token: token,
greenHighlight: greenHighlight,
);
}
@ -129,7 +130,7 @@ class MessageEmojiChoice extends StatelessWidget {
onDoubleTap: () => onDoubleTapOrLongPress(context, emoji),
onLongPress: () => onDoubleTapOrLongPress(context, emoji),
isSelected: overlayController.isTokenSelected(token),
token: token,
greenHighlight: greenHighlight,
);
}).toList();

View file

@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/toolbar/enums/activity_type_enum.dart';
class MessageEmojiChoiceItem extends StatefulWidget {
const MessageEmojiChoiceItem({
@ -15,7 +13,7 @@ class MessageEmojiChoiceItem extends StatefulWidget {
this.onLongPress,
required this.isSelected,
this.contentOpacity = 1.0,
required this.token,
required this.greenHighlight,
});
final Widget? topContent;
@ -26,7 +24,7 @@ class MessageEmojiChoiceItem extends StatefulWidget {
final bool isSelected;
final double textSize;
final double contentOpacity;
final PangeaToken? token;
final bool greenHighlight;
@override
MessageEmojiChoiceItemState createState() => MessageEmojiChoiceItemState();
@ -54,12 +52,7 @@ class MessageEmojiChoiceItemState extends State<MessageEmojiChoiceItem> {
? AppConfig.primaryColor.withAlpha((0.2 * 255).toInt())
: _isHovered
? AppConfig.primaryColor.withAlpha((0.1 * 255).toInt())
: widget.token?.shouldDoActivity(
a: ActivityTypeEnum.wordMeaning,
feature: null,
tag: null,
) ??
false
: widget.greenHighlight
? AppConfig.success.withAlpha((0.1 * 255).toInt())
: Colors.transparent,
borderRadius: BorderRadius.circular(AppConfig.borderRadius),

View file

@ -87,8 +87,10 @@ class ReadingAssistanceInputBar extends StatelessWidget {
case MessageMode.wordEmoji:
return WordEmojiChoice(
overlayController: overlayController,
token: overlayController.selectedToken!,
form: overlayController.selectedToken!.text.content,
onEmojiChosen: () =>
overlayController.onActivityFinish(ActivityTypeEnum.emoji),
constructID: overlayController.selectedToken!.vocabConstructID,
);
case MessageMode.wordMeaning:

View file

@ -9,22 +9,26 @@ import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
import 'package:fluffychat/pangea/choreographer/widgets/choice_array.dart';
import 'package:fluffychat/pangea/choreographer/widgets/it_shimmer.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/instructions/instructions_enum.dart';
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
import 'package:fluffychat/pangea/toolbar/enums/activity_type_enum.dart';
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
import 'package:fluffychat/widgets/matrix.dart';
class WordEmojiChoice extends StatefulWidget {
const WordEmojiChoice({
super.key,
required this.overlayController,
required this.token,
required this.constructID,
required this.onEmojiChosen,
required this.form,
this.roomId,
this.eventId,
});
final MessageOverlayController overlayController;
final PangeaToken token;
final ConstructIdentifier constructID;
final String form;
final String? roomId;
final String? eventId;
final void Function() onEmojiChosen;
@override
WordEmojiChoiceState createState() => WordEmojiChoiceState();
@ -36,7 +40,7 @@ class WordEmojiChoiceState extends State<WordEmojiChoice> {
@override
void initState() {
super.initState();
localSelected = widget.token.getEmoji();
localSelected = widget.constructID.userSetEmoji;
}
Future<void> onChoice(BuildContext context, emoji) async {
@ -44,33 +48,33 @@ class WordEmojiChoiceState extends State<WordEmojiChoice> {
MatrixState.pangeaController.putAnalytics.setState(
AnalyticsStream(
eventId: widget.overlayController.pangeaMessageEvent!.eventId,
roomId: widget.overlayController.pangeaMessageEvent!.room.id,
eventId: widget.eventId,
roomId: widget.roomId,
constructs: [
OneConstructUse(
useType: ConstructUseTypeEnum.em,
lemma: widget.token.text.content,
lemma: widget.constructID.lemma,
constructType: ConstructTypeEnum.vocab,
metadata: ConstructUseMetaData(
roomId: widget.overlayController.pangeaMessageEvent!.room.id,
roomId: widget.roomId,
timeStamp: DateTime.now(),
eventId: widget.overlayController.pangeaMessageEvent!.eventId,
eventId: widget.eventId,
),
category: widget.token.pos,
form: widget.token.text.content,
category: widget.constructID.category,
form: widget.form,
),
],
origin: AnalyticsUpdateOrigin.wordZoom,
),
);
await widget.token.setEmoji(emoji);
await widget.constructID.setEmoji(emoji);
await Future.delayed(
const Duration(milliseconds: choiceArrayAnimationDuration),
);
widget.overlayController.onActivityFinish(ActivityTypeEnum.emoji);
widget.onEmojiChosen();
setState(() => {});
}
@ -83,7 +87,7 @@ class WordEmojiChoiceState extends State<WordEmojiChoice> {
mainAxisSize: MainAxisSize.max,
children: [
FutureBuilder(
future: widget.token.getEmojiChoices(),
future: widget.constructID.getEmojiChoices(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text(L10n.of(context).oopsSomethingWentWrong);
@ -111,7 +115,7 @@ class WordEmojiChoiceState extends State<WordEmojiChoice> {
originalSpan: "😀",
uniqueKeyForLayerLink: (int index) => "emojiChoice$index",
selectedChoiceIndex: snapshot.data!.indexWhere(
(element) => element == widget.token.getEmoji(),
(element) => element == widget.constructID.userSetEmoji,
),
tts: null,
fontSize: 26,

View file

@ -2,9 +2,9 @@ import 'dart:async';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_repo.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart';
import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart';

View file

@ -4,9 +4,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/morphs/morph_categories_enum.dart';
import 'package:fluffychat/pangea/toolbar/enums/activity_type_enum.dart';

View file

@ -1,5 +1,5 @@
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart';
import 'package:fluffychat/pangea/lemmas/lemma.dart';

View file

@ -0,0 +1,33 @@
import 'package:flutter/material.dart';
class ShrinkableText extends StatelessWidget {
final String text;
final double maxWidth;
final TextStyle? style;
const ShrinkableText({
super.key,
required this.text,
required this.maxWidth,
this.style,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return Container(
constraints: BoxConstraints(maxWidth: maxWidth),
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerLeft,
child: Text(
text,
style: style,
),
),
);
},
);
}
}

View file

@ -65,14 +65,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
bool initialized = false;
bool isPlayingAudio = false;
/// If non-null and not complete, the activity will be shown regardless of shouldDoActivity.
/// Used to show the practice activity card's savor the joy animation.
/// (Analytics sending triggers the point gain animation, do also
/// causes shouldDoActivity to be false. This is a workaround.)
Completer<void>? _activityLock;
// final bool _hideCenterContent = false;
/// The text that the toolbar should target
/// If there is no selectedSpan, then the whole message is the target
/// If there is a selectedSpan, then the target is the selected text
@ -326,10 +318,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
selectedMorphFeature = null;
break;
case MessageMode.practiceActivity:
if (messageAnalyticsEntry?.nextActivity?.activityType ==
ActivityTypeEnum.hiddenWordListening) {
_lockActivity();
}
break;
case MessageMode.messageTextToSpeech:
if (isPlayingAudio) {
@ -432,18 +420,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
/// Functions
/////////////////////////////////////
///@ggurdin - is this still needed?
void _lockActivity() {
if (mounted) setState(() => _activityLock = Completer());
}
void _unlockActivity() {
if (_activityLock == null) return;
_activityLock!.complete();
_activityLock = null;
if (mounted) setState(() {});
}
/// If sentence TTS is playing a word, highlight that word in message overlay
void highlightCurrentText(int currentPosition, List<TTSToken> ttsTokens) {
final List<TTSToken> textToSelect = [];
@ -485,10 +461,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
/// When an activity is completed, we need to update the state
/// and check if the toolbar should be unlocked
void onActivityFinish(ActivityTypeEnum activityType) {
if (activityType == ActivityTypeEnum.hiddenWordListening) {
_unlockActivity();
}
messageAnalyticsEntry!.onActivityComplete();
if (selectedToken == null) {

View file

@ -6,11 +6,11 @@ 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_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_level_enum.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.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/events/models/tokens_event_content_model.dart';

View file

@ -63,8 +63,6 @@ class WordZoomWidget extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.stretch,
// spacing: 4.0,
children: [
Container(
constraints: const BoxConstraints(

View file

@ -6,12 +6,12 @@ import 'package:flutter/foundation.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/network/requests.dart';
import 'package:fluffychat/pangea/common/network/urls.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/learning_settings/models/language_model.dart';
import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart';

View file

@ -1,4 +1,4 @@
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
class VocabResponse {
List<ConstructIdentifier> vocab;

View file

@ -9,8 +9,8 @@ import 'package:flutter/material.dart';
import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/analytics_misc/analytics_constants.dart';
import 'package:fluffychat/pangea/analytics_misc/construct_identifier.dart';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_emoji_choice_item.dart';
import 'package:fluffychat/pangea/word_bank/vocab_bank_repo.dart';
@ -102,7 +102,7 @@ class WritingAssistanceInputRowState extends State<WritingAssistanceInputRow> {
},
isSelected: false,
textSize: 16,
token: null,
greenHighlight: false,
),
)
.toList(),