From 0cb3d472c7a11acf7cbc42f9e98f1386752faaa2 Mon Sep 17 00:00:00 2001 From: Ava Shilling <165050625+avashilling@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:17:55 -0500 Subject: [PATCH] chore: show correct answer hint button and don't show answer description on selection of correct answer --- .../analytics_practice_page.dart | 10 + .../analytics_practice_view.dart | 206 +++++++++++++----- 2 files changed, 166 insertions(+), 50 deletions(-) diff --git a/lib/pangea/analytics_practice/analytics_practice_page.dart b/lib/pangea/analytics_practice/analytics_practice_page.dart index 4f88cd84a..3988184ae 100644 --- a/lib/pangea/analytics_practice/analytics_practice_page.dart +++ b/lib/pangea/analytics_practice/analytics_practice_page.dart @@ -101,6 +101,8 @@ class AnalyticsPracticeState extends State final ValueNotifier selectedMorphChoice = ValueNotifier(null); + final ValueNotifier hintPressedNotifier = ValueNotifier(false); + final Map> _choiceTexts = {}; final Map> _choiceEmojis = {}; @@ -125,6 +127,7 @@ class AnalyticsPracticeState extends State progressNotifier.dispose(); enableChoicesNotifier.dispose(); selectedMorphChoice.dispose(); + hintPressedNotifier.dispose(); super.dispose(); } @@ -210,6 +213,7 @@ class AnalyticsPracticeState extends State activityState.value = const AsyncState.loading(); activityTarget.value = null; selectedMorphChoice.value = null; + hintPressedNotifier.value = false; enableChoicesNotifier.value = true; progressNotifier.value = 0.0; _queue.clear(); @@ -282,6 +286,7 @@ class AnalyticsPracticeState extends State try { activityState.value = const AsyncState.loading(); selectedMorphChoice.value = null; + hintPressedNotifier.value = false; final req = activityTarget.value!; final res = await _fetchActivity(req); @@ -324,6 +329,7 @@ class AnalyticsPracticeState extends State while (_queue.isNotEmpty) { activityState.value = const AsyncState.loading(); selectedMorphChoice.value = null; + hintPressedNotifier.value = false; final nextActivityCompleter = _queue.removeFirst(); try { @@ -477,6 +483,10 @@ class AnalyticsPracticeState extends State await _analyticsService.updateService.addAnalytics(null, [use]); } + void onHintPressed() { + hintPressedNotifier.value = !hintPressedNotifier.value; + } + Future onSelectChoice( String choiceContent, ) async { diff --git a/lib/pangea/analytics_practice/analytics_practice_view.dart b/lib/pangea/analytics_practice/analytics_practice_view.dart index 2fd5c9af5..5e5b31f76 100644 --- a/lib/pangea/analytics_practice/analytics_practice_view.dart +++ b/lib/pangea/analytics_practice/analytics_practice_view.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; @@ -23,7 +25,6 @@ import 'package:fluffychat/pangea/practice_activities/practice_activity_model.da import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; -import 'package:flutter/material.dart'; class AnalyticsPracticeView extends StatelessWidget { final AnalyticsPracticeState controller; @@ -162,28 +163,7 @@ class _AnalyticsActivityView extends StatelessWidget { const SizedBox(height: 16.0), _ActivityChoicesWidget(controller), const SizedBox(height: 16.0), - ListenableBuilder( - listenable: Listenable.merge([ - controller.activityState, - controller.selectedMorphChoice, - ]), - builder: (context, _) { - final activityState = controller.activityState.value; - final selectedChoice = controller.selectedMorphChoice.value; - - if (activityState - is! AsyncLoaded || - selectedChoice == null) { - return const SizedBox.shrink(); - } - - return MorphMeaningWidget( - feature: selectedChoice.feature, - tag: selectedChoice.tag, - blankErrorFeedback: true, - ); - }, - ), + _WrongAnswerFeedback(controller: controller), ], ); } @@ -228,6 +208,26 @@ class _AnalyticsPracticeCenterContent extends StatelessWidget { ), ), ), + ActivityTypeEnum.grammarCategory => Center( + child: Column( + children: [ + _CorrectAnswerHint(controller: controller), + _ExampleMessageWidget( + controller.getExampleMessage(target!.target), + ), + const SizedBox(height: 12), + ValueListenableBuilder( + valueListenable: controller.hintPressedNotifier, + builder: (context, hintPressed, __) { + return HintButton( + depressed: hintPressed, + onPressed: controller.onHintPressed, + ); + }, + ), + ], + ), + ), _ => SizedBox( height: 100.0, child: Center( @@ -282,6 +282,96 @@ class _ExampleMessageWidget extends StatelessWidget { } } +class _CorrectAnswerHint extends StatelessWidget { + final AnalyticsPracticeState controller; + + const _CorrectAnswerHint({ + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller.hintPressedNotifier, + builder: (context, hintPressed, __) { + if (!hintPressed) { + return const SizedBox.shrink(); + } + + return ValueListenableBuilder( + valueListenable: controller.activityState, + builder: (context, state, __) { + if (state is! AsyncLoaded) { + return const SizedBox.shrink(); + } + + final activity = state.value; + if (activity is! MorphPracticeActivityModel) { + return const SizedBox.shrink(); + } + + final correctAnswerTag = + activity.multipleChoiceContent.answers.first; + + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: MorphMeaningWidget( + feature: activity.morphFeature, + tag: correctAnswerTag, + ), + ); + }, + ); + }, + ); + } +} + +class _WrongAnswerFeedback extends StatelessWidget { + final AnalyticsPracticeState controller; + + const _WrongAnswerFeedback({ + required this.controller, + }); + + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: Listenable.merge([ + controller.activityState, + controller.selectedMorphChoice, + ]), + builder: (context, _) { + final activityState = controller.activityState.value; + final selectedChoice = controller.selectedMorphChoice.value; + + if (activityState + is! AsyncLoaded || + selectedChoice == null) { + return const SizedBox.shrink(); + } + + final activity = activityState.value; + final isWrongAnswer = + !activity.multipleChoiceContent.isCorrect(selectedChoice.tag); + + if (!isWrongAnswer) { + return const SizedBox.shrink(); + } + + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: MorphMeaningWidget( + feature: selectedChoice.feature, + tag: selectedChoice.tag, + blankErrorFeedback: true, + ), + ); + }, + ); + } +} + class _ErrorBlankWidget extends StatefulWidget { final GrammarErrorPracticeActivityModel activity; @@ -413,38 +503,54 @@ class _ErrorBlankWidgetState extends State<_ErrorBlankWidget> { ), ), const SizedBox(height: 8), - PressableButton( - borderRadius: BorderRadius.circular(20), - color: Theme.of(context).colorScheme.primaryContainer, - depressed: _showTranslation, - onPressed: _toggleTranslation, - playSound: true, - colorFactor: 0.3, - builder: (context, depressed, shadowColor) => Stack( - alignment: Alignment.center, - children: [ - Container( - height: 40.0, - width: 40.0, - decoration: BoxDecoration( - color: depressed - ? shadowColor - : Theme.of(context).colorScheme.primaryContainer, - shape: BoxShape.circle, - ), - ), - const Icon( - Icons.translate, - size: 20, - ), - ], - ), - ), + HintButton(depressed: _showTranslation, onPressed: _toggleTranslation), ], ); } } +class HintButton extends StatelessWidget { + final VoidCallback onPressed; + final bool depressed; + + const HintButton({ + required this.onPressed, + required this.depressed, + super.key, + }); + + @override + Widget build(BuildContext context) { + return PressableButton( + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.primaryContainer, + onPressed: onPressed, + depressed: depressed, + playSound: true, + colorFactor: 0.3, + builder: (context, depressed, shadowColor) => Stack( + alignment: Alignment.center, + children: [ + Container( + height: 40.0, + width: 40.0, + decoration: BoxDecoration( + color: depressed + ? shadowColor + : Theme.of(context).colorScheme.primaryContainer, + shape: BoxShape.circle, + ), + ), + const Icon( + Icons.lightbulb_outline, + size: 20, + ), + ], + ), + ); + } +} + class _ActivityChoicesWidget extends StatelessWidget { final AnalyticsPracticeState controller;