feat(ui): shared-element hero covers from search and recents to detail screens

Tab navigators get a HeroController (nested navigators have none, so
Heroes never flew inside tabs). Search result rows, recent-access items,
and artist discography cards tag their covers; Album/Artist/Downloaded
screens accept a heroTag and mount the matching Hero on the header
cover. Tags are scoped per list section to stay unique per page.
This commit is contained in:
zarzet
2026-07-14 09:09:49 +07:00
parent 702fc9825e
commit c580280890
7 changed files with 180 additions and 93 deletions
+20 -4
View File
@@ -648,7 +648,7 @@ class _MainShellState extends ConsumerState<MainShell>
}
}
class _TabNavigator extends StatelessWidget {
class _TabNavigator extends StatefulWidget {
final GlobalKey<NavigatorState> navigatorKey;
final Widget child;
final List<NavigatorObserver> observers;
@@ -660,13 +660,29 @@ class _TabNavigator extends StatelessWidget {
this.observers = const [],
});
@override
State<_TabNavigator> createState() => _TabNavigatorState();
}
class _TabNavigatorState extends State<_TabNavigator> {
// Nested navigators get no HeroController from MaterialApp; without one,
// Hero widgets on routes pushed inside a tab never fly.
final HeroController _heroController =
MaterialApp.createMaterialHeroController();
@override
void dispose() {
_heroController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
observers: observers,
key: widget.navigatorKey,
observers: [_heroController, ...widget.observers],
onGenerateInitialRoutes: (_, _) => [
MaterialPageRoute<void>(builder: (_) => child),
MaterialPageRoute<void>(builder: (_) => widget.child),
],
);
}