fix(profiles): binder settle, active-id, and rebind correctness

This commit is contained in:
edde746
2026-07-02 11:41:25 +02:00
parent 2025e1f9fd
commit f9123eb40f
13 changed files with 446 additions and 144 deletions
+8 -46
View File
@@ -133,7 +133,7 @@ bool shouldPassTvosMenuToSystem({
}
@visibleForTesting
enum ProfileInvalidationAction { none, waitForProfileSwitch, invalidateNow }
enum ProfileInvalidationAction { none, invalidateNow }
@visibleForTesting
ProfileInvalidationAction profileInvalidationAction({
@@ -141,13 +141,13 @@ ProfileInvalidationAction profileInvalidationAction({
required String? currentProfileId,
required bool wasBindingPreviously,
required bool isBindingNow,
required bool hasPendingProfileSwitchInvalidation,
required String? pendingProfileSwitchInvalidationId,
}) {
// An active-id change remounts the whole session subtree
// ([ProfileSessionScreen] keys on the id), recreating this screen and
// every session-scoped provider — nothing to invalidate from here. The
// app-global pieces (ApiCache volatile rows) are cleared at that remount
// seam, where the unmount can't outrun the work.
if (currentProfileId != previousProfileId) {
return ProfileInvalidationAction.waitForProfileSwitch;
}
if (hasPendingProfileSwitchInvalidation && pendingProfileSwitchInvalidationId == currentProfileId) {
return ProfileInvalidationAction.none;
}
if (wasBindingPreviously && !isBindingNow) {
@@ -242,8 +242,6 @@ class _MainScreenState extends State<MainScreen>
// we only invalidate on id change and the libraries sidebar keeps
// stale entries until the user switches profiles.
bool _wasBindingPrev = false;
bool _hasPendingProfileSwitchInvalidation = false;
String? _pendingProfileSwitchInvalidationId;
/// Subscription to MultiServerManager status changes. Used to resume any
/// queued downloads as soon as a Plex client comes online for the first
@@ -508,53 +506,17 @@ class _MainScreenState extends State<MainScreen>
currentProfileId: id,
wasBindingPreviously: _wasBindingPrev,
isBindingNow: isBindingNow,
hasPendingProfileSwitchInvalidation: _hasPendingProfileSwitchInvalidation,
pendingProfileSwitchInvalidationId: _pendingProfileSwitchInvalidationId,
);
if (action == ProfileInvalidationAction.waitForProfileSwitch) {
_lastSeenProfileId = id;
_wasBindingPrev = isBindingNow;
_hasPendingProfileSwitchInvalidation = true;
_pendingProfileSwitchInvalidationId = id;
// We're called inside the synchronous notify cascade *before* the
// binder's listener has fired (registration order). At this exact
// instant `_isBinding` is still false, so calling awaitBindingSettle
// here would resolve immediately. Hop to a microtask so the binder's
// listener gets to flip the flag first, then wait properly.
unawaited(
Future.microtask(() async {
final scheduledProfileId = id;
if (!mounted) return;
await activeProfile.awaitBindingSettle();
if (!mounted) return;
try {
if (_hasPendingProfileSwitchInvalidation &&
_pendingProfileSwitchInvalidationId == scheduledProfileId &&
activeProfile.activeId == scheduledProfileId) {
await _invalidateAllScreens();
}
} finally {
if (_hasPendingProfileSwitchInvalidation && _pendingProfileSwitchInvalidationId == scheduledProfileId) {
_hasPendingProfileSwitchInvalidation = false;
_pendingProfileSwitchInvalidationId = null;
}
}
}),
);
return;
}
_lastSeenProfileId = id;
_wasBindingPrev = isBindingNow;
// Same active id, but a rebind cycle for that profile just settled
// (true → false transition). Fires after borrow / connection-removal
// flows trigger ActiveProfileBinder.rebindIfActive, so the libraries
// sidebar reflects the new server set without an app restart.
if (action == ProfileInvalidationAction.invalidateNow) {
_wasBindingPrev = isBindingNow;
unawaited(_invalidateAllScreens());
return;
}
_wasBindingPrev = isBindingNow;
}
Future<void> _promptForInitialProfileSelection() async {