Просмотр исходного кода

Remove migration step 1 and the legacy UserDefaults framework it kept alive (#698)

Step 1 shipped in v3.0.0 (2025-07-07) and its one-year window has passed.
It was already unreachable: migrationStep defaults to 9, so any user without
a stored migrationStep skips every step, and users who have one are at >= 1.

Removing migrateStep1() also retires its exclusive dependencies: the
UserDefaultsValue / ObservableUserDefaultsValue stack, UserDefaultsValueGroups,
ObservableUserDefaults, ObservationToken, AnyConvertible, and the orphaned
HKQuantity+AnyConvertible. Nothing in Swift reads the app-group UserDefaults
suite anymore, so AppConstants.APP_GROUP_ID goes too.
Jonas Björkert 1 неделя назад
Родитель
Сommit
043349f21b

+ 0 - 24
LoopFollow.xcodeproj/project.pbxproj

@@ -251,8 +251,6 @@
 		DDCF9A882D85FD33004DF4DD /* AlarmData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF9A872D85FD33004DF4DD /* AlarmData.swift */; };
 		DDCF9A8C2D86005E004DF4DD /* AlarmManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF9A8B2D86005E004DF4DD /* AlarmManager.swift */; };
 		DDCFCAF22B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = DDCFCAF12B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig */; };
-		DDD10EFF2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10EFE2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift */; };
-		DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */; };
 		DDD10F032C518A6500D76A8E /* TreatmentResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F022C518A6500D76A8E /* TreatmentResponse.swift */; };
 		DDD10F052C529DA200D76A8E /* ObservableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F042C529DA200D76A8E /* ObservableValue.swift */; };
 		DDD10F072C529DE800D76A8E /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDD10F062C529DE800D76A8E /* Observable.swift */; };
@@ -427,10 +425,6 @@
 		FCC0FAC224922A22003E610E /* DictionaryKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC0FAC124922A22003E610E /* DictionaryKeyPath.swift */; };
 		FCC6885C2489559400A0279D /* blank.wav in Resources */ = {isa = PBXBuildFile; fileRef = FCC6885B2489559400A0279D /* blank.wav */; };
 		FCC6885E24896A6C00A0279D /* silence.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = FCC6885D24896A6C00A0279D /* silence.mp3 */; };
-		FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886624898F8000A0279D /* UserDefaultsValue.swift */; };
-		FCC6886924898FB100A0279D /* UserDefaultsValueGroups.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */; };
-		FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886A24898FD800A0279D /* ObservationToken.swift */; };
-		FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886C2489909D00A0279D /* AnyConvertible.swift */; };
 		FCC6886F2489A53800A0279D /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC6886E2489A53800A0279D /* AppConstants.swift */; };
 		9C7FB98C98BE4FF98F4815EE /* Telemetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFBE69CEF18416D84959974 /* Telemetry.swift */; };
 		FCD2A27D24C9D044009F7B7B /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD2A27C24C9D044009F7B7B /* Globals.swift */; };
@@ -720,8 +714,6 @@
 		DDCF9A872D85FD33004DF4DD /* AlarmData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmData.swift; sourceTree = "<group>"; };
 		DDCF9A8B2D86005E004DF4DD /* AlarmManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmManager.swift; sourceTree = "<group>"; };
 		DDCFCAF12B17273200BE5751 /* LoopFollowDisplayNameConfig.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = LoopFollowDisplayNameConfig.xcconfig; sourceTree = "<group>"; };
-		DDD10EFE2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableUserDefaultsValue.swift; sourceTree = "<group>"; };
-		DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableUserDefaults.swift; sourceTree = "<group>"; };
 		DDD10F022C518A6500D76A8E /* TreatmentResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentResponse.swift; sourceTree = "<group>"; };
 		DDD10F042C529DA200D76A8E /* ObservableValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableValue.swift; sourceTree = "<group>"; };
 		DDD10F062C529DE800D76A8E /* Observable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = "<group>"; };
@@ -899,10 +891,6 @@
 		A1A1A10001000000A0CFEED2 /* APNsCredentialValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNsCredentialValidator.swift; sourceTree = "<group>"; };
 		FCC6885B2489559400A0279D /* blank.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = blank.wav; sourceTree = "<group>"; };
 		FCC6885D24896A6C00A0279D /* silence.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = silence.mp3; sourceTree = "<group>"; };
-		FCC6886624898F8000A0279D /* UserDefaultsValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsValue.swift; sourceTree = "<group>"; };
-		FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsValueGroups.swift; sourceTree = "<group>"; };
-		FCC6886A24898FD800A0279D /* ObservationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservationToken.swift; sourceTree = "<group>"; };
-		FCC6886C2489909D00A0279D /* AnyConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyConvertible.swift; sourceTree = "<group>"; };
 		FCC6886E2489A53800A0279D /* AppConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
 		BDFBE69CEF18416D84959974 /* Telemetry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Telemetry.swift; sourceTree = "<group>"; };
 		FCC688702489A57C00A0279D /* Loop Follow.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Loop Follow.entitlements"; sourceTree = "<group>"; };
@@ -1339,7 +1327,6 @@
 			isa = PBXGroup;
 			children = (
 				FCC688512489363F00A0279D /* Framework */,
-				DDD10F002C510C6B00D76A8E /* ObservableUserDefaults.swift */,
 				DDD10F062C529DE800D76A8E /* Observable.swift */,
 				DD4878042C7B2C970048F05C /* Storage.swift */,
 				DD8316472DE49EE5004467AA /* Storage+Migrate.swift */,
@@ -1698,9 +1685,6 @@
 		FCC688512489363F00A0279D /* Framework */ = {
 			isa = PBXGroup;
 			children = (
-				FCC6886624898F8000A0279D /* UserDefaultsValue.swift */,
-				FCC6886824898FB100A0279D /* UserDefaultsValueGroups.swift */,
-				DDD10EFE2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift */,
 				DDD10F042C529DA200D76A8E /* ObservableValue.swift */,
 				DD4878022C7B297E0048F05C /* StorageValue.swift */,
 				DD16AF0C2C98485400FB655A /* SecureStorageValue.swift */,
@@ -1724,8 +1708,6 @@
 				DDF6999C2C5AAA4C0058A8D9 /* Views */,
 				FCC6886E2489A53800A0279D /* AppConstants.swift */,
 				BDFBE69CEF18416D84959974 /* Telemetry.swift */,
-				FCC6886A24898FD800A0279D /* ObservationToken.swift */,
-				FCC6886C2489909D00A0279D /* AnyConvertible.swift */,
 				FCC688592489554800A0279D /* BackgroundTaskAudio.swift */,
 				A1A1A10001000000A0CFEED2 /* APNsCredentialValidator.swift */,
 				A8CA8BE0B3D247408FE088B4 /* BackgroundRefreshManager.swift */,
@@ -2174,7 +2156,6 @@
 				DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
 				656F8C122E49F3780008DC1D /* QRCodeGenerator.swift in Sources */,
 				6541341A2E1DC27900BDBE08 /* OverridePresetData.swift in Sources */,
-				FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
 				DD7F4C092DD504A700D449E9 /* OverrideStartCondition.swift in Sources */,
 				DDEF503F2D32754F00999A5D /* ProfileTask.swift in Sources */,
 				DD7F4C052DD4BBE200D449E9 /* NotLoopingCondition.swift in Sources */,
@@ -2203,7 +2184,6 @@
 				DD07B5C929E2F9C400C6A635 /* NightscoutUtils.swift in Sources */,
 				DDCF9A8C2D86005E004DF4DD /* AlarmManager.swift in Sources */,
 				DDCC3A4F2DDC5B54006F1C10 /* BatteryDropCondition.swift in Sources */,
-				FCC6886924898FB100A0279D /* UserDefaultsValueGroups.swift in Sources */,
 				DDF2C0122BEFB733007A20E6 /* AppVersionManager.swift in Sources */,
 				DDB9FC7B2DDB573F00EFAA76 /* IOBCondition.swift in Sources */,
 				DD7E19862ACDA59700DBD158 /* BGCheck.swift in Sources */,
@@ -2329,7 +2309,6 @@
 				DDC6CA492DD8E47A0060EE25 /* PumpVolumeCondition.swift in Sources */,
 				DD9ACA0A2D33095600415D8A /* MinAgoTask.swift in Sources */,
 				DD9ED0CC2D35526E000D2A63 /* SearchBar.swift in Sources */,
-				DDD10EFF2C510C3C00D76A8E /* ObservableUserDefaultsValue.swift in Sources */,
 				DDB9FC7D2DDB575300EFAA76 /* IOBAlarmEditor.swift in Sources */,
 				DD58171A2D299EF80041FB98 /* DexcomHeartbeatBluetoothDevice.swift in Sources */,
 				DD7F4C0B2DD51C5500D449E9 /* OverrideEndCondition.swift in Sources */,
@@ -2367,7 +2346,6 @@
 				DD493AD92ACF2171009A6922 /* Carbs.swift in Sources */,
 				DD493AE92ACF2445009A6922 /* BGData.swift in Sources */,
 				DD0650A92DCA8A10004D3B41 /* AlarmBGSection.swift in Sources */,
-				FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */,
 				DD4AFB6B2DB6BF2A00BB593F /* Binding+Optional.swift in Sources */,
 				DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */,
 				DD5DA27C2DC930D6003D44FC /* GlucoseValue.swift in Sources */,
@@ -2385,7 +2363,6 @@
 				DD4878132C7B750D0048F05C /* TempTargetView.swift in Sources */,
 				65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */,
 				DD0C0C682C48529400DBADDF /* Metric.swift in Sources */,
-				FCC6886D2489909D00A0279D /* AnyConvertible.swift in Sources */,
 				DDC6CA432DD8CED20060EE25 /* SensorAgeCondition.swift in Sources */,
 				DD7F4C212DD66BB200D449E9 /* FastRiseAlarmEditor.swift in Sources */,
 				DD0C0C662C46E54C00DBADDF /* InfoDataSeparator.swift in Sources */,
@@ -2423,7 +2400,6 @@
 				379BECAA2F6588300069DC62 /* RestartLiveActivityIntent.swift in Sources */,
 				DDCF9A802D85FD0B004DF4DD /* Alarm.swift in Sources */,
 				DD7F4C132DD51FD500D449E9 /* TempTargetEndCondition.swift in Sources */,
-				DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */,
 				DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */,
 				DDC7E5CF2DC77C2000EB1127 /* SnoozerViewModel.swift in Sources */,
 				DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */,

+ 0 - 31
LoopFollow/Extensions/HKQuantity+AnyConvertible.swift

@@ -1,31 +0,0 @@
-// LoopFollow
-// HKQuantity+AnyConvertible.swift
-
-import HealthKit
-
-class HKQuantityWrapper: AnyConvertible {
-    let quantity: HKQuantity
-
-    init(quantity: HKQuantity) {
-        self.quantity = quantity
-    }
-
-    func toAny() -> Any {
-        return ["unit": UserDefaultsRepository.getPreferredUnit().unitString,
-                "value": quantity.doubleValue(for: UserDefaultsRepository.getPreferredUnit())]
-    }
-
-    static func fromAny(_ anyValue: Any) -> HKQuantityWrapper? {
-        // Convert dictionary back to HKQuantity
-        guard let dict = anyValue as? [String: Any],
-              let unitString = dict["unit"] as? String,
-              let value = dict["value"] as? Double
-        else {
-            return nil
-        }
-
-        let unit = HKUnit(from: unitString)
-        let quantity = HKQuantity(unit: unit, doubleValue: value)
-        return HKQuantityWrapper(quantity: quantity)
-    }
-}

+ 0 - 130
LoopFollow/Helpers/AnyConvertible.swift

@@ -1,130 +0,0 @@
-// LoopFollow
-// AnyConvertible.swift
-
-import Foundation
-
-/// A type that can be converted to/from Any
-protocol AnyConvertible {
-    func toAny() -> Any
-    static func fromAny(_ anyValue: Any) -> Self?
-}
-
-// common type implementations
-extension Bool: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> Bool? {
-        return anyValue as? Bool
-    }
-}
-
-extension String: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> String? {
-        return anyValue as? String
-    }
-}
-
-extension Int: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> Int? {
-        return anyValue as? Int
-    }
-}
-
-extension Float: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> Float? {
-        return anyValue as? Float
-    }
-}
-
-extension Double: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> Double? {
-        return anyValue as? Double
-    }
-}
-
-extension Date: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> Date? {
-        return anyValue as? Date
-    }
-}
-
-extension Data: AnyConvertible {
-    func toAny() -> Any {
-        return self
-    }
-
-    static func fromAny(_ anyValue: Any) -> Data? {
-        return anyValue as? Data
-    }
-}
-
-extension UUID: AnyConvertible {
-    func toAny() -> Any {
-        return uuidString
-    }
-
-    static func fromAny(_ anyValue: Any) -> UUID? {
-        guard let uuidString = anyValue as? String else {
-            return nil
-        }
-
-        return UUID(uuidString: uuidString)
-    }
-}
-
-// extension Array: AnyConvertible {
-//    func toAny() -> Any {
-//        return self
-//    }
-//
-//    static func fromAny(_ anyValue: Any) -> Array? {
-//        return anyValue as? Array
-//    }
-// }
-
-extension Array: AnyConvertible where Element: AnyConvertible {
-    func toAny() -> Any {
-        return map { $0.toAny() }
-    }
-
-    static func fromAny(_ anyValue: Any) -> Array? {
-        return (anyValue as? Array)?.compactMap { Element.fromAny($0) }
-    }
-}
-
-extension Optional: AnyConvertible where Wrapped: AnyConvertible {
-    func toAny() -> Any {
-        switch self {
-        case let .some(value):
-            return value.toAny()
-        case .none:
-            return self as Any
-        }
-    }
-
-    static func fromAny(_ anyValue: Any) -> Wrapped?? {
-        return Wrapped.fromAny(anyValue)
-    }
-}

+ 0 - 2
LoopFollow/Helpers/AppConstants.swift

@@ -5,8 +5,6 @@ import Foundation
 
 // Class that contains general constants used in different classes
 class AppConstants {
-    static let APP_GROUP_ID = "group.com.$(unique_id).LoopFollow"
-
     /// Extracts the app suffix from the bundle identifier
     /// Bundle identifier format: com.$(unique_id).LoopFollow$(app_suffix)
     /// Returns the suffix part (e.g., "2" for "com.example.LoopFollow2")

+ 0 - 17
LoopFollow/Helpers/ObservationToken.swift

@@ -1,17 +0,0 @@
-// LoopFollow
-// ObservationToken.swift
-
-import Foundation
-
-/// The token received by an observe when subscribes to its subject. The observer can cancel observation, so the subject will remove it from its observers list.
-class ObservationToken {
-    private let cancellationClosure: () -> Void
-
-    init(cancellationClosure: @escaping () -> Void) {
-        self.cancellationClosure = cancellationClosure
-    }
-
-    func cancel() {
-        cancellationClosure()
-    }
-}

+ 0 - 1
LoopFollow/Settings/ContactSettingsViewModel.swift

@@ -106,7 +106,6 @@ class ContactSettingsViewModel: ObservableObject {
         }
     }
 
-    private let storage = ObservableUserDefaults.shared
     private var cancellables = Set<AnyCancellable>()
 
     init() {

+ 0 - 111
LoopFollow/Storage/Framework/ObservableUserDefaultsValue.swift

@@ -1,111 +0,0 @@
-// LoopFollow
-// ObservableUserDefaultsValue.swift
-
-import Combine
-import Foundation
-
-class ObservableUserDefaultsValue<T: AnyConvertible & Equatable>: ObservableObject, UserDefaultsAnyValue {
-    // user defaults key (UserDefaultsAnyValue protocol implementation)
-    let key: String
-    typealias ValueType = T
-
-    // The value (strong typed)
-    @Published var value: T {
-        didSet {
-            // Continue only if the new value is different from the old value
-            guard value != oldValue else { return }
-
-            if let validation = validation {
-                guard let validatedValue = validation(value) else {
-                    DispatchQueue.main.async {
-                        self.value = oldValue
-                    }
-                    return
-                }
-                value = validatedValue
-            }
-
-            // Store value to user defaults
-            ObservableUserDefaultsValue.defaults.setValue(value.toAny(), forKey: key)
-
-            // Execute custom closure
-            DispatchQueue.main.async {
-                self.onChange?(self.value)
-
-                // Notify observers
-                self.observers.values.forEach { $0(self.value) }
-
-                // Notify UserDefaultsValueGroups that value has changed
-                UserDefaultsValueGroups.valueChanged(self)
-            }
-        }
-    }
-
-    /// Get/set the value from Any value (UserDefaultsAnyValue protocol implementation)
-    var anyValue: Any? {
-        get {
-            return value.toAny()
-        }
-        set {
-            guard let newValue = T.fromAny(newValue) as T? else {
-                return
-            }
-            DispatchQueue.main.async {
-                self.value = newValue
-            }
-        }
-    }
-
-    /// Is there this key already stored in UserDefaults?
-    var exists: Bool {
-        return ObservableUserDefaultsValue.defaults.object(forKey: key) != nil
-    }
-
-    // On change closure
-    private let onChange: ((T) -> Void)?
-
-    // Validate & transform closure : given the new value, validate it; if validation passes, return the new value;
-    // if validation fails, transform the value, returning a modified version or return nil and the change will not happen
-    private let validation: ((T) -> T?)?
-
-    // Value change observers
-    private var observers: [UUID: (T) -> Void] = [:]
-
-    // User defaults used for persistence
-    private class var defaults: UserDefaults {
-        return UserDefaults(suiteName: AppConstants.APP_GROUP_ID)!
-    }
-
-    init(key: String, default defaultValue: T, onChange: ((T) -> Void)? = nil, validation: ((T) -> T?)? = nil) {
-        self.key = key
-        self.onChange = onChange
-        self.validation = validation
-
-        if let anyValue = ObservableUserDefaultsValue.defaults.object(forKey: key), let value = T.fromAny(anyValue) as T? {
-            self.value = validation?(value) ?? value
-        } else {
-            value = defaultValue
-        }
-    }
-
-    /// Insert this value in a group, useful for observing changes in the whole group, instead of particular values
-    func group(_ groupName: String) -> Self {
-        UserDefaultsValueGroups.add(self, to: groupName)
-        return self
-    }
-
-    /// Register observers, will be notified when value changes
-    @discardableResult
-    func observeChanges(using closure: @escaping (T) -> Void) -> ObservationToken {
-        let id = UUID()
-        observers[id] = closure
-
-        return ObservationToken { [weak self] in
-            self?.observers.removeValue(forKey: id)
-        }
-    }
-
-    func setNil(key: String) {
-        ObservableUserDefaultsValue.defaults.removeObject(forKey: key)
-    }
-}

Разница между файлами не показана из-за своего большого размера
+ 0 - 121
LoopFollow/Storage/Framework/UserDefaultsValue.swift


+ 0 - 66
LoopFollow/Storage/Framework/UserDefaultsValueGroups.swift

@@ -1,66 +0,0 @@
-// LoopFollow
-// UserDefaultsValueGroups.swift
-
-import Foundation
-
-/// UserDefaultValue groups manager class, providing change observation capabilities (keep clients informed when a change occured in a given group).
-class UserDefaultsValueGroups {
-    class func values(from groupName: String) -> [UserDefaultsAnyValue]? {
-        return groupNameToValues[groupName]
-    }
-
-    class func add(_ value: UserDefaultsAnyValue, to groupName: String) {
-        // add to "value-key to groupNames" dictionary
-        var groupNames = valueKeyToGroupNames[value.key] ?? []
-        guard !groupNames.contains(groupName) else {
-            // already added value to this group!
-            return
-        }
-
-        groupNames.append(groupName)
-        valueKeyToGroupNames[value.key] = groupNames
-
-        // add to "groupName to value" dictionary
-        var values = groupNameToValues[groupName] ?? []
-        values.append(value)
-        groupNameToValues[groupName] = values
-    }
-
-    @discardableResult
-    class func observeChanges(in groupName: String, using closure: @escaping (UserDefaultsAnyValue, String) -> Void) -> ObservationToken {
-        let id = UUID()
-
-        var observers = groupNameToObservers[groupName] ?? [:]
-        observers[id] = closure
-        groupNameToObservers[groupName] = observers
-
-        return ObservationToken {
-            groupNameToObservers[groupName]?.removeValue(forKey: id)
-        }
-    }
-
-    // called by UserDefaultsValue instances when value changes
-    class func valueChanged(_ value: UserDefaultsAnyValue) {
-        valueKeyToGroupNames[value.key]?.forEach { groupName in
-            notifyValueChanged(value, in: groupName)
-        }
-    }
-
-    private class func notifyValueChanged(_ value: UserDefaultsAnyValue, in groupName: String) {
-        groupNameToObservers[groupName]?.values.forEach { closure in
-            closure(value, groupName)
-        }
-    }
-
-    private static var groupNameToValues: [String: [UserDefaultsAnyValue]] = [:]
-    private static var valueKeyToGroupNames: [String: [String]] = [:]
-    private static var groupNameToObservers: [String: [UUID: (UserDefaultsAnyValue, String) -> Void]] = [:]
-}
-
-// user default values group definitions
-extension UserDefaultsValueGroups {
-    enum GroupNames {
-        static let watchSync = "watchSync"
-        static let alarm = "alarm"
-    }
-}

+ 0 - 20
LoopFollow/Storage/ObservableUserDefaults.swift

@@ -1,20 +0,0 @@
-// LoopFollow
-// ObservableUserDefaults.swift
-
-import Combine
-import Foundation
-
-/*
- Legacy storage, we are moving away from this
- */
-
-class ObservableUserDefaults {
-    static let shared = ObservableUserDefaults()
-
-    var old_url = ObservableUserDefaultsValue<String>(key: "url", default: "")
-    var old_device = ObservableUserDefaultsValue<String>(key: "device", default: "")
-    var old_nsWriteAuth = ObservableUserDefaultsValue<Bool>(key: "nsWriteAuth", default: false)
-    var old_nsAdminAuth = ObservableUserDefaultsValue<Bool>(key: "nsAdminAuth", default: false)
-
-    private init() {}
-}

Разница между файлами не показана из-за своего большого размера
+ 0 - 1673
LoopFollow/Storage/Storage+Migrate.swift


+ 0 - 6
LoopFollow/ViewControllers/MainViewController.swift

@@ -623,12 +623,6 @@ class MainViewController: UIViewController, ChartViewDelegate, UNUserNotificatio
         //   2. Update any other StorageValue defaults in Storage.swift that this new step
         //      mutates, so a fresh install ends up in the same state as a migrated user.
 
-        // Step 1: Released in v3.0.0 (2025-07-07). Can be removed after 2026-07-07.
-        if Storage.shared.migrationStep.value < 1 {
-            Storage.shared.migrateStep1()
-            Storage.shared.migrationStep.value = 1
-        }
-
         // Step 2: Released in v3.1.0 (2025-07-21). Can be removed after 2026-07-21.
         if Storage.shared.migrationStep.value < 2 {
             Storage.shared.migrateStep2()