fix(learn): Settings no longer jumps while scrolled up slowly

Settings built its cards as they scrolled into view and let go of those
far above. Scrolled back up, a card built anew asked again for what it
shows and grew once answered: Japanese on this device stays hidden until
the instance says what it offers devices, and Jellyfin's form fills in
the server the instance suggests and looks it up. Whatever was on the
screen was pushed down by as much, and near the top the list put right
where it had thought the first cards were.

Settings is short, so it is built whole now (`SingleChildScrollView`):
nothing is let go of on the way, text typed into Jellyfin's form stays
when scrolled away, and the scrollbar knows the list's length.

The new test scrolls a phone's Settings up from the bottom, 20 pixels at
a time, and holds every card to its place in the list; before, a card
moved 64 pixels. The citation test now scrolls to the dropdown it taps,
not to the guide's link below it, which only left the dropdown on the
screen while the cards beneath weren't built yet.

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 13:19:53 +01:00
commit c38dc69018
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -40,56 +40,64 @@ class const SettingsScreen({super.key}) extends StatelessWidget {
constraints: const BoxConstraints(
maxWidth: readableContentMaxWidth,
),
child: ListView(
// Built whole, not as it scrolls into view (decided
// 2026-10-25): a card built anew on the way back up asks again
// for what it shows (the dictionaries the instance offers, the
// Jellyfin server it suggests) and grows once answered, which
// pushed everything on the screen down. Settings is short.
child: SingleChildScrollView(
padding: const EdgeInsets.all(12),
children: [
const SyncStatusCard(),
const SizedBox(height: 12),
const DeviceSettingsCard(),
const SizedBox(height: 12),
const CitationSettingsCard(),
const SizedBox(height: 12),
const _StudyCard(),
const SizedBox(height: 12),
const AssistantSettingsCard(),
const SizedBox(height: 12),
const JellyfinSettingsCard(),
const SizedBox(height: 12),
const DeviceDictionarySettingsCard(),
const _PrivacyCard(),
const SizedBox(height: 12),
SectionCard(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
child: Text(
l10n.settingsAccount,
style: theme.textTheme.titleMedium,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SyncStatusCard(),
const SizedBox(height: 12),
const DeviceSettingsCard(),
const SizedBox(height: 12),
const CitationSettingsCard(),
const SizedBox(height: 12),
const _StudyCard(),
const SizedBox(height: 12),
const AssistantSettingsCard(),
const SizedBox(height: 12),
const JellyfinSettingsCard(),
const SizedBox(height: 12),
const DeviceDictionarySettingsCard(),
const _PrivacyCard(),
const SizedBox(height: 12),
SectionCard(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
child: Text(
l10n.settingsAccount,
style: theme.textTheme.titleMedium,
),
),
),
// Which account, on which instance (decided
// 2026-10-16), switching, signing out and deleting it:
// halkyon_app's, in Learn's words where only Learn can
// say it.
AccountTiles(deleteMessage: l10n.settingsDeleteMessage),
],
// Which account, on which instance (decided
// 2026-10-16), switching, signing out and deleting it:
// halkyon_app's, in Learn's words where only Learn can
// say it.
AccountTiles(deleteMessage: l10n.settingsDeleteMessage),
],
),
),
),
const SizedBox(height: 12),
SectionCard(
padding: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
leading: const Icon(LucideIcons.info),
title: Text(l10n.settingsAbout),
onTap: () => showAbout(context),
const SizedBox(height: 12),
SectionCard(
padding: const EdgeInsets.symmetric(vertical: 8),
child: ListTile(
leading: const Icon(LucideIcons.info),
title: Text(l10n.settingsAbout),
onTap: () => showAbout(context),
),
),
),
const SizedBox(height: 24),
const _Version(),
],
const SizedBox(height: 24),
const _Version(),
],
),
),
),
),