mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 22:32:01 +00:00
Present view options and search filters as popovers on macOS
Plain-Form sheets rendered as columns-style content floating inside oversized fixed frames on macOS. Anchor these panels to their toolbar buttons as popovers instead, sized to content; iOS/tvOS keep sheets. - Search and PeerTube explore filters apply live (Cancel never reverted anything anyway) and dismiss on outside click - Subscriptions/Manage Channels "Subscriptions Data" opens as a sheet from the popover's onDisappear via a pending flag, since presenting while the popover dismisses gets swallowed - Media browser view options drop navigation chrome on macOS
This commit is contained in:
@@ -26,6 +26,10 @@ struct SubscriptionsView: View {
|
||||
@State private var errorMessage: String?
|
||||
@State private var watchEntriesMap: [String: WatchEntry] = [:]
|
||||
@State private var showViewOptions = false
|
||||
#if os(macOS)
|
||||
@State private var showSubscriptionsData = false
|
||||
@State private var pendingSubscriptionsData = false
|
||||
#endif
|
||||
|
||||
// View options (persisted)
|
||||
@AppStorage("subscriptionsLayout") private var layout: VideoListLayout = .list
|
||||
@@ -53,6 +57,108 @@ struct SubscriptionsView: View {
|
||||
GridLayoutConfiguration(viewWidth: viewWidth, gridColumns: gridColumns)
|
||||
}
|
||||
|
||||
private var viewOptionsForm: some View {
|
||||
Form {
|
||||
Section {
|
||||
#if os(macOS)
|
||||
Toggle("viewOptions.showSidebar", isOn: $showSidebar)
|
||||
#elseif os(iOS)
|
||||
if isIPadRegular {
|
||||
Toggle("viewOptions.showSidebar", isOn: $showSidebar)
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
PlatformMenuPicker(String(localized: "viewOptions.showSidebar"), selection: $showSidebar) {
|
||||
Text("common.on").tag(true)
|
||||
Text("common.off").tag(false)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Layout picker (inline menu)
|
||||
PlatformMenuPicker(String(localized: "viewOptions.layout"), selection: $layout) {
|
||||
ForEach(VideoListLayout.allCases, id: \.self) { option in
|
||||
Text(option.displayName).tag(option)
|
||||
}
|
||||
}
|
||||
|
||||
// List-specific options
|
||||
if layout == .list {
|
||||
PlatformMenuPicker(String(localized: "viewOptions.rowSize"), selection: $rowStyle) {
|
||||
Text("viewOptions.rowSize.compact").tag(VideoRowStyle.compact)
|
||||
Text("viewOptions.rowSize.regular").tag(VideoRowStyle.regular)
|
||||
Text("viewOptions.rowSize.large").tag(VideoRowStyle.large)
|
||||
}
|
||||
}
|
||||
|
||||
// Grid-specific options
|
||||
if layout == .grid {
|
||||
#if os(tvOS)
|
||||
Picker("viewOptions.columns.header", selection: $gridColumns) {
|
||||
ForEach(GridConstants.minAllowedColumns...max(GridConstants.minAllowedColumns, gridConfig.maxColumns), id: \.self) { count in
|
||||
Text("\(count)").tag(count)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
#else
|
||||
Stepper(
|
||||
"viewOptions.columns \(min(max(GridConstants.minAllowedColumns, gridColumns), gridConfig.maxColumns))",
|
||||
value: $gridColumns,
|
||||
in: GridConstants.minAllowedColumns...max(GridConstants.minAllowedColumns, gridConfig.maxColumns)
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
PlatformMenuPicker(String(localized: "viewOptions.hideWatched"), selection: $hideWatched) {
|
||||
Text("common.on").tag(true)
|
||||
Text("common.off").tag(false)
|
||||
}
|
||||
#else
|
||||
Toggle("viewOptions.hideWatched", isOn: $hideWatched)
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
Picker("viewOptions.channelStrip", selection: $channelStripSize) {
|
||||
ForEach(ChannelStripSize.allCases, id: \.self) { size in
|
||||
Text(size.displayName).tag(size)
|
||||
}
|
||||
}
|
||||
.disabled(isIPadRegular && showSidebar)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
Section {
|
||||
Button {
|
||||
pendingSubscriptionsData = true
|
||||
showViewOptions = false
|
||||
} label: {
|
||||
Label(String(localized: "manageChannels.subscriptionsData"), systemImage: "person.2.badge.gearshape")
|
||||
}
|
||||
}
|
||||
#elseif os(iOS)
|
||||
Section {
|
||||
NavigationLink {
|
||||
SubscriptionsSettingsView()
|
||||
} label: {
|
||||
Label(String(localized: "manageChannels.subscriptionsData"), systemImage: "person.2.badge.gearshape")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private var subscriptionsDataSheet: some View {
|
||||
NavigationStack {
|
||||
SubscriptionsSettingsView()
|
||||
.toolbar {
|
||||
sheetCloseToolbarItem { showSubscriptionsData = false }
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 500, minHeight: 450)
|
||||
}
|
||||
#endif
|
||||
|
||||
private var isShowingFullScreenError: Bool {
|
||||
if case .error = feedCache.feedLoadState, feedCache.videos.isEmpty {
|
||||
return true
|
||||
@@ -319,89 +425,32 @@ struct SubscriptionsView: View {
|
||||
Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3")
|
||||
}
|
||||
.liquidGlassTransitionSource(id: "subscriptionsViewOptions", in: sheetTransition)
|
||||
#if os(macOS)
|
||||
.popover(isPresented: $showViewOptions, arrowEdge: .bottom) {
|
||||
viewOptionsForm
|
||||
.padding()
|
||||
.frame(width: 300)
|
||||
.onDisappear {
|
||||
// Present the sheet only after the popover is gone,
|
||||
// otherwise the presentation is swallowed.
|
||||
if pendingSubscriptionsData {
|
||||
pendingSubscriptionsData = false
|
||||
showSubscriptionsData = true
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.sheet(isPresented: $showSubscriptionsData) {
|
||||
subscriptionsDataSheet
|
||||
}
|
||||
#else
|
||||
.sheet(isPresented: $showViewOptions) {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
#if os(macOS)
|
||||
Toggle("viewOptions.showSidebar", isOn: $showSidebar)
|
||||
#elseif os(iOS)
|
||||
if isIPadRegular {
|
||||
Toggle("viewOptions.showSidebar", isOn: $showSidebar)
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
PlatformMenuPicker(String(localized: "viewOptions.showSidebar"), selection: $showSidebar) {
|
||||
Text("common.on").tag(true)
|
||||
Text("common.off").tag(false)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Layout picker (inline menu)
|
||||
PlatformMenuPicker(String(localized: "viewOptions.layout"), selection: $layout) {
|
||||
ForEach(VideoListLayout.allCases, id: \.self) { option in
|
||||
Text(option.displayName).tag(option)
|
||||
}
|
||||
}
|
||||
|
||||
// List-specific options
|
||||
if layout == .list {
|
||||
PlatformMenuPicker(String(localized: "viewOptions.rowSize"), selection: $rowStyle) {
|
||||
Text("viewOptions.rowSize.compact").tag(VideoRowStyle.compact)
|
||||
Text("viewOptions.rowSize.regular").tag(VideoRowStyle.regular)
|
||||
Text("viewOptions.rowSize.large").tag(VideoRowStyle.large)
|
||||
}
|
||||
}
|
||||
|
||||
// Grid-specific options
|
||||
if layout == .grid {
|
||||
#if os(tvOS)
|
||||
Picker("viewOptions.columns.header", selection: $gridColumns) {
|
||||
ForEach(GridConstants.minAllowedColumns...max(GridConstants.minAllowedColumns, gridConfig.maxColumns), id: \.self) { count in
|
||||
Text("\(count)").tag(count)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
#else
|
||||
Stepper(
|
||||
"viewOptions.columns \(min(max(GridConstants.minAllowedColumns, gridColumns), gridConfig.maxColumns))",
|
||||
value: $gridColumns,
|
||||
in: GridConstants.minAllowedColumns...max(GridConstants.minAllowedColumns, gridConfig.maxColumns)
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
PlatformMenuPicker(String(localized: "viewOptions.hideWatched"), selection: $hideWatched) {
|
||||
Text("common.on").tag(true)
|
||||
Text("common.off").tag(false)
|
||||
}
|
||||
#else
|
||||
Toggle("viewOptions.hideWatched", isOn: $hideWatched)
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
Picker("viewOptions.channelStrip", selection: $channelStripSize) {
|
||||
ForEach(ChannelStripSize.allCases, id: \.self) { size in
|
||||
Text(size.displayName).tag(size)
|
||||
}
|
||||
}
|
||||
.disabled(isIPadRegular && showSidebar)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
Section {
|
||||
NavigationLink {
|
||||
SubscriptionsSettingsView()
|
||||
} label: {
|
||||
Label(String(localized: "manageChannels.subscriptionsData"), systemImage: "person.2.badge.gearshape")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
viewOptionsForm
|
||||
#if os(tvOS)
|
||||
.scrollClipDisabled()
|
||||
.padding(.horizontal, 40)
|
||||
@@ -413,13 +462,11 @@ struct SubscriptionsView: View {
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 500, minHeight: 450)
|
||||
#endif
|
||||
.presentationDetents([.height(520), .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
.liquidGlassSheetContent(sourceID: "subscriptionsViewOptions", in: sheetTransition)
|
||||
}
|
||||
#endif
|
||||
.task {
|
||||
await loadSubscriptionsAsync()
|
||||
loadWatchEntries()
|
||||
|
||||
Reference in New Issue
Block a user