From d742fec41a8e6979929711cf78f821e72e2a6f3d Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 17 Sep 2026 23:59:32 +0200 Subject: [PATCH] Replace the instance browse view options button with a three-dots menu The toolbar (and tvOS inline) button now opens a menu with Edit and Remove actions for the browsed instance above the existing View Options entry. Edit opens the shared EditSourceView; Remove confirms with the same account-deletion disclosure as Settings and pops the view. --- Yattee/Localizable.xcstrings | 22 ++++++ .../Views/Instances/InstanceBrowseView.swift | 71 +++++++++++++++++-- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 5c0daed2..c6e2194c 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -5547,6 +5547,17 @@ }, "Initializing player..." : { + }, + "instance.browse.edit %@" : { + "comment" : "Menu item to edit the browsed instance, with its name", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Edit %@" + } + } + } }, "instance.browse.noVideos" : { "localizations" : { @@ -5558,6 +5569,17 @@ } } }, + "instance.browse.remove %@" : { + "comment" : "Menu item to remove the browsed instance, with its name", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove %@" + } + } + } + }, "instance.browse.search.placeholder" : { "localizations" : { "en" : { diff --git a/Yattee/Views/Instances/InstanceBrowseView.swift b/Yattee/Views/Instances/InstanceBrowseView.swift index aec80a97..d41d013b 100644 --- a/Yattee/Views/Instances/InstanceBrowseView.swift +++ b/Yattee/Views/Instances/InstanceBrowseView.swift @@ -9,6 +9,7 @@ import SwiftUI struct InstanceBrowseView: View { @Environment(\.appEnvironment) private var appEnvironment + @Environment(\.dismiss) private var dismiss let instance: Instance let initialTab: BrowseTab? @@ -51,6 +52,8 @@ struct InstanceBrowseView: View { // View options UI state @State private var showViewOptions = false + @State private var showEditInstance = false + @State private var showRemoveConfirmation = false @State private var viewWidth: CGFloat = 0 @State private var watchEntriesMap: [String: WatchEntry] = [:] @State private var hasInitializedTab = false @@ -126,6 +129,31 @@ struct InstanceBrowseView: View { ) } + /// Shared content of the three-dots menu: instance management on top, + /// view options below. + @ViewBuilder + private var instanceMenuContent: some View { + Button { + showEditInstance = true + } label: { + Label(String(localized: "instance.browse.edit \(instance.displayName)"), systemImage: "pencil") + } + + Button(role: .destructive) { + showRemoveConfirmation = true + } label: { + Label(String(localized: "instance.browse.remove \(instance.displayName)"), systemImage: "trash") + } + + Divider() + + Button { + showViewOptions = true + } label: { + Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") + } + } + /// View options button lives on the leading edge on macOS, trailing elsewhere. private var viewOptionsPlacement: ToolbarItemPlacement { #if os(macOS) @@ -165,10 +193,10 @@ struct InstanceBrowseView: View { tvOSFiltersMenu } - Button { - showViewOptions = true + Menu { + instanceMenuContent } label: { - Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") + Label(String(localized: "common.more"), systemImage: "ellipsis") } } .focusSection() @@ -345,12 +373,12 @@ struct InstanceBrowseView: View { .navigationTitle(instance.displayName) .toolbarTitleDisplayMode(.inlineLarge) .toolbar { - // View options first so it is the leftmost navigation item on macOS. + // Instance menu first so it is the leftmost navigation item on macOS. ToolbarItem(placement: viewOptionsPlacement) { - Button { - showViewOptions = true + Menu { + instanceMenuContent } label: { - Label(String(localized: "viewOptions.title"), systemImage: "slider.horizontal.3") + Label(String(localized: "common.more"), systemImage: "ellipsis") } .liquidGlassTransitionSource(id: "instanceBrowseViewOptions", in: sheetTransition) #if os(macOS) @@ -409,6 +437,35 @@ struct InstanceBrowseView: View { .liquidGlassSheetContent(sourceID: "instanceBrowseViewOptions", in: sheetTransition) } #endif + #if os(tvOS) + .fullScreenCover(isPresented: $showEditInstance) { + TVSidebarDetailContainer(systemImage: "pencil.circle", title: String(localized: "sources.editSource")) { + EditSourceView(source: .remoteServer(instance)) + } + .background(Color.black.ignoresSafeArea()) + } + #else + .sheet(isPresented: $showEditInstance) { + EditSourceView(source: .remoteServer(instance)) + } + #endif + .confirmationDialog( + String(localized: "sources.delete.confirmation.single \(instance.displayName)"), + isPresented: $showRemoveConfirmation, + titleVisibility: .visible + ) { + Button(String(localized: "common.delete"), role: .destructive) { + appEnvironment?.removeInstance(instance) + dismiss() + } + Button(String(localized: "common.cancel"), role: .cancel) {} + } message: { + // The same cascade runs from here as from Settings, so the same + // logins go with it. + if let accounts = InstanceDeletionDisclosure.message(for: instance, in: appEnvironment) { + Text(accounts) + } + } .task { // Initialize search view model if let appEnvironment {