diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index c67e8b4..5b1c024 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -46,7 +46,6 @@ 7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2E11282CCE395300E5CA17 /* OptionalDatePicker.swift */; }; 7A2E6FA72C42B3AD00C40DA7 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; 7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3399AA299063370087DF98 /* SearchControllerExt.swift */; }; - 7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */; }; 7A3F07AB24360DC800E59687 /* Dated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3F07AA24360DC800E59687 /* Dated.swift */; }; 7A4322912CB2CC8A00085CF6 /* FiltersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */; }; 7A4322932CB2CCAA00085CF6 /* FiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */; }; @@ -115,7 +114,9 @@ 7A761C0B267E8FF90005F28F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A761C0A267E8FF90005F28F /* Error.swift */; }; 7A7AA2C42DA2A3CB00276D83 /* LocationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AA2C32DA2A3CB00276D83 /* LocationError.swift */; }; 7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C62DA2A45600276D83 /* RealmSwift */; }; - 7A7AA2C82DA2A45600276D83 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C62DA2A45600276D83 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 7A7AA2CA2DA2C85100276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; }; + 7A7AA2CB2DA2C85100276D83 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 7A7AA2CE2DA3120500276D83 /* ObservableDefaults in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2CD2DA3120500276D83 /* ObservableDefaults */; }; 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */; }; 7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A809F382D66755B00CF1B3C /* Error+Canceled.swift */; }; 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; }; @@ -254,7 +255,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 7A7AA2C82DA2A45600276D83 /* RealmSwift in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -265,6 +265,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + 7A7AA2CB2DA2C85100276D83 /* RealmSwift in Embed Frameworks */, 7AF6D2052677C03B0086EA64 /* AutoCatCore.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -325,7 +326,6 @@ 7A2E6FA32C42B3AD00C40DA7 /* AutoCatCoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutoCatCoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7A333813249A532400D878F1 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; 7A3399AA299063370087DF98 /* SearchControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControllerExt.swift; sourceTree = ""; }; - 7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Settings.swift"; sourceTree = ""; }; 7A3F07AA24360DC800E59687 /* Dated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dated.swift; sourceTree = ""; }; 7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersScreen.swift; sourceTree = ""; }; 7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = ""; }; @@ -516,6 +516,7 @@ files = ( 7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */, 7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */, + 7A7AA2CA2DA2C85100276D83 /* RealmSwift in Frameworks */, 7AC3554A2969652F00889457 /* SwiftEntryKit in Frameworks */, 7ACBB91E2CB9B155005A5168 /* Mockable in Frameworks */, 7AF6D2042677C03B0086EA64 /* AutoCatCore.framework in Frameworks */, @@ -547,6 +548,7 @@ files = ( 7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */, 7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */, + 7A7AA2CE2DA3120500276D83 /* ObservableDefaults in Frameworks */, 7AF8606E2CB9B86300954D2F /* Mockable in Frameworks */, 7A6C4D9E2C56BCA600982597 /* SwiftLocation in Frameworks */, ); @@ -570,7 +572,6 @@ children = ( 7A06E0B22C707E13005731AC /* SettingsServiceProtocol.swift */, 7A06E0B42C707E2B005731AC /* SettingsService.swift */, - 7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */, ); path = SettingsService; sourceTree = ""; @@ -1238,6 +1239,7 @@ 7AABDE1C2532F3EB0041AFC6 /* PKHUD */, 7AC355492969652F00889457 /* SwiftEntryKit */, 7ACBB91D2CB9B155005A5168 /* Mockable */, + 7A7AA2C92DA2C85100276D83 /* RealmSwift */, ); productName = AutoCat; productReference = 7A1146FD23FDE7E500B424AF /* AutoCat.app */; @@ -1312,6 +1314,7 @@ 7A6C4D9D2C56BCA600982597 /* SwiftLocation */, 7AF8606D2CB9B86300954D2F /* Mockable */, 7A7AA2C62DA2A45600276D83 /* RealmSwift */, + 7A7AA2CD2DA3120500276D83 /* ObservableDefaults */, ); productName = AutoCatCore; productReference = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; @@ -1360,6 +1363,7 @@ 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */, 7A6C4D9C2C56BCA600982597 /* XCRemoteSwiftPackageReference "SwiftLocation" */, 7ACBB91C2CB9B155005A5168 /* XCRemoteSwiftPackageReference "Mockable" */, + 7A7AA2CC2DA3120500276D83 /* XCRemoteSwiftPackageReference "ObservableDefaults" */, ); productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */; projectDirPath = ""; @@ -1597,7 +1601,6 @@ 7AB4E4332D3C21C00006D052 /* FileManagerExt.swift in Sources */, 7A9519792D80B3E800E69883 /* AudioRecordService.swift in Sources */, 7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */, - 7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */, 7AB4E43B2D3D3F4F0006D052 /* VehicleServiceProtocol.swift in Sources */, 7AA514E02D0B75B3001CAC50 /* StorageService+Events.swift in Sources */, 7A64A2222C19E99E00284124 /* DebugInfoDto.swift in Sources */, @@ -2115,6 +2118,14 @@ minimumVersion = 6.0.0; }; }; + 7A7AA2CC2DA3120500276D83 /* XCRemoteSwiftPackageReference "ObservableDefaults" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/fatbobman/ObservableDefaults"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.6.2; + }; + }; 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/ExceptionCatcher"; @@ -2160,6 +2171,16 @@ package = 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */; productName = RealmSwift; }; + 7A7AA2C92DA2C85100276D83 /* RealmSwift */ = { + isa = XCSwiftPackageProductDependency; + package = 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */; + productName = RealmSwift; + }; + 7A7AA2CD2DA3120500276D83 /* ObservableDefaults */ = { + isa = XCSwiftPackageProductDependency; + package = 7A7AA2CC2DA3120500276D83 /* XCRemoteSwiftPackageReference "ObservableDefaults" */; + productName = ObservableDefaults; + }; 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */ = { isa = XCSwiftPackageProductDependency; package = 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */; diff --git a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 027891f..ebeff55 100644 --- a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "6fccb9fdc0d29647d4f0b927aef60f375302d72b5b724992eab52ac0d8ec71c3", + "originHash" : "3cc3aec63412867029bf57f8fff11744df284387a47994473d82e7aab44a4293", "pins" : [ { "identity" : "exceptioncatcher", @@ -19,6 +19,15 @@ "version" : "0.3.1" } }, + { + "identity" : "observabledefaults", + "kind" : "remoteSourceControl", + "location" : "https://github.com/fatbobman/ObservableDefaults", + "state" : { + "revision" : "4740e2c39043b7daff946aa3bbec22e6a68850d3", + "version" : "0.6.2" + } + }, { "identity" : "pkhud", "kind" : "remoteSourceControl", diff --git a/AutoCat/SceneDelegate.swift b/AutoCat/SceneDelegate.swift index ef50614..4276d3b 100644 --- a/AutoCat/SceneDelegate.swift +++ b/AutoCat/SceneDelegate.swift @@ -38,7 +38,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let container = ServiceContainer.shared - let settingsService = SettingsService(defaults: .standard) + let settingsService = SettingsService() container.register(SettingsServiceProtocol.self, instance: settingsService) diff --git a/AutoCatCore/Services/SettingsService/SettingsService.swift b/AutoCatCore/Services/SettingsService/SettingsService.swift index e392685..d385ed7 100644 --- a/AutoCatCore/Services/SettingsService/SettingsService.swift +++ b/AutoCatCore/Services/SettingsService/SettingsService.swift @@ -6,21 +6,20 @@ // Copyright © 2024 Selim Mustafaev. All rights reserved. // -import SwiftUI +import Foundation +import ObservableDefaults -@Observable +@ObservableDefaults public final class SettingsService: SettingsServiceProtocol { - - let defaults: UserDefaults - var observations: [NSKeyValueObservation] = [] - let jsonEncoder = JSONEncoder() - let jsonDecoder = JSONDecoder() + @Ignore let jsonEncoder = JSONEncoder() + @Ignore let jsonDecoder = JSONDecoder() + @ObservableOnly public var user: User { get { - if let data = userData { - let result = try? jsonDecoder.decode(User.self, from: data) + if userData.count > 0 { + let result = try? jsonDecoder.decode(User.self, from: userData) return result ?? User() } else { return User() @@ -34,92 +33,20 @@ public final class SettingsService: SettingsServiceProtocol { } } - public var userData: Data? = nil { - didSet { - defaults.user = userData - } - } + @DefaultsKey(userDefaultsKey: "user") + public var userData: Data = .init() - public var recognizeAlternativeOrder: Bool = false { - didSet { - defaults.recognizeAlternativeOrder = recognizeAlternativeOrder - } - } - - public var recognizeShortenedNumbers: Bool = false { - didSet { - defaults.recognizeShortenedNumbers = recognizeShortenedNumbers - } - } - - public var defaultRegion: String = "161" { - didSet { - defaults.defaultRegion = defaultRegion - } - } - - public var recordBeep: Bool = false { - didSet { - defaults.recordBeep = recordBeep - } - } - - public var showDebugInfo: Bool = false { - didSet { - defaults.showDebugInfo = showDebugInfo - } - } + public var recognizeAlternativeOrder: Bool = false + public var recognizeShortenedNumbers: Bool = false + public var defaultRegion: String = "161" + public var recordBeep: Bool = false + public var showDebugInfo: Bool = false + @ObservableOnly public var backend: Constants.Backend { get { Constants.Backend(rawValue: backendString) ?? .de } set { backendString = newValue.rawValue } } - public var backendString: String = Constants.Backend.de.rawValue { - didSet { - defaults.backendString = backendString - } - } - - public init(defaults: UserDefaults) { - self.defaults = defaults - - observe(key: \.recognizeAlternativeOrder, for: \.recognizeAlternativeOrder) - observe(key: \.recognizeShortenedNumbers, for: \.recognizeShortenedNumbers) - observe(key: \.defaultRegion, for: \.defaultRegion) - observe(key: \.recordBeep, for: \.recordBeep) - observe(key: \.showDebugInfo, for: \.showDebugInfo) - observe(key: \.backendString, for: \.backendString) - observe(key: \.user, for: \.userData) - - register(defaultValues: [ - .recognizeAlternativeOrder: false, - .recognizeShortenedNumbers: false, - .defaultRegion: "761", - .recordBeep: false, - .showDebugInfo: false, - .backendString: Constants.Backend.de.rawValue - ]) - } - - deinit { - observations.forEach { $0.invalidate() } - } - - func observe(key userDefaultsKey: KeyPath, for settingsKey: ReferenceWritableKeyPath) where T: Equatable { - let observation = defaults.observe(userDefaultsKey, options: [.initial, .new]) { [weak self] _, change in - guard let self else { return } - - if let new = change.newValue, self[keyPath: settingsKey] != new { - self[keyPath: settingsKey] = new - } - } - observations.append(observation) - } - - func register(defaultValues: [SettingsKey: Any]) { - defaults.register(defaults: defaultValues.reduce(into: [:], { (partialResult: inout [String: Any], tuple) in - partialResult[tuple.key.rawValue] = tuple.value - })) - } + public var backendString: String = Constants.Backend.de.rawValue } diff --git a/AutoCatCore/Services/SettingsService/UserDefaults+Settings.swift b/AutoCatCore/Services/SettingsService/UserDefaults+Settings.swift deleted file mode 100644 index 751834d..0000000 --- a/AutoCatCore/Services/SettingsService/UserDefaults+Settings.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// UserDefaults+Settings.swift -// AutoCatCore -// -// Created by Selim Mustafaev on 25.08.2024. -// Copyright © 2024 Selim Mustafaev. All rights reserved. -// - -import Foundation - -enum SettingsKey: String { - - case user - case recognizeAlternativeOrder - case recognizeShortenedNumbers - case defaultRegion - case recordBeep - case showDebugInfo - case backendString -} - -extension UserDefaults { - - func bool(for key: SettingsKey) -> Bool { - bool(forKey: key.rawValue) - } - - func string(for key: SettingsKey, defaultValue: String = "") -> String { - string(forKey: key.rawValue) ?? defaultValue - } - - func data(for key: SettingsKey) -> Data? { - data(forKey: key.rawValue) - } - - func set(value: T, for key: SettingsKey) { - setValue(value, forKey: key.rawValue) - } - - @objc dynamic var recognizeAlternativeOrder: Bool { - get { bool(for: .recognizeAlternativeOrder) } - set { set(value: newValue, for: .recognizeAlternativeOrder) } - } - - @objc dynamic var recognizeShortenedNumbers: Bool { - get { bool(for: .recognizeShortenedNumbers) } - set { set(value: newValue, for: .recognizeShortenedNumbers) } - } - - @objc dynamic var defaultRegion: String { - get { string(for: .defaultRegion) } - set { set(value: newValue, for: .defaultRegion) } - } - - @objc dynamic var recordBeep: Bool { - get { bool(for: .recordBeep) } - set { set(value: newValue, for: .recordBeep) } - } - - @objc dynamic var showDebugInfo: Bool { - get { bool(for: .showDebugInfo) } - set { set(value: newValue, for: .showDebugInfo) } - } - - @objc dynamic var backendString: String { - get { string(for: .backendString, defaultValue: Constants.Backend.de.rawValue) } - set { set(value: newValue, for: .backendString) } - } - - @objc dynamic var user: Data? { - get { data(for: .user) } - set { set(value: newValue, for: .user) } - } -} diff --git a/AutoCatCoreTests/SettingsServiceTests.swift b/AutoCatCoreTests/SettingsServiceTests.swift index 64b8d28..1464f3b 100644 --- a/AutoCatCoreTests/SettingsServiceTests.swift +++ b/AutoCatCoreTests/SettingsServiceTests.swift @@ -34,7 +34,7 @@ struct SettingsServiceTests { self.testUser = User(email: testEmail, token: testToken) self.defaults = UserDefaults(suiteName: dbName)! self.defaults.removePersistentDomain(forName: dbName) - self.settingsService = SettingsService(defaults: defaults) + self.settingsService = SettingsService(userDefaults: self.defaults) } @Test("Creating default user") @@ -50,7 +50,7 @@ struct SettingsServiceTests { let data = try #require(testUser.data) defaults.setPersistentDomain(["user": data], forName: dbName) - let service = SettingsService(defaults: defaults) + let service = SettingsService(userDefaults: defaults) #expect(service.user.email == testEmail) #expect(service.user.token == testToken) @@ -73,7 +73,7 @@ struct SettingsServiceTests { settingsService.backend = value - #expect(defaults.string(forKey: SettingsKey.backendString.rawValue) == value.rawValue) + #expect(defaults.string(forKey: "backendString") == value.rawValue) } @Test("Save settings", .serialized, arguments: [true, false]) @@ -85,10 +85,10 @@ struct SettingsServiceTests { settingsService.recordBeep = value settingsService.showDebugInfo = value - #expect(defaults.bool(forKey: SettingsKey.recognizeAlternativeOrder.rawValue) == value) - #expect(defaults.bool(forKey: SettingsKey.recognizeShortenedNumbers.rawValue) == value) - #expect(defaults.string(forKey: SettingsKey.defaultRegion.rawValue) == testRegion) - #expect(defaults.bool(forKey: SettingsKey.recordBeep.rawValue) == value) - #expect(defaults.bool(forKey: SettingsKey.showDebugInfo.rawValue) == value) + #expect(defaults.bool(forKey: "recognizeAlternativeOrder") == value) + #expect(defaults.bool(forKey: "recognizeShortenedNumbers") == value) + #expect(defaults.string(forKey: "defaultRegion") == testRegion) + #expect(defaults.bool(forKey: "recordBeep") == value) + #expect(defaults.bool(forKey: "showDebugInfo") == value) } }