Fix: PlayerExpansionController oberhalb des Navigators bereitstellen
Bug (Code-Review, P0 — blockierte den Push): PlayerExpansionController wurde bisher nur INNERHALB von HomeShell.build() bereitgestellt. Per Navigator.push geöffnete Screens (PlaylistDetailScreen, ServerTitelScreen) sind im Widget-Baum aber Geschwister von HomeShell im selben Navigator/Overlay, keine Nachfahren — sie sahen den Provider nicht. context.read<PlayerExpansionController>().open(context) warf dort zur Laufzeit ProviderNotFoundException, vom bestehenden try/catch abgefangen zu einer "Wiedergabe fehlgeschlagen"-Snackbar — der Player öffnete sich nie. MeloApp ist jetzt ein StatefulWidget (TickerProviderStateMixin), erzeugt PlayerExpansionController in initState() und stellt ihn in seiner MultiProvider-Liste bereit — oberhalb von MaterialApp/Navigator, wie alle anderen App-weiten Provider. HomeShell erzeugt keinen eigenen mehr, sondern liest die Referenz per context.read<PlayerExpansionController>() einmalig (keine Subscription) und reicht sie an seine AnimatedBuilder weiter, die weiterhin direkt am Controller lauschen — die bewusste "Consumer nur um PopScope"-Optimierung (Scaffold wird nicht bei jedem progress-Tick neu gebaut) bleibt dabei erhalten. Test zuerst (RED mit der heutigen, echten ProviderNotFoundException reproduziert — via echtem HomeShell + echtem Navigator.push, nicht dem bisherigen Kurzschluss-Testaufbau, der den Provider manuell direkt neben das Test-Widget klebt): test/player/provider_scope_navigator_push_test.dart. Bestehende Tests, die HomeShell direkt aufbauen (hauptmenue_test.dart, home_shell_test.dart, home_shell_real_expansion_test.dart), an die neue Verdrahtung angepasst — der Controller kommt jetzt aus ihrer eigenen MultiProvider-Liste statt aus HomeShell selbst. Volle Suite (625 Tests) grün. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SM6e73V9ZethPiqHSocxUA
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6687f2f67c
commit
0fe624d1e8
@@ -44,6 +44,7 @@ void main() {
|
||||
final playlists = PlaylistService(db);
|
||||
final handler = MeloAudioHandler(db: db);
|
||||
final auth = BakaAuth(speicher: _MemorySpeicher());
|
||||
final expansion = PlayerExpansionController(vsync: tester);
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
@@ -64,6 +65,8 @@ void main() {
|
||||
ChangeNotifierProvider<SyncService>(
|
||||
create: (_) =>
|
||||
SyncService(db: db, cloud: MeloCloudService(auth: auth))),
|
||||
ChangeNotifierProvider<PlayerExpansionController>.value(
|
||||
value: expansion),
|
||||
],
|
||||
child: const MaterialApp(home: HomeShell()),
|
||||
));
|
||||
@@ -71,13 +74,11 @@ void main() {
|
||||
|
||||
expect(find.byType(BackdropFilter), findsNothing);
|
||||
|
||||
// PlayerExpansionController wird von HomeShell selbst per
|
||||
// ChangeNotifierProvider INNERHALB seines eigenen build() bereitgestellt
|
||||
// — der Provider sitzt also unterhalb (nicht oberhalb) des HomeShell-
|
||||
// Elements im Baum. Provider.of von einem HomeShell-Element aus würde
|
||||
// deshalb ins Leere laufen (Ancestor-Lookup findet nur Vorfahren, nicht
|
||||
// die eigenen Nachfahren) — stattdessen ein Element unterhalb nehmen,
|
||||
// z.B. MiniPlayer, das immer im Stack steckt, auch ohne laufenden Titel.
|
||||
// PlayerExpansionController sitzt seit dem Provider-Scope-Fix oberhalb
|
||||
// von MaterialApp/HomeShell (analog zu MeloApps MultiProvider in
|
||||
// main.dart), nicht mehr innerhalb von HomeShell.build() — jedes Element
|
||||
// im Baum sieht denselben Controller, hier über ein Element unterhalb von
|
||||
// HomeShell (MiniPlayer, immer im Stack, auch ohne laufenden Titel).
|
||||
final descendantContext = tester.element(find.byType(MiniPlayer));
|
||||
final controller = Provider.of<PlayerExpansionController>(
|
||||
descendantContext,
|
||||
@@ -98,6 +99,7 @@ void main() {
|
||||
// test/home_shell_test.dart (offener Positions-Timer des Audio-Handlers).
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
handler.dispose();
|
||||
expansion.dispose();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
});
|
||||
|
||||
@@ -111,6 +113,7 @@ void main() {
|
||||
final playlists = PlaylistService(db);
|
||||
final handler = MeloAudioHandler(db: db);
|
||||
final auth = BakaAuth(speicher: _MemorySpeicher());
|
||||
final expansion = PlayerExpansionController(vsync: tester);
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
@@ -127,10 +130,13 @@ void main() {
|
||||
ChangeNotifierProvider<YtSearchService>(
|
||||
create: (_) => YtSearchService(auth: auth)),
|
||||
ChangeNotifierProvider<DownloadService>(
|
||||
create: (_) => DownloadService(db: db, navidrome: NavidromeService())),
|
||||
create: (_) =>
|
||||
DownloadService(db: db, navidrome: NavidromeService())),
|
||||
ChangeNotifierProvider<SyncService>(
|
||||
create: (_) =>
|
||||
SyncService(db: db, cloud: MeloCloudService(auth: auth))),
|
||||
ChangeNotifierProvider<PlayerExpansionController>.value(
|
||||
value: expansion),
|
||||
],
|
||||
child: const MaterialApp(home: HomeShell()),
|
||||
));
|
||||
@@ -169,6 +175,7 @@ void main() {
|
||||
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
handler.dispose();
|
||||
expansion.dispose();
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user