Fix layout jump when subscriptions sidebar appears after load on macOS

Load local-account subscriptions synchronously on appear so the channels
sidebar is present in the first layout pass, and reserve the sidebar
column with a spinner while async loads are in flight (gated by a
persisted flag so users without enough channels never see a phantom
column).
This commit is contained in:
Arkadiusz Fal
2026-07-04 17:04:09 +02:00
parent ecb6a9dd7a
commit f5b86effd3

View File

@@ -38,6 +38,9 @@ struct SubscriptionsView: View {
@AppStorage("subscriptionsHideWatched") private var hideWatched = false
@AppStorage("subscriptionsChannelStripSize") private var channelStripSize: ChannelStripSize = .normal
@AppStorage("subscriptionsShowSidebar") private var showSidebar = true
/// Whether the last completed load produced enough channels for the sidebar,
/// so its space can be reserved before the next load finishes.
@AppStorage("subscriptionsSidebarExpected") private var sidebarExpected = false
#if os(macOS)
@AppStorage("subscriptionsMacOSSidebarWidth") private var macOSSidebarWidth = 240.0
@State private var macOSSidebarDragStartWidth: Double?
@@ -364,6 +367,11 @@ struct SubscriptionsView: View {
macOSChannelsSidebar
.frame(width: clampedMacOSSidebarWidth)
macOSSidebarResizeHandle
} else if showSidebar && !subscriptionsLoaded && sidebarExpected {
macOSSidebarPlaceholder
.frame(width: clampedMacOSSidebarWidth)
macOSSidebarResizeHandle
}
@@ -476,6 +484,11 @@ struct SubscriptionsView: View {
.liquidGlassSheetContent(sourceID: "subscriptionsViewOptions", in: sheetTransition)
}
#endif
.onAppear {
// Synchronous load for local accounts so the sidebar is
// present in the first layout pass (avoids layout jump).
loadSubscriptions()
}
.task {
await loadSubscriptionsAsync()
loadWatchEntries()
@@ -934,6 +947,12 @@ struct SubscriptionsView: View {
.scrollContentBackground(.hidden)
}
private var macOSSidebarPlaceholder: some View {
ProgressView()
.controlSize(.small)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
private var macOSSidebarResizeHandle: some View {
ZStack {
Rectangle()
@@ -1299,6 +1318,7 @@ struct SubscriptionsView: View {
if appEnvironment?.settingsManager.subscriptionAccount.type == .local {
subscriptions = dataManager?.subscriptions() ?? []
subscriptionsLoaded = true
sidebarExpected = subscriptions.count > 1
}
if let selectedID = selectedChannelID,
@@ -1316,6 +1336,7 @@ struct SubscriptionsView: View {
if appEnvironment.settingsManager.subscriptionAccount.type == .local {
subscriptions = dataManager?.subscriptions() ?? []
subscriptionsLoaded = true
sidebarExpected = subscriptions.count > 1
return
}
@@ -1325,6 +1346,7 @@ struct SubscriptionsView: View {
// Convert channels to Subscription objects for UI (not persisted)
subscriptions = channels.map { Subscription.from(channel: $0) }
subscriptionsLoaded = true
sidebarExpected = subscriptions.count > 1
} catch {
LoggingService.shared.error(
"Failed to load subscriptions: \(error.localizedDescription)",
@@ -1332,6 +1354,7 @@ struct SubscriptionsView: View {
)
subscriptions = []
subscriptionsLoaded = true
sidebarExpected = false
}
}