diff --git a/DerivedDataReview/Logs/Package/D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog b/DerivedDataReview/Logs/Package/D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog new file mode 100644 index 00000000..4d8d5af5 Binary files /dev/null and b/DerivedDataReview/Logs/Package/D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog differ diff --git a/DerivedDataReview/Logs/Package/LogStoreManifest.plist b/DerivedDataReview/Logs/Package/LogStoreManifest.plist new file mode 100644 index 00000000..35ceb865 --- /dev/null +++ b/DerivedDataReview/Logs/Package/LogStoreManifest.plist @@ -0,0 +1,47 @@ + + + + + logFormatVersion + 13 + logs + + D50ED115-45BF-4DD0-BB7E-A46C31531159 + + className + IDECommandLineBuildLog + documentTypeString + <nil> + domainType + Xcode.IDEActivityLogDomainType.BuildLog + fileName + D50ED115-45BF-4DD0-BB7E-A46C31531159.xcactivitylog + hasPrimaryLog + + primaryObservable + + highLevelStatus + E + totalNumberOfAnalyzerIssues + 0 + totalNumberOfErrors + 2 + totalNumberOfTestFailures + 0 + totalNumberOfWarnings + 0 + + signature + Resolve Packages + timeStartedRecording + 807236581.56917596 + timeStoppedRecording + 807236581.71271205 + title + Resolve Packages + uniqueIdentifier + D50ED115-45BF-4DD0-BB7E-A46C31531159 + + + + diff --git a/Yattee/Core/AppEnvironment.swift b/Yattee/Core/AppEnvironment.swift index d5f0675c..494b477c 100644 --- a/Yattee/Core/AppEnvironment.swift +++ b/Yattee/Core/AppEnvironment.swift @@ -629,8 +629,13 @@ final class AppEnvironment { // logins on the same instance collapse onto the same one; and not on // the stored context, because that does not move when a credential // arrives or disappears and the app falls back to local and back again. + // Never while the identity is unresolved. With iCloud Drive off the + // scope drops to unresolved on every activation and comes back + // unchanged; the context reads as local in between, and treating that + // as a move would throw the caches away several times a day. "Cannot + // tell yet" is not grounds for anything destructive. let effective = dataContextValidator.effectiveContext - if effective != lastCachedContext { + if effective != lastCachedContext, credentialIdentityScope.isResolved { lastCachedContext = effective clearAccountScopedCaches() } diff --git a/Yattee/Services/SubscriptionFeedCache.swift b/Yattee/Services/SubscriptionFeedCache.swift index 1a206638..510ceff4 100644 --- a/Yattee/Services/SubscriptionFeedCache.swift +++ b/Yattee/Services/SubscriptionFeedCache.swift @@ -433,8 +433,8 @@ final class SubscriptionFeedCache { feedLoadState = .ready } catch { LoggingService.shared.error("Failed to fetch Piped feed: \(error.localizedDescription)", category: .general) - recordOutcome(error, backend: .piped, using: appEnvironment) guard isCurrentAccountsResult(generation) else { return } + recordOutcome(error, backend: .piped, using: appEnvironment) feedLoadState = .error(.networkError(error.localizedDescription)) } @@ -508,8 +508,8 @@ final class SubscriptionFeedCache { "Failed to fetch Invidious feed: \(error.localizedDescription)", category: .general ) - recordOutcome(error, backend: .invidious, using: appEnvironment) guard isCurrentAccountsResult(generation) else { return } + recordOutcome(error, backend: .invidious, using: appEnvironment) feedLoadState = .error(.networkError(error.localizedDescription)) } diff --git a/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift b/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift index 95823446..e02a03dc 100644 --- a/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift +++ b/Yattee/Services/Subscriptions/SubscriptionAccountValidator.swift @@ -74,13 +74,11 @@ final class SubscriptionAccountValidator { var availableAccounts: [SubscriptionAccount] { var accounts: [SubscriptionAccount] = [] - // Add local if Yattee Server is configured and enabled - let hasYatteeServer = instancesManager.instances.contains { - $0.type == .yatteeServer && $0.isEnabled - } - if hasYatteeServer { - accounts.append(.local) - } + // Local is always offered. Its *feed* needs a Yattee Server and says so + // itself when there is none; that is not a reason to make the user's + // own data unselectable — and since correction no longer moves away + // from local, leaving it out would make it unreachable once left. + accounts.append(.local) // Add logged-in and enabled Invidious instances for instance in instancesManager.instances { diff --git a/Yattee/Views/Instances/InstanceBrowseView.swift b/Yattee/Views/Instances/InstanceBrowseView.swift index cdaf7f56..1349c86d 100644 --- a/Yattee/Views/Instances/InstanceBrowseView.swift +++ b/Yattee/Views/Instances/InstanceBrowseView.swift @@ -447,6 +447,14 @@ struct InstanceBrowseView: View { .onReceive(NotificationCenter.default.publisher(for: .accountsDidChange)) { _ in refreshLoginState() } + // Selecting the other login on this instance changes whose feed and + // playlists these are. Nothing else here would notice: the instance, + // the tab and the "already has data" shortcut are all unchanged. + .onReceive(NotificationCenter.default.publisher(for: .dataContextDidChange)) { _ in + discardAccountScopedContent() + refreshLoginState() + Task { await startContentLoad() } + } .onChange(of: selectedTab) { _, _ in isLoading = true errorMessage = nil @@ -1179,6 +1187,15 @@ struct InstanceBrowseView: View { // MARK: - Data Loading + /// Drops what belonged to the previously selected login. + /// + /// Public browsing — popular and trending — is unauthenticated and stays. + private func discardAccountScopedContent() { + feedVideos = [] + feedSubscriptions = [] + userPlaylists = [] + } + private func refreshLoginState() { guard instance.supportsAuthentication else { return } isLoggedIn = appEnvironment?.isSignedIn(to: instance) ?? false diff --git a/Yattee/Views/Subscriptions/SubscriptionsView.swift b/Yattee/Views/Subscriptions/SubscriptionsView.swift index c6e1dc72..194b0a25 100644 --- a/Yattee/Views/Subscriptions/SubscriptionsView.swift +++ b/Yattee/Views/Subscriptions/SubscriptionsView.swift @@ -518,9 +518,14 @@ struct SubscriptionsView: View { .onReceive(NotificationCenter.default.publisher(for: .dataContextDidChange)) { _ in reloadForContextChange() } + // Instances count for the same reason: switching one off + // moves reads to local without touching the stored context + // or any account status. .onReceive(NotificationCenter.default.publisher(for: .accountStatusesDidChange)) { _ in - guard appEnvironment?.dataContextValidator.effectiveContext != loadedContext else { return } - reloadForContextChange() + reloadIfEffectiveContextMoved() + } + .onReceive(NotificationCenter.default.publisher(for: .instancesDidChange)) { _ in + reloadIfEffectiveContextMoved() } .task(id: instanceConfigurationID) { LoggingService.shared.debug("SubscriptionsView task triggered, instanceConfigurationID: \(instanceConfigurationID)", category: .general) @@ -1338,6 +1343,11 @@ struct SubscriptionsView: View { /// Loads subscriptions asynchronously from the current provider. /// For Invidious, this fetches from the API and creates temporary Subscription objects for UI. + private func reloadIfEffectiveContextMoved() { + guard appEnvironment?.dataContextValidator.effectiveContext != loadedContext else { return } + reloadForContextChange() + } + /// Drops what is on screen and reloads it for whatever context is current. private func reloadForContextChange() { loadedContext = appEnvironment?.dataContextValidator.effectiveContext