diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index f0e1f2838..e186e2304 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -14,6 +14,7 @@ import 'package:fluffychat/pages/chat/chat.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/layout/reading_assistance_mode_enum.dart'; +import 'package:fluffychat/pangea/toolbar/message_practice/message_practice_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/message_practice/token_practice_button.dart'; import 'package:fluffychat/pangea/toolbar/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/reading_assistance/token_emoji_button.dart'; @@ -513,6 +514,26 @@ class HtmlMessage extends StatelessWidget { ), ), ), + if (renderer.showCenterStyling && + token != null && + overlayController != null) + ListenableBuilder( + listenable: overlayController!.practiceController, + builder: (context, _) => AnimatedSize( + duration: const Duration( + milliseconds: AppConfig.overlayAnimationDuration, + ), + curve: Curves.easeOut, + child: SizedBox( + height: overlayController! + .practiceController.practiceMode != + MessagePracticeMode.noneSelected + ? 16.0 + : 0.0, + width: tokenWidth, + ), + ), + ), ], // ), ), diff --git a/lib/pangea/analytics_misc/put_analytics_controller.dart b/lib/pangea/analytics_misc/put_analytics_controller.dart index 4e0453027..5679bdf5d 100644 --- a/lib/pangea/analytics_misc/put_analytics_controller.dart +++ b/lib/pangea/analytics_misc/put_analytics_controller.dart @@ -111,7 +111,7 @@ class PutAnalyticsController { String? targetId, }) { final level = _pangeaController.getAnalytics.constructListModel.level; - _addLocalMessage(eventId, constructs).then( + _addLocalMessage(eventId, List.from(constructs)).then( (_) => _sendAnalytics(level, targetId, constructs), ); } diff --git a/lib/pangea/toolbar/layout/message_selection_positioner.dart b/lib/pangea/toolbar/layout/message_selection_positioner.dart index 9af6d7c94..612521b86 100644 --- a/lib/pangea/toolbar/layout/message_selection_positioner.dart +++ b/lib/pangea/toolbar/layout/message_selection_positioner.dart @@ -395,28 +395,6 @@ class MessageSelectionPositionerState extends State alignment: ownMessage ? Alignment.centerRight : Alignment.centerLeft, children: [ - Positioned( - top: 0, - left: 0, - right: 0, - child: ListenableBuilder( - listenable: widget.overlayController.practiceController, - builder: (context, _) { - final instruction = widget.overlayController - .practiceController.practiceMode.instruction; - if (instruction != null) { - return InstructionsInlineTooltip( - instructionsEnum: widget.overlayController - .practiceController.practiceMode.instruction!, - padding: const EdgeInsets.all(16.0), - animate: false, - ); - } - - return const SizedBox(); - }, - ), - ), ValueListenableBuilder( valueListenable: _startedTransition, builder: (context, started, __) { @@ -458,6 +436,36 @@ class MessageSelectionPositionerState extends State selectedToken: widget.overlayController.selectedToken, ), ), + Positioned( + top: switch (MediaQuery.heightOf(context)) { + < 700 => 0, + > 900 => 160, + _ => 80, + }, + left: 0, + right: 0, + child: ListenableBuilder( + listenable: + widget.overlayController.practiceController, + builder: (context, _) { + final instruction = widget.overlayController + .practiceController.practiceMode.instruction; + if (instruction != null) { + return InstructionsInlineTooltip( + instructionsEnum: widget + .overlayController + .practiceController + .practiceMode + .instruction!, + padding: const EdgeInsets.all(16.0), + animate: false, + ); + } + + return const SizedBox(); + }, + ), + ), ], ], ), diff --git a/lib/pangea/toolbar/message_practice/practice_controller.dart b/lib/pangea/toolbar/message_practice/practice_controller.dart index 00fee09d3..0e388d57e 100644 --- a/lib/pangea/toolbar/message_practice/practice_controller.dart +++ b/lib/pangea/toolbar/message_practice/practice_controller.dart @@ -54,6 +54,30 @@ class PracticeController with ChangeNotifier { practiceSelection?.activities(activityType).every((a) => a.isComplete) == true; + bool isPracticeButtonEmpty(PangeaToken token) { + final target = practiceTargetForToken(token); + + if (MessagePracticeMode.wordEmoji == practiceMode) { + if (token.vocabConstructID.userSetEmoji.firstOrNull != null) { + return false; + } + // Keep open even when completed to show emoji + return target == null; + } + + if (MessagePracticeMode.wordMorph == practiceMode) { + // Keep open even when completed to show morph icon + return target == null; + } + + return target == null || + target.isCompleteByToken( + token, + _activity?.morphFeature, + ) == + true; + } + Future> fetchActivityModel( PracticeTarget target, ) async { diff --git a/lib/pangea/toolbar/message_practice/reading_assistance_input_bar.dart b/lib/pangea/toolbar/message_practice/reading_assistance_input_bar.dart index 4d0b367b7..9481d4634 100644 --- a/lib/pangea/toolbar/message_practice/reading_assistance_input_bar.dart +++ b/lib/pangea/toolbar/message_practice/reading_assistance_input_bar.dart @@ -176,11 +176,10 @@ class _ReadingAssistanceBarContent extends StatelessWidget { } if (target == null) { - return Center( - child: Text( - L10n.of(context).selectForGrammar, - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, + return const Center( + child: Icon( + Symbols.fitness_center, + size: 60.0, ), ); } diff --git a/lib/pangea/toolbar/message_practice/token_practice_button.dart b/lib/pangea/toolbar/message_practice/token_practice_button.dart index b3341fe51..44e675b9e 100644 --- a/lib/pangea/toolbar/message_practice/token_practice_button.dart +++ b/lib/pangea/toolbar/message_practice/token_practice_button.dart @@ -55,20 +55,7 @@ class TokenPracticeButton extends StatelessWidget { true; } - bool get _isEmpty { - final mode = controller.practiceMode; - if (MessagePracticeMode.wordEmoji == mode && - token.vocabConstructID.userSetEmoji.firstOrNull != null) { - return false; - } - - return _activity == null || - (isActivityCompleteOrNullForToken && - ![MessagePracticeMode.wordEmoji, MessagePracticeMode.wordMorph] - .contains(mode)) || - (MessagePracticeMode.wordMorph == mode && - _activity?.morphFeature == null); - } + bool get _isEmpty => controller.isPracticeButtonEmpty(token); bool get _isSelected => controller.selectedMorph?.token == token && @@ -98,6 +85,7 @@ class TokenPracticeButton extends StatelessWidget { child = _MorphMatchButton( active: _isSelected, textColor: textColor, + width: tokenButtonHeight, onTap: () => controller.onSelectMorph( MorphSelection( token, @@ -123,8 +111,14 @@ class TokenPracticeButton extends StatelessWidget { curve: Curves.easeOut, alignment: Alignment.bottomCenter, child: _isEmpty - ? const SizedBox(height: 0) - : SizedBox(height: tokenButtonHeight, child: child), + ? const SizedBox() + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 16.0), + SizedBox(height: tokenButtonHeight, child: child), + ], + ), ); }, ); @@ -198,10 +192,12 @@ class _MorphMatchButton extends StatelessWidget { final bool active; final Color textColor; final bool shimmer; + final double width; const _MorphMatchButton({ required this.active, required this.textColor, + required this.width, this.shimmer = false, this.onTap, }); @@ -218,7 +214,7 @@ class _MorphMatchButton extends StatelessWidget { child: ShimmerBackground( enabled: shimmer, child: SizedBox( - width: 24.0, + width: width, child: Center( child: Opacity( opacity: active ? 1.0 : 0.6, @@ -282,7 +278,6 @@ class _NoActivityContentButton extends StatelessWidget { context: context, ), child: SizedBox( - width: 24.0, child: Center( child: MorphIcon( morphFeature: morphFeature,