Set duplicate as current inside ProfileManager

When setting duplicate as current, batch save original profile and
duplicate in a single call via profilesToSave. This is to avoid a
double call to willUpdateProfiles() when saving Core Data context.

In order to set current profile to one that has not been persisted
yet (the duplicate), we need to resort to a pendingProfiles map
where to look the duplicate up when setting currentProfileId.

Either way, iOS 14 cannot handle updating a "hot" change in a
presented NavigationLink. Changing currentProfileId binding while
in ProfileView messes up navigation completely (multiple push and
pop events). Avoid.
This commit is contained in:
Davide De Rosa 2022-05-03 16:45:36 +02:00
parent 943bce5515
commit 4cb18965c9
3 changed files with 36 additions and 17 deletions

View File

@ -105,7 +105,7 @@ extension OrganizerView {
private func profileMenu(forHeader header: Profile.Header) -> some View {
ProfileView.DuplicateButton(
header: header,
switchCurrentProfile: false
setAsCurrent: false
)
}

View File

@ -76,7 +76,7 @@ extension ProfileView {
)
DuplicateButton(
header: currentProfile.value.header,
switchCurrentProfile: true
setAsCurrent: true
)
uninstallVPNButton
Divider()
@ -191,12 +191,12 @@ extension ProfileView {
private let header: Profile.Header
private let switchCurrentProfile: Bool
private let setAsCurrent: Bool
init(header: Profile.Header, switchCurrentProfile: Bool) {
init(header: Profile.Header, setAsCurrent: Bool) {
profileManager = .shared
self.header = header
self.switchCurrentProfile = switchCurrentProfile
self.setAsCurrent = setAsCurrent
}
var body: some View {
@ -208,12 +208,7 @@ extension ProfileView {
}
private func duplicateProfile(withId id: UUID) {
guard let copy = profileManager.duplicateProfile(withId: id) else {
return
}
if switchCurrentProfile {
profileManager.currentProfileId = copy.id
}
profileManager.duplicateProfile(withId: id, setAsCurrent: setAsCurrent)
}
}
}

View File

@ -72,6 +72,8 @@ public class ProfileManager: ObservableObject {
public let didCreateProfile = PassthroughSubject<Profile, Never>()
private var pendingProfiles: [UUID: Profile] = [:]
private var cancellables: Set<AnyCancellable> = []
public init(
@ -172,9 +174,13 @@ extension ProfileManager {
// IMPORTANT: fetch live copy first (see intents)
if isCurrentProfile(id) {
pp_log.debug("Profile \(currentProfile.value.logDescription) found in memory")
pp_log.debug("Profile \(currentProfile.value.logDescription) found in memory (current profile)")
return currentProfile.value
}
if let pending = pendingProfiles[id] {
pp_log.debug("Profile \(pending.logDescription) found in memory (pending profile)")
return pending
}
guard let profile = strategy.profile(withId: id) else {
assertionFailure("Profile in headers yet not found in persistent store")
@ -239,16 +245,34 @@ extension ProfileManager {
removeProfiles(withIds: ids)
}
public func duplicateProfile(withId id: UUID) -> Profile? {
public func duplicateProfile(withId id: UUID, setAsCurrent: Bool) {
guard let source = liveProfile(withId: id) else {
return nil
return
}
let copy = source
.withNewId()
.renamedUniquely(withLastUpdate: false)
saveProfile(copy, isActive: nil)
return copy
//
// XXX: we want to batch save the duplicate together with the former current
// profile, which is done in setCurrentProfile(). however, setting
// currentProfileId (for navigation), requires the profile ID to exist in
// the persistent store when looked up via liveProfile(withId:)
//
// the pendingProfiles workaround allows setting currentProfileId to a
// profile that has not been persisted yet
//
if setAsCurrent {
if #available(iOS 15, *) {
pendingProfiles[copy.id] = copy
currentProfileId = copy.id
pendingProfiles.removeValue(forKey: copy.id)
} else {
setCurrentProfile(copy)
}
} else {
strategy.saveProfile(copy)
}
}
public func persist() {