From 09e894dd60190ff7da7264f415cee7416d9574e1 Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 15 Nov 2024 01:47:52 +0100 Subject: [PATCH] Add explicit environment objects to TableColumn (#873) For some reason, Table doesn't seem to inherit the environment in some cases. Reapply environment to each TableColumn (only Theme is required). Work around what clearly seems to be a SwiftUI bug. Fixes #872 --- .../Views/Migration/MigrateView+Section.swift | 87 ++++++++++++++----- .../Views/Migration/MigrateView+Table.swift | 46 +++++++--- .../macOS/VPNProviderServerView+macOS.swift | 7 ++ Passepartout/LoginItem/AppDelegate.swift | 4 +- 4 files changed, 109 insertions(+), 35 deletions(-) diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Section.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Section.swift index fbf98fc5..f5b501ef 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Section.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Section.swift @@ -38,13 +38,12 @@ extension MigrateView { var body: some View { Section { ForEach(profiles, id: \.id) { - switch step { - case .initial, .fetching, .fetched: - button(forProfile: $0) - - default: - row(forProfile: $0, status: statuses[$0.id]) - } + ControlView( + step: step, + profile: $0, + isIncluded: isIncludedBinding(for: $0.id), + status: statusBinding(for: $0.id) + ) } } } @@ -52,25 +51,73 @@ extension MigrateView { } private extension MigrateView.SectionView { - func button(forProfile profile: MigratableProfile) -> some View { - Button { - if statuses[profile.id] == .excluded { - statuses.removeValue(forKey: profile.id) + func isIncludedBinding(for profileId: UUID) -> Binding { + Binding { + statuses[profileId] != .excluded + } set: { + if $0 { + statuses.removeValue(forKey: profileId) } else { - statuses[profile.id] = .excluded + statuses[profileId] = .excluded } - } label: { - row(forProfile: profile, status: nil) } } - func row(forProfile profile: MigratableProfile, status: MigrationStatus?) -> some View { - HStack { - CardView(profile: profile) - Spacer() - StatusView(isIncluded: statuses[profile.id] != .excluded, status: status) + func statusBinding(for profileId: UUID) -> Binding { + Binding { + statuses[profileId] + } set: { + if let newValue = $0 { + statuses[profileId] = newValue + } else { + statuses.removeValue(forKey: profileId) + } + } + } +} + +private extension MigrateView.SectionView { + struct ControlView: View { + let step: MigrateView.Model.Step + + let profile: MigratableProfile + + @Binding + var isIncluded: Bool + + @Binding + var status: MigrationStatus? + + var body: some View { + switch step { + case .initial, .fetching, .fetched: + buttonView + + default: + rowView + } + } + + var buttonView: some View { + Button { + if status == .excluded { + status = nil + } else { + status = .excluded + } + } label: { + rowView + } + } + + var rowView: some View { + HStack { + CardView(profile: profile) + Spacer() + StatusView(isIncluded: status != .excluded, status: status) + } + .foregroundStyle(status.style) } - .foregroundStyle(statuses[profile.id].style) } } diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Table.swift b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Table.swift index 48ca730e..cd7a9a47 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Table.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Migration/MigrateView+Table.swift @@ -28,6 +28,10 @@ import SwiftUI extension MigrateView { struct TableView: View { + + @EnvironmentObject + private var theme: Theme + let step: Model.Step let profiles: [MigratableProfile] @@ -45,17 +49,13 @@ extension MigrateView { Text($0.timestamp) .foregroundStyle(statuses.style(for: $0.id)) } - TableColumn("") { profile in - switch step { - case .initial, .fetching, .fetched: - Toggle("", isOn: isIncludedBinding(for: profile.id)) - .labelsHidden() - - default: - if let status = statuses[profile.id] { - StatusView(status: status) - } - } + TableColumn("") { + ControlView( + step: step, + isIncluded: isIncludedBinding(for: $0.id), + status: statuses[$0.id] + ) + .environmentObject(theme) // TODO: #873, Table loses environment } } } @@ -77,10 +77,27 @@ private extension MigrateView.TableView { } private extension MigrateView.TableView { - struct StatusView: View { - let status: MigrationStatus + struct ControlView: View { + let step: MigrateView.Model.Step + + @Binding + var isIncluded: Bool + + let status: MigrationStatus? var body: some View { + switch step { + case .initial, .fetching, .fetched: + Toggle("", isOn: $isIncluded) + .labelsHidden() + + default: + statusView + } + } + + @ViewBuilder + var statusView: some View { switch status { case .excluded: Text("--") @@ -93,6 +110,9 @@ private extension MigrateView.TableView { case .failed: ThemeImage(.failure) + + case .none: + EmptyView() } } } diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Provider/macOS/VPNProviderServerView+macOS.swift b/Passepartout/Library/Sources/AppUIMain/Views/Provider/macOS/VPNProviderServerView+macOS.swift index 2cd5e917..10373e36 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Provider/macOS/VPNProviderServerView+macOS.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Provider/macOS/VPNProviderServerView+macOS.swift @@ -44,6 +44,10 @@ extension VPNProviderServerView { extension VPNProviderServerView { struct ServersSubview: View { + + @EnvironmentObject + private var theme: Theme + let servers: [VPNServer] let selectedServer: VPNServer? @@ -69,12 +73,14 @@ extension VPNProviderServerView { TableColumn("") { server in ThemeImage(.marked) .opaque(server.id == selectedServer?.id) + .environmentObject(theme) // TODO: #873, Table loses environment } .width(10.0) TableColumn(Strings.Global.region) { server in ThemeCountryText(server.provider.countryCode, title: server.region) .help(server.region) + .environmentObject(theme) // TODO: #873, Table loses environment } TableColumn(Strings.Global.address, value: \.address) @@ -84,6 +90,7 @@ extension VPNProviderServerView { value: server.serverId, selection: $favoritesManager.serverIds ) + .environmentObject(theme) // TODO: #873, Table loses environment } .width(15.0) diff --git a/Passepartout/LoginItem/AppDelegate.swift b/Passepartout/LoginItem/AppDelegate.swift index 355b9da0..979eedc0 100644 --- a/Passepartout/LoginItem/AppDelegate.swift +++ b/Passepartout/LoginItem/AppDelegate.swift @@ -85,11 +85,11 @@ private extension AppDelegate { // MARK: - Preconcurrency warnings -extension NSWorkspace: @unchecked Sendable { +extension NSWorkspace: @retroactive @unchecked Sendable { } extension NSRunningApplication: @unchecked Sendable { } -extension NSWorkspace.OpenConfiguration: @unchecked Sendable { +extension NSWorkspace.OpenConfiguration: @retroactive @unchecked Sendable { }