diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 469cafa..4b330c3 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 7A11470D23FDE7E600B424AF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A11470B23FDE7E600B424AF /* LaunchScreen.storyboard */; }; 7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471523FDEB2A00B424AF /* MainSplitController.swift */; }; 7A11471A23FE839000B424AF /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471923FE839000B424AF /* AuthController.swift */; }; + 7A123C742D9DC40A00781F24 /* RecordScreenOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A123C732D9DC40A00781F24 /* RecordScreenOutput.swift */; }; 7A131FD32D37B75500DC7755 /* HistoryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A131FD22D37B75500DC7755 /* HistoryScreen.swift */; }; 7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */; }; 7A131FD72D37B77E00DC7755 /* HistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A131FD62D37B77E00DC7755 /* HistoryCoordinator.swift */; }; @@ -318,6 +319,7 @@ 7A11474623FF2AA500B424AF /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 7A11474823FF2B2D00B424AF /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = ""; }; 7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = ""; }; + 7A123C732D9DC40A00781F24 /* RecordScreenOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordScreenOutput.swift; sourceTree = ""; }; 7A131FD22D37B75500DC7755 /* HistoryScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryScreen.swift; sourceTree = ""; }; 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = ""; }; 7A131FD62D37B77E00DC7755 /* HistoryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryCoordinator.swift; sourceTree = ""; }; @@ -1046,6 +1048,7 @@ 7A95197F2D80B6C100E69883 /* RecordsScreen.swift */, 7A9519812D80B6E500E69883 /* RecordsViewModel.swift */, 7A9519832D80B72B00E69883 /* RecordsCoordinator.swift */, + 7A123C732D9DC40A00781F24 /* RecordScreenOutput.swift */, ); path = RecordsScreen; sourceTree = ""; @@ -1585,6 +1588,7 @@ 7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */, 7A9519802D80B6C100E69883 /* RecordsScreen.swift in Sources */, 7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */, + 7A123C742D9DC40A00781F24 /* RecordScreenOutput.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0c5c026..027891f 100644 --- a/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/AutoCat.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Kolos65/Mockable", "state" : { - "revision" : "a9e5e1d222035567069ed6fff8429c327229b5f6", - "version" : "0.0.11" + "revision" : "68f3ed6c4b62afab27a84425494cb61421a61ac1", + "version" : "0.3.1" } }, { @@ -33,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/realm/realm-core.git", "state" : { - "revision" : "85eeca41654cc9070ad81a21a4b1d153ad467c33", - "version" : "14.13.1" + "revision" : "cccb3ca9e26ec452a29f2f0d4050d1e38b8a3d43", + "version" : "14.14.0" } }, { @@ -42,17 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/realm/realm-swift.git", "state" : { - "revision" : "5553cfd1c789efdb3d6daf7f0cc0733cfe601846", - "version" : "10.54.1" - } - }, - { - "identity" : "swift-issue-reporting", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-issue-reporting", - "state" : { - "revision" : "770f990d3e4eececb57ac04a6076e22f8c97daeb", - "version" : "1.4.2" + "revision" : "8cb364a6a63695df296f05b53f7c3f3b1dda6af3", + "version" : "10.54.3" } }, { @@ -60,8 +51,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "2bc86522d115234d1f588efe2bcb4ce4be8f8b82", - "version" : "510.0.3" + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" } }, { @@ -90,6 +81,15 @@ "revision" : "010073e62cea4daefea61042a51b8619d23cdc35", "version" : "6.0.0" } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "39de59b2d47f7ef3ca88a039dff3084688fe27f4", + "version" : "1.5.2" + } } ], "version" : 3 diff --git a/AutoCat/Controllers/MainTabController.swift b/AutoCat/Controllers/MainTabController.swift index 7581303..8898280 100644 --- a/AutoCat/Controllers/MainTabController.swift +++ b/AutoCat/Controllers/MainTabController.swift @@ -40,7 +40,7 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate { func addRecordsTab() { let coordinator = RecordsCoordinator() - let controller = coordinator.start() + let controller = coordinator.start(output: self) controller.tabBarItem = UITabBarItem(title: NSLocalizedString("Records", comment: ""), image: UIImage(named: "record"), tag: 0) viewControllers?[1] = controller @@ -108,3 +108,11 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate { Task { try? await RxLocationManager.requestCurrentLocation() } } } + +extension MainTabController: RecordScreenOutput { + + func checkRecord(number: String, event: VehicleEventDto?) { + selectedIndex = 0 + Task { await historyViewModel?.checkRecord(number: number, event: event) } + } +} diff --git a/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift b/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift index b6f7de3..04dc295 100644 --- a/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift +++ b/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift @@ -9,6 +9,13 @@ import SwiftUI import AutoCatCore +enum CheckType { + + case normal + case update + case record(VehicleEventDto?) +} + @MainActor @Observable final class HistoryViewModel: ACHudContainer { @@ -112,11 +119,15 @@ final class HistoryViewModel: ACHudContainer { } } - func checkVehicle(number: String, isUpdate: Bool) async { + func checkVehicle(number: String, type: CheckType) async { do { hud = .progress - let (vehicle, errors) = isUpdate ? try await vehicleService.updateHistory(number: number) - : try await vehicleService.check(number: number) + let (vehicle, errors) = switch type { + case .normal: try await vehicleService.check(number: number) + case .update: try await vehicleService.updateHistory(number: number) + case .record(let event): try await vehicleService.checkRecord(number: number, event: event) + } + await loadVehicles() if errors.isEmpty { @@ -133,7 +144,7 @@ final class HistoryViewModel: ACHudContainer { } func checkNewNumber(_ number: String) async { - await checkVehicle(number: number, isUpdate: false) + await checkVehicle(number: number, type: .normal) } func deleteVehicle(_ vehicle: VehicleDto) async { @@ -145,6 +156,10 @@ final class HistoryViewModel: ACHudContainer { } func updateVehicle(_ vehicle: VehicleDto) async { - await checkVehicle(number: vehicle.getNumber(), isUpdate: true) + await checkVehicle(number: vehicle.getNumber(), type: .update) + } + + func checkRecord(number: String, event: VehicleEventDto?) async { + await checkVehicle(number: number, type: .record(event)) } } diff --git a/AutoCat/Screens/RecordsScreen/RecordScreenOutput.swift b/AutoCat/Screens/RecordsScreen/RecordScreenOutput.swift new file mode 100644 index 0000000..1fb44d0 --- /dev/null +++ b/AutoCat/Screens/RecordsScreen/RecordScreenOutput.swift @@ -0,0 +1,15 @@ +// +// RecordScreenOutput.swift +// AutoCat +// +// Created by Selim Mustafaev on 02.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import AutoCatCore + +@MainActor +protocol RecordScreenOutput: AnyObject { + + func checkRecord(number: String, event: VehicleEventDto?) +} diff --git a/AutoCat/Screens/RecordsScreen/RecordsCoordinator.swift b/AutoCat/Screens/RecordsScreen/RecordsCoordinator.swift index ff86444..5d49e83 100644 --- a/AutoCat/Screens/RecordsScreen/RecordsCoordinator.swift +++ b/AutoCat/Screens/RecordsScreen/RecordsCoordinator.swift @@ -15,7 +15,7 @@ final class RecordsCoordinator { var navController = UINavigationController() - func start() -> UIViewController { + func start(output: RecordScreenOutput?) -> UIViewController { let resolver = ServiceContainer.shared let viewModel = RecordsViewModel( @@ -25,6 +25,7 @@ final class RecordsCoordinator { ) viewModel.coordinator = self + viewModel.output = output let view = RecordsScreen(viewModel: viewModel) let controller = UIHostingController(rootView: view) diff --git a/AutoCat/Screens/RecordsScreen/RecordsScreen.swift b/AutoCat/Screens/RecordsScreen/RecordsScreen.swift index f334050..99effe8 100644 --- a/AutoCat/Screens/RecordsScreen/RecordsScreen.swift +++ b/AutoCat/Screens/RecordsScreen/RecordsScreen.swift @@ -100,5 +100,11 @@ struct RecordsScreen: View { } label: { Label("Show on map", systemImage: "map") } + + Button { + viewModel.check(id: record.id) + } label: { + Label("Check", systemImage: "eye") + } } } diff --git a/AutoCat/Screens/RecordsScreen/RecordsViewModel.swift b/AutoCat/Screens/RecordsScreen/RecordsViewModel.swift index 845af3d..8aa08e6 100644 --- a/AutoCat/Screens/RecordsScreen/RecordsViewModel.swift +++ b/AutoCat/Screens/RecordsScreen/RecordsViewModel.swift @@ -17,6 +17,7 @@ final class RecordsViewModel: ACHudContainer { let storageService: StorageServiceProtocol let recordPlayer: RecordPlayerServiceProtocol var coordinator: RecordsCoordinator? + weak var output: RecordScreenOutput? var hud: ACHud? var showRecordingAlert: Bool = false @@ -122,4 +123,14 @@ final class RecordsViewModel: ACHudContainer { coordinator?.showOnMap(event: event) } + + func check(id: String) { + guard let record = records.first(where: { $0.id == id }), + let number = record.number + else { + return + } + + output?.checkRecord(number: number, event: record.event) + } } diff --git a/AutoCatCore/Services/VehicleService/VehicleService.swift b/AutoCatCore/Services/VehicleService/VehicleService.swift index 6b293e6..789379b 100644 --- a/AutoCatCore/Services/VehicleService/VehicleService.swift +++ b/AutoCatCore/Services/VehicleService/VehicleService.swift @@ -31,17 +31,27 @@ public final class VehicleService { extension VehicleService: VehicleServiceProtocol { - func check(number: String, - forceUpdate: Bool, - trackLocation: Bool, - dbUpdatePolicy: DbUpdatePolicy) async throws -> VehicleWithErrors { + func check( + number: String, + forceUpdate: Bool, + trackLocation: Bool, + additionalEvents: [VehicleEventDto], + dbUpdatePolicy: DbUpdatePolicy + ) async throws -> VehicleWithErrors { var vehicle = (try? await storageService.loadVehicle(number: number)) ?? VehicleDto(number: number) var errors: [Error] = [] - let events = vehicle.events + // TODO: Check if additionalEvents already was added earlier (avoid duplication) + let events = vehicle.events + additionalEvents let notes = vehicle.notes + if !additionalEvents.isEmpty { + vehicle.events = events + vehicle.updatedDate = Date().timeIntervalSince1970 + vehicle.synchronized = false + } + async let locationTask = trackLocation ? locationService.getRecentLocation() : nil async let vehicleTask = apiService.checkVehicle(by: number, notes: notes, events: events, force: forceUpdate) @@ -75,19 +85,54 @@ extension VehicleService: VehicleServiceProtocol { public func check(number: String) async throws -> VehicleWithErrors { #if targetEnvironment(macCatalyst) - try await check(number: number, forceUpdate: false, trackLocation: false, dbUpdatePolicy: .always) + try await check( + number: number, + forceUpdate: false, + trackLocation: false, + additionalEvents: [], + dbUpdatePolicy: .always + ) #else - try await check(number: number, forceUpdate: false, trackLocation: true, dbUpdatePolicy: .always) + try await check( + number: number, + forceUpdate: false, + trackLocation: true, + additionalEvents: [], + dbUpdatePolicy: .always + ) #endif } public func updateHistory(number: String) async throws -> VehicleWithErrors { - try await check(number: number, forceUpdate: true, trackLocation: false, dbUpdatePolicy: .always) + try await check( + number: number, + forceUpdate: true, + trackLocation: false, + additionalEvents: [], + dbUpdatePolicy: .always + ) } public func updateSearch(number: String) async throws -> VehicleWithErrors { - try await check(number: number, forceUpdate: true, trackLocation: false, dbUpdatePolicy: .ifExists) + try await check( + number: number, + forceUpdate: true, + trackLocation: false, + additionalEvents: [], + dbUpdatePolicy: .ifExists + ) + } + + public func checkRecord(number: String, event: VehicleEventDto?) async throws -> VehicleWithErrors { + + try await check( + number: number, + forceUpdate: false, + trackLocation: false, + additionalEvents: event.flatMap { [$0] } ?? [], + dbUpdatePolicy: .always + ) } } diff --git a/AutoCatCore/Services/VehicleService/VehicleServiceProtocol.swift b/AutoCatCore/Services/VehicleService/VehicleServiceProtocol.swift index ef14d65..20b519a 100644 --- a/AutoCatCore/Services/VehicleService/VehicleServiceProtocol.swift +++ b/AutoCatCore/Services/VehicleService/VehicleServiceProtocol.swift @@ -14,4 +14,5 @@ public protocol VehicleServiceProtocol: Sendable { func check(number: String) async throws -> VehicleWithErrors func updateHistory(number: String) async throws -> VehicleWithErrors func updateSearch(number: String) async throws -> VehicleWithErrors + func checkRecord(number: String, event: VehicleEventDto?) async throws -> VehicleWithErrors }