feat(shell): the rail's logo leads Home

From medium windows on, the kingfisher atop the navigation rail is a
button to Home, as a website's logo leads to its start, with the tick the
rail's own choices make.

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 14:15:46 +02:00
commit ca8c3659bc
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 45 additions and 18 deletions

View file

@ -77,7 +77,7 @@ class _AppShellState() extends State<AppShell> {
selectedIndex: navigationShell.currentIndex,
labelType: NavigationRailLabelType.all,
onDestinationSelected: _select,
leading: const _RailLogo(),
leading: _RailLogo(onTap: () => _select(0)),
destinations: [
for (final destination in destinations)
NavigationRailDestination(
@ -141,25 +141,40 @@ class _AppShellState() extends State<AppShell> {
}
}
/// The kingfisher atop the rail. The icon has a white background, so it sits
/// in a rounded tile that reads on light and dark rails alike.
class const _RailLogo() extends StatelessWidget {
/// The kingfisher atop the rail, leading Home ([onTap]) as a website's
/// logo does. The icon has a white background, so it sits in a rounded tile
/// that reads on light and dark rails alike.
class const _RailLogo({required final VoidCallback onTap})
extends StatelessWidget {
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: ClipRRect(
borderRadius: BorderRadius.circular(9),
child: Image.asset(
'assets/icon/halkyon_icon.png',
width: 40,
height: 40,
// The source is 1024 px square; a 40 dp logo needs no more than this
// in memory, even at three times the density.
cacheWidth: 120,
semanticLabel: AppLocalizations.of(context).appTitle,
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Tooltip(
message: l10n.shellTabHome,
child: Material(
type: MaterialType.transparency,
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(9),
child: ClipRRect(
borderRadius: BorderRadius.circular(9),
child: Image.asset(
'assets/icon/halkyon_icon.png',
width: 40,
height: 40,
// The source is 1024 px square; a 40 dp logo needs no more
// than this in memory, even at three times the density.
cacheWidth: 120,
semanticLabel: l10n.appTitle,
),
),
),
),
),
),
);
);
}
}
/// A tab: its label and icons. Lucide's icons are outlines with few filled

View file

@ -9,6 +9,7 @@ import 'package:halkyon_learn_flutter/src/core/widget/screen_tint.dart';
import 'package:halkyon_learn_flutter/src/core/widget/top_gradient.dart';
import 'package:halkyon_learn_flutter/src/feature/card/data/card_repository.dart';
import 'package:halkyon_learn_flutter/src/feature/card/model/card_content.dart';
import 'package:halkyon_learn_flutter/src/feature/home/widget/home_screen.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/data/studyset_payload.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/model/studyset_content.dart';
import 'package:halkyon_learn_flutter/src/feature/studyset/widget/studyset_screen.dart';
@ -175,6 +176,17 @@ void main() {
expect(find.byType(NavigationBar), findsOneWidget);
});
testWidgets("in wider windows, the rail's logo leads Home, as a "
"website's does", (tester) async {
await showApp(tester);
await openJapanese(tester);
await tester.tap(find.bySemanticsLabel('Halkyon Learn'));
await tester.pumpAndSettle();
expect(find.byType(HomeScreen), findsOneWidget);
});
testWidgets('in wider windows, the rail stays inside a studyset', (
tester,
) async {