Rewrite ReloadingSection to be semantic only
Use Group instead of Section. Also fix elements not loading if initially empty.
This commit is contained in:
parent
f5c87d43c2
commit
017182fe81
|
@ -68,7 +68,7 @@
|
|||
0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; };
|
||||
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C232F27F47032007D5FC7 /* IntentsManager.swift */; };
|
||||
0E9C233327F47E95007D5FC7 /* IntentDispatcher+Activities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */; };
|
||||
0E9C3B6C27FB3A9C00D0F02E /* ReloadingSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C3B6B27FB3A9C00D0F02E /* ReloadingSection.swift */; };
|
||||
0E9C3B6C27FB3A9C00D0F02E /* ReloadingContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9C3B6B27FB3A9C00D0F02E /* ReloadingContent.swift */; };
|
||||
0E9C3B6F27FC573E00D0F02E /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */; };
|
||||
0E9E5AEF27B44CF1008C95DA /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0E9E5AE227B44CF1008C95DA /* Localizable.strings */; };
|
||||
0E9ED48127FD9BAE003B2316 /* CopySavingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9ED48027FD9BAE003B2316 /* CopySavingButton.swift */; };
|
||||
|
@ -250,7 +250,7 @@
|
|||
0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||
0E9C232F27F47032007D5FC7 /* IntentsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentsManager.swift; sourceTree = "<group>"; };
|
||||
0E9C233227F47E95007D5FC7 /* IntentDispatcher+Activities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IntentDispatcher+Activities.swift"; sourceTree = "<group>"; };
|
||||
0E9C3B6B27FB3A9C00D0F02E /* ReloadingSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadingSection.swift; sourceTree = "<group>"; };
|
||||
0E9C3B6B27FB3A9C00D0F02E /* ReloadingContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadingContent.swift; sourceTree = "<group>"; };
|
||||
0E9C3B6E27FC573E00D0F02E /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
|
||||
0E9E5AE327B44CF1008C95DA /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
0E9E5AE427B44CF1008C95DA /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -448,7 +448,7 @@
|
|||
0E5324A527D297BB002565C3 /* InApp.swift */,
|
||||
0EF0FAF827DD212C007EB181 /* IntentActivity.swift */,
|
||||
0E5324A827D2AC55002565C3 /* LongContentView.swift */,
|
||||
0E9C3B6B27FB3A9C00D0F02E /* ReloadingSection.swift */,
|
||||
0E9C3B6B27FB3A9C00D0F02E /* ReloadingContent.swift */,
|
||||
0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */,
|
||||
0E2C172A27CB63F9007E8488 /* Reviewer.swift */,
|
||||
0ED89C1427DE0A0C008B36D6 /* Shortcut.swift */,
|
||||
|
@ -944,7 +944,7 @@
|
|||
0EF2213127E674BD001D0BD7 /* AddProviderViewModel.swift in Sources */,
|
||||
0E90DFE627BACC1500EF5078 /* AddHostViewModel.swift in Sources */,
|
||||
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */,
|
||||
0E9C3B6C27FB3A9C00D0F02E /* ReloadingSection.swift in Sources */,
|
||||
0E9C3B6C27FB3A9C00D0F02E /* ReloadingContent.swift in Sources */,
|
||||
0E5324A627D297BB002565C3 /* InApp.swift in Sources */,
|
||||
0E3B7FCD27E47B3700C66F13 /* AddHostView.swift in Sources */,
|
||||
0EF2212D27E66EB5001D0BD7 /* AddProviderView.swift in Sources */,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// ReloadingSection.swift
|
||||
// ReloadingContent.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 4/4/22.
|
||||
|
@ -25,36 +25,49 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct ReloadingSection<Header: View, Footer: View, T: Equatable, Content: View>: View {
|
||||
struct ReloadingContent<T: Equatable, Content: View>: View {
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
let header: Header
|
||||
private let elements: [T]
|
||||
|
||||
let footer: Footer
|
||||
private let equality: ([T], [T]) -> Bool
|
||||
|
||||
let elements: [T]
|
||||
private let reload: (() -> Void)?
|
||||
|
||||
var equality: ([T], [T]) -> Bool = { $0 == $1 }
|
||||
|
||||
var isReloading = false
|
||||
|
||||
var reload: (() -> Void)?
|
||||
|
||||
@ViewBuilder let content: ([T]) -> Content
|
||||
@ViewBuilder private let content: ([T]) -> Content
|
||||
|
||||
@State private var localElements: [T] = []
|
||||
|
||||
init(
|
||||
observing elements: [T],
|
||||
equality: @escaping ([T], [T]) -> Bool = { $0 == $1 },
|
||||
reload: (() -> Void)? = nil,
|
||||
@ViewBuilder content: @escaping ([T]) -> Content
|
||||
) {
|
||||
self.elements = elements
|
||||
self.equality = equality
|
||||
self.reload = reload
|
||||
self.content = content
|
||||
|
||||
// XXX: not sure about this, but if content() is empty .onAppear() will
|
||||
// never trigger, thus never setting initial elements
|
||||
//
|
||||
// BEWARE: localElements will not be automatically bound to changes
|
||||
// in elements (use a Binding for that), but this is actually intended
|
||||
_localElements = State(initialValue: elements)
|
||||
if elements.isEmpty {
|
||||
reload?()
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Section(
|
||||
header: header,//progressHeader,
|
||||
footer: footer
|
||||
) {
|
||||
Group {
|
||||
content(localElements)
|
||||
}.onAppear {
|
||||
localElements = elements
|
||||
if localElements.isEmpty {
|
||||
reload?()
|
||||
}
|
||||
// }.onAppear {
|
||||
// localElements = elements
|
||||
// if localElements.isEmpty {
|
||||
// reload?()
|
||||
// }
|
||||
}.onChange(of: elements) { newElements in
|
||||
guard !equality(localElements, newElements) else {
|
||||
return
|
||||
|
@ -68,14 +81,4 @@ struct ReloadingSection<Header: View, Footer: View, T: Equatable, Content: View>
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private var progressHeader: some View {
|
||||
// HStack {
|
||||
// header
|
||||
// if isReloading {
|
||||
// ProgressView()
|
||||
// .padding(.leading, 5)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -82,20 +82,22 @@ struct DonateView: View {
|
|||
}
|
||||
|
||||
private var productsSection: some View {
|
||||
ReloadingSection(
|
||||
Section(
|
||||
header: Text(L10n.Donate.Sections.OneTime.header),
|
||||
footer: Text(L10n.Donate.Sections.OneTime.footer)
|
||||
.xxxThemeTruncation(),
|
||||
elements: productManager.donations,
|
||||
equality: {
|
||||
Set($0.map(\.productIdentifier)) == Set($1.map(\.productIdentifier))
|
||||
},
|
||||
isReloading: productManager.isRefreshingProducts,
|
||||
reload: {
|
||||
productManager.refreshProducts()
|
||||
}
|
||||
.xxxThemeTruncation()
|
||||
) {
|
||||
ForEach($0, id: \.productIdentifier, content: productRow)
|
||||
ReloadingContent(
|
||||
observing: productManager.donations,
|
||||
equality: {
|
||||
Set($0.map(\.productIdentifier)) == Set($1.map(\.productIdentifier))
|
||||
},
|
||||
reload: {
|
||||
productManager.refreshProducts()
|
||||
}
|
||||
) {
|
||||
ForEach($0, id: \.productIdentifier, content: productRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,22 +53,22 @@ extension OrganizerView {
|
|||
|
||||
var body: some View {
|
||||
debugChanges()
|
||||
return ReloadingSection(
|
||||
header: Text(Unlocalized.VPN.vpn),
|
||||
footer: EmptyView(),
|
||||
elements: profileManager.headers,
|
||||
equality: {
|
||||
Set($0) == Set($1)
|
||||
}
|
||||
) {
|
||||
if !$0.isEmpty {
|
||||
ForEach($0.sorted(), content: navigationLink(forHeader:))
|
||||
.onAppear(perform: selectActiveProfile)
|
||||
} else {
|
||||
AddProfileMenu(
|
||||
withImportedURLs: false,
|
||||
bindings: addProfileMenuBindings
|
||||
)
|
||||
return Section {
|
||||
ReloadingContent(
|
||||
observing: profileManager.headers,
|
||||
equality: {
|
||||
Set($0) == Set($1)
|
||||
}
|
||||
) {
|
||||
if !$0.isEmpty {
|
||||
ForEach($0.sorted(), content: navigationLink(forHeader:))
|
||||
.onAppear(perform: selectActiveProfile)
|
||||
} else {
|
||||
AddProfileMenu(
|
||||
withImportedURLs: false,
|
||||
bindings: addProfileMenuBindings
|
||||
)
|
||||
}
|
||||
}
|
||||
}.onAppear(perform: performMigrationsIfNeeded)
|
||||
|
||||
|
|
|
@ -100,22 +100,23 @@ extension PaywallView {
|
|||
}
|
||||
|
||||
private var productsSection: some View {
|
||||
ReloadingSection(
|
||||
Section(
|
||||
header: Text(L10n.Paywall.title),
|
||||
footer: Text(L10n.Paywall.Sections.Products.footer),
|
||||
elements: productManager.products,
|
||||
equality: {
|
||||
Set($0.map(\.productIdentifier)) == Set($1.map(\.productIdentifier))
|
||||
},
|
||||
isReloading: productManager.isRefreshingProducts,
|
||||
reload: {
|
||||
productManager.refreshProducts()
|
||||
},
|
||||
content: { _ in
|
||||
footer: Text(L10n.Paywall.Sections.Products.footer)
|
||||
) {
|
||||
ReloadingContent(
|
||||
observing: productManager.products,
|
||||
equality: {
|
||||
Set($0.map(\.productIdentifier)) == Set($1.map(\.productIdentifier))
|
||||
},
|
||||
reload: {
|
||||
productManager.refreshProducts()
|
||||
}
|
||||
) { _ in
|
||||
ForEach(productRowModels, id: \.product.productIdentifier, content: productRow)
|
||||
restoreRow
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func productRow(_ model: RowModel) -> some View {
|
||||
|
|
Loading…
Reference in New Issue