fix(player): request POST_NOTIFICATIONS; auto-minimize player on teardown

Device-surfaced on physical Android 13+ (worked on emulator):

A) The media notification never appeared because the app never
   requested POST_NOTIFICATIONS at runtime — the manifest declares it
   and the foreground service is correct, but Android 13+ denies it by
   default until asked. Add permission_handler ^12.0.1 and request
   Permission.notification once at startup (post-first-frame,
   Platform.isAndroid-guarded; no-op on <13 / once decided).

B) When the #52 idle/dismiss teardown nulled mediaItem while the full
   NowPlayingScreen was open, it stranded the user on an empty
   "Nothing playing." Scaffold. Now post-frame maybePop() so it
   auto-minimizes (the mini bar is already gone).

pubspec.lock + db.g.dart regenerated by CI/build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 23:03:31 -04:00
parent eaddb2478a
commit bc34d96329
3 changed files with 22 additions and 0 deletions
+11
View File
@@ -1,5 +1,8 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:permission_handler/permission_handler.dart';
import 'cache/cache_filler.dart';
import 'cache/metadata_prefetcher.dart';
@@ -77,6 +80,14 @@ class _MinstrelAppState extends ConsumerState<MinstrelApp> {
// probe → offlineProvider. Read here to start the poller; S4
// gates system-playlist play + Shuffle-all on it.
ref.read(offlineProvider);
// POST_NOTIFICATIONS (Android 13+) is denied-by-default until
// requested; without it the media notification is silently
// suppressed on physical devices. One-shot, post-first-frame so
// it never blocks launch; no-op on <13 / once already decided.
if (Platform.isAndroid) {
// ignore: unawaited_futures
Permission.notification.request();
}
});
}
@@ -222,6 +222,14 @@ class _NowPlayingScreenState extends ConsumerState<NowPlayingScreen> {
_displayedDominant = null;
_pendingPreloadId = null;
});
// Session was torn down (#52 idle/dismiss) while the full
// player was open. Don't strand the user on an empty
// "Nothing playing." screen — minimize back (the mini bar is
// already gone too). maybePop is a no-op if this is somehow
// the root route.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) Navigator.of(context).maybePop();
});
}
return;
}