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 {
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<Bool> {
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<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 {
CardView(profile: profile)
Spacer()
StatusView(isIncluded: status != .excluded, status: status)
}
.foregroundStyle(status.style)
}
.foregroundStyle(statuses[profile.id].style)
}
}

View File

@ -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()
}
}
}

View File

@ -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)

View File

@ -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 {
}