Replace sheets with navigation destinations on tvOS

This commit is contained in:
世界
2025-12-02 13:53:07 +08:00
parent b3714ed957
commit e9d6b380bf
4 changed files with 174 additions and 3 deletions
@@ -61,20 +61,53 @@ import SwiftUI
#if os(iOS) || os(tvOS) #if os(iOS) || os(tvOS)
.toolbar { .toolbar {
toolbar toolbar
}.sheet(isPresented: $showGroups) { }
#if os(tvOS)
.navigationDestination(isPresented: $showGroups) {
GroupListView()
.navigationTitle("Groups")
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
.navigationDestination(isPresented: $showConnections) {
ConnectionListView()
.navigationTitle("Connections")
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
.navigationDestination(isPresented: $showCardManagement) {
CardManagementView(onDisappear: {
cardConfigurationVersion += 1
})
.navigationTitle("Dashboard Items")
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
#else
.sheet(isPresented: $showGroups) {
groupsSheetContent groupsSheetContent
}.sheet(isPresented: $showConnections) { }.sheet(isPresented: $showConnections) {
connectionsSheetContent connectionsSheetContent
}.sheet(isPresented: $showCardManagement, onDismiss: { }.sheet(isPresented: $showCardManagement, onDismiss: {
cardConfigurationVersion += 1 cardConfigurationVersion += 1
}, content: { }, content: {
if #available(iOS 16.0, tvOS 17.0, *) { if #available(iOS 16.0, *) {
CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible) CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible)
} else { } else {
CardManagementSheet() CardManagementSheet()
} }
}) })
#endif #endif
#endif
.onAppear { .onAppear {
if ApplicationLibrary.inPreview { if ApplicationLibrary.inPreview {
environments.commandClient.connect() environments.commandClient.connect()
@@ -97,6 +97,55 @@ import SwiftUI
} }
} }
#if os(tvOS)
@MainActor public struct CardManagementView: View {
@StateObject private var configuration = DashboardCardConfiguration()
private let onDisappear: (() -> Void)?
public init(onDisappear: (() -> Void)? = nil) {
self.onDisappear = onDisappear
}
public var body: some View {
Group {
if configuration.isLoading {
ProgressView()
} else {
List {
ForEach(configuration.cardOrder) { card in
CardRow(
card: card,
isEnabled: configuration.isEnabled(card),
onToggle: {
configuration.toggleCard(card)
}
)
}
.onMove { source, destination in
Task {
await configuration.moveCard(from: source, to: destination)
}
}
}
.applyContentMargins()
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Reset", role: .destructive) {
Task {
await configuration.resetToDefault()
}
}
}
}
.onDisappear {
onDisappear?()
}
}
}
#endif
private struct CardRow: View { private struct CardRow: View {
let card: DashboardCard let card: DashboardCard
let isEnabled: Bool let isEnabled: Bool
@@ -32,6 +32,44 @@ public struct ProfileCard: View {
} }
} }
.disabled(viewModel.isUpdating) .disabled(viewModel.isUpdating)
#if os(tvOS)
.navigationDestination(isPresented: $viewModel.showNewProfile) {
NewProfileContentView(onDisappear: {
environments.profileUpdate.send()
})
.environmentObject(environments)
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
.navigationDestination(isPresented: $viewModel.showManageProfiles) {
ManageProfilesView()
.environmentObject(environments)
.navigationTitle("Manage profiles")
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
.navigationDestination(item: $viewModel.profileToEdit) { profile in
EditProfileView()
.environmentObject(profile)
.environmentObject(environments)
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
.sheet(isPresented: $viewModel.showQRCode) {
if let profile = selectedProfile, let remoteURL = profile.remoteURL {
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
}
}
#else
.sheet(isPresented: $viewModel.showNewProfile, onDismiss: { .sheet(isPresented: $viewModel.showNewProfile, onDismiss: {
environments.profileUpdate.send() environments.profileUpdate.send()
}, content: { }, content: {
@@ -44,13 +82,14 @@ public struct ProfileCard: View {
.sheet(item: $viewModel.profileToEdit) { profile in .sheet(item: $viewModel.profileToEdit) { profile in
editProfileSheet(for: profile) editProfileSheet(for: profile)
} }
#if os(iOS) || os(tvOS) #if os(iOS)
.sheet(isPresented: $viewModel.showQRCode) { .sheet(isPresented: $viewModel.showQRCode) {
if let profile = selectedProfile, let remoteURL = profile.remoteURL { if let profile = selectedProfile, let remoteURL = profile.remoteURL {
QRCodeSheet(profileName: profile.name, remoteURL: remoteURL) QRCodeSheet(profileName: profile.name, remoteURL: remoteURL)
} }
} }
#endif #endif
#endif
.alert($viewModel.alert) .alert($viewModel.alert)
} }
@@ -326,6 +365,41 @@ extension ProfileCard {
} }
} }
// MARK: - NewProfileContentView (tvOS)
#if os(tvOS)
extension ProfileCard {
@MainActor
struct NewProfileContentView: View {
@EnvironmentObject private var environments: ExtensionEnvironments
@State private var createdProfile: Profile?
private let onDisappear: (() -> Void)?
init(onDisappear: (() -> Void)? = nil) {
self.onDisappear = onDisappear
}
var body: some View {
Group {
if let profile = createdProfile {
EditProfileView()
.environmentObject(profile)
.environmentObject(environments)
} else {
NewProfileView { profile in
createdProfile = profile
}
.environmentObject(environments)
}
}
.onDisappear {
onDisappear?()
}
}
}
}
#endif
// MARK: - ManageProfilesView // MARK: - ManageProfilesView
extension ProfileCard { extension ProfileCard {
@@ -35,9 +35,24 @@ public struct DashboardView: View {
.onChangeCompat(of: importRemoteProfile.wrappedValue) { _ in .onChangeCompat(of: importRemoteProfile.wrappedValue) { _ in
handleImportRemoteProfile() handleImportRemoteProfile()
} }
#if os(tvOS)
.navigationDestination(item: $importRemoteProfileRequest) { request in
NewProfileView(request)
.environmentObject(environments)
.onDisappear {
environments.profileUpdate.send()
}
.toolbar {
ToolbarItemGroup(placement: .topBarLeading) {
BackButton()
}
}
}
#else
.sheet(item: $importRemoteProfileRequest) { request in .sheet(item: $importRemoteProfileRequest) { request in
importRemoteProfileSheet(for: request) importRemoteProfileSheet(for: request)
} }
#endif
#if os(macOS) #if os(macOS)
.onChangeCompat(of: controlActiveState) { state in .onChangeCompat(of: controlActiveState) { state in
guard state != .inactive, Variant.useSystemExtension, !coordinator.isLoading else { return } guard state != .inactive, Variant.useSystemExtension, !coordinator.isLoading else { return }