Removing ObservableDefaults, back to old dumb UserDefaults wrapper

This commit is contained in:
Selim Mustafaev 2025-04-09 18:33:29 +03:00
parent 0a6426b7f1
commit dc6281ac46
15 changed files with 195 additions and 76 deletions

View File

@ -116,7 +116,6 @@
7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C62DA2A45600276D83 /* RealmSwift */; }; 7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C62DA2A45600276D83 /* RealmSwift */; };
7A7AA2CA2DA2C85100276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; }; 7A7AA2CA2DA2C85100276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; };
7A7AA2CB2DA2C85100276D83 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 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 */; }; 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */; };
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A809F382D66755B00CF1B3C /* Error+Canceled.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 */; }; 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; };
@ -548,7 +547,6 @@
files = ( files = (
7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */, 7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */,
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */, 7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */,
7A7AA2CE2DA3120500276D83 /* ObservableDefaults in Frameworks */,
7AF8606E2CB9B86300954D2F /* Mockable in Frameworks */, 7AF8606E2CB9B86300954D2F /* Mockable in Frameworks */,
7A6C4D9E2C56BCA600982597 /* SwiftLocation in Frameworks */, 7A6C4D9E2C56BCA600982597 /* SwiftLocation in Frameworks */,
); );
@ -1314,7 +1312,6 @@
7A6C4D9D2C56BCA600982597 /* SwiftLocation */, 7A6C4D9D2C56BCA600982597 /* SwiftLocation */,
7AF8606D2CB9B86300954D2F /* Mockable */, 7AF8606D2CB9B86300954D2F /* Mockable */,
7A7AA2C62DA2A45600276D83 /* RealmSwift */, 7A7AA2C62DA2A45600276D83 /* RealmSwift */,
7A7AA2CD2DA3120500276D83 /* ObservableDefaults */,
); );
productName = AutoCatCore; productName = AutoCatCore;
productReference = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; productReference = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */;
@ -1363,7 +1360,6 @@
7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */, 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */,
7A6C4D9C2C56BCA600982597 /* XCRemoteSwiftPackageReference "SwiftLocation" */, 7A6C4D9C2C56BCA600982597 /* XCRemoteSwiftPackageReference "SwiftLocation" */,
7ACBB91C2CB9B155005A5168 /* XCRemoteSwiftPackageReference "Mockable" */, 7ACBB91C2CB9B155005A5168 /* XCRemoteSwiftPackageReference "Mockable" */,
7A7AA2CC2DA3120500276D83 /* XCRemoteSwiftPackageReference "ObservableDefaults" */,
); );
productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */; productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -2118,14 +2114,6 @@
minimumVersion = 6.0.0; 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" */ = { 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sindresorhus/ExceptionCatcher"; repositoryURL = "https://github.com/sindresorhus/ExceptionCatcher";
@ -2176,11 +2164,6 @@
package = 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */; package = 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */;
productName = RealmSwift; productName = RealmSwift;
}; };
7A7AA2CD2DA3120500276D83 /* ObservableDefaults */ = {
isa = XCSwiftPackageProductDependency;
package = 7A7AA2CC2DA3120500276D83 /* XCRemoteSwiftPackageReference "ObservableDefaults" */;
productName = ObservableDefaults;
};
7A813DC02508C4D900CC93B9 /* ExceptionCatcher */ = { 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */; package = 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */;

View File

@ -1,5 +1,5 @@
{ {
"originHash" : "3cc3aec63412867029bf57f8fff11744df284387a47994473d82e7aab44a4293", "originHash" : "6fccb9fdc0d29647d4f0b927aef60f375302d72b5b724992eab52ac0d8ec71c3",
"pins" : [ "pins" : [
{ {
"identity" : "exceptioncatcher", "identity" : "exceptioncatcher",
@ -19,15 +19,6 @@
"version" : "0.3.1" "version" : "0.3.1"
} }
}, },
{
"identity" : "observabledefaults",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fatbobman/ObservableDefaults",
"state" : {
"revision" : "4740e2c39043b7daff946aa3bbec22e6a68850d3",
"version" : "0.6.2"
}
},
{ {
"identity" : "pkhud", "identity" : "pkhud",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",

View File

@ -38,7 +38,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let container = ServiceContainer.shared let container = ServiceContainer.shared
let settingsService = SettingsService() let settingsService = await SettingsService()
container.register(SettingsServiceProtocol.self, instance: settingsService) container.register(SettingsServiceProtocol.self, instance: settingsService)

View File

@ -60,6 +60,8 @@ class EventsViewModel: ACHudContainer {
func updateEvents() { func updateEvents() {
let email = settingsService.user.email
events = vehicle.events.sorted { $0.date > $1.date }.map { event in events = vehicle.events.sorted { $0.date > $1.date }.map { event in
let date = Date(timeIntervalSince1970: event.date) let date = Date(timeIntervalSince1970: event.date)
@ -71,7 +73,7 @@ class EventsViewModel: ACHudContainer {
date: dateString, date: dateString,
coordinate: coordinate, coordinate: coordinate,
address: event.address ?? "Lat: \(event.latitude), Lon: \(event.longitude)", address: event.address ?? "Lat: \(event.latitude), Lon: \(event.longitude)",
isMe: event.addedBy == settingsService.user.email isMe: event.addedBy == email
) )
} }
} }

View File

@ -39,7 +39,7 @@ struct SettingsScreen: View {
} }
Section { Section {
Picker("Server", selection: $viewModel.settingService.backend) { Picker("Server", selection: $viewModel.backend) {
ForEach(Constants.Backend.allCases, id: \.self) { backend in ForEach(Constants.Backend.allCases, id: \.self) { backend in
Text(backend.name) Text(backend.name)
} }
@ -49,26 +49,26 @@ struct SettingsScreen: View {
Section("Plate number recognition") { Section("Plate number recognition") {
ToggleRowView(title: "Alternative order", ToggleRowView(title: "Alternative order",
description: "Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'", description: "Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'",
toggle: $viewModel.settingService.recognizeAlternativeOrder) toggle: $viewModel.recognizeAlternativeOrder)
ToggleRowView(title: "Shortened numbers", ToggleRowView(title: "Shortened numbers",
description: "If enabled, app will try to recognize shortened plate numbers (without region) and add default region", description: "If enabled, app will try to recognize shortened plate numbers (without region) and add default region",
toggle: $viewModel.settingService.recognizeShortenedNumbers) toggle: $viewModel.recognizeShortenedNumbers)
if viewModel.settingService.recognizeShortenedNumbers { if viewModel.recognizeShortenedNumbers {
LabeledContent("Default region") { LabeledContent("Default region") {
TextField("", text: $viewModel.settingService.defaultRegion) TextField("", text: $viewModel.defaultRegion)
.frame(width: 50) .frame(width: 50)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
} }
} }
ToggleRowView(title: "Beep before record", ToggleRowView(title: "Beep before record",
description: "When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri", description: "When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri",
toggle: $viewModel.settingService.recordBeep) toggle: $viewModel.recordBeep)
} }
Section("Debug") { Section("Debug") {
ToggleRowView(title: "Show debug info", ToggleRowView(title: "Show debug info",
description: nil, description: nil,
toggle: $viewModel.settingService.showDebugInfo) toggle: $viewModel.showDebugInfo)
} }
} }
.navigationTitle("Settings") .navigationTitle("Settings")

View File

@ -42,6 +42,36 @@ class SettingsViewModel {
return jwt.payload.email return jwt.payload.email
} }
var recognizeAlternativeOrder: Bool {
get { settingService.recognizeAlternativeOrder }
set { settingService.recognizeAlternativeOrder = newValue }
}
var recognizeShortenedNumbers: Bool {
get { settingService.recognizeShortenedNumbers }
set { settingService.recognizeShortenedNumbers = newValue }
}
var backend: Constants.Backend {
get { settingService.backend }
set { settingService.backend = newValue }
}
var recordBeep: Bool {
get { settingService.recordBeep }
set { settingService.recordBeep = newValue }
}
var showDebugInfo: Bool {
get { settingService.showDebugInfo }
set { settingService.showDebugInfo = newValue }
}
var defaultRegion: String {
get { settingService.defaultRegion }
set { settingService.defaultRegion = newValue }
}
init(settingsService: SettingsServiceProtocol) { init(settingsService: SettingsServiceProtocol) {
self.settingService = settingsService self.settingService = settingsService

View File

@ -1,5 +1,6 @@
import Foundation import Foundation
@MainActor
public struct User: Codable, Sendable { public struct User: Codable, Sendable {
public let email: String public let email: String
public var token: String public var token: String

View File

@ -20,7 +20,7 @@ public actor ApiService: ApiServiceProtocol {
private func createRequest<B,P>(api: String, method: String, body: B? = nil, params: [String:P]? = nil) async -> URLRequest? where B: Encodable, P: LosslessStringConvertible { private func createRequest<B,P>(api: String, method: String, body: B? = nil, params: [String:P]? = nil) async -> URLRequest? where B: Encodable, P: LosslessStringConvertible {
let baseUrl = settingsService.backend.baseUrl let baseUrl = await settingsService.backend.baseUrl
guard var urlComponents = URLComponents(string: baseUrl + api) else { return nil } guard var urlComponents = URLComponents(string: baseUrl + api) else { return nil }
@ -32,7 +32,7 @@ public actor ApiService: ApiServiceProtocol {
request.httpMethod = method request.httpMethod = method
request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Bearer " + settingsService.user.token, forHTTPHeaderField: "Authorization") request.addValue("Bearer " + (await settingsService.user.token), forHTTPHeaderField: "Authorization")
if let body = body, method.uppercased() != "GET" { if let body = body, method.uppercased() != "GET" {
let encoder = JSONEncoder() let encoder = JSONEncoder()
@ -109,8 +109,8 @@ public actor ApiService: ApiServiceProtocol {
// MARK: - Firebase API // MARK: - Firebase API
public func refreshFbToken() async throws { public func refreshFbToken() async throws {
guard let token = settingsService.user.firebaseIdToken, guard let token = await settingsService.user.firebaseIdToken,
let refreshToken = settingsService.user.firebaseRefreshToken, let refreshToken = await settingsService.user.firebaseRefreshToken,
let jwt = JWT<FirebasePayload>(string: token), jwt.expired else { let jwt = JWT<FirebasePayload>(string: token), jwt.expired else {
return return
} }
@ -138,11 +138,11 @@ public actor ApiService: ApiServiceProtocol {
let model = try JSONDecoder().decode(FbRefreshTokenModel.self, from: data) let model = try JSONDecoder().decode(FbRefreshTokenModel.self, from: data)
if let idToken = model.id_token { if let idToken = model.id_token {
await settingsService.user.firebaseIdToken = idToken await settingsService.setFirebaseIdToken(idToken)
} }
if let refreshToken = model.refresh_token { if let refreshToken = model.refresh_token {
await settingsService.user.firebaseRefreshToken = refreshToken await settingsService.setFirebaseRefreshToken(refreshToken)
} }
} }
@ -181,11 +181,11 @@ public actor ApiService: ApiServiceProtocol {
let model = try JSONDecoder().decode(FbVerifyTokenModel.self, from: data) let model = try JSONDecoder().decode(FbVerifyTokenModel.self, from: data)
if let idToken = model.idToken { if let idToken = model.idToken {
await settingsService.user.firebaseIdToken = idToken await settingsService.setFirebaseIdToken(idToken)
} }
if let refreshToken = model.refreshToken { if let refreshToken = model.refreshToken {
await settingsService.user.firebaseRefreshToken = refreshToken await settingsService.setFirebaseRefreshToken(refreshToken)
} }
} catch { } catch {
@ -237,7 +237,7 @@ public actor ApiService: ApiServiceProtocol {
"forceUpdate": AnyEncodable(force) "forceUpdate": AnyEncodable(force)
] ]
if let token = settingsService.user.firebaseIdToken { if let token = await settingsService.user.firebaseIdToken {
body["googleIdToken"] = AnyEncodable(token) body["googleIdToken"] = AnyEncodable(token)
} }
@ -324,7 +324,7 @@ public actor ApiService: ApiServiceProtocol {
var body = ["number": number] var body = ["number": number]
if let token = settingsService.user.firebaseIdToken { if let token = await settingsService.user.firebaseIdToken {
body["token"] = token body["token"] = token
} }

View File

@ -52,7 +52,11 @@ public final class LocationService {
throw LocationError.generic throw LocationError.generic
} }
return VehicleEventDto(lat: coordinate.latitude, lon: coordinate.longitude, addedBy: settingsService.user.email) return VehicleEventDto(
lat: coordinate.latitude,
lon: coordinate.longitude,
addedBy: await settingsService.user.email
)
} }
func setLastEvent(_ event: VehicleEventDto) { func setLastEvent(_ event: VehicleEventDto) {

View File

@ -7,18 +7,37 @@
// //
import Foundation import Foundation
import ObservableDefaults
@ObservableDefaults enum SettingsKey: String {
case user
case recognizeAlternativeOrder
case recognizeShortenedNumbers
case defaultRegion
case recordBeep
case showDebugInfo
case backendString
}
@MainActor
public final class SettingsService: SettingsServiceProtocol { public final class SettingsService: SettingsServiceProtocol {
@Ignore let jsonEncoder = JSONEncoder() let jsonEncoder = JSONEncoder()
@Ignore let jsonDecoder = JSONDecoder() let jsonDecoder = JSONDecoder()
let suiteName: String?
var defaults: UserDefaults {
if let suiteName {
return UserDefaults(suiteName: suiteName) ?? .standard
} else {
return .standard
}
}
@ObservableOnly
public var user: User { public var user: User {
get { get {
if userData.count > 0 { if let userData, userData.count > 0 {
let result = try? jsonDecoder.decode(User.self, from: userData) let result = try? jsonDecoder.decode(User.self, from: userData)
return result ?? User() return result ?? User()
} else { } else {
@ -33,20 +52,102 @@ public final class SettingsService: SettingsServiceProtocol {
} }
} }
@DefaultsKey(userDefaultsKey: "user") public var userData: Data? {
public var userData: Data = .init() didSet {
set(value: userData, for: .user)
}
}
public var recognizeAlternativeOrder: Bool = false public var recognizeAlternativeOrder: Bool = false {
public var recognizeShortenedNumbers: Bool = false didSet {
public var defaultRegion: String = "161" set(value: recognizeAlternativeOrder, for: .recognizeAlternativeOrder)
public var recordBeep: Bool = false }
public var showDebugInfo: Bool = false }
public var recognizeShortenedNumbers: Bool = false {
didSet {
set(value: recognizeShortenedNumbers, for: .recognizeShortenedNumbers)
}
}
public var defaultRegion: String = "161" {
didSet {
set(value: defaultRegion, for: .defaultRegion)
}
}
public var recordBeep: Bool = false {
didSet {
set(value: recordBeep, for: .recordBeep)
}
}
public var showDebugInfo: Bool = false {
didSet {
set(value: showDebugInfo, for: .showDebugInfo)
}
}
@ObservableOnly
public var backend: Constants.Backend { public var backend: Constants.Backend {
get { Constants.Backend(rawValue: backendString) ?? .de } get { Constants.Backend(rawValue: backendString) ?? .de }
set { backendString = newValue.rawValue } set { backendString = newValue.rawValue }
} }
public var backendString: String = Constants.Backend.de.rawValue public var backendString: String = Constants.Backend.de.rawValue {
didSet {
set(value: backendString, for: .backendString)
}
}
public init(suiteName: String? = nil) async {
self.suiteName = suiteName
register(defaultValues: [
.recognizeAlternativeOrder: false,
.recognizeShortenedNumbers: false,
.defaultRegion: "761",
.recordBeep: false,
.showDebugInfo: false,
.backendString: Constants.Backend.de.rawValue
])
userData = data(for: .user)
recognizeAlternativeOrder = bool(for: .recognizeAlternativeOrder)
recognizeShortenedNumbers = bool(for: .recognizeShortenedNumbers)
defaultRegion = string(for: .defaultRegion)
recordBeep = bool(for: .recordBeep)
showDebugInfo = bool(for: .showDebugInfo)
backendString = string(for: .backendString)
}
func register(defaultValues: [SettingsKey: Any]) {
defaults.register(defaults: defaultValues.reduce(into: [:], { (partialResult: inout [String: Any], tuple) in
partialResult[tuple.key.rawValue] = tuple.value
}))
}
func set<T>(value: T, for key: SettingsKey) {
defaults.setValue(value, forKey: key.rawValue)
}
func bool(for key: SettingsKey) -> Bool {
defaults.bool(forKey: key.rawValue)
}
func string(for key: SettingsKey, defaultValue: String = "") -> String {
defaults.string(forKey: key.rawValue) ?? defaultValue
}
func data(for key: SettingsKey) -> Data? {
defaults.data(forKey: key.rawValue)
}
public func setFirebaseIdToken(_ idToken: String?) {
user.firebaseIdToken = idToken
}
public func setFirebaseRefreshToken(_ refreshToken: String?) {
user.firebaseRefreshToken = refreshToken
}
} }

View File

@ -8,15 +8,18 @@
import Mockable import Mockable
@MainActor
@Mockable @Mockable
public protocol SettingsServiceProtocol: Sendable { public protocol SettingsServiceProtocol: Sendable {
var user: User { get set } var user: User { get set }
var recognizeAlternativeOrder: Bool { get set } var recognizeAlternativeOrder: Bool { get set }
var recognizeShortenedNumbers: Bool { get set } var recognizeShortenedNumbers: Bool { get set }
var defaultRegion: String { get set } var defaultRegion: String { get set }
var recordBeep: Bool { get set } var recordBeep: Bool { get set }
var showDebugInfo: Bool { get set } var showDebugInfo: Bool { get set }
var backend: Constants.Backend { get set } var backend: Constants.Backend { get set }
func setFirebaseIdToken(_ idToken: String?)
func setFirebaseRefreshToken(_ refreshToken: String?)
} }

View File

@ -16,7 +16,7 @@ extension StorageService {
throw StorageError.vehicleNotFound throw StorageError.vehicleNotFound
} }
let note = VehicleNote(text: text, user: settingsService.user.email) let note = VehicleNote(text: text, user: await settingsService.user.email)
try await realm.asyncWrite { try await realm.asyncWrite {
vehicle.notes.append(note) vehicle.notes.append(note)

View File

@ -33,7 +33,7 @@ public actor VehicleRecordService {
self.settingsService = settingsService self.settingsService = settingsService
} }
func getPlateNumber(from recognizedText: String?) -> String? { func getPlateNumber(from recognizedText: String?) async -> String? {
guard let recognizedText else { guard let recognizedText else {
return nil return nil
} }
@ -45,17 +45,21 @@ public actor VehicleRecordService {
.replacingOccurrences(of: "НОЛЬ", with: "0") .replacingOccurrences(of: "НОЛЬ", with: "0")
.replacingOccurrences(of: "Э", with: "") .replacingOccurrences(of: "Э", with: "")
let recognizeAlternativeOrder = await settingsService.recognizeAlternativeOrder
let recognizeShortenedNumbers = await settingsService.recognizeShortenedNumbers
let defaultRegion = await settingsService.defaultRegion
var result = "" var result = ""
if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) { if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
result = String(trimmed[range]) result = String(trimmed[range])
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression), settingsService.recognizeAlternativeOrder { } else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression), recognizeAlternativeOrder {
let n = String(trimmed[range]) let n = String(trimmed[range])
result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6) result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6)
} else if let range = trimmed.range(of: #"\S\d\d\d\S\S"#, options: .regularExpression), settingsService.recognizeShortenedNumbers { } else if let range = trimmed.range(of: #"\S\d\d\d\S\S"#, options: .regularExpression), recognizeShortenedNumbers {
result = String(trimmed[range]) + settingsService.defaultRegion result = String(trimmed[range]) + defaultRegion
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression), settingsService.recognizeAlternativeOrder && settingsService.recognizeShortenedNumbers { } else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression), recognizeAlternativeOrder && recognizeShortenedNumbers {
let n = String(trimmed[range]) let n = String(trimmed[range])
result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + settingsService.defaultRegion result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + defaultRegion
} }
if !result.isEmpty && valid(number: result) { if !result.isEmpty && valid(number: result) {
@ -123,7 +127,7 @@ extension VehicleRecordService: VehicleRecordServiceProtocol {
let record = AudioRecordDto( let record = AudioRecordDto(
path: url.lastPathComponent, path: url.lastPathComponent,
number: getPlateNumber(from: text), number: await getPlateNumber(from: text),
raw: text ?? "", raw: text ?? "",
duration: duration ?? 0, duration: duration ?? 0,
event: location event: location

View File

@ -30,11 +30,11 @@ struct SettingsServiceTests {
let defaults: UserDefaults let defaults: UserDefaults
var settingsService: SettingsService var settingsService: SettingsService
init() { init() async {
self.testUser = User(email: testEmail, token: testToken) self.testUser = User(email: testEmail, token: testToken)
self.defaults = UserDefaults(suiteName: dbName)! self.defaults = UserDefaults(suiteName: dbName)!
self.defaults.removePersistentDomain(forName: dbName) self.defaults.removePersistentDomain(forName: dbName)
self.settingsService = SettingsService(userDefaults: self.defaults) self.settingsService = await SettingsService(suiteName: dbName)
} }
@Test("Creating default user") @Test("Creating default user")
@ -50,7 +50,7 @@ struct SettingsServiceTests {
let data = try #require(testUser.data) let data = try #require(testUser.data)
defaults.setPersistentDomain(["user": data], forName: dbName) defaults.setPersistentDomain(["user": data], forName: dbName)
let service = SettingsService(userDefaults: defaults) let service = await SettingsService(suiteName: dbName)
#expect(service.user.email == testEmail) #expect(service.user.email == testEmail)
#expect(service.user.token == testToken) #expect(service.user.token == testToken)

View File

@ -34,7 +34,7 @@ struct StorageServiceTests {
try addTestVehicle(config: realmConfig) try addTestVehicle(config: realmConfig)
given(settingsServiceMock) await given(settingsServiceMock)
.user.willReturn(User()) .user.willReturn(User())
} }