Support iCloud sync as an option (#350)
Sync will be enabled on upgrade for consistency with current behavior, and disabled for new installs. Fixes #227
This commit is contained in:
parent
2d046181b0
commit
a4ca8cc996
|
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Make iCloud an opt-in preference. [#227](https://github.com/passepartoutvpn/passepartout-apple/issues/227)
|
||||||
- OpenVPN: Endpoint UX. [#332](https://github.com/passepartoutvpn/passepartout-apple/pull/332)
|
- OpenVPN: Endpoint UX. [#332](https://github.com/passepartoutvpn/passepartout-apple/pull/332)
|
||||||
- Convert trusted networks to on-demand activation. [#119](https://github.com/passepartoutvpn/passepartout-apple/issues/119)
|
- Convert trusted networks to on-demand activation. [#119](https://github.com/passepartoutvpn/passepartout-apple/issues/119)
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; };
|
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; };
|
||||||
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; };
|
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; };
|
||||||
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; };
|
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; };
|
||||||
|
0E3A3C102AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */; };
|
||||||
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; };
|
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; };
|
||||||
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; };
|
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; };
|
||||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
|
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
|
||||||
|
@ -346,6 +347,7 @@
|
||||||
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
|
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
|
||||||
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
|
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
|
||||||
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
|
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
|
||||||
|
0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueStore+CloudKit.swift"; sourceTree = "<group>"; };
|
||||||
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
|
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
|
||||||
0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; };
|
0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; };
|
||||||
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
|
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -744,6 +746,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */,
|
0EBC075C27EC529000208AD9 /* DebugLog+Constants.swift */,
|
||||||
|
0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */,
|
||||||
0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */,
|
0EB17EB927D2560300D473B5 /* PassepartoutProviders+Extensions.swift */,
|
||||||
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */,
|
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */,
|
||||||
0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Extensions.swift */,
|
0E2DE71B27DCCFE80067B9E1 /* TunnelKit+Extensions.swift */,
|
||||||
|
@ -1542,6 +1545,7 @@
|
||||||
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
|
0E53249927D26B51002565C3 /* ProductManager.swift in Sources */,
|
||||||
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */,
|
0E9C233027F47032007D5FC7 /* IntentsManager.swift in Sources */,
|
||||||
0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */,
|
0EB4042C27CA0E8C00378B1A /* Unlocalized.swift in Sources */,
|
||||||
|
0E3A3C102AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift in Sources */,
|
||||||
0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */,
|
0EB4042E27CA136300378B1A /* AddingTextField.swift in Sources */,
|
||||||
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */,
|
0EE8B7E327FF340F00B68621 /* VPNProtocolType+FileExtensions.swift in Sources */,
|
||||||
0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */,
|
0EF2212B27E667EA001D0BD7 /* AddProviderView+Name.swift in Sources */,
|
||||||
|
|
|
@ -31,6 +31,8 @@ import PassepartoutLibrary
|
||||||
final class AppContext {
|
final class AppContext {
|
||||||
private let coreContext: CoreContext
|
private let coreContext: CoreContext
|
||||||
|
|
||||||
|
private var lastIsCloudSyncingEnabled: Bool?
|
||||||
|
|
||||||
let productManager: ProductManager
|
let productManager: ProductManager
|
||||||
|
|
||||||
private let reviewer: Reviewer
|
private let reviewer: Reviewer
|
||||||
|
@ -114,3 +116,25 @@ private extension AppContext {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: CloudKit
|
||||||
|
|
||||||
|
extension AppContext {
|
||||||
|
var shouldEnableCloudSyncing: Bool {
|
||||||
|
get {
|
||||||
|
coreContext.store.shouldEnableCloudSyncing
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
coreContext.store.shouldEnableCloudSyncing = newValue
|
||||||
|
|
||||||
|
// iCloud may be externally disabled from the device settings
|
||||||
|
let isCloudSyncingEnabled = coreContext.store.isCloudSyncingEnabled
|
||||||
|
guard isCloudSyncingEnabled != lastIsCloudSyncingEnabled else {
|
||||||
|
pp_log.debug("CloudKit state did not change")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
coreContext.reloadCloudKitObjects(isEnabled: isCloudSyncingEnabled)
|
||||||
|
lastIsCloudSyncingEnabled = isCloudSyncingEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,6 +31,10 @@ import TunnelKitManager
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
final class CoreContext {
|
final class CoreContext {
|
||||||
|
let store: KeyValueStore
|
||||||
|
|
||||||
|
private let persistenceManager: PersistenceManager
|
||||||
|
|
||||||
let upgradeManager: UpgradeManager
|
let upgradeManager: UpgradeManager
|
||||||
|
|
||||||
let providerManager: ProviderManager
|
let providerManager: ProviderManager
|
||||||
|
@ -42,6 +46,8 @@ final class CoreContext {
|
||||||
private var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
init(store: KeyValueStore) {
|
init(store: KeyValueStore) {
|
||||||
|
self.store = store
|
||||||
|
|
||||||
let logger = SwiftyBeaverLogger(
|
let logger = SwiftyBeaverLogger(
|
||||||
logFile: Constants.Log.App.url,
|
logFile: Constants.Log.App.url,
|
||||||
logLevel: Constants.Log.level,
|
logLevel: Constants.Log.level,
|
||||||
|
@ -50,18 +56,20 @@ final class CoreContext {
|
||||||
Passepartout.shared.logger = logger
|
Passepartout.shared.logger = logger
|
||||||
pp_log.info("Logging to: \(logger.logFile!)")
|
pp_log.info("Logging to: \(logger.logFile!)")
|
||||||
|
|
||||||
let persistenceManager = PersistenceManager(store: store)
|
|
||||||
let vpnPersistence = persistenceManager.vpnPersistence(
|
|
||||||
withName: Constants.Persistence.profilesContainerName
|
|
||||||
)
|
|
||||||
let providersPersistence = persistenceManager.providersPersistence(
|
|
||||||
withName: Constants.Persistence.providersContainerName
|
|
||||||
)
|
|
||||||
|
|
||||||
upgradeManager = UpgradeManager(
|
upgradeManager = UpgradeManager(
|
||||||
store: store,
|
store: store,
|
||||||
strategy: DefaultUpgradeManagerStrategy()
|
strategy: DefaultUpgradeManagerStrategy()
|
||||||
)
|
)
|
||||||
|
upgradeManager.migrate(toVersion: Constants.Global.appVersionNumber)
|
||||||
|
|
||||||
|
persistenceManager = PersistenceManager(store: store)
|
||||||
|
let vpnPersistence = persistenceManager.vpnPersistence(
|
||||||
|
withName: Constants.Persistence.profilesContainerName,
|
||||||
|
cloudKit: store.isCloudSyncingEnabled
|
||||||
|
)
|
||||||
|
let providersPersistence = persistenceManager.providersPersistence(
|
||||||
|
withName: Constants.Persistence.providersContainerName
|
||||||
|
)
|
||||||
|
|
||||||
let remoteProvidersStrategy = APIRemoteProvidersStrategy(
|
let remoteProvidersStrategy = APIRemoteProvidersStrategy(
|
||||||
appBuild: Constants.Global.appBuildNumber,
|
appBuild: Constants.Global.appBuildNumber,
|
||||||
|
@ -127,3 +135,19 @@ private extension CoreContext {
|
||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: CloudKit
|
||||||
|
|
||||||
|
extension CoreContext {
|
||||||
|
func reloadCloudKitObjects(isEnabled: Bool) {
|
||||||
|
let vpnPersistence = persistenceManager.vpnPersistence(
|
||||||
|
withName: Constants.Persistence.profilesContainerName,
|
||||||
|
cloudKit: isEnabled
|
||||||
|
)
|
||||||
|
profileManager.swapProfileRepository(vpnPersistence.profileRepository())
|
||||||
|
}
|
||||||
|
|
||||||
|
func eraseCloudKitStore() {
|
||||||
|
// TODO: iCloud, erase remote records
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ import PassepartoutLibrary
|
||||||
enum AppPreference: String, KeyStoreDomainLocation {
|
enum AppPreference: String, KeyStoreDomainLocation {
|
||||||
case launchesOnLogin
|
case launchesOnLogin
|
||||||
|
|
||||||
|
case shouldEnableCloudSyncing
|
||||||
|
|
||||||
case isShowingFavorites
|
case isShowingFavorites
|
||||||
|
|
||||||
case didHandleSubreddit
|
case didHandleSubreddit
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
//
|
||||||
|
// KeyValueStore+CloudKit.swift
|
||||||
|
// Passepartout
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 9/7/23.
|
||||||
|
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of Passepartout.
|
||||||
|
//
|
||||||
|
// Passepartout is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Passepartout is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import PassepartoutLibrary
|
||||||
|
|
||||||
|
extension KeyValueStore {
|
||||||
|
|
||||||
|
// MARK: Support
|
||||||
|
|
||||||
|
private var cloudKitToken: Any? {
|
||||||
|
FileManager.default.ubiquityIdentityToken
|
||||||
|
}
|
||||||
|
|
||||||
|
var isCloudKitSupported: Bool {
|
||||||
|
cloudKitToken != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Preference
|
||||||
|
|
||||||
|
var shouldEnableCloudSyncing: Bool {
|
||||||
|
get {
|
||||||
|
value(forLocation: AppPreference.shouldEnableCloudSyncing) ?? false
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
setValue(newValue, forLocation: AppPreference.shouldEnableCloudSyncing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Computed state
|
||||||
|
|
||||||
|
var isCloudSyncingEnabled: Bool {
|
||||||
|
guard isCloudKitSupported else {
|
||||||
|
pp_log.debug("CloudKit unavailable")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let isEnabled = shouldEnableCloudSyncing
|
||||||
|
pp_log.debug("CloudKit enabled: \(isEnabled)")
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,11 +30,22 @@ public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func doMigrate(store: KeyValueStore, lastVersion: String?) {
|
public func migrate(store: KeyValueStore, lastVersion: String?) {
|
||||||
|
|
||||||
|
// legacy check before lastVersion was even stored
|
||||||
|
let isUpgradeFromBefore_2_2_0: Bool? = store.value(forLocation: UpgradeManager.StoreKey.existingKeyBefore_2_2_0)
|
||||||
|
if isUpgradeFromBefore_2_2_0 != nil {
|
||||||
|
pp_log.debug("Upgrading from < 2.2.0, iCloud syncing defaults to enabled")
|
||||||
|
store.setValue(true, forLocation: AppPreference.shouldEnableCloudSyncing)
|
||||||
|
}
|
||||||
|
|
||||||
guard let lastVersion else {
|
guard let lastVersion else {
|
||||||
pp_log.debug("Fresh install")
|
pp_log.debug("Fresh install")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pp_log.debug("Upgrade from \(lastVersion)")
|
pp_log.debug("Upgrade from \(lastVersion)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func migrateData() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,8 @@ final class PersistenceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func vpnPersistence(withName containerName: String) -> VPNPersistence {
|
func vpnPersistence(withName containerName: String, cloudKit: Bool) -> VPNPersistence {
|
||||||
VPNPersistence(withName: containerName, cloudKit: true, author: persistenceAuthor)
|
VPNPersistence(withName: containerName, cloudKit: cloudKit, author: persistenceAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
func providersPersistence(withName containerName: String) -> ProvidersPersistence {
|
func providersPersistence(withName containerName: String) -> ProvidersPersistence {
|
||||||
|
|
|
@ -47,11 +47,14 @@ public final class UpgradeManager: ObservableObject {
|
||||||
self.strategy = strategy
|
self.strategy = strategy
|
||||||
}
|
}
|
||||||
|
|
||||||
public func doMigrations(toVersion currentVersion: String, profileManager: ProfileManager) {
|
public func migrate(toVersion currentVersion: String) {
|
||||||
if let lastVersion, currentVersion > lastVersion {
|
if let lastVersion, currentVersion > lastVersion {
|
||||||
strategy.doMigrate(store: store, lastVersion: lastVersion)
|
strategy.migrate(store: store, lastVersion: lastVersion)
|
||||||
}
|
}
|
||||||
lastVersion = currentVersion
|
lastVersion = currentVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
public func migrateData(profileManager: ProfileManager) {
|
||||||
isDoingMigrations = false
|
isDoingMigrations = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,8 +72,12 @@ private extension UpgradeManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension UpgradeManager {
|
extension UpgradeManager {
|
||||||
enum StoreKey: String, KeyStoreDomainLocation {
|
enum StoreKey: String, KeyStoreDomainLocation {
|
||||||
|
|
||||||
|
@available(*, deprecated, message: "Retain temporarily for migrations")
|
||||||
|
case existingKeyBefore_2_2_0 = "didMigrateToV2"
|
||||||
|
|
||||||
case lastVersion
|
case lastVersion
|
||||||
|
|
||||||
var domain: String {
|
var domain: String {
|
||||||
|
|
|
@ -27,5 +27,6 @@ import Foundation
|
||||||
import PassepartoutCore
|
import PassepartoutCore
|
||||||
|
|
||||||
public protocol UpgradeManagerStrategy {
|
public protocol UpgradeManagerStrategy {
|
||||||
func doMigrate(store: KeyValueStore, lastVersion: String?)
|
func migrate(store: KeyValueStore, lastVersion: String?)
|
||||||
|
func migrateData()
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,16 +26,14 @@
|
||||||
import PassepartoutLibrary
|
import PassepartoutLibrary
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DiagnosticsSection: View {
|
struct DiagnosticsRow: View {
|
||||||
@ObservedObject var currentProfile: ObservableProfile
|
@ObservedObject var currentProfile: ObservableProfile
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section {
|
NavigationLink {
|
||||||
NavigationLink {
|
DiagnosticsView(profile: currentProfile.value)
|
||||||
DiagnosticsView(profile: currentProfile.value)
|
} label: {
|
||||||
} label: {
|
Text(L10n.Diagnostics.title)
|
||||||
Text(L10n.Diagnostics.title)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,10 +183,7 @@ private extension OrganizerView.ProfilesList {
|
||||||
|
|
||||||
func performMigrationsIfNeeded() {
|
func performMigrationsIfNeeded() {
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
UpgradeManager.shared.doMigrations(
|
UpgradeManager.shared.migrateData(profileManager: profileManager)
|
||||||
toVersion: Constants.Global.appVersionNumber,
|
|
||||||
profileManager: profileManager
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,9 @@ private extension ProfileView {
|
||||||
modalType: $modalType
|
modalType: $modalType
|
||||||
)
|
)
|
||||||
ExtraSection(currentProfile: currentProfile)
|
ExtraSection(currentProfile: currentProfile)
|
||||||
DiagnosticsSection(currentProfile: currentProfile)
|
Section {
|
||||||
|
DiagnosticsRow(currentProfile: currentProfile)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,19 +35,24 @@ struct SettingsView: View {
|
||||||
|
|
||||||
@AppStorage(AppPreference.locksInBackground.key) private var locksInBackground = false
|
@AppStorage(AppPreference.locksInBackground.key) private var locksInBackground = false
|
||||||
|
|
||||||
|
@Binding private var shouldEnableCloudSyncing: Bool
|
||||||
|
|
||||||
private let versionString = Constants.Global.appVersionString
|
private let versionString = Constants.Global.appVersionString
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
profileManager = .shared
|
profileManager = .shared
|
||||||
productManager = .shared
|
productManager = .shared
|
||||||
|
|
||||||
|
_shouldEnableCloudSyncing = .init {
|
||||||
|
AppContext.shared.shouldEnableCloudSyncing
|
||||||
|
} set: {
|
||||||
|
AppContext.shared.shouldEnableCloudSyncing = $0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
#if !targetEnvironment(macCatalyst)
|
|
||||||
preferencesSection
|
preferencesSection
|
||||||
#endif
|
|
||||||
DiagnosticsSection(currentProfile: profileManager.currentProfile)
|
|
||||||
aboutSection
|
aboutSection
|
||||||
}.toolbar {
|
}.toolbar {
|
||||||
themeCloseItem(presentationMode: presentationMode)
|
themeCloseItem(presentationMode: presentationMode)
|
||||||
|
@ -61,7 +66,12 @@ struct SettingsView: View {
|
||||||
private extension SettingsView {
|
private extension SettingsView {
|
||||||
var preferencesSection: some View {
|
var preferencesSection: some View {
|
||||||
Section {
|
Section {
|
||||||
|
#if !targetEnvironment(macCatalyst)
|
||||||
Toggle(L10n.Settings.Items.LocksInBackground.caption, isOn: $locksInBackground)
|
Toggle(L10n.Settings.Items.LocksInBackground.caption, isOn: $locksInBackground)
|
||||||
|
#endif
|
||||||
|
Toggle(L10n.Settings.Items.ShouldEnableCloudSyncing.caption, isOn: $shouldEnableCloudSyncing)
|
||||||
|
} header: {
|
||||||
|
Text(L10n.Preferences.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +87,8 @@ private extension SettingsView {
|
||||||
} label: {
|
} label: {
|
||||||
Text(L10n.Settings.Items.Donate.caption)
|
Text(L10n.Settings.Items.Donate.caption)
|
||||||
}.disabled(!productManager.canMakePayments())
|
}.disabled(!productManager.canMakePayments())
|
||||||
|
|
||||||
|
DiagnosticsRow(currentProfile: profileManager.currentProfile)
|
||||||
} footer: {
|
} footer: {
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
|
@ -332,6 +332,7 @@
|
||||||
|
|
||||||
"settings.title" = "Settings";
|
"settings.title" = "Settings";
|
||||||
"settings.items.locks_in_background.caption" = "Lock app access";
|
"settings.items.locks_in_background.caption" = "Lock app access";
|
||||||
|
"settings.items.should_enable_cloud_syncing.caption" = "Sync with iCloud";
|
||||||
"settings.items.donate.caption" = "Make a donation";
|
"settings.items.donate.caption" = "Make a donation";
|
||||||
|
|
||||||
/* MARK: AboutView */
|
/* MARK: AboutView */
|
||||||
|
|
|
@ -925,6 +925,10 @@ internal enum L10n {
|
||||||
/// Lock app access
|
/// Lock app access
|
||||||
internal static let caption = L10n.tr("Localizable", "settings.items.locks_in_background.caption", fallback: "Lock app access")
|
internal static let caption = L10n.tr("Localizable", "settings.items.locks_in_background.caption", fallback: "Lock app access")
|
||||||
}
|
}
|
||||||
|
internal enum ShouldEnableCloudSyncing {
|
||||||
|
/// Sync with iCloud
|
||||||
|
internal static let caption = L10n.tr("Localizable", "settings.items.should_enable_cloud_syncing.caption", fallback: "Sync with iCloud")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
internal enum Shortcuts {
|
internal enum Shortcuts {
|
||||||
|
|
|
@ -33,7 +33,7 @@ public protocol KeyStoreDomainLocation: KeyStoreLocation {
|
||||||
var domain: String { get }
|
var domain: String { get }
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol KeyValueStore {
|
public protocol KeyValueStore: AnyObject {
|
||||||
func setValue<L, V>(_ value: V?, forLocation location: L) where L: KeyStoreLocation
|
func setValue<L, V>(_ value: V?, forLocation location: L) where L: KeyStoreLocation
|
||||||
|
|
||||||
func value<L, V>(forLocation location: L) -> V? where L: KeyStoreLocation
|
func value<L, V>(forLocation location: L) -> V? where L: KeyStoreLocation
|
||||||
|
|
|
@ -42,7 +42,7 @@ public final class ProfileManager: ObservableObject {
|
||||||
|
|
||||||
private let providerManager: ProviderManager
|
private let providerManager: ProviderManager
|
||||||
|
|
||||||
private let profileRepository: ProfileRepository
|
private var profileRepository: ProfileRepository
|
||||||
|
|
||||||
private let keychain: SecretRepository
|
private let keychain: SecretRepository
|
||||||
|
|
||||||
|
@ -488,6 +488,18 @@ extension ProfileManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Repository
|
||||||
|
|
||||||
|
extension ProfileManager {
|
||||||
|
public func swapProfileRepository(_ newProfileRepository: ProfileRepository) {
|
||||||
|
cancellables.removeAll()
|
||||||
|
|
||||||
|
objectWillChange.send()
|
||||||
|
profileRepository = newProfileRepository
|
||||||
|
observeUpdates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: KeyValueStore
|
// MARK: KeyValueStore
|
||||||
|
|
||||||
extension ProfileManager {
|
extension ProfileManager {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PassepartoutCore
|
import PassepartoutCore
|
||||||
|
|
||||||
public struct UserDefaultsStore: KeyValueStore {
|
public class UserDefaultsStore: KeyValueStore {
|
||||||
private let defaults: UserDefaults
|
private let defaults: UserDefaults
|
||||||
|
|
||||||
private let key: (any KeyStoreLocation) -> String
|
private let key: (any KeyStoreLocation) -> String
|
||||||
|
@ -41,11 +41,11 @@ public struct UserDefaultsStore: KeyValueStore {
|
||||||
defaults.removeObject(forKey: key(location))
|
defaults.removeObject(forKey: key(location))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defaults.setValue(value, forKey: key(location))
|
defaults.set(value, forKey: key(location))
|
||||||
}
|
}
|
||||||
|
|
||||||
public func value<L, V>(forLocation location: L) -> V? where L: KeyStoreLocation {
|
public func value<L, V>(forLocation location: L) -> V? where L: KeyStoreLocation {
|
||||||
defaults.value(forKey: key(location)) as? V
|
defaults.object(forKey: key(location)) as? V
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeValue<L>(forLocation location: L) where L: KeyStoreLocation {
|
public func removeValue<L>(forLocation location: L) where L: KeyStoreLocation {
|
||||||
|
|
Loading…
Reference in New Issue