Move all persisted state out of AppManager to where it really
belongs. To do that, inject a shared KeyValueStore object into
managers that need to persist part of their state in a strongly
typed manner.
Below are persisted states:
- PersistenceManager
- persistenceAuthor
- ProfileManager
- activeProfileId
- UpgradeManager (formerly AppManager)
- didMigrateToV2 (migrate former value)
- VPNManager
- tunnelLogFormat
- masksPrivateData
A similar approach is used for app-specific preferences, by using
a strongly typed enum (AppPreference) together with SwiftUI
@AppStorage property wrapper.
Worth moving logging logic into a specific LogManager.
Finally, drop any former view dependency on AppManager, as states
are now accessed through specific managers.
On configuration error, retain information about the profile that
triggered the error. For now, present an alert, but with this
information the UI can be easily changed later.
Providers are not fetched at migration time, they only are after
opening a profile (marked non-ready until then).
Still retain Task for migration to be executed asynchronously.
App disconnects VPN on launch otherwise, because active
profile is still nil. Where was the regression introduced?
Also .dropFirst() to skip initial values, but keep in mind that
if VPN is connected and active profile was not properly persisted,
the app will show the VPN as disabled.
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.
- Do the profile loading inside the model
- Allow setting current profile to a transient profile
- Check .placeholder before saving current profile
XXX: avoid loading active profile on iPad portrait.
Notifications deemed not relevant because currentBundleIdentifier
not set unless connect/reinstate.
Assume initial relevant identifier to be the first notified.
See 7ba41a0e73
Flashing on activation was caused by VPNManager.disable() in
ProfileView+VPN, because by setting lastError to nil it would
notify a change to ProfileRow (via VPNStateView) during the
profile row activation animation. That caused the flicker.
Instead, disable VPN first, then start the animation.
Anyway, avoid clearing a nil lastError.
ProfileView is not interested in changes in other profiles
notified by ProfileManager. Set isLoading inside
ObservableObject for observable to be self-contained.
Loses observation of profile deletion, but dismiss on removal is
actually handled by OrganizerView, not ProfileView.
Also drop unused presentationMode.
Make action sync, but internally async (makeProfileReady). If not
doing so, UI on launch will not be able to show active profile
immediately. WelcomeView would appear for a moment.
Observe isReloadingCurrentProfile.
See 2b1efb8fec
- Update TunnelKit
- Receive TunnelKit notifications on main queue
- Bind VPN toggle to VPNManager directly (implicit animations)
- Update state on VPN didFail
- Set isEnabled = false after uninstalling VPN (not notified)