diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index b8cd93cb5..eebdfea0a 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -4671,7 +4671,7 @@ } } }, - "chooseLemmaMeaningInstructionsBody": "Match the meanings below with the underlined words in the message.", + "chooseLemmaMeaningInstructionsBody": "Match the meanings to the words in the message!", "doubleClickToEdit": "Double-click to edit.", "removeFeature": "Remove {feature}", "@removeFeature": { @@ -4808,7 +4808,7 @@ "joinByCode": "Join by code", "createASpace": "Create a space", "chooseWordAudioInstructionsBody": "Listen to the full message then match the word audios to the right blanks!", - "chooseMorphsInstructionsBody": "Match the grammar tags with the words in the message. Click and hold an option for a hint!", + "chooseMorphsInstructionsBody": "Click the puzzle pieces for grammar questions!", "inviteAndLaunch": "Launch and invite", "createOwnChat": "Create your own chat", "pleaseEnterInt": "Please enter a number", @@ -4830,5 +4830,6 @@ "referFriends": "Refer friends", "referFriendDialogTitle": "Invite a friend to your conversation", "referFriendDialogDesc": "Do you have a friend who is excited to learn a new language with you? Then copy and send this invitation link to join and start chatting with you today.", - "youUnlocked": "You've unlocked" + "youUnlocked": "You've unlocked", + "selectForGrammar": "Select a grammar icon for activities and details." } \ No newline at end of file diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart index 7ce51ecd7..b2e6b018e 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_details_view.dart @@ -78,9 +78,13 @@ class VocabDetailsView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ LemmaEmojiRow( + isSelected: false, + shouldShowEmojis: true, cId: constructId, - onTap: () => {}, - removeCallback: null, + onTapOverride: null, + emojiSetCallback: () { + debugPrint('Emoji set callback'); + }, ), ], ), diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart index cfd03b6fe..957f8e008 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_tile.dart @@ -53,7 +53,7 @@ class VocabAnalyticsListTileState extends State { height: (maxWidth - padding * 2) * 0.6, child: Opacity( opacity: - widget.constructUse.id.userSetEmoji.isEmpty ? 0.2 : 1, + widget.constructUse.id.userSetEmoji.isEmpty ? 0.5 : 1, child: widget.constructUse.id.userSetEmoji.isNotEmpty ? Text( widget.constructUse.id.userSetEmoji.first, diff --git a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart index 9266c63e4..c80ece10c 100644 --- a/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart +++ b/lib/pangea/analytics_details_popup/vocab_analytics_list_view.dart @@ -98,7 +98,7 @@ class VocabAnalyticsListViewState extends State { padding: const EdgeInsets.all(8.0), child: Badge( label: Text(count.toString()), - child: constructLevelCategory.icon(24), + child: constructLevelCategory.icon(40), ), ), ); @@ -125,50 +125,54 @@ class VocabAnalyticsListViewState extends State { curve: Curves.easeInOut, padding: EdgeInsets.symmetric(horizontal: _isSearching ? 8.0 : 24.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 225.0), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: (child, animation) => FadeTransition( - opacity: animation, - child: child, - ), - child: _isSearching - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - key: const ValueKey('search'), - children: [ - Expanded( - child: TextField( - autofocus: true, - controller: _searchController, - decoration: const InputDecoration( - contentPadding: EdgeInsets.symmetric( - vertical: 6.0, - horizontal: 12.0, + child: Container( + height: 60, + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 225.0), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) => FadeTransition( + opacity: animation, + child: child, + ), + child: _isSearching + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + key: const ValueKey('search'), + children: [ + Expanded( + child: TextField( + autofocus: true, + controller: _searchController, + decoration: const InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: 6.0, + horizontal: 12.0, + ), + isDense: true, + border: OutlineInputBorder(), ), - isDense: true, - border: OutlineInputBorder(), ), ), - ), - IconButton( - icon: const Icon(Icons.close), - onPressed: _toggleSearching, - ), - ], - ) - : Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - key: const ValueKey('filters'), - children: filters, - ), + IconButton( + icon: const Icon(Icons.close), + onPressed: _toggleSearching, + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + key: const ValueKey('filters'), + children: filters, + ), + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pangea/common/utils/any_state_holder.dart b/lib/pangea/common/utils/any_state_holder.dart index e1947d94d..94a1682ca 100644 --- a/lib/pangea/common/utils/any_state_holder.dart +++ b/lib/pangea/common/utils/any_state_holder.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; @@ -65,20 +66,24 @@ class PangeaAnyState { Overlay.of(context).insert(entry); } - void closeOverlay() { - if (entries.isNotEmpty) { + void closeOverlay([String? overlayKey]) { + final entry = overlayKey != null + ? entries.firstWhereOrNull((element) => element.key == overlayKey) + : entries.lastOrNull; + + if (entry != null) { try { - entries.last.entry.remove(); + entry.entry.remove(); } catch (err, s) { ErrorHandler.logError( e: err, s: s, data: { - "overlay": entries.last, + "overlay": entry, }, ); } - entries.removeLast(); + entries.remove(entry); } } @@ -124,4 +129,16 @@ class LayerLinkAndKey { "link": link.toString(), "transformTargetId": transformTargetId, }; + + @override + operator ==(Object other) => + identical(this, other) || + other is LayerLinkAndKey && + runtimeType == other.runtimeType && + key == other.key && + link == other.link && + transformTargetId == other.transformTargetId; + + @override + int get hashCode => key.hashCode ^ link.hashCode ^ transformTargetId.hashCode; } diff --git a/lib/pangea/events/models/pangea_token_model.dart b/lib/pangea/events/models/pangea_token_model.dart index 558d992d6..fd990b0f2 100644 --- a/lib/pangea/events/models/pangea_token_model.dart +++ b/lib/pangea/events/models/pangea_token_model.dart @@ -489,6 +489,9 @@ class PangeaToken { Future setEmoji(List emojis) => vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(emojis: emojis)); + Future setMeaning(String meaning) => + vocabConstructID.setUserLemmaInfo(UserSetLemmaInfo(meaning: meaning)); + /// [getEmoji] gets the emoji for the lemma /// NOTE: assumes that the language of the lemma is the same as the user's current l2 List getEmoji() => vocabConstructID.userSetEmoji; diff --git a/lib/pangea/learning_settings/pages/settings_learning.dart b/lib/pangea/learning_settings/pages/settings_learning.dart index 6cc977371..4aa83ac89 100644 --- a/lib/pangea/learning_settings/pages/settings_learning.dart +++ b/lib/pangea/learning_settings/pages/settings_learning.dart @@ -1,9 +1,13 @@ +import 'dart:developer'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:country_picker/country_picker.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/instructions/instruction_settings.dart'; import 'package:fluffychat/pangea/learning_settings/enums/language_level_type_enum.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; @@ -129,6 +133,13 @@ class SettingsLearningController extends State { }, waitForDataInSync: true, ), + onError: (e, s) { + debugPrint("Error resetting instruction tooltips: $e"); + debugger(when: kDebugMode); + ErrorHandler.logError( + e: e, s: s, data: {"resetInstructionTooltips": true}); + return null; + }, ); if (mounted) setState(() {}); } diff --git a/lib/pangea/lemmas/lemma_emoji_row.dart b/lib/pangea/lemmas/lemma_emoji_row.dart index c7e928088..c79849dd9 100644 --- a/lib/pangea/lemmas/lemma_emoji_row.dart +++ b/lib/pangea/lemmas/lemma_emoji_row.dart @@ -1,73 +1,270 @@ +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/message_token_text/message_token_button.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 StatelessWidget { +class LemmaEmojiRow extends StatefulWidget { final ConstructIdentifier cId; - final VoidCallback onTap; + final VoidCallback? onTapOverride; final bool isSelected; + final bool shouldShowEmojis; /// 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()? removeCallback; + final void Function()? emojiSetCallback; const LemmaEmojiRow({ - required this.cId, - required this.onTap, - required this.removeCallback, - this.isSelected = false, super.key, + required this.cId, + required this.onTapOverride, + required this.isSelected, + required this.shouldShowEmojis, + this.emojiSetCallback, }); - List get emojis => cId.userSetEmoji; + @override + LemmaEmojiRowState createState() => LemmaEmojiRowState(); +} - Future onEmojiTap(String toRemove) async { - await cId.setUserLemmaInfo( - UserSetLemmaInfo( - emojis: emojis.where((e) => e != toRemove).toList(), - ), - ); - removeCallback!(); +class LemmaEmojiRowState extends State { + 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.disposeByWidgetKey(widget.cId.string); + super.dispose(); + } + + void openEmojiSetOverlay() async { + List 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, + ); + } catch (e, s) { + debugger(when: kDebugMode); + ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s); + } + } + + Future setEmoji(String emoji) async { + try { + displayEmoji = emoji; + + widget.cId + .setUserLemmaInfo( + UserSetLemmaInfo( + emojis: [emoji], + ), + ) + .catchError((e, s) { + debugger(when: kDebugMode); + ErrorHandler.logError(data: widget.cId.toJson(), e: e, s: s); + }); + + 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 Row( - children: [ - for (var i = 0; i < maxEmojisPerLemma; i++) - Container( - height: 40, - width: 40, - alignment: Alignment.center, - child: i < emojis.length - ? GestureDetector( - onTap: removeCallback == null - ? null - : () => onEmojiTap(emojis[i]), - child: Text( - emojis[i], - style: Theme.of(context).textTheme.bodyLarge, - ), - ) - : WordZoomActivityButton( - icon: Icon( - Icons.add_reaction_outlined, - color: isSelected - ? Theme.of(context).colorScheme.primary - : null, - ), - isSelected: isSelected, - onPressed: onTap, - opacity: isSelected ? 1 : 0.4, - tooltip: MessageMode.wordEmoji.title(context), + return 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: Theme.of(context).textTheme.headlineSmall, ), - ), - ], + ), + ) + : 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 emojis; + + const EmojiEditOverlay({ + super.key, + required this.onSelectEmoji, + required this.cId, + required this.emojis, + }); + + @override + Widget build(BuildContext context) { + return Material( + 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 { + 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, + ), + ), + ), ); } } diff --git a/lib/pangea/lemmas/lemma_info_repo.dart b/lib/pangea/lemmas/lemma_info_repo.dart index 89f389faf..5624a6f38 100644 --- a/lib/pangea/lemmas/lemma_info_repo.dart +++ b/lib/pangea/lemmas/lemma_info_repo.dart @@ -12,8 +12,6 @@ import 'package:fluffychat/pangea/common/network/urls.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_request.dart'; import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; -import 'package:fluffychat/pangea/lemmas/user_set_lemma_info.dart'; -import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; import 'package:fluffychat/widgets/matrix.dart'; class LemmaInfoRepo { @@ -69,33 +67,34 @@ class LemmaInfoRepo { /// Get lemma info, prefering user set data over fetched data static Future get(LemmaInfoRequest request) async { try { + return await _fetch(request); // if the user has either emojis or meaning in the past, use those first - final UserSetLemmaInfo? userSetLemmaInfo = request.cId.userLemmaInfo; + // final UserSetLemmaInfo? userSetLemmaInfo = request.cId.userLemmaInfo; - final List emojis = userSetLemmaInfo?.emojis ?? []; - String? meaning = userSetLemmaInfo?.meaning; + // final List emojis = userSetLemmaInfo?.emojis ?? []; + // String? meaning = userSetLemmaInfo?.meaning; // if the user has not set these, fetch from the server - if (emojis.length < maxEmojisPerLemma || meaning == null) { - final LemmaInfoResponse fetched = await _fetch(request); + // if (emojis.length < maxEmojisPerLemma || meaning == null) { + // final LemmaInfoResponse fetched = await _fetch(request); - while (emojis.length < maxEmojisPerLemma && fetched.emoji.isNotEmpty) { - final maybeToAdd = fetched.emoji.removeAt(0); - if (!emojis.contains(maybeToAdd)) { - emojis.add(maybeToAdd); - } - } - meaning ??= fetched.meaning; - } else { - // debugPrint( - // 'using user set data for ${request.lemma} ${userSetLemmaInfo?.toJson()}', - // ); - } + // while (emojis.length < maxEmojisPerLemma && fetched.emoji.isNotEmpty) { + // final maybeToAdd = fetched.emoji.removeAt(0); + // if (!emojis.contains(maybeToAdd)) { + // emojis.add(maybeToAdd); + // } + // } + // meaning ??= fetched.meaning; + // } else { + // // debugPrint( + // // 'using user set data for ${request.lemma} ${userSetLemmaInfo?.toJson()}', + // // ); + // } - return LemmaInfoResponse( - emoji: emojis, - meaning: meaning, - ); + // return LemmaInfoResponse( + // emoji: emojis, + // meaning: meaning, + // ); } catch (e) { debugger(when: kDebugMode); ErrorHandler.logError(e: e, data: request.toJson()); diff --git a/lib/pangea/lemmas/lemma_info_response.dart b/lib/pangea/lemmas/lemma_info_response.dart index 05840ce2c..fb5bca198 100644 --- a/lib/pangea/lemmas/lemma_info_response.dart +++ b/lib/pangea/lemmas/lemma_info_response.dart @@ -1,5 +1,4 @@ import 'package:fluffychat/pangea/events/models/content_feedback.dart'; -import 'package:fluffychat/pangea/message_token_text/message_token_button.dart'; class LemmaInfoResponse implements JsonSerializable { final List emoji; @@ -15,11 +14,7 @@ class LemmaInfoResponse implements JsonSerializable { factory LemmaInfoResponse.fromJson(Map json) { return LemmaInfoResponse( // NOTE: This is a workaround for the fact that the server sometimes sends more than 3 emojis - emoji: (json['emoji'] as List) - .map((e) => e as String) - .toList() - .take(maxEmojisPerLemma) - .toList(), + emoji: (json['emoji'] as List).map((e) => e as String).toList(), meaning: json['meaning'] as String, expireAt: json['expireAt'] == null ? null diff --git a/lib/pangea/message_token_text/dotted_border_painter.dart b/lib/pangea/message_token_text/dotted_border_painter.dart new file mode 100644 index 000000000..a52879ab0 --- /dev/null +++ b/lib/pangea/message_token_text/dotted_border_painter.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +class DottedBorderPainter extends CustomPainter { + final Color color; + final double strokeWidth; + final double dashWidth; + final double dashSpace; + final BorderRadius borderRadius; + + DottedBorderPainter({ + required this.color, + this.strokeWidth = 2.0, + this.dashWidth = 4.0, + this.dashSpace = 4.0, + required this.borderRadius, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..strokeWidth = strokeWidth + ..style = PaintingStyle.stroke; + + final path = Path() + ..addRRect( + borderRadius.toRRect(Rect.fromLTWH(0, 0, size.width, size.height))); + + final dashPath = Path(); + final pathMetrics = path.computeMetrics(); + for (final pathMetric in pathMetrics) { + double distance = 0.0; + while (distance < pathMetric.length) { + final segment = pathMetric.extractPath(distance, distance + dashWidth); + dashPath.addPath(segment, Offset.zero); + distance += dashWidth + dashSpace; + } + } + + canvas.drawPath(dashPath, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} diff --git a/lib/pangea/message_token_text/message_token_button.dart b/lib/pangea/message_token_text/message_token_button.dart index f1128b9d8..6d14dcc0e 100644 --- a/lib/pangea/message_token_text/message_token_button.dart +++ b/lib/pangea/message_token_text/message_token_button.dart @@ -10,6 +10,7 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/constructs/construct_form.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/message_token_text/dotted_border_painter.dart'; import 'package:fluffychat/pangea/practice_activities/target_tokens_and_activity_type.dart'; import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/utils/shrinkable_text.dart'; @@ -181,7 +182,8 @@ class MessageTokenButtonState extends State return DragTarget( builder: (BuildContext context, accepted, rejected) { final double colorAlpha = 0.3 + - (widget.overlayController?.selectedChoice != null ? 0.3 : 0.0); + (widget.overlayController?.selectedChoice != null ? 0.4 : 0.0) + + (accepted.isNotEmpty || _isHovered ? 0.3 : 0.0); return InkWell( onHover: (isHovered) => setState(() => _isHovered = isHovered), @@ -192,28 +194,29 @@ class MessageTokenButtonState extends State ) : null, borderRadius: borderRadius, - child: Container( - height: height, - padding: EdgeInsets.only(top: topPadding), - width: - MessageMode.wordMeaning == widget.overlayController?.toolbarMode - ? widget.width - : min(widget.width, height), - alignment: Alignment.center, - decoration: BoxDecoration( + child: CustomPaint( + painter: DottedBorderPainter( color: Theme.of(context) .colorScheme .primary .withAlpha((colorAlpha * 255).toInt()), borderRadius: borderRadius, - border: accepted.isNotEmpty || - (widget.overlayController?.selectedChoice != null && - _isHovered) - ? Border.all( - color: Theme.of(context).colorScheme.primary, - width: 2, - ) - : null, + ), + child: Container( + height: height, + padding: EdgeInsets.only(top: topPadding), + width: MessageMode.wordMeaning == + widget.overlayController?.toolbarMode + ? widget.width + : min(widget.width, height), + alignment: Alignment.center, + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primary + .withAlpha((max(0, colorAlpha - 0.7) * 255).toInt()), + borderRadius: borderRadius, + ), ), ), ); diff --git a/lib/pangea/morphs/morph_icon.dart b/lib/pangea/morphs/morph_icon.dart index e2f884b8a..577e28e37 100644 --- a/lib/pangea/morphs/morph_icon.dart +++ b/lib/pangea/morphs/morph_icon.dart @@ -26,6 +26,27 @@ class MorphIcon extends StatelessWidget { final ThemeData theme = Theme.of(context); + final content = CustomizedSvg( + svgUrl: getMorphSvgLink( + morphFeature: morphFeature.name, + morphTag: morphTag, + context: context, + ), + colorReplacements: theme.brightness == Brightness.dark + ? { + "white": theme.cardColor.hexValue.toString(), + "black": "white", + } + : {}, + errorIcon: Icon(morphFeature.fallbackIcon), + width: size?.width, + height: size?.height, + ); + + if (!showTooltip) { + return content; + } + return Tooltip( message: morphTag == null ? morphFeature.getDisplayCopy(context) @@ -35,22 +56,7 @@ class MorphIcon extends StatelessWidget { context: context, ), triggerMode: TooltipTriggerMode.tap, - child: CustomizedSvg( - svgUrl: getMorphSvgLink( - morphFeature: morphFeature.name, - morphTag: morphTag, - context: context, - ), - colorReplacements: theme.brightness == Brightness.dark - ? { - "white": theme.cardColor.hexValue.toString(), - "black": "white", - } - : {}, - errorIcon: Icon(morphFeature.fallbackIcon), - width: size?.width, - height: size?.height, - ), + child: content, ); } } diff --git a/lib/pangea/practice_activities/message_analytics_controller.dart b/lib/pangea/practice_activities/message_analytics_controller.dart index be5e52366..8f4d12244 100644 --- a/lib/pangea/practice_activities/message_analytics_controller.dart +++ b/lib/pangea/practice_activities/message_analytics_controller.dart @@ -6,6 +6,7 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/lemmas/lemma_info_response.dart'; import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/practice_activities/target_tokens_and_activity_type.dart'; @@ -217,6 +218,29 @@ class MessageAnalyticsEntry { bool isTokenInHiddenWordActivity(PangeaToken token) => _activityQueue[ActivityTypeEnum.hiddenWordListening]?.isNotEmpty ?? false; + + Future> getLemmaInfoForActivityTokens() async { + // make a list of unique tokens in emoji and wordMeaning activities + final List uniqueTokens = []; + for (final t in _activityQueue[ActivityTypeEnum.emoji] ?? []) { + if (!uniqueTokens.contains(t.tokens.first)) { + uniqueTokens.add(t.tokens.first); + } + } + for (final t in _activityQueue[ActivityTypeEnum.wordMeaning] ?? []) { + if (!uniqueTokens.contains(t.tokens.first)) { + uniqueTokens.add(t.tokens.first); + } + } + + // get the lemma info for each token + final List> lemmaInfoFutures = []; + for (final t in uniqueTokens) { + lemmaInfoFutures.add(t.vocabConstructID.getLemmaInfo()); + } + + return Future.wait(lemmaInfoFutures); + } } /// computes TokenWithXP for given a pangeaMessageEvent and caches the result, according to the full text of the message diff --git a/lib/pangea/toolbar/enums/message_mode_enum.dart b/lib/pangea/toolbar/enums/message_mode_enum.dart index 95307a425..7ff388e40 100644 --- a/lib/pangea/toolbar/enums/message_mode_enum.dart +++ b/lib/pangea/toolbar/enums/message_mode_enum.dart @@ -123,6 +123,7 @@ extension MessageModeExtension on MessageMode { case MessageMode.noneSelected: return InstructionsEnum.readingAssistanceOverview; case MessageMode.messageTranslation: + return InstructionsEnum.completeActivitiesToUnlock; case MessageMode.messageMeaning: case MessageMode.wordZoom: case MessageMode.practiceActivity: diff --git a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart index b60e460cb..955d54be5 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice.dart @@ -21,7 +21,7 @@ import 'package:fluffychat/pangea/morphs/morph_repo.dart'; import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morphs/morphological_center_widget.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morph_focus_widget.dart'; import 'package:fluffychat/widgets/matrix.dart'; // this widget will handle the content of the input bar when mode == MessageMode.wordMorph @@ -133,7 +133,7 @@ class MessageMorphInputBarContentState ? ConstructUseTypeEnum.corM : ConstructUseTypeEnum.incM, lemma: choice, - constructType: ConstructTypeEnum.vocab, + constructType: ConstructTypeEnum.morph, metadata: ConstructUseMetaData( roomId: overlay.pangeaMessageEvent!.room.id, timeStamp: DateTime.now(), @@ -181,6 +181,7 @@ class MessageMorphInputBarContentState morphFeature: morph!, morphTag: null, size: const Size(30, 30), + showTooltip: false, ), Text( L10n.of(context).whatIsTheMorphTag( @@ -227,8 +228,11 @@ class MessageMorphInputBarContentState ); } - return const Center( - child: Text("Select a grammar icon for activities and details."), + return Center( + child: Text( + L10n.of(context).selectForGrammar, + style: Theme.of(context).textTheme.bodyLarge, + ), ); } } diff --git a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart index c823f2b3c..41857a8bf 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/message_morph_choice_item.dart @@ -85,6 +85,7 @@ class MessageMorphChoiceItemState extends State { ), morphTag: widget.cId.lemma, size: const Size(40, 40), + showTooltip: false, ), ), Text( diff --git a/lib/pangea/toolbar/widgets/message_mode_locked_card.dart b/lib/pangea/toolbar/widgets/message_mode_locked_card.dart index a63ff1148..1ca6f1519 100644 --- a/lib/pangea/toolbar/widgets/message_mode_locked_card.dart +++ b/lib/pangea/toolbar/widgets/message_mode_locked_card.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; -import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; class MessageModeLockedCard extends StatelessWidget { @@ -21,13 +19,13 @@ class MessageModeLockedCard extends StatelessWidget { size: 40, color: Theme.of(context).colorScheme.primary, ), - if (!InstructionsEnum.completeActivitiesToUnlock.isToggledOff) ...[ - const SizedBox(height: 8), - const InstructionsInlineTooltip( - instructionsEnum: InstructionsEnum.completeActivitiesToUnlock, - bold: true, - ), - ], + // if (!InstructionsEnum.completeActivitiesToUnlock.isToggledOff) ...[ + // const SizedBox(height: 8), + // const InstructionsInlineTooltip( + // instructionsEnum: InstructionsEnum.completeActivitiesToUnlock, + // bold: true, + // ), + // ], ], ); } diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index 019c3cefe..6cce65962 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -377,6 +377,20 @@ class MessageOverlayController extends State if (isCorrect) { messageAnalyticsEntry?.onActivityComplete(activityType, token); + + if (activityType == ActivityTypeEnum.emoji && + !token.vocabConstructID.userSetEmoji.contains(choice.form)) { + final allEmojis = token.vocabConstructID.userSetEmoji + [choice.form]; + token.setEmoji(allEmojis).then((_) async { + setState(() {}); + }); + } + + if (activityType == ActivityTypeEnum.wordMeaning) { + token.setMeaning(choice.form).then((_) async { + setState(() {}); + }); + } } feedbackStates.removeWhere((e) => e.form == choice); diff --git a/lib/pangea/toolbar/widgets/toolbar_button_column.dart b/lib/pangea/toolbar/widgets/toolbar_button_column.dart index f24f76ced..12ee53636 100644 --- a/lib/pangea/toolbar/widgets/toolbar_button_column.dart +++ b/lib/pangea/toolbar/widgets/toolbar_button_column.dart @@ -53,31 +53,52 @@ class ToolbarButtonRow extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, - spacing: 4.0, children: [ - ToolbarButton( - mode: MessageMode.wordMorph, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, + // wrapping these with a container to prevent the buttons from + // moving around when they press and depress + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.wordMorph, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), ), - ToolbarButton( - mode: MessageMode.wordMeaning, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.wordMeaning, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), ), - ToolbarButton( - mode: MessageMode.listening, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.listening, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), ), - ToolbarButton( - mode: MessageMode.wordEmoji, - overlayController: overlayController, - onPressed: overlayController.updateToolbarMode, - buttonSize: buttonSize, + Container( + width: buttonSize + 4, + height: buttonSize + 4, + alignment: Alignment.center, + child: ToolbarButton( + mode: MessageMode.wordEmoji, + overlayController: overlayController, + onPressed: overlayController.updateToolbarMode, + buttonSize: buttonSize, + ), ), ], ), diff --git a/lib/pangea/toolbar/widgets/word_zoom/lemma_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/lemma_widget.dart index ccf5750d1..f48156a0a 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/lemma_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/lemma_widget.dart @@ -168,19 +168,20 @@ class LemmaWidgetState extends State { return Row( children: [ - Tooltip( - triggerMode: TooltipTriggerMode.tap, - message: L10n.of(context).doubleClickToEdit, - child: GestureDetector( - onLongPress: () => _toggleEditMode(true), - onDoubleTap: () => _toggleEditMode(true), - child: Text( - widget.token.lemma.text, - style: Theme.of(context).textTheme.headlineSmall, - overflow: TextOverflow.ellipsis, - ), - ), + // Tooltip( + // triggerMode: TooltipTriggerMode.tap, + // message: L10n.of(context).doubleClickToEdit, + // child: GestureDetector( + // onLongPress: () => _toggleEditMode(true), + // onDoubleTap: () => _toggleEditMode(true), + // child: + Text( + widget.token.lemma.text, + style: Theme.of(context).textTheme.headlineSmall, + overflow: TextOverflow.ellipsis, ), + // ), + // ), if (widget.token.lemma.text.toLowerCase() == widget.token.text.content.toLowerCase()) WordAudioButton( diff --git a/lib/pangea/toolbar/widgets/word_zoom/morphs/morphological_center_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/morphs/morphological_center_widget.dart deleted file mode 100644 index cd3204fc2..000000000 --- a/lib/pangea/toolbar/widgets/word_zoom/morphs/morphological_center_widget.dart +++ /dev/null @@ -1,355 +0,0 @@ -// stateful widget that displays morphological label and a shimmer effect while the text is loading -// takes a token and morphological feature as input - -import 'package:flutter/material.dart'; - -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; -import 'package:fluffychat/pangea/analytics_details_popup/morph_meaning_widget.dart'; -import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; -import 'package:fluffychat/pangea/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'; -import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -import 'package:fluffychat/pangea/lemmas/construct_xp_widget.dart'; -import 'package:fluffychat/pangea/morphs/default_morph_mapping.dart'; -import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; -import 'package:fluffychat/pangea/morphs/morph_feature_display.dart'; -import 'package:fluffychat/pangea/morphs/morph_features_enum.dart'; -import 'package:fluffychat/pangea/morphs/morph_repo.dart'; -import 'package:fluffychat/pangea/morphs/morph_tag_display.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/widgets/future_loading_dialog.dart'; - -class MorphFocusWidget extends StatefulWidget { - final PangeaToken token; - final String morphFeature; - final PangeaMessageEvent pangeaMessageEvent; - final MessageOverlayController overlayController; - - final VoidCallback onEditDone; - - const MorphFocusWidget({ - required this.token, - required this.morphFeature, - required this.pangeaMessageEvent, - required this.overlayController, - required this.onEditDone, - super.key, - }); - - @override - MorphFocusWidgetState createState() => MorphFocusWidgetState(); -} - -class MorphFocusWidgetState extends State { - bool editMode = false; - - /// the morphological tag that the user has selected in edit mode - String selectedMorphTag = ""; - - final ScrollController _scrollController = ScrollController(); - - void resetMorphTag() => setState( - () => selectedMorphTag = - widget.token.getMorphTag(widget.morphFeature) ?? "X", - ); - - @override - void didUpdateWidget(MorphFocusWidget oldWidget) { - if (widget.token != oldWidget.token || - widget.morphFeature != oldWidget.morphFeature) { - resetMorphTag(); - setState(() => editMode = false); - } - super.didUpdateWidget(oldWidget); - } - - @override - void initState() { - super.initState(); - resetMorphTag(); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void enterEditMode() { - setState(() { - editMode = true; - }); - } - - PangeaMessageEvent get pm => widget.pangeaMessageEvent; - - /// confirm the changes made by the user - /// this will send a new message to the server - /// with the new morphological tag - Future saveChanges( - PangeaToken Function(PangeaToken token) changeCallback, - ) async { - try { - // NOTE: it is not clear how this would work if the user was not editing the originalSent tokens - // this case would only happen in immersion mode which is disabled until further notice - // this flow assumes that the user is editing the originalSent tokens - // if not, we'll get an error and we'll cross that bridge - - // make a copy of the original tokens - final existingTokens = pm.originalSent!.tokens! - .map((token) => PangeaToken.fromJson(token.toJson())) - .toList(); - - // change the morphological tag in the selected token - final tokenIndex = existingTokens - .indexWhere((token) => token.text.offset == widget.token.text.offset); - if (tokenIndex == -1) { - throw Exception("Token not found in message"); - } - existingTokens[tokenIndex] = changeCallback(existingTokens[tokenIndex]); - - // send a new message as an edit to original message to the server - // including the new tokens - // marking the message as a morphological edit will allow use to filter - // from some processing and potentially find the data for LLM fine-tuning - await pm.room.pangeaSendTextEvent( - pm.messageDisplayText, - editEventId: pm.eventId, - originalSent: pm.originalSent?.content, - originalWritten: pm.originalWritten?.content, - tokensSent: PangeaMessageTokens( - tokens: existingTokens, - detections: pm.originalSent?.detections, - ), - tokensWritten: pm.originalWritten?.tokens != null - ? PangeaMessageTokens( - tokens: pm.originalWritten!.tokens!, - detections: pm.originalWritten?.detections, - ) - : null, - choreo: pm.originalSent?.choreo, - messageTag: ModelKey.messageTagMorphEdit, - ); - - setState(() => editMode = false); - widget.onEditDone(); - } catch (e) { - SnackBar( - content: Text(L10n.of(context).oopsSomethingWentWrong), - ); - ErrorHandler.logError( - e: e, - data: { - "selectedMorphTag": selectedMorphTag, - "morphFeature": widget.morphFeature, - "token": widget.token.toJson(), - "pangeaMessageEvent": widget.pangeaMessageEvent.event.content, - }, - ); - } - } - - ConstructIdentifier get id { - return ConstructIdentifier( - lemma: selectedMorphTag, - type: ConstructTypeEnum.morph, - category: widget.morphFeature, - ); - } - - @override - Widget build(BuildContext context) { - if (!editMode) { - return Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - MorphFeatureDisplay( - morphFeature: widget.morphFeature, - ), - if (widget.token.getMorphTag(widget.morphFeature) != null) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Tooltip( - triggerMode: TooltipTriggerMode.tap, - message: L10n.of(context).doubleClickToEdit, - child: GestureDetector( - onLongPress: enterEditMode, - onDoubleTap: enterEditMode, - child: MorphTagDisplay( - morphFeature: MorphFeaturesEnumExtension.fromString( - widget.morphFeature, - ), - morphTag: - widget.token.getMorphTag(widget.morphFeature) ?? - L10n.of(context).nan, - textColor: Theme.of(context).brightness == - Brightness.light - ? id.constructUses.lemmaCategory.darkColor(context) - : id.constructUses.lemmaCategory.color(context), - ), - ), - ), - const SizedBox(width: 6), - ConstructXpWidget( - id: id, - onTap: () => showDialog( - context: context, - builder: (context) => AnalyticsPopupWrapper( - constructZoom: id, - view: ConstructTypeEnum.morph, - ), - ), - ), - ], - ), - MorphMeaningWidget( - feature: widget.morphFeature, - tag: widget.token.getMorphTag(widget.morphFeature)!, - ), - ] else - Text(L10n.of(context).nan), - ], - ), - ); - } - - return Expanded( - child: Padding( - padding: const EdgeInsets.all(4.0), - child: Column( - children: [ - Text( - "${L10n.of(context).pangeaBotIsFallible} ${L10n.of(context).chooseCorrectLabel}", - textAlign: TextAlign.center, - style: const TextStyle(fontStyle: FontStyle.italic), - ), - FutureBuilder( - future: MorphsRepo.get(), - builder: (context, snapshot) { - final allMorphTagsForEdit = - snapshot.data?.getDisplayTags(widget.morphFeature) ?? - defaultMorphMapping.getDisplayTags(widget.morphFeature); - - if (snapshot.connectionState == ConnectionState.waiting) { - return const CircularProgressIndicator(); - } - - return Wrap( - children: allMorphTagsForEdit.map((tag) { - return Container( - margin: const EdgeInsets.all(2), - padding: EdgeInsets.zero, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(10), - ), - border: Border.all( - color: selectedMorphTag == tag - ? Theme.of(context).colorScheme.primary - : Colors.transparent, - style: BorderStyle.solid, - width: 2.0, - ), - ), - child: TextButton( - style: ButtonStyle( - padding: WidgetStateProperty.all( - const EdgeInsets.symmetric( - horizontal: 7, - ), - ), - backgroundColor: WidgetStateProperty.all( - selectedMorphTag == tag - ? Theme.of(context) - .colorScheme - .primary - .withAlpha(50) - : Colors.transparent, - ), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - ), - onPressed: () { - setState(() => selectedMorphTag = tag); - }, - child: Text( - getGrammarCopy( - category: widget.morphFeature, - lemma: tag, - context: context, - ) ?? - tag, - textAlign: TextAlign.center, - ), - ), - ); - }).toList(), - ); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 10, - children: [ - ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), - ), - onPressed: () { - setState(() { - editMode = false; - }); - }, - child: Text(L10n.of(context).cancel), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - padding: const EdgeInsets.symmetric(horizontal: 10), - ), - onPressed: selectedMorphTag == - widget.token.morph[widget.morphFeature] - ? null - : () => showFutureLoadingDialog( - context: context, - future: () => saveChanges( - (token) { - token.morph[widget.morphFeature] = - selectedMorphTag; - if (widget.morphFeature.toLowerCase() == - 'pos') { - token.pos = selectedMorphTag; - } - return token; - }, - ), - ), - child: Text(L10n.of(context).saveChanges), - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/lib/pangea/toolbar/widgets/word_zoom/morphs/morphological_list_item.dart b/lib/pangea/toolbar/widgets/word_zoom/morphs/morphological_list_item.dart deleted file mode 100644 index df0c3523a..000000000 --- a/lib/pangea/toolbar/widgets/word_zoom/morphs/morphological_list_item.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:material_symbols_icons/symbols.dart'; - -import 'package:fluffychat/pangea/events/models/pangea_token_model.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/practice_activities/activity_type_enum.dart'; -import 'package:fluffychat/pangea/toolbar/enums/message_mode_enum.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_zoom_activity_button.dart'; - -class MorphologicalListItem extends StatelessWidget { - final MorphFeaturesEnum morphFeature; - final PangeaToken token; - final MessageOverlayController overlayController; - - const MorphologicalListItem({ - required this.morphFeature, - required this.token, - required this.overlayController, - super.key, - }); - - bool get shouldDoActivity => - overlayController.messageAnalyticsEntry?.hasActivity( - ActivityTypeEnum.morphId, - token, - morphFeature, - ) == - true; - - bool get isSelected => overlayController.toolbarMode == MessageMode.wordMorph; - - String get morphTag => token.getMorphTag(morphFeature.name) ?? "X"; - - @override - Widget build(BuildContext context) { - return SizedBox( - width: 40, - height: 40, - child: WordZoomActivityButton( - icon: shouldDoActivity - ? const Icon(Symbols.toys_and_games) - : MorphIcon( - morphFeature: morphFeature, - morphTag: token.getMorphTag(morphFeature.name), - size: const Size(24, 24), - ), - isSelected: isSelected, - // onPressed: shouldDoActivity - // ? () => overlayController.updateToolbarMode(MessageMode.wordMorph) - // : () => (feature) => showDialog( - // context: context, - // builder: (context) => AnalyticsPopupWrapper( - // constructZoom: token.morphIdByFeature(feature), - // view: ConstructTypeEnum.vocab, - // ), - // ), - onPressed: () => - overlayController.onMorphActivitySelect(token, morphFeature), - tooltip: shouldDoActivity - ? morphFeature.getDisplayCopy(context) - : getGrammarCopy( - category: morphFeature.name, - lemma: morphTag, - context: context, - ), - opacity: isSelected - ? 1 - : shouldDoActivity - ? 0.4 - : 1, - ), - ); - } -} diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_selection_enum.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_selection_enum.dart deleted file mode 100644 index b6a86fea8..000000000 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_selection_enum.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:fluffychat/pangea/practice_activities/activity_type_enum.dart'; - -enum WordZoomSelection { - meaning, - emoji, - lemma, - morph, -} - -extension WordZoomSelectionUtils on WordZoomSelection { - ActivityTypeEnum get activityType { - switch (this) { - case WordZoomSelection.meaning: - return ActivityTypeEnum.wordMeaning; - case WordZoomSelection.emoji: - return ActivityTypeEnum.emoji; - case WordZoomSelection.lemma: - return ActivityTypeEnum.lemmaId; - case WordZoomSelection.morph: - return ActivityTypeEnum.morphId; - } - } -} diff --git a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart index 9b04fb247..a80534c29 100644 --- a/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart +++ b/lib/pangea/toolbar/widgets/word_zoom/word_zoom_widget.dart @@ -17,7 +17,7 @@ import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart import 'package:fluffychat/pangea/toolbar/widgets/practice_activity/word_audio_button.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_meaning_widget.dart'; import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/lemma_widget.dart'; -import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morphs/morphological_list_item.dart'; +import 'package:fluffychat/pangea/toolbar/widgets/word_zoom/morphological_list_item.dart'; import 'package:fluffychat/widgets/matrix.dart'; class WordZoomWidget extends StatelessWidget { @@ -114,12 +114,21 @@ class WordZoomWidget extends StatelessWidget { alignment: Alignment.center, child: LemmaEmojiRow( cId: _selectedToken.vocabConstructID, - onTap: () => overlayController.updateToolbarMode( + onTapOverride: () => + overlayController.updateToolbarMode( MessageMode.wordEmoji, ), isSelected: overlayController.toolbarMode == MessageMode.wordEmoji, - removeCallback: () => overlayController.setState(() {}), + emojiSetCallback: () => + overlayController.setState(() {}), + shouldShowEmojis: overlayController + .messageAnalyticsEntry + ?.hasActivity( + MessageMode.wordEmoji.associatedActivityType!, + _selectedToken, + ) == + false, ), ), ],