diff --git a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift index 3f7c749..0b92382 100644 --- a/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/ActiveDashboardView.swift @@ -61,20 +61,53 @@ import SwiftUI #if os(iOS) || os(tvOS) .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 }.sheet(isPresented: $showConnections) { connectionsSheetContent }.sheet(isPresented: $showCardManagement, onDismiss: { cardConfigurationVersion += 1 }, content: { - if #available(iOS 16.0, tvOS 17.0, *) { + if #available(iOS 16.0, *) { CardManagementSheet().presentationDetents([.large]).presentationDragIndicator(.visible) } else { CardManagementSheet() } }) #endif + #endif .onAppear { if ApplicationLibrary.inPreview { environments.commandClient.connect() diff --git a/ApplicationLibrary/Views/Dashboard/Cards/CardManagementSheet.swift b/ApplicationLibrary/Views/Dashboard/Cards/CardManagementSheet.swift index 7fb7eef..359fb1c 100644 --- a/ApplicationLibrary/Views/Dashboard/Cards/CardManagementSheet.swift +++ b/ApplicationLibrary/Views/Dashboard/Cards/CardManagementSheet.swift @@ -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 { let card: DashboardCard let isEnabled: Bool diff --git a/ApplicationLibrary/Views/Dashboard/Cards/ProfileCard.swift b/ApplicationLibrary/Views/Dashboard/Cards/ProfileCard.swift index 43a5fa0..eb100da 100644 --- a/ApplicationLibrary/Views/Dashboard/Cards/ProfileCard.swift +++ b/ApplicationLibrary/Views/Dashboard/Cards/ProfileCard.swift @@ -32,6 +32,44 @@ public struct ProfileCard: View { } } .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: { environments.profileUpdate.send() }, content: { @@ -44,13 +82,14 @@ public struct ProfileCard: View { .sheet(item: $viewModel.profileToEdit) { profile in editProfileSheet(for: profile) } - #if os(iOS) || os(tvOS) + #if os(iOS) .sheet(isPresented: $viewModel.showQRCode) { if let profile = selectedProfile, let remoteURL = profile.remoteURL { QRCodeSheet(profileName: profile.name, remoteURL: remoteURL) } } #endif + #endif .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 extension ProfileCard { diff --git a/ApplicationLibrary/Views/Dashboard/DashboardView.swift b/ApplicationLibrary/Views/Dashboard/DashboardView.swift index f5171d9..230646f 100644 --- a/ApplicationLibrary/Views/Dashboard/DashboardView.swift +++ b/ApplicationLibrary/Views/Dashboard/DashboardView.swift @@ -35,9 +35,24 @@ public struct DashboardView: View { .onChangeCompat(of: importRemoteProfile.wrappedValue) { _ in 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 importRemoteProfileSheet(for: request) } + #endif #if os(macOS) .onChangeCompat(of: controlActiveState) { state in guard state != .inactive, Variant.useSystemExtension, !coordinator.isLoading else { return }