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 l10n = AppLocalizations.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
final state = context.watch<CardProposalsCubit>().state; final state = context.watch<CardProposalsCubit>().state;
final proposals = context.read<CardProposalsCubit>(); CardProposalsCubit proposals() => context.read<CardProposalsCubit>();
final count = state.chosen.length; final count = state.chosen.length;
return SheetScaffold( return SheetScaffold(
title: l10n.proposalsTitle(widget.name), title: l10n.proposalsTitle(widget.name),
@ -163,7 +163,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
TextButton.icon( TextButton.icon(
icon: const Icon(LucideIcons.rotateCw), icon: const Icon(LucideIcons.rotateCw),
label: Text(l10n.commonTryAgain), label: Text(l10n.commonTryAgain),
onPressed: () => unawaited(proposals.propose()), onPressed: () => unawaited(proposals().propose()),
), ),
], ],
), ),
@ -183,7 +183,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
leading: Checkbox( leading: Checkbox(
value: state.chosen.contains(index), value: state.chosen.contains(index),
onChanged: (_) => proposals.toggle(index), onChanged: (_) => proposals().toggle(index),
), ),
title: Text(card.front), title: Text(card.front),
subtitle: Text(card.back), subtitle: Text(card.back),
@ -192,7 +192,7 @@ class _ProposalsSheetState() extends State<_ProposalsSheet> {
icon: const Icon(LucideIcons.pencil), icon: const Icon(LucideIcons.pencil),
onPressed: () => unawaited(_edit(index, card)), 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:flutter_bloc/flutter_bloc.dart';
import 'package:halkyon_app/halkyon_app.dart'; import 'package:halkyon_app/halkyon_app.dart';
import 'package:halkyon_learn_flutter/l10n/app_localizations.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/content/rich_content.dart';
import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies.dart'; import 'package:halkyon_learn_flutter/src/core/dependencies/dependencies.dart';
import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.dart'; import 'package:halkyon_learn_flutter/src/core/dependencies/person_dependencies.dart';
@ -66,7 +67,7 @@ Future<void> showConversation(
builder: (context) => BlocProvider( builder: (context) => BlocProvider(
// Closing the sheet closes the cubit, which stops an answer on its // Closing the sheet closes the cubit, which stops an answer on its
// way. // way.
create: (_) => cubit, create: (context) => cubit,
child: _ConversationSheet(name: name, scope: scope, choice: choice), child: _ConversationSheet(name: name, scope: scope, choice: choice),
), ),
); );
@ -385,7 +386,6 @@ class const _ImageApproval({
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context); final l10n = AppLocalizations.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
final conversation = context.read<ConversationCubit>();
return Padding( return Padding(
padding: const EdgeInsets.only(top: 8), padding: const EdgeInsets.only(top: 8),
child: DecoratedBox( child: DecoratedBox(
@ -401,13 +401,32 @@ class const _ImageApproval({
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon( // A card's image as the person knows it, so they see what
image.kind == LookupKind.pageImage // they approve; a page by its number and title.
? LucideIcons.fileImage if (image.assetId case final assetId?)
: LucideIcons.image, ClipRRect(
size: 20, borderRadius: BorderRadius.circular(8),
color: theme.colorScheme.primary, 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), const SizedBox(width: 12),
Expanded( Expanded(
child: Text(switch (image.kind) { child: Text(switch (image.kind) {
@ -430,11 +449,17 @@ class const _ImageApproval({
spacing: 8, spacing: 8,
children: [ children: [
TextButton( TextButton(
onPressed: () => conversation.approveImage(approved: false), onPressed: () =>
context.read<ConversationCubit>().approveImage(
approved: false,
),
child: Text(l10n.assistantImageDontShow), child: Text(l10n.assistantImageDontShow),
), ),
FilledButton.tonal( FilledButton.tonal(
onPressed: () => conversation.approveImage(approved: true), onPressed: () =>
context.read<ConversationCubit>().approveImage(
approved: true,
),
child: Text(l10n.assistantImageShow), child: Text(l10n.assistantImageShow),
), ),
], ],