fix(explore): the summary in blocks: what it is, what it holds, its release

The summary sheet ran its parts together, each aligned its own way. Now
it groups them in cards, as the Profile tab does its parts: what the
studyset is (its colour along the top, its secondary title, author and
description), what it holds (languages, contents, learners), and its
latest release with its notes and Reports and suggestions; its credits
centred beneath them, as at the foot of a studyset.

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-14 18:03:19 +02:00
commit 500ce1a25b
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
6 changed files with 106 additions and 59 deletions

View file

@ -1623,6 +1623,7 @@
"exploreNothingMatches": "Nichts Veröffentlichtes passt dazu.",
"exploreBy": "von {author}",
"exploreRelease": "v{version} · {date}",
"exploreHolds": "Inhalt",
"exploreLatestRelease": "Neueste Veröffentlichung: v{version} · {date}",
"exploreAddToLibrary": "Zur Bibliothek hinzufügen",
"exploreAdd": "Hinzufügen",

View file

@ -1904,6 +1904,7 @@
"@exploreBy": {"placeholders": {"author": {"type": "String"}}},
"exploreRelease": "v{version} · {date}",
"@exploreRelease": {"placeholders": {"version": {"type": "int"}, "date": {"type": "String"}}},
"exploreHolds": "What it holds",
"exploreLatestRelease": "Latest release: v{version} · {date}",
"@exploreLatestRelease": {"placeholders": {"version": {"type": "int"}, "date": {"type": "String"}}},
"exploreAddToLibrary": "Add to library",

View file

@ -9603,6 +9603,12 @@ abstract class AppLocalizations {
/// **'v{version} · {date}'**
String exploreRelease(int version, String date);
/// No description provided for @exploreHolds.
///
/// In en, this message translates to:
/// **'What it holds'**
String get exploreHolds;
/// No description provided for @exploreLatestRelease.
///
/// In en, this message translates to:

View file

@ -6044,6 +6044,9 @@ class AppLocalizationsDe extends AppLocalizations {
return 'v$version · $date';
}
@override
String get exploreHolds => 'Inhalt';
@override
String exploreLatestRelease(int version, String date) {
return 'Neueste Veröffentlichung: v$version · $date';

View file

@ -5991,6 +5991,9 @@ class AppLocalizationsEn extends AppLocalizations {
return 'v$version · $date';
}
@override
String get exploreHolds => 'What it holds';
@override
String exploreLatestRelease(int version, String date) {
return 'Latest release: v$version · $date';

View file

@ -3,6 +3,7 @@ import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
import 'package:halkyon_learn_flutter/src/core/editor/rich_content_view.dart';
import 'package:halkyon_learn_flutter/src/core/l10n/content_languages.dart';
import 'package:halkyon_learn_flutter/src/core/widget/adaptive_sheet.dart';
import 'package:halkyon_learn_flutter/src/core/widget/section_card.dart';
import 'package:halkyon_learn_flutter/src/feature/explore/bloc/explore_bloc.dart';
import 'package:halkyon_learn_flutter/src/feature/explore/model/explore_studyset.dart';
import 'package:halkyon_learn_flutter/src/feature/explore/widget/explore_screen.dart';
@ -51,74 +52,106 @@ class const _Summary({required final ExploreStudyset studyset})
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Its colour, as along the top of its card.
Container(
height: 4,
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: exploreColor(context, studyset),
borderRadius: BorderRadius.circular(2),
// What it is, in its colour along the top as on its card.
SectionCard(
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(height: 4, color: exploreColor(context, studyset)),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (studyset.secondaryTitle case final secondary?)
Text(
secondary,
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.primary,
),
),
Text(
l10n.exploreBy(authorLine(studyset)),
style: muted,
),
if (studyset.basedOn case final original?)
Text(l10n.exploreBasedOn(original), style: muted),
const SizedBox(height: 12),
if (studyset.description.isBlank)
Text(
l10n.exploreNoDescription,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontStyle: FontStyle.italic,
),
)
else
RichContentView(content: studyset.description),
],
),
),
],
),
),
if (studyset.secondaryTitle case final secondary?)
Text(secondary, style: theme.textTheme.titleSmall),
Text(l10n.exploreBy(authorLine(studyset)), style: muted),
if (studyset.basedOn case final original?)
Text(l10n.exploreBasedOn(original), style: muted),
const SizedBox(height: 12),
if (studyset.description.isBlank)
Text(
l10n.exploreNoDescription,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontStyle: FontStyle.italic,
),
)
else
RichContentView(content: studyset.description),
const SizedBox(height: 16),
if (languages.isNotEmpty)
Text(
languages.length == 2
? l10n.exploreLanguagePair(languages[0], languages[1])
: languages.single,
style: muted,
// What it holds.
SectionCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(l10n.exploreHolds, style: theme.textTheme.titleSmall),
const SizedBox(height: 8),
if (languages.isNotEmpty)
Text(
languages.length == 2
? l10n.exploreLanguagePair(languages[0], languages[1])
: languages.single,
style: muted,
),
ExploreCounts(studyset: studyset),
if (studyset.learners case final learners?)
Text(l10n.exploreLearners(learners), style: muted),
],
),
ExploreCounts(studyset: studyset),
if (studyset.learners case final learners?)
Text(l10n.exploreLearners(learners), style: muted),
const SizedBox(height: 12),
Text(
l10n.exploreLatestRelease(
studyset.version,
releaseDate(context, studyset.releasedAt),
),
style: theme.textTheme.labelLarge,
),
if (studyset.notes.isNotEmpty) ...[
const SizedBox(height: 4),
Text(studyset.notes),
],
const SizedBox(height: 4),
Align(
alignment: AlignmentDirectional.centerStart,
child: TextButton.icon(
// Its icon on the edge the lines above start at.
style: TextButton.styleFrom(
padding: const EdgeInsetsDirectional.only(end: 8),
),
icon: const Icon(LucideIcons.messagesSquare, size: 18),
label: Text(l10n.studysetFeedback),
onPressed: () => showStudysetFeedback(
context,
publicId: studyset.publicId,
title: studyset.title,
),
const SizedBox(height: 12),
// Its latest release, and what people say about it.
SectionCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.exploreLatestRelease(
studyset.version,
releaseDate(context, studyset.releasedAt),
),
style: theme.textTheme.titleSmall,
),
if (studyset.notes.isNotEmpty) ...[
const SizedBox(height: 4),
Text(studyset.notes, style: muted),
],
const SizedBox(height: 4),
TextButton.icon(
// Its icon on the edge the lines above start at.
style: TextButton.styleFrom(
padding: const EdgeInsetsDirectional.only(end: 8),
),
icon: const Icon(LucideIcons.messagesSquare, size: 18),
label: Text(l10n.studysetFeedback),
onPressed: () => showStudysetFeedback(
context,
publicId: studyset.publicId,
title: studyset.title,
),
),
],
),
),
const SizedBox(height: 8),
// Its credits beneath the blocks, as at the foot of a studyset.
LicenseFooter(
centred: false,
content: StudysetContent(
title: studyset.title,
description: studyset.description,