feat(shell): Material 3 Expressive's shorter navigation bar on phones

The navigation bar below medium windows becomes Material 3 Expressive's
flexible one, which Flutter's Material library doesn't have yet: 64 dp
high instead of 80, the label of the tab in view in the secondary colour
(the tokens of Compose's ShortNavigationBar). Its pill stays Flutter's,
64 dp wide rather than 56, as Flutter fixes it.

A trial for review, on its own branch, beside the floating one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-11 12:09:21 +02:00
commit f4c502f128
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 52 additions and 4 deletions

View file

@ -4,9 +4,37 @@ import 'package:halkyon_learn_flutter/src/core/theme/theme_extras.dart';
import 'package:material_ui/material_ui.dart';
/// The app's theme for [brightness]: Material 3 with a hand-authored colour
/// scheme, the app's own fonts ([appFontFamily]), [ThemeExtras], and the
/// current style of progress indicators.
ThemeData appTheme(Brightness brightness) => ThemeData(
/// scheme, the app's own fonts ([appFontFamily]), [ThemeExtras], the
/// current style of progress indicators, and Material 3 Expressive's
/// shorter navigation bar.
ThemeData appTheme(Brightness brightness) {
final theme = _baseTheme(brightness);
return theme.copyWith(navigationBarTheme: _flexibleNavigationBar(theme));
}
/// Material 3 Expressive's flexible navigation bar, which Flutter's Material
/// library doesn't have yet (checked with v1.6.0): 64 dp high instead of 80,
/// the label of the tab in view in the secondary colour (the tokens of
/// Compose's `ShortNavigationBar`). Its pill stays Flutter's, 64 dp wide
/// rather than the 56 dp the tokens give, as Flutter fixes it.
NavigationBarThemeData _flexibleNavigationBar(ThemeData theme) {
final scheme = theme.colorScheme;
final label = theme.textTheme.labelMedium;
return NavigationBarThemeData(
height: 64,
labelTextStyle: WidgetStateProperty.resolveWith(
(states) => label?.copyWith(
color: states.contains(WidgetState.disabled)
? scheme.onSurfaceVariant.withValues(alpha: 0.38)
: states.contains(WidgetState.selected)
? scheme.secondary
: scheme.onSurfaceVariant,
),
),
);
}
ThemeData _baseTheme(Brightness brightness) => ThemeData(
useMaterial3: true,
fontFamily: appFontFamily,
fontFamilyFallback: appFontFallback,

View file

@ -118,6 +118,26 @@ void main() {
expect(find.byType(NavigationRail), findsNothing);
});
testWidgets("the navigation bar is Material 3 Expressive's shorter one, "
'the label of the tab in view in the secondary colour', (tester) async {
await showApp(tester, width: 400);
expect(tester.getSize(find.byType(NavigationBar)).height, 64);
final scheme = Theme.of(tester.element(find.byType(NavigationBar)))
.colorScheme;
Color? colorOf(String label) {
final text = find.descendant(
of: find.byType(NavigationBar),
matching: find.text(label),
);
return tester.widget<Text>(text).style?.color ??
DefaultTextStyle.of(tester.element(text)).style.color;
}
expect(colorOf('Home'), scheme.secondary);
expect(colorOf('Library'), scheme.onSurfaceVariant);
});
testWidgets("a studyset's note keeps the tabs beside it", (tester) async {
tester.view.physicalSize = const Size(1200, 900);
tester.view.devicePixelRatio = 1;