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,