Merge: restructure mobile Settings into a two-level category menu
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+162
-20
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../theme/design_tokens.dart';
|
||||
import '../widgets/noo/lists/noo_grouped_list.dart';
|
||||
import '../widgets/noo/lists/noo_settings_row.dart';
|
||||
import '../widgets/noo/nav/noo_top_bar.dart';
|
||||
import '../widgets/noo/nav/noo_toolbar.dart';
|
||||
import '../widgets/noo/noo_layout.dart';
|
||||
@@ -15,18 +18,32 @@ import '../widgets/settings/settings_tabs.dart';
|
||||
|
||||
/// Settings, pushed on top of the shell (DESIGN_SYSTEM.md 4's 9-section
|
||||
/// order: account card, accounts, security, file sync, files cache,
|
||||
/// appearance, tabs, action bar, swipe on a file). One column of
|
||||
/// [SettingsSection]s (and [SettingsActionBarSection], which has no option
|
||||
/// rows of its own to put in one - just the reorder list) on mobile, each
|
||||
/// individually collapsible (expanded by default, tap its label to
|
||||
/// collapse - see `SettingsSection`/`NooGroupedList`'s `collapsible` param)
|
||||
/// so a long Settings screen can be collapsed down rather than needing a
|
||||
/// separate jump rail (an earlier version had one; it added a second,
|
||||
/// redundant way to navigate on top of plain scrolling and still didn't
|
||||
/// shorten the page). Desktop uses a 2-column grid of cards instead, wide
|
||||
/// enough to see most sections without scrolling, so it gets neither. See
|
||||
/// each `widgets/settings/*.dart` file for a section's own content and any
|
||||
/// setting that had to be slotted in or grouped under "Advanced appearance".
|
||||
/// appearance, tabs, action bar, swipe on a file).
|
||||
///
|
||||
/// Desktop is unchanged: a 2-column grid of cards ([_DesktopGrid]) wide
|
||||
/// enough to see every section at once, so it has no scroll-depth problem
|
||||
/// and needs no menu.
|
||||
///
|
||||
/// Mobile is a two-level menu, the way native iOS/Android Settings apps
|
||||
/// work: [SettingsAccountCard] (the account summary, not a settings picker)
|
||||
/// stays pinned at the top of a single top-level list ([_MobileMenu]), and
|
||||
/// every other section becomes one tappable [NooSettingsRow] - icon, title,
|
||||
/// chevron - in a [NooGroupedList] below it. Tapping a row pushes a new
|
||||
/// [_SettingsCategoryScreen] with its own [NooTopBar]/[NooTopBarBack],
|
||||
/// containing just that section's content full-screen. This replaces an
|
||||
/// earlier design where every section rendered inline in one long
|
||||
/// collapsible-sections column (`_MobileList`, since removed) - and before
|
||||
/// that, a trailing jump rail (an even earlier version) that scrolled that
|
||||
/// *same* page to an anchor. Both were rejected: the jump rail added a
|
||||
/// second, redundant way to navigate on top of plain scrolling without
|
||||
/// shortening the page, and the collapsible-sections column still left a
|
||||
/// long page to scroll past even collapsed. A genuinely separate pushed
|
||||
/// screen per category removes the scroll-depth problem outright, so
|
||||
/// neither a jump rail nor per-section collapsing is needed any more - see
|
||||
/// [SettingsSection]'s doc comment for how that reflects in its mobile
|
||||
/// layout. See each `widgets/settings/*.dart` file for a section's own
|
||||
/// content and any setting that had to be slotted in or grouped under
|
||||
/// "Advanced appearance".
|
||||
class AccountView extends StatelessWidget {
|
||||
const AccountView({super.key});
|
||||
|
||||
@@ -42,6 +59,49 @@ class AccountView extends StatelessWidget {
|
||||
SettingsSwipeSection(),
|
||||
];
|
||||
|
||||
static final _categories = <_SettingsCategory>[
|
||||
_SettingsCategory(
|
||||
title: 'Accounts',
|
||||
icon: LucideIcons.users,
|
||||
builder: (_) => const SettingsAccountsSection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'Security',
|
||||
icon: LucideIcons.lock,
|
||||
builder: (_) => const SettingsSecuritySection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'File sync',
|
||||
icon: LucideIcons.cloud,
|
||||
builder: (_) => const SettingsFileSyncSection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'Files cache',
|
||||
icon: LucideIcons.database,
|
||||
builder: (_) => const SettingsFilesCacheSection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'Appearance',
|
||||
icon: LucideIcons.sunMoon,
|
||||
builder: (_) => const SettingsAppearanceSection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'Tabs',
|
||||
icon: LucideIcons.layoutGrid,
|
||||
builder: (_) => const SettingsTabsSection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'Action bar',
|
||||
icon: LucideIcons.slidersHorizontal,
|
||||
builder: (_) => const SettingsActionBarSection(),
|
||||
),
|
||||
_SettingsCategory(
|
||||
title: 'Swipe on a file',
|
||||
icon: LucideIcons.chevronsLeftRight,
|
||||
builder: (_) => const SettingsSwipeSection(),
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = context.nooColors;
|
||||
@@ -60,18 +120,49 @@ class AccountView extends StatelessWidget {
|
||||
top: false,
|
||||
child: desktop
|
||||
? _DesktopGrid(sections: _sections)
|
||||
: _MobileList(sections: _sections),
|
||||
: _MobileMenu(categories: _categories),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileList extends StatelessWidget {
|
||||
final List<Widget> sections;
|
||||
const _MobileList({required this.sections});
|
||||
/// One row of [AccountView]'s mobile top-level menu: a title, a leading
|
||||
/// icon (reused from that section's own first/most-representative row, so
|
||||
/// the menu icon and the content the user lands on agree), and a builder
|
||||
/// for the section content shown on [_SettingsCategoryScreen].
|
||||
class _SettingsCategory {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final WidgetBuilder builder;
|
||||
|
||||
_SettingsCategory({
|
||||
required this.title,
|
||||
required this.icon,
|
||||
required this.builder,
|
||||
});
|
||||
}
|
||||
|
||||
/// The mobile top-level Settings list: [SettingsAccountCard] pinned above a
|
||||
/// single [NooGroupedList] of category rows, one per [AccountView._categories]
|
||||
/// entry - the menu half of the menu-then-pushed-screen pattern described on
|
||||
/// [AccountView]'s own doc comment.
|
||||
class _MobileMenu extends StatelessWidget {
|
||||
final List<_SettingsCategory> categories;
|
||||
const _MobileMenu({required this.categories});
|
||||
|
||||
void _open(BuildContext context, _SettingsCategory category) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => _SettingsCategoryScreen(category: category),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = context.nooColors;
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
NooSpace.sm,
|
||||
@@ -81,15 +172,66 @@ class _MobileList extends StatelessWidget {
|
||||
),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [
|
||||
for (final section in sections) ...[
|
||||
section,
|
||||
const SizedBox(height: NooSpace.xl),
|
||||
],
|
||||
const SettingsAccountCard(),
|
||||
const SizedBox(height: NooSpace.xl),
|
||||
NooGroupedList(
|
||||
children: [
|
||||
for (final category in categories)
|
||||
NooSettingsRow(
|
||||
icon: category.icon,
|
||||
label: Text(category.title),
|
||||
trailing: Icon(
|
||||
LucideIcons.chevronRight,
|
||||
size: 18,
|
||||
color: colors.fg3,
|
||||
),
|
||||
onTap: () => _open(context, category),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A pushed, single-category Settings screen: [NooTopBar] titled with the
|
||||
/// category, a back button, and just that section's own content - the
|
||||
/// pushed half of [AccountView]'s mobile menu-then-screen pattern. Always
|
||||
/// built in a mobile-width context (desktop never opens this screen; it
|
||||
/// shows every section inline in its own grid instead), so the section
|
||||
/// widgets inside render their normal mobile layout unchanged.
|
||||
class _SettingsCategoryScreen extends StatelessWidget {
|
||||
final _SettingsCategory category;
|
||||
const _SettingsCategoryScreen({required this.category});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = context.nooColors;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.bg,
|
||||
appBar: NooTopBar(
|
||||
style: NooLayout.navStyle(context),
|
||||
title: category.title,
|
||||
leading: const NooTopBarBack(),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
NooSpace.sm,
|
||||
NooSpace.sm,
|
||||
NooSpace.sm,
|
||||
NooSpace.xxl,
|
||||
),
|
||||
physics: const BouncingScrollPhysics(),
|
||||
children: [category.builder(context)],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits the sections between two columns rather than a strict grid, since
|
||||
/// each card's content height varies a lot (the tab reorder list and the
|
||||
/// accounts list can both run much taller than, say, Security) - a fixed
|
||||
|
||||
@@ -5,7 +5,7 @@ import '../noo/noo_layout.dart';
|
||||
import '../noo/overlays/noo_dialog.dart';
|
||||
import '../noo/overlays/noo_sheet.dart';
|
||||
|
||||
/// One block of Settings (DESIGN_SYSTEM.md 4's 8-part order): a
|
||||
/// One block of Settings (DESIGN_SYSTEM.md 4's 9-part order): a
|
||||
/// [NooGroupedList] on mobile (label above a radius-20 card), or a titled,
|
||||
/// bordered radius-20 card holding a flat row group on desktop
|
||||
/// ("Mobile uses one column of grouped lists. Desktop uses a 2-column grid
|
||||
@@ -31,11 +31,13 @@ class SettingsSection extends StatelessWidget {
|
||||
return NooGroupedList(
|
||||
label: title,
|
||||
footer: subtitle != null ? Text(subtitle!) : null,
|
||||
// Every mobile Settings section is individually collapsible,
|
||||
// expanded by default - replaces the old trailing jump rail (see
|
||||
// `account_view.dart`'s doc comment) as the way to navigate a long
|
||||
// Settings screen quickly.
|
||||
collapsible: true,
|
||||
// Not collapsible: each mobile Settings section now renders on its
|
||||
// own pushed screen (see `account_view.dart`'s doc comment for the
|
||||
// menu-then-pushed-screen pattern), so there's no long single-scroll
|
||||
// page left to collapse sections *within* - an earlier design had
|
||||
// every section inline in one column and made them individually
|
||||
// collapsible for exactly that reason; that's gone now that each
|
||||
// one is already isolated on its own screen.
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user