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:
parent
962ffdf678
commit
09e894dd60
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue