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
This commit is contained in:
Davide 2024-11-15 01:47:52 +01:00 committed by GitHub
parent 962ffdf678
commit 09e894dd60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 109 additions and 35 deletions

View File

@ -38,13 +38,12 @@ extension MigrateView {
var body: some View { var body: some View {
Section { Section {
ForEach(profiles, id: \.id) { ForEach(profiles, id: \.id) {
switch step { ControlView(
case .initial, .fetching, .fetched: step: step,
button(forProfile: $0) profile: $0,
isIncluded: isIncludedBinding(for: $0.id),
default: status: statusBinding(for: $0.id)
row(forProfile: $0, status: statuses[$0.id]) )
}
} }
} }
} }
@ -52,25 +51,73 @@ extension MigrateView {
} }
private extension MigrateView.SectionView { private extension MigrateView.SectionView {
func button(forProfile profile: MigratableProfile) -> some View { func isIncludedBinding(for profileId: UUID) -> Binding<Bool> {
Button { Binding {
if statuses[profile.id] == .excluded { statuses[profileId] != .excluded
statuses.removeValue(forKey: profile.id) } set: {
if $0 {
statuses.removeValue(forKey: profileId)
} else { } else {
statuses[profile.id] = .excluded statuses[profileId] = .excluded
} }
} label: {
row(forProfile: profile, status: nil)
} }
} }
func row(forProfile profile: MigratableProfile, status: MigrationStatus?) -> some View { func statusBinding(for profileId: UUID) -> Binding<MigrationStatus?> {
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 { HStack {
CardView(profile: profile) CardView(profile: profile)
Spacer() Spacer()
StatusView(isIncluded: statuses[profile.id] != .excluded, status: status) StatusView(isIncluded: status != .excluded, status: status)
}
.foregroundStyle(status.style)
} }
.foregroundStyle(statuses[profile.id].style)
} }
} }

View File

@ -28,6 +28,10 @@ import SwiftUI
extension MigrateView { extension MigrateView {
struct TableView: View { struct TableView: View {
@EnvironmentObject
private var theme: Theme
let step: Model.Step let step: Model.Step
let profiles: [MigratableProfile] let profiles: [MigratableProfile]
@ -45,17 +49,13 @@ extension MigrateView {
Text($0.timestamp) Text($0.timestamp)
.foregroundStyle(statuses.style(for: $0.id)) .foregroundStyle(statuses.style(for: $0.id))
} }
TableColumn("") { profile in TableColumn("") {
switch step { ControlView(
case .initial, .fetching, .fetched: step: step,
Toggle("", isOn: isIncludedBinding(for: profile.id)) isIncluded: isIncludedBinding(for: $0.id),
.labelsHidden() status: statuses[$0.id]
)
default: .environmentObject(theme) // TODO: #873, Table loses environment
if let status = statuses[profile.id] {
StatusView(status: status)
}
}
} }
} }
} }
@ -77,10 +77,27 @@ private extension MigrateView.TableView {
} }
private extension MigrateView.TableView { private extension MigrateView.TableView {
struct StatusView: View { struct ControlView: View {
let status: MigrationStatus let step: MigrateView.Model.Step
@Binding
var isIncluded: Bool
let status: MigrationStatus?
var body: some View { var body: some View {
switch step {
case .initial, .fetching, .fetched:
Toggle("", isOn: $isIncluded)
.labelsHidden()
default:
statusView
}
}
@ViewBuilder
var statusView: some View {
switch status { switch status {
case .excluded: case .excluded:
Text("--") Text("--")
@ -93,6 +110,9 @@ private extension MigrateView.TableView {
case .failed: case .failed:
ThemeImage(.failure) ThemeImage(.failure)
case .none:
EmptyView()
} }
} }
} }

View File

@ -44,6 +44,10 @@ extension VPNProviderServerView {
extension VPNProviderServerView { extension VPNProviderServerView {
struct ServersSubview: View { struct ServersSubview: View {
@EnvironmentObject
private var theme: Theme
let servers: [VPNServer] let servers: [VPNServer]
let selectedServer: VPNServer? let selectedServer: VPNServer?
@ -69,12 +73,14 @@ extension VPNProviderServerView {
TableColumn("") { server in TableColumn("") { server in
ThemeImage(.marked) ThemeImage(.marked)
.opaque(server.id == selectedServer?.id) .opaque(server.id == selectedServer?.id)
.environmentObject(theme) // TODO: #873, Table loses environment
} }
.width(10.0) .width(10.0)
TableColumn(Strings.Global.region) { server in TableColumn(Strings.Global.region) { server in
ThemeCountryText(server.provider.countryCode, title: server.region) ThemeCountryText(server.provider.countryCode, title: server.region)
.help(server.region) .help(server.region)
.environmentObject(theme) // TODO: #873, Table loses environment
} }
TableColumn(Strings.Global.address, value: \.address) TableColumn(Strings.Global.address, value: \.address)
@ -84,6 +90,7 @@ extension VPNProviderServerView {
value: server.serverId, value: server.serverId,
selection: $favoritesManager.serverIds selection: $favoritesManager.serverIds
) )
.environmentObject(theme) // TODO: #873, Table loses environment
} }
.width(15.0) .width(15.0)

View File

@ -85,11 +85,11 @@ private extension AppDelegate {
// MARK: - Preconcurrency warnings // MARK: - Preconcurrency warnings
extension NSWorkspace: @unchecked Sendable { extension NSWorkspace: @retroactive @unchecked Sendable {
} }
extension NSRunningApplication: @unchecked Sendable { extension NSRunningApplication: @unchecked Sendable {
} }
extension NSWorkspace.OpenConfiguration: @unchecked Sendable { extension NSWorkspace.OpenConfiguration: @retroactive @unchecked Sendable {
} }