Tinker-Feedback: 5 Bugfixes (Shuffle/Repeat, Full-Cover, Auto-Play, Scan-Button, Back-Geste) #6
@@ -5,6 +5,21 @@ Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### ⬅️ Zurück-Geste auf Nicht-Start-Tabs minimierte die App (2026-08-27)
|
||||||
|
|
||||||
|
- 🐛 **Auf "Favoriten" (oder einem anderen Tab außer "Meine Musik") tippen und
|
||||||
|
dann die System-Zurück-Geste nutzen minimierte die App**, statt zur
|
||||||
|
vorherigen Ansicht zu navigieren. Grund: Tab-Wechsel in `HomeShell`
|
||||||
|
laufen nur über `setState` (`IndexedStack` + `BottomNavigationBar`), nie
|
||||||
|
über `Navigator.push` — es lag also nie eine Route auf dem
|
||||||
|
Navigator-Stack, die eine Zurück-Geste hätte poppen können.
|
||||||
|
- 🔧 **Fix:** `build()` in `main.dart` in ein `PopScope` gewickelt
|
||||||
|
(`canPop: _index == 0`), das bei einem Nicht-Start-Tab zuerst zu Tab 0
|
||||||
|
("Meine Musik") zurückspringt, statt die App zu verlassen.
|
||||||
|
- ✅ **Getestet:** neuer Widget-Test in `home_shell_test.dart` (RED→GREEN,
|
||||||
|
simuliert die System-Zurück-Geste über `WidgetsApp.didPopRoute()`), volle
|
||||||
|
Suite (593 Tests) grün, `flutter analyze` ohne Befund.
|
||||||
|
|
||||||
### 🔄 Doppelten Scan-Button auf Android entfernt (2026-08-27)
|
### 🔄 Doppelten Scan-Button auf Android entfernt (2026-08-27)
|
||||||
|
|
||||||
- 🐛 **In den Einstellungen gab es auf Android zwei Buttons für dieselbe
|
- 🐛 **In den Einstellungen gab es auf Android zwei Buttons für dieselbe
|
||||||
|
|||||||
+41
-32
@@ -222,39 +222,48 @@ class _HomeShellState extends State<HomeShell> with WidgetsBindingObserver {
|
|||||||
const FavoritesScreen(),
|
const FavoritesScreen(),
|
||||||
const YoutubeSearchScreen(),
|
const YoutubeSearchScreen(),
|
||||||
];
|
];
|
||||||
return Scaffold(
|
return PopScope(
|
||||||
body: Column(
|
// Tab-Wechsel laufen nur über setState, nie über Navigator.push — die
|
||||||
children: [
|
// Zurück-Geste auf einem Nicht-Start-Tab hätte sonst nichts zum
|
||||||
Expanded(child: IndexedStack(index: _index, children: tabs)),
|
// Poppen und würde die App minimieren statt zu "Meine Musik" zu gehen.
|
||||||
const MiniPlayer(),
|
canPop: _index == 0,
|
||||||
],
|
onPopInvokedWithResult: (didPop, _) {
|
||||||
),
|
if (!didPop) setState(() => _index = 0);
|
||||||
// Haarlinie darüber: ohne sie geht die Leiste auf schwarzem Grund
|
},
|
||||||
// optisch im Inhalt auf und wirkt nicht wie ein Hauptmenü.
|
child: Scaffold(
|
||||||
bottomNavigationBar: DecoratedBox(
|
body: Column(
|
||||||
decoration: const BoxDecoration(
|
children: [
|
||||||
border: Border(top: BorderSide(color: MeloTheme.border)),
|
Expanded(child: IndexedStack(index: _index, children: tabs)),
|
||||||
|
const MiniPlayer(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: BottomNavigationBar(
|
// Haarlinie darüber: ohne sie geht die Leiste auf schwarzem Grund
|
||||||
currentIndex: _index,
|
// optisch im Inhalt auf und wirkt nicht wie ein Hauptmenü.
|
||||||
onTap: _goTo,
|
bottomNavigationBar: DecoratedBox(
|
||||||
items: const [
|
decoration: const BoxDecoration(
|
||||||
BottomNavigationBarItem(
|
border: Border(top: BorderSide(color: MeloTheme.border)),
|
||||||
icon: Icon(Icons.headphones), label: 'Meine Musik'),
|
),
|
||||||
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suchen'),
|
child: BottomNavigationBar(
|
||||||
BottomNavigationBarItem(
|
currentIndex: _index,
|
||||||
icon: Icon(Icons.download_outlined),
|
onTap: _goTo,
|
||||||
activeIcon: Icon(Icons.download),
|
items: const [
|
||||||
label: 'Download'),
|
BottomNavigationBarItem(
|
||||||
BottomNavigationBarItem(
|
icon: Icon(Icons.headphones), label: 'Meine Musik'),
|
||||||
icon: Icon(Icons.favorite_border),
|
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suchen'),
|
||||||
activeIcon: Icon(Icons.favorite),
|
BottomNavigationBarItem(
|
||||||
label: 'Favoriten'),
|
icon: Icon(Icons.download_outlined),
|
||||||
BottomNavigationBarItem(
|
activeIcon: Icon(Icons.download),
|
||||||
icon: Icon(Icons.smart_display_outlined),
|
label: 'Download'),
|
||||||
activeIcon: Icon(Icons.smart_display),
|
BottomNavigationBarItem(
|
||||||
label: 'YT-Suche'),
|
icon: Icon(Icons.favorite_border),
|
||||||
],
|
activeIcon: Icon(Icons.favorite),
|
||||||
|
label: 'Favoriten'),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.smart_display_outlined),
|
||||||
|
activeIcon: Icon(Icons.smart_display),
|
||||||
|
label: 'YT-Suche'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -72,4 +72,71 @@ void main() {
|
|||||||
handler.dispose();
|
handler.dispose();
|
||||||
await db.close();
|
await db.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Zurück-Geste auf einem Nicht-Start-Tab wechselt zu '
|
||||||
|
'"Meine Musik" statt die App zu minimieren', (tester) async {
|
||||||
|
final db = MeloDb(NativeDatabase.memory());
|
||||||
|
final lib = LibraryService(db);
|
||||||
|
final playlists = PlaylistService(db);
|
||||||
|
final handler = MeloAudioHandler(db: db);
|
||||||
|
final auth = BakaAuth(speicher: _MemorySpeicher());
|
||||||
|
|
||||||
|
await tester.pumpWidget(MultiProvider(
|
||||||
|
providers: [
|
||||||
|
Provider<MeloDb>.value(value: db),
|
||||||
|
ChangeNotifierProvider<LibraryService>.value(value: lib),
|
||||||
|
ChangeNotifierProvider<PlaylistService>.value(value: playlists),
|
||||||
|
Provider<MeloAudioHandler>.value(value: handler),
|
||||||
|
ChangeNotifierProvider<CategoryService>(create: (_) => CategoryService(db)),
|
||||||
|
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||||
|
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||||
|
ChangeNotifierProvider<BakaAuth>(create: (_) => auth),
|
||||||
|
ChangeNotifierProvider<YtDownloadService>(
|
||||||
|
create: (_) => YtDownloadService(auth: auth)),
|
||||||
|
ChangeNotifierProvider<YtSearchService>(
|
||||||
|
create: (_) => YtSearchService(auth: auth)),
|
||||||
|
ChangeNotifierProvider<DownloadService>(
|
||||||
|
create: (_) => DownloadService(db: db, navidrome: NavidromeService())),
|
||||||
|
ChangeNotifierProvider<SyncService>(
|
||||||
|
create: (_) => SyncService(
|
||||||
|
db: db, cloud: MeloCloudService(auth: auth))),
|
||||||
|
],
|
||||||
|
child: const MaterialApp(home: HomeShell()),
|
||||||
|
));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Auf "Favoriten" wechseln (Index 3) — kein Navigator.push, nur der Tab.
|
||||||
|
await tester.tap(find.descendant(
|
||||||
|
of: find.byType(BottomNavigationBar), matching: find.text('Favoriten')));
|
||||||
|
await tester.pump();
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<BottomNavigationBar>(find.byType(BottomNavigationBar))
|
||||||
|
.currentIndex,
|
||||||
|
3);
|
||||||
|
|
||||||
|
// System-Zurück-Geste simulieren — derselbe Weg, über den auch das
|
||||||
|
// echte Android-Zurück-Gesten-Signal bei WidgetsApp ankommt.
|
||||||
|
final widgetsAppState = tester.state(find.byType(WidgetsApp));
|
||||||
|
// ignore: avoid_dynamic_calls
|
||||||
|
await (widgetsAppState as dynamic).didPopRoute();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Ohne Abfangen bliebe der Tab unverändert (Flutter minimiert dann die
|
||||||
|
// App statt zu navigieren) — die Geste muss stattdessen zu Tab 0 führen.
|
||||||
|
expect(
|
||||||
|
tester
|
||||||
|
.widget<BottomNavigationBar>(find.byType(BottomNavigationBar))
|
||||||
|
.currentIndex,
|
||||||
|
0);
|
||||||
|
|
||||||
|
// Baum abbauen, bevor der Test endet: der Positions-Timer des Audio-
|
||||||
|
// Handlers läuft sonst über das Testende hinaus weiter, und der
|
||||||
|
// Test-Rahmen meldet einen offenen Timer (wie in hauptmenue_test.dart
|
||||||
|
// dokumentiert). Aus demselben Grund bewusst kein db.close() nach
|
||||||
|
// einem tester.tap.
|
||||||
|
await tester.pumpWidget(const SizedBox());
|
||||||
|
handler.dispose();
|
||||||
|
await tester.pump(const Duration(milliseconds: 1));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user