The DeltaMark ran an endless 9s breathing animation (60fps repaint) inside the persistent nav rail, so it repainted on every scroll frame and stuttered scrolling on Flutter web. Make the rail mark static (animated: false) and isolate the animated variant (landing page) behind a RepaintBoundary. Signed-off-by: flemming-it <sf@flemming.it>
104 lines
3.4 KiB
Dart
104 lines
3.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../data/repository.dart';
|
|
import '../theme/reclaim_tokens.dart';
|
|
import '../widgets/brand_wordmark.dart';
|
|
import '../widgets/delta_mark.dart';
|
|
import 'about_page.dart';
|
|
import 'heatmap_page.dart';
|
|
import 'hub_status_page.dart';
|
|
import 'legal_page.dart';
|
|
import 'norms_list_page.dart';
|
|
|
|
/// App shell with a NavigationRail. Each rail destination is one
|
|
/// page; pages do their own scrolling.
|
|
class ShellPage extends StatefulWidget {
|
|
const ShellPage({super.key, required this.repository});
|
|
|
|
final EvaluationRepository repository;
|
|
|
|
@override
|
|
State<ShellPage> createState() => _ShellPageState();
|
|
}
|
|
|
|
class _ShellPageState extends State<ShellPage> {
|
|
int _index = 0;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final t = Theme.of(context).textTheme;
|
|
final pages = <Widget>[
|
|
HeatmapPage(repository: widget.repository),
|
|
NormsListPage(repository: widget.repository),
|
|
HubStatusPage(repository: widget.repository),
|
|
const AboutPage(),
|
|
const LegalPage(),
|
|
];
|
|
|
|
return Scaffold(
|
|
body: Row(
|
|
children: [
|
|
NavigationRail(
|
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
extended: MediaQuery.of(context).size.width > 1100,
|
|
selectedIndex: _index,
|
|
onDestinationSelected: (i) => setState(() => _index = i),
|
|
leading: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: ReclaimSpace.lg,
|
|
horizontal: ReclaimSpace.md,
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Static in the persistent nav rail: a 60fps pulse
|
|
// here repaints every scroll frame and stutters web
|
|
// scrolling. The breathing mark stays on the
|
|
// landing page (which isn't scrolled).
|
|
const DeltaMark(size: 36, animated: false),
|
|
const SizedBox(height: ReclaimSpace.sm),
|
|
const BrandWordmark(size: 18),
|
|
Text(
|
|
'F∆I',
|
|
style: t.labelSmall?.copyWith(
|
|
letterSpacing: 2,
|
|
color: ReclaimColors.mute,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
destinations: const [
|
|
NavigationRailDestination(
|
|
icon: Icon(Icons.grid_view_outlined),
|
|
selectedIcon: Icon(Icons.grid_view),
|
|
label: Text('Heatmap'),
|
|
),
|
|
NavigationRailDestination(
|
|
icon: Icon(Icons.list_alt_outlined),
|
|
selectedIcon: Icon(Icons.list_alt),
|
|
label: Text('Normen'),
|
|
),
|
|
NavigationRailDestination(
|
|
icon: Icon(Icons.hub_outlined),
|
|
selectedIcon: Icon(Icons.hub),
|
|
label: Text('Hub'),
|
|
),
|
|
NavigationRailDestination(
|
|
icon: Icon(Icons.info_outline),
|
|
selectedIcon: Icon(Icons.info),
|
|
label: Text('Methodik'),
|
|
),
|
|
NavigationRailDestination(
|
|
icon: Icon(Icons.gavel_outlined),
|
|
selectedIcon: Icon(Icons.gavel),
|
|
label: Text('Recht'),
|
|
),
|
|
],
|
|
),
|
|
const VerticalDivider(width: 1),
|
|
Expanded(child: pages[_index]),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|