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