You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
meshcore-client/lib/screens/settings/settings_shell.dart

146 lines
4.2 KiB

import 'package:flutter/material.dart';
/// One entry in the responsive settings shell: an icon, a title, an optional
/// subtitle, and a builder for the category's detail pane.
class SettingsCategory {
final IconData icon;
final String title;
final String? subtitle;
final WidgetBuilder builder;
const SettingsCategory({
required this.icon,
required this.title,
this.subtitle,
required this.builder,
});
}
/// Responsive master-detail container for settings.
///
/// Wide layouts (>= [_wideBreakpoint] logical pixels) show a pinned category
/// rail beside the selected detail pane. Narrow layouts show the category list
/// and push the detail onto the navigator. Same categories, same code, every
/// platform — the layout is chosen from the available width, not the OS.
class SettingsShell extends StatefulWidget {
final String title;
final List<SettingsCategory> categories;
final PreferredSizeWidget? appBarBottom;
const SettingsShell({
super.key,
required this.title,
required this.categories,
this.appBarBottom,
});
@override
State<SettingsShell> createState() => _SettingsShellState();
}
class _SettingsShellState extends State<SettingsShell> {
static const double _wideBreakpoint = 720;
static const double _railWidth = 280;
int _selected = 0;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return constraints.maxWidth >= _wideBreakpoint
? _buildWide(context)
: _buildNarrow(context);
},
);
}
PreferredSizeWidget _appBar(String title) {
return AppBar(
title: Text(title),
centerTitle: true,
bottom: widget.appBarBottom,
);
}
Widget _buildWide(BuildContext context) {
final categories = widget.categories;
final selected = _selected.clamp(0, categories.length - 1);
return Scaffold(
appBar: _appBar(widget.title),
body: SafeArea(
top: false,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
width: _railWidth,
child: ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: categories.length,
itemBuilder: (context, i) {
final category = categories[i];
return ListTile(
leading: Icon(category.icon),
title: Text(category.title),
selected: i == selected,
selectedTileColor:
Theme.of(context).colorScheme.surfaceContainerHighest,
onTap: () => setState(() => _selected = i),
);
},
),
),
const VerticalDivider(width: 1),
Expanded(
child: KeyedSubtree(
key: ValueKey<int>(selected),
child: categories[selected].builder(context),
),
),
],
),
),
);
}
Widget _buildNarrow(BuildContext context) {
final categories = widget.categories;
return Scaffold(
appBar: _appBar(widget.title),
body: SafeArea(
top: false,
child: ListView.builder(
itemCount: categories.length,
itemBuilder: (context, i) {
final category = categories[i];
return ListTile(
leading: Icon(category.icon),
title: Text(category.title),
subtitle:
category.subtitle != null ? Text(category.subtitle!) : null,
trailing: const Icon(Icons.chevron_right),
onTap: () => _openDetail(context, category),
);
},
),
),
);
}
void _openDetail(BuildContext context, SettingsCategory category) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Scaffold(
appBar: AppBar(
title: Text(category.title),
centerTitle: true,
bottom: widget.appBarBottom,
),
body: SafeArea(top: false, child: category.builder(context)),
),
),
);
}
}

Powered by TurnKey Linux.