feat(learn): the image an assistant asks to see shows beside the question

A card's image the model asks for (ADR 0021, D12) shows as a thumbnail in
the approval box, so the person sees what they approve; a PDF's page is
named by its number and title. The proposals and conversation sheets read
their cubits only in callbacks, as leancode_lint asks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 22:06:22 +01:00
commit e25c867616
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 40 additions and 15 deletions

View file

@ -129,7 +129,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);
final state = context.watch<CardProposalsCubit>().state;
final proposals = context.read<CardProposalsCubit>();
CardProposalsCubit proposals() => context.read<CardProposalsCubit>();
final count = state.chosen.length;
return SheetScaffold(
title: l10n.proposalsTitle(widget.name),
@ -163,7 +163,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
TextButton.icon(
icon: const Icon(LucideIcons.rotateCw),
label: Text(l10n.commonTryAgain),
onPressed: () => unawaited(proposals.propose()),
onPressed: () => unawaited(proposals().propose()),
),
],
),
@ -183,7 +183,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
contentPadding: EdgeInsets.zero,
leading: Checkbox(
value: state.chosen.contains(index),
onChanged: (_) => proposals.toggle(index),
onChanged: (_) => proposals().toggle(index),
),
title: Text(card.front),
subtitle: Text(card.back),
@ -192,7 +192,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
icon: const Icon(LucideIcons.pencil),
onPressed: () => unawaited(_edit(index, card)),
),
onTap: () => proposals.toggle(index),
onTap: () => proposals().toggle(index),
),
],
),

View file

@ -5,6 +5,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
import 'package:halkyon_learn_flutter/src/core/asset/widget/stored_file_image.dart';
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies.dart';
import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.dart';
@ -66,7 +67,7 @@ Future<void> showConversation(
builder: (context) => BlocProvider(
// Closing the sheet closes the cubit, which stops an answer on its
// way.
create: (_) => cubit,
create: (context) => cubit,
child: _ConversationSheet(name: name, scope: scope, choice: choice),
),
);
@ -385,7 +386,6 @@ class const _ImageApproval({
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
final theme = Theme.of(context);
final conversation = context.read<ConversationCubit>();
return Padding(
padding: const EdgeInsets.only(top: 8),
child: DecoratedBox(
@ -401,13 +401,32 @@ class const _ImageApproval({
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
image.kind == LookupKind.pageImage
? LucideIcons.fileImage
: LucideIcons.image,
size: 20,
color: theme.colorScheme.primary,
),
// A card's image as the person knows it, so they see what
// they approve; a page by its number and title.
if (image.assetId case final assetId?)
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: SizedBox.square(
dimension: 56,
child: Image(
image: StoredFileImage(
PersonDependencies.of(context).assets,
assetId,
),
fit: BoxFit.cover,
errorBuilder: (context, _, _) => Icon(
LucideIcons.image,
color: theme.colorScheme.primary,
),
),
),
)
else
Icon(
LucideIcons.fileImage,
size: 20,
color: theme.colorScheme.primary,
),
const SizedBox(width: 12),
Expanded(
child: Text(switch (image.kind) {
@ -430,11 +449,17 @@ class const _ImageApproval({
spacing: 8,
children: [
TextButton(
onPressed: () => conversation.approveImage(approved: false),
onPressed: () =>
context.read<ConversationCubit>().approveImage(
approved: false,
),
child: Text(l10n.assistantImageDontShow),
),
FilledButton.tonal(
onPressed: () => conversation.approveImage(approved: true),
onPressed: () =>
context.read<ConversationCubit>().approveImage(
approved: true,
),
child: Text(l10n.assistantImageShow),
),
],