chore: show correct answer hint button

and don't show answer description on selection of correct answer
This commit is contained in:
Ava Shilling 2026-01-29 16:17:55 -05:00
parent aa597b8698
commit 0cb3d472c7
2 changed files with 166 additions and 50 deletions

View file

@ -101,6 +101,8 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
final ValueNotifier<SelectedMorphChoice?> selectedMorphChoice =
ValueNotifier<SelectedMorphChoice?>(null);
final ValueNotifier<bool> hintPressedNotifier = ValueNotifier<bool>(false);
final Map<String, Map<String, String>> _choiceTexts = {};
final Map<String, Map<String, String?>> _choiceEmojis = {};
@ -125,6 +127,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
progressNotifier.dispose();
enableChoicesNotifier.dispose();
selectedMorphChoice.dispose();
hintPressedNotifier.dispose();
super.dispose();
}
@ -210,6 +213,7 @@ class AnalyticsPracticeState extends State<AnalyticsPractice>
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<AnalyticsPractice>
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<AnalyticsPractice>
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<AnalyticsPractice>
await _analyticsService.updateService.addAnalytics(null, [use]);
}
void onHintPressed() {
hintPressedNotifier.value = !hintPressedNotifier.value;
}
Future<void> onSelectChoice(
String choiceContent,
) async {

View file

@ -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<MultipleChoicePracticeActivityModel> ||
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<MultipleChoicePracticeActivityModel>) {
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<MultipleChoicePracticeActivityModel> ||
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;