Replacing UINavigationController with SwiftUI's NavigationStack
This commit is contained in:
parent
0ca4c232db
commit
180866c31b
@ -14,11 +14,9 @@
|
||||
7A06E0B52C707E2B005731AC /* SettingsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A06E0B42C707E2B005731AC /* SettingsService.swift */; };
|
||||
7A10226C2C551EC500B84627 /* LocationEditScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226B2C551EC500B84627 /* LocationEditScreen.swift */; };
|
||||
7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */; };
|
||||
7A1022702C551EFD00B84627 /* LocationEditCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10226F2C551EFD00B84627 /* LocationEditCoordinator.swift */; };
|
||||
7A1022722C554A1300B84627 /* CustomHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022712C554A1300B84627 /* CustomHostingController.swift */; };
|
||||
7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022762C557EC400B84627 /* LocationPickerScreen.swift */; };
|
||||
7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022782C557ED600B84627 /* LocationPickerViewModel.swift */; };
|
||||
7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A10227A2C557EE900B84627 /* LocationPickerCoordinator.swift */; };
|
||||
7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1090EB24A4E3E100B4F0B2 /* CellProgressView.swift */; };
|
||||
7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11470023FDE7E500B424AF /* AppDelegate.swift */; };
|
||||
7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11470223FDE7E500B424AF /* SceneDelegate.swift */; };
|
||||
@ -32,7 +30,6 @@
|
||||
7A131FD72D37B77E00DC7755 /* HistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A131FD62D37B77E00DC7755 /* HistoryCoordinator.swift */; };
|
||||
7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441652C297EDE00E79018 /* NotesScreen.swift */; };
|
||||
7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441672C297EFD00E79018 /* NotesViewModel.swift */; };
|
||||
7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A14416B2C297F2100E79018 /* NotesCoordinator.swift */; };
|
||||
7A14416E2C297F7C00E79018 /* Coordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A14416D2C297F7C00E79018 /* Coordinator.swift */; };
|
||||
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A17CE492A2E820300626A6E /* UIStackView.swift */; };
|
||||
7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */; };
|
||||
@ -40,7 +37,6 @@
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */; };
|
||||
7A1E78F62CE900330004B740 /* ReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F52CE900330004B740 /* ReportScreen.swift */; };
|
||||
7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F72CE900440004B740 /* ReportViewModel.swift */; };
|
||||
7A1E78FA2CE9005C0004B740 /* ReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78F92CE9005C0004B740 /* ReportCoordinator.swift */; };
|
||||
7A1E78FF2CE91A740004B740 /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1E78FE2CE91A740004B740 /* Vehicle.swift */; };
|
||||
7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */; };
|
||||
7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A2E11282CCE395300E5CA17 /* OptionalDatePicker.swift */; };
|
||||
@ -48,14 +44,12 @@
|
||||
7A3399AB299063370087DF98 /* SearchControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3399AA299063370087DF98 /* SearchControllerExt.swift */; };
|
||||
7A386A402DABDC190051676A /* MapScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A3F2DABDC190051676A /* MapScreen.swift */; };
|
||||
7A386A442DABDC360051676A /* MapViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A432DABDC360051676A /* MapViewModel.swift */; };
|
||||
7A386A462DABDC660051676A /* MapCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A452DABDC660051676A /* MapCoordinator.swift */; };
|
||||
7A386A482DABE0D00051676A /* MapMarkerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A386A472DABE0D00051676A /* MapMarkerModel.swift */; };
|
||||
7A386A4B2DAC35F10051676A /* ClusterMap in Frameworks */ = {isa = PBXBuildFile; productRef = 7A386A4A2DAC35F10051676A /* ClusterMap */; };
|
||||
7A386A4D2DAC35F10051676A /* ClusterMapSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7A386A4C2DAC35F10051676A /* ClusterMapSwiftUI */; };
|
||||
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 */; };
|
||||
7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322942CB2CD0F00085CF6 /* FiltersCoordinator.swift */; };
|
||||
7A45FB382C27073700618694 /* StorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A45FB372C27073700618694 /* StorageService.swift */; };
|
||||
7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4927D42CCE438600851C01 /* OptionalBinding.swift */; };
|
||||
7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4955812D58CCF900912E66 /* HistoryFilter.swift */; };
|
||||
@ -105,11 +99,8 @@
|
||||
7A6F096026DBF588003A965D /* VehicleNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F095F26DBF588003A965D /* VehicleNote.swift */; };
|
||||
7A7097C22C9EC139007CFDCA /* ServiceContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7097C12C9EC139007CFDCA /* ServiceContainer.swift */; };
|
||||
7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7157FF2C43EA6900852088 /* OwnersScreen.swift */; };
|
||||
7A7158042C43EAA200852088 /* OwnersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158032C43EAA200852088 /* OwnersCoordinator.swift */; };
|
||||
7A7158072C44085600852088 /* OsagoScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158062C44085600852088 /* OsagoScreen.swift */; };
|
||||
7A7158092C44087E00852088 /* OsagoCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158082C44087E00852088 /* OsagoCoordinator.swift */; };
|
||||
7A71580C2C44453200852088 /* AdsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A71580B2C44453200852088 /* AdsScreen.swift */; };
|
||||
7A71580E2C4445A200852088 /* AdsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A71580D2C4445A200852088 /* AdsCoordinator.swift */; };
|
||||
7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7158112C444A6400852088 /* AdsViewModel.swift */; };
|
||||
7A71EF572D0A26B200943129 /* EventModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A71EF562D0A26B200943129 /* EventModel.swift */; };
|
||||
7A761C042677F18E0005F28F /* ApiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474323FF06CA00B424AF /* ApiService.swift */; };
|
||||
@ -149,6 +140,7 @@
|
||||
7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */; };
|
||||
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; };
|
||||
7AABBE3B2CF9F85600346588 /* Binding+Map.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABBE3A2CF9F85600346588 /* Binding+Map.swift */; };
|
||||
7AADD4452DB2D4D60027FD7B /* MapInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AADD4442DB2D4D60027FD7B /* MapInput.swift */; };
|
||||
7AB0EF812C5CC0FE00291EE6 /* SwiftLocationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */; };
|
||||
7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */; };
|
||||
7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */; };
|
||||
@ -165,12 +157,9 @@
|
||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
|
||||
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8D2435D1A000258F61 /* CustomButton.swift */; };
|
||||
7AB9FE222D08C2A5005DE374 /* EventsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB9FE212D08C2A5005DE374 /* EventsScreen.swift */; };
|
||||
7AB9FE262D08C2D7005DE374 /* EventsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB9FE252D08C2D7005DE374 /* EventsCoordinator.swift */; };
|
||||
7AB9FE282D08C2F4005DE374 /* EventsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB9FE272D08C2F4005DE374 /* EventsViewModel.swift */; };
|
||||
7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB9FE292D08CF35005DE374 /* EventsScreenMode.swift */; };
|
||||
7ABD1B472D044A3200B43213 /* GalleryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD1B462D044A3200B43213 /* GalleryScreen.swift */; };
|
||||
7ABD1B492D044A4700B43213 /* GalleryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD1B482D044A4700B43213 /* GalleryViewModel.swift */; };
|
||||
7ABD1B4B2D044A7D00B43213 /* GalleryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD1B4A2D044A7D00B43213 /* GalleryCoordinator.swift */; };
|
||||
7ABDA8032D8704F70083C715 /* VehicleRecordService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABDA8022D8704F70083C715 /* VehicleRecordService.swift */; };
|
||||
7ABDA8052D8705210083C715 /* VehicleRecordServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABDA8042D8705210083C715 /* VehicleRecordServiceProtocol.swift */; };
|
||||
7ABDA8092D8710F80083C715 /* AutoCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABDA8082D8710F80083C715 /* AutoCancellable.swift */; };
|
||||
@ -290,11 +279,9 @@
|
||||
7A06E0B42C707E2B005731AC /* SettingsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsService.swift; sourceTree = "<group>"; };
|
||||
7A10226B2C551EC500B84627 /* LocationEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditScreen.swift; sourceTree = "<group>"; };
|
||||
7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditViewModel.swift; sourceTree = "<group>"; };
|
||||
7A10226F2C551EFD00B84627 /* LocationEditCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationEditCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A1022712C554A1300B84627 /* CustomHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomHostingController.swift; sourceTree = "<group>"; };
|
||||
7A1022762C557EC400B84627 /* LocationPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerScreen.swift; sourceTree = "<group>"; };
|
||||
7A1022782C557ED600B84627 /* LocationPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerViewModel.swift; sourceTree = "<group>"; };
|
||||
7A10227A2C557EE900B84627 /* LocationPickerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A1090EB24A4E3E100B4F0B2 /* CellProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellProgressView.swift; sourceTree = "<group>"; };
|
||||
7A1146FD23FDE7E500B424AF /* AutoCat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AutoCat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7A11470023FDE7E500B424AF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@ -314,7 +301,6 @@
|
||||
7A131FD62D37B77E00DC7755 /* HistoryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A1441652C297EDE00E79018 /* NotesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesScreen.swift; sourceTree = "<group>"; };
|
||||
7A1441672C297EFD00E79018 /* NotesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesViewModel.swift; sourceTree = "<group>"; };
|
||||
7A14416B2C297F2100E79018 /* NotesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A14416D2C297F7C00E79018 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
|
||||
7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||
7A17CE492A2E820300626A6E /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
|
||||
@ -323,7 +309,6 @@
|
||||
7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
7A1E78F52CE900330004B740 /* ReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportScreen.swift; sourceTree = "<group>"; };
|
||||
7A1E78F72CE900440004B740 /* ReportViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportViewModel.swift; sourceTree = "<group>"; };
|
||||
7A1E78F92CE9005C0004B740 /* ReportCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A1E78FE2CE91A740004B740 /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = "<group>"; };
|
||||
7A27ADF824A09CAD0035F39E /* CocoaError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CocoaError.swift; sourceTree = "<group>"; };
|
||||
7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteAlertModifier.swift; sourceTree = "<group>"; };
|
||||
@ -334,12 +319,10 @@
|
||||
7A3399AA299063370087DF98 /* SearchControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchControllerExt.swift; sourceTree = "<group>"; };
|
||||
7A386A3F2DABDC190051676A /* MapScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapScreen.swift; sourceTree = "<group>"; };
|
||||
7A386A432DABDC360051676A /* MapViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapViewModel.swift; sourceTree = "<group>"; };
|
||||
7A386A452DABDC660051676A /* MapCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A386A472DABE0D00051676A /* MapMarkerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapMarkerModel.swift; sourceTree = "<group>"; };
|
||||
7A3F07AA24360DC800E59687 /* Dated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dated.swift; sourceTree = "<group>"; };
|
||||
7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersScreen.swift; sourceTree = "<group>"; };
|
||||
7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = "<group>"; };
|
||||
7A4322942CB2CD0F00085CF6 /* FiltersCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A43F9F7246C8A6200BA5B49 /* JWT.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWT.swift; sourceTree = "<group>"; };
|
||||
7A45FB372C27073700618694 /* StorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageService.swift; sourceTree = "<group>"; };
|
||||
7A4927D42CCE438600851C01 /* OptionalBinding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalBinding.swift; sourceTree = "<group>"; };
|
||||
@ -399,11 +382,8 @@
|
||||
7A6F095F26DBF588003A965D /* VehicleNote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleNote.swift; sourceTree = "<group>"; };
|
||||
7A7097C12C9EC139007CFDCA /* ServiceContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceContainer.swift; sourceTree = "<group>"; };
|
||||
7A7157FF2C43EA6900852088 /* OwnersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnersScreen.swift; sourceTree = "<group>"; };
|
||||
7A7158032C43EAA200852088 /* OwnersCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnersCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A7158062C44085600852088 /* OsagoScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OsagoScreen.swift; sourceTree = "<group>"; };
|
||||
7A7158082C44087E00852088 /* OsagoCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OsagoCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A71580B2C44453200852088 /* AdsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsScreen.swift; sourceTree = "<group>"; };
|
||||
7A71580D2C4445A200852088 /* AdsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A7158112C444A6400852088 /* AdsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsViewModel.swift; sourceTree = "<group>"; };
|
||||
7A71EF562D0A26B200943129 /* EventModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventModel.swift; sourceTree = "<group>"; };
|
||||
7A761C0A267E8FF90005F28F /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
|
||||
@ -437,6 +417,7 @@
|
||||
7AAAFADB2C4D1E130050410D /* ACImageSliderView+Modifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACImageSliderView+Modifier.swift"; sourceTree = "<group>"; };
|
||||
7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACImageSliderModel.swift; sourceTree = "<group>"; };
|
||||
7AABBE3A2CF9F85600346588 /* Binding+Map.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Map.swift"; sourceTree = "<group>"; };
|
||||
7AADD4442DB2D4D60027FD7B /* MapInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapInput.swift; sourceTree = "<group>"; };
|
||||
7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = "<group>"; };
|
||||
7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocationProtocol.swift; sourceTree = "<group>"; };
|
||||
7AB490282D6B1217002F39C6 /* ACKeyboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACKeyboardView.swift; sourceTree = "<group>"; };
|
||||
@ -455,12 +436,9 @@
|
||||
7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
|
||||
7AB67E8D2435D1A000258F61 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; };
|
||||
7AB9FE212D08C2A5005DE374 /* EventsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsScreen.swift; sourceTree = "<group>"; };
|
||||
7AB9FE252D08C2D7005DE374 /* EventsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsCoordinator.swift; sourceTree = "<group>"; };
|
||||
7AB9FE272D08C2F4005DE374 /* EventsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsViewModel.swift; sourceTree = "<group>"; };
|
||||
7AB9FE292D08CF35005DE374 /* EventsScreenMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsScreenMode.swift; sourceTree = "<group>"; };
|
||||
7ABD1B462D044A3200B43213 /* GalleryScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryScreen.swift; sourceTree = "<group>"; };
|
||||
7ABD1B482D044A4700B43213 /* GalleryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryViewModel.swift; sourceTree = "<group>"; };
|
||||
7ABD1B4A2D044A7D00B43213 /* GalleryCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryCoordinator.swift; sourceTree = "<group>"; };
|
||||
7ABDA8022D8704F70083C715 /* VehicleRecordService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleRecordService.swift; sourceTree = "<group>"; };
|
||||
7ABDA8042D8705210083C715 /* VehicleRecordServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleRecordServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
7ABDA8082D8710F80083C715 /* AutoCancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCancellable.swift; sourceTree = "<group>"; };
|
||||
@ -593,7 +571,6 @@
|
||||
children = (
|
||||
7A10226B2C551EC500B84627 /* LocationEditScreen.swift */,
|
||||
7A10226D2C551EE000B84627 /* LocationEditViewModel.swift */,
|
||||
7A10226F2C551EFD00B84627 /* LocationEditCoordinator.swift */,
|
||||
);
|
||||
path = LocationEditScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -603,7 +580,6 @@
|
||||
children = (
|
||||
7A1022762C557EC400B84627 /* LocationPickerScreen.swift */,
|
||||
7A1022782C557ED600B84627 /* LocationPickerViewModel.swift */,
|
||||
7A10227A2C557EE900B84627 /* LocationPickerCoordinator.swift */,
|
||||
);
|
||||
path = LocationPickerScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -755,7 +731,6 @@
|
||||
children = (
|
||||
7A1441652C297EDE00E79018 /* NotesScreen.swift */,
|
||||
7A1441672C297EFD00E79018 /* NotesViewModel.swift */,
|
||||
7A14416B2C297F2100E79018 /* NotesCoordinator.swift */,
|
||||
7A2C96112C3B155B00AE46B5 /* NoteAlertModifier.swift */,
|
||||
);
|
||||
path = NotesScreen;
|
||||
@ -766,7 +741,6 @@
|
||||
children = (
|
||||
7A1E78F52CE900330004B740 /* ReportScreen.swift */,
|
||||
7A1E78F72CE900440004B740 /* ReportViewModel.swift */,
|
||||
7A1E78F92CE9005C0004B740 /* ReportCoordinator.swift */,
|
||||
);
|
||||
path = ReportScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -784,8 +758,8 @@
|
||||
children = (
|
||||
7A386A3F2DABDC190051676A /* MapScreen.swift */,
|
||||
7A386A432DABDC360051676A /* MapViewModel.swift */,
|
||||
7A386A452DABDC660051676A /* MapCoordinator.swift */,
|
||||
7A386A472DABE0D00051676A /* MapMarkerModel.swift */,
|
||||
7AADD4442DB2D4D60027FD7B /* MapInput.swift */,
|
||||
);
|
||||
path = MapScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -811,7 +785,6 @@
|
||||
children = (
|
||||
7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */,
|
||||
7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */,
|
||||
7A4322942CB2CD0F00085CF6 /* FiltersCoordinator.swift */,
|
||||
);
|
||||
path = FiltersScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -954,7 +927,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A7157FF2C43EA6900852088 /* OwnersScreen.swift */,
|
||||
7A7158032C43EAA200852088 /* OwnersCoordinator.swift */,
|
||||
);
|
||||
path = OwnersScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -963,7 +935,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A7158062C44085600852088 /* OsagoScreen.swift */,
|
||||
7A7158082C44087E00852088 /* OsagoCoordinator.swift */,
|
||||
);
|
||||
path = OsagoScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -972,7 +943,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A71580B2C44453200852088 /* AdsScreen.swift */,
|
||||
7A71580D2C4445A200852088 /* AdsCoordinator.swift */,
|
||||
7A7158112C444A6400852088 /* AdsViewModel.swift */,
|
||||
);
|
||||
path = AdsScreen;
|
||||
@ -1075,7 +1045,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7AB9FE212D08C2A5005DE374 /* EventsScreen.swift */,
|
||||
7AB9FE252D08C2D7005DE374 /* EventsCoordinator.swift */,
|
||||
7AB9FE272D08C2F4005DE374 /* EventsViewModel.swift */,
|
||||
7AB9FE292D08CF35005DE374 /* EventsScreenMode.swift */,
|
||||
7A71EF562D0A26B200943129 /* EventModel.swift */,
|
||||
@ -1087,8 +1056,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7ABD1B462D044A3200B43213 /* GalleryScreen.swift */,
|
||||
7ABD1B482D044A4700B43213 /* GalleryViewModel.swift */,
|
||||
7ABD1B4A2D044A7D00B43213 /* GalleryCoordinator.swift */,
|
||||
);
|
||||
path = GalleryScreen;
|
||||
sourceTree = "<group>";
|
||||
@ -1451,7 +1418,6 @@
|
||||
7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */,
|
||||
7A4322932CB2CCAA00085CF6 /* FiltersViewModel.swift in Sources */,
|
||||
7A5D7E0C2C71EB25002C17E7 /* ToggleRowView.swift in Sources */,
|
||||
7A7158092C44087E00852088 /* OsagoCoordinator.swift in Sources */,
|
||||
7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */,
|
||||
7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */,
|
||||
7A1E78FF2CE91A740004B740 /* Vehicle.swift in Sources */,
|
||||
@ -1472,11 +1438,9 @@
|
||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
|
||||
7AF860702CBAA24500954D2F /* NavigationLink.swift in Sources */,
|
||||
7A386A482DABE0D00051676A /* MapMarkerModel.swift in Sources */,
|
||||
7AB9FE262D08C2D7005DE374 /* EventsCoordinator.swift in Sources */,
|
||||
7A386A402DABDC190051676A /* MapScreen.swift in Sources */,
|
||||
7AB9FE282D08C2F4005DE374 /* EventsViewModel.swift in Sources */,
|
||||
7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */,
|
||||
7A386A462DABDC660051676A /* MapCoordinator.swift in Sources */,
|
||||
7A5911EE2D63226F00EC51BA /* SearchScreen.swift in Sources */,
|
||||
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */,
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
|
||||
@ -1489,8 +1453,6 @@
|
||||
7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */,
|
||||
7AC3554E29696C4500889457 /* DummyNewController.swift in Sources */,
|
||||
7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */,
|
||||
7A1E78FA2CE9005C0004B740 /* ReportCoordinator.swift in Sources */,
|
||||
7A71580E2C4445A200852088 /* AdsCoordinator.swift in Sources */,
|
||||
7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */,
|
||||
7AB4E4662D58A16C0006D052 /* GenericError.swift in Sources */,
|
||||
7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */,
|
||||
@ -1499,7 +1461,6 @@
|
||||
7A1E78F62CE900330004B740 /* ReportScreen.swift in Sources */,
|
||||
7A10226C2C551EC500B84627 /* LocationEditScreen.swift in Sources */,
|
||||
7A7158072C44085600852088 /* OsagoScreen.swift in Sources */,
|
||||
7ABD1B492D044A4700B43213 /* GalleryViewModel.swift in Sources */,
|
||||
7A386A442DABDC360051676A /* MapViewModel.swift in Sources */,
|
||||
7ADFC9592DAD1C3D001A43E3 /* GoogleAuthViewModel.swift in Sources */,
|
||||
7AAAFAD32C4D0FD00050410D /* ACImageSliderView.swift in Sources */,
|
||||
@ -1513,7 +1474,6 @@
|
||||
7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */,
|
||||
7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */,
|
||||
7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */,
|
||||
7A10227B2C557EE900B84627 /* LocationPickerCoordinator.swift in Sources */,
|
||||
7AB490292D6B1217002F39C6 /* ACKeyboardView.swift in Sources */,
|
||||
7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */,
|
||||
7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */,
|
||||
@ -1526,7 +1486,6 @@
|
||||
7ADFC95B2DAD1F45001A43E3 /* WebView.swift in Sources */,
|
||||
7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */,
|
||||
7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */,
|
||||
7A14416C2C297F2100E79018 /* NotesCoordinator.swift in Sources */,
|
||||
7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */,
|
||||
7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */,
|
||||
7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */,
|
||||
@ -1538,7 +1497,6 @@
|
||||
7AFBE8C42C302561003C491D /* ACHudContainer.swift in Sources */,
|
||||
7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */,
|
||||
7A06E0AC2C7065AC005731AC /* SettingsScreen.swift in Sources */,
|
||||
7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */,
|
||||
7AF231992DA27C1B00AE5EB3 /* ACButtonView.swift in Sources */,
|
||||
7A131FD72D37B77E00DC7755 /* HistoryCoordinator.swift in Sources */,
|
||||
7A7158002C43EA6900852088 /* OwnersScreen.swift in Sources */,
|
||||
@ -1546,6 +1504,7 @@
|
||||
7A4322912CB2CC8A00085CF6 /* FiltersScreen.swift in Sources */,
|
||||
7ABD1B472D044A3200B43213 /* GalleryScreen.swift in Sources */,
|
||||
7A71580C2C44453200852088 /* AdsScreen.swift in Sources */,
|
||||
7AADD4452DB2D4D60027FD7B /* MapInput.swift in Sources */,
|
||||
7A06E0B02C7065D8005731AC /* SettingsCoordinator.swift in Sources */,
|
||||
7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */,
|
||||
7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */,
|
||||
@ -1553,11 +1512,8 @@
|
||||
7AB9FE222D08C2A5005DE374 /* EventsScreen.swift in Sources */,
|
||||
7ADF6C93250B954900F237B2 /* Navigation.swift in Sources */,
|
||||
7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */,
|
||||
7ABD1B4B2D044A7D00B43213 /* GalleryCoordinator.swift in Sources */,
|
||||
7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */,
|
||||
7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */,
|
||||
7A1022702C551EFD00B84627 /* LocationEditCoordinator.swift in Sources */,
|
||||
7A7158042C43EAA200852088 /* OwnersCoordinator.swift in Sources */,
|
||||
7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */,
|
||||
7A9519802D80B6C100E69883 /* RecordsScreen.swift in Sources */,
|
||||
7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */,
|
||||
|
||||
@ -5,6 +5,7 @@ import PKHUD
|
||||
import AutoCatCore
|
||||
import SwiftLocation
|
||||
import CoreLocation
|
||||
import SwiftUI
|
||||
|
||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
@ -165,8 +166,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
let vehicle = try await apiService.getReport(for: number)
|
||||
|
||||
Task {
|
||||
let coordinator = ReportCoordinator(controller: rootController, vehicle: vehicle, isPersistent: false)
|
||||
_ = try? await coordinator.start()
|
||||
let screen = ReportScreen(vehicle: vehicle, isPersistent: false, onUpdate: { _ in })
|
||||
let controller = UIHostingController(rootView: screen)
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
rootController.present(navController, animated: true)
|
||||
}
|
||||
|
||||
HUD.hide()
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
//
|
||||
// AdsCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 14.07.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
class AdsCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController?
|
||||
let ads: [VehicleAdDto]
|
||||
|
||||
init(navController: UINavigationController, ads: [VehicleAdDto]) {
|
||||
|
||||
self.viewController = navController
|
||||
self.ads = ads
|
||||
}
|
||||
|
||||
func start() async throws {
|
||||
|
||||
let viewModel = await AdsViewModel(ads: ads)
|
||||
let controller = await UIHostingController(rootView: AdsScreen(viewModel: viewModel))
|
||||
await viewController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct AdsScreen: View {
|
||||
|
||||
@ -14,6 +15,10 @@ struct AdsScreen: View {
|
||||
|
||||
@State var galleryModel: ACImageSliderModel?
|
||||
|
||||
init(ads: [VehicleAdDto]) {
|
||||
self.viewModel = AdsViewModel(ads: ads)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List(viewModel.ads, id: \.self) { ad in
|
||||
Section(viewModel.dateStringFrom(ad.date)) {
|
||||
@ -68,7 +73,3 @@ struct AdsScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AdsScreen(viewModel: AdsViewModel(ads: []))
|
||||
}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
//
|
||||
// EventsCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 10.12.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class EventsCoordinator {
|
||||
|
||||
let navController: UINavigationController
|
||||
|
||||
init(navController: UINavigationController) {
|
||||
|
||||
self.navController = navController
|
||||
}
|
||||
|
||||
func start(vehicle: VehicleDto) async -> VehicleDto {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = EventsViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
settingsService: resolver.resolve(SettingsServiceProtocol.self),
|
||||
vehicle: vehicle
|
||||
)
|
||||
viewModel.coordinator = self
|
||||
let controller = CustomHostingController(rootView: EventsScreen(viewModel: viewModel))
|
||||
navController.pushViewController(controller, animated: true)
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.vehicle
|
||||
}
|
||||
|
||||
func editEvent(event: VehicleEventDto) async -> VehicleEventDto? {
|
||||
|
||||
let coordinator = LocationEditCoordinator(navController: navController)
|
||||
return await coordinator.start(event: event)
|
||||
}
|
||||
}
|
||||
@ -12,13 +12,27 @@ import MapKit
|
||||
|
||||
struct EventsScreen: View {
|
||||
|
||||
enum Screen: Hashable {
|
||||
|
||||
case newEvent(VehicleEventDto)
|
||||
case editEvent(VehicleEventDto)
|
||||
}
|
||||
|
||||
@State var viewModel: EventsViewModel
|
||||
@State var selectedEvent: EventModel?
|
||||
@State var deleteConfirmationPresented: Bool = false
|
||||
@State var eventToDelete: EventModel?
|
||||
|
||||
init(viewModel: EventsViewModel) {
|
||||
self.viewModel = viewModel
|
||||
init(vehicle: VehicleDto, onUpdate: @escaping (VehicleDto) -> Void) {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
self.viewModel = EventsViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
settingsService: resolver.resolve(SettingsServiceProtocol.self),
|
||||
vehicle: vehicle,
|
||||
onUpdate: onUpdate
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -39,8 +53,14 @@ struct EventsScreen: View {
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "plus") {
|
||||
Task { await viewModel.addNewEvent() }
|
||||
NavigationLink(
|
||||
value: Screen.newEvent(VehicleEventDto(
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
addedBy: viewModel.settingsService.user.email
|
||||
))
|
||||
) {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
@ -72,7 +92,14 @@ struct EventsScreen: View {
|
||||
} message: {
|
||||
Text("Are you sure you want to delete this event?")
|
||||
}
|
||||
|
||||
.navigationDestination(for: Screen.self) { screen in
|
||||
switch screen {
|
||||
case .newEvent(let event):
|
||||
LocationEditScreen( event: event, onUpdate: viewModel.onNewEvent)
|
||||
case .editEvent(let event):
|
||||
LocationEditScreen(event: event, onUpdate: viewModel.onEventUpdated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var map: some View {
|
||||
@ -125,10 +152,10 @@ struct EventsScreen: View {
|
||||
@ViewBuilder
|
||||
func makeActions(for event: EventModel, useLabels: Bool = false) -> some View {
|
||||
|
||||
Button() {
|
||||
Task { await viewModel.editEvent(event) }
|
||||
} label: {
|
||||
Label(useLabels ? "Edit" : "", systemImage: "pencil")
|
||||
if let dto = viewModel.vehicle.events.first(where: { $0.id == event.id }) {
|
||||
NavigationLink(value: Screen.editEvent(dto)) {
|
||||
Label(useLabels ? "Edit" : "", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
|
||||
Button() {
|
||||
@ -157,14 +184,3 @@ struct EventsScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#Preview {
|
||||
EventsScreen(viewModel: .init(apiService: MockApiServiceProtocol(),
|
||||
storageService: MockStorageServiceProtocol(),
|
||||
settingsService: MockSettingsServiceProtocol(),
|
||||
vehicle: .preview))
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -30,8 +30,6 @@ class EventsViewModel: ACHudContainer {
|
||||
let storageService: StorageServiceProtocol
|
||||
let settingsService: SettingsServiceProtocol
|
||||
|
||||
weak var coordinator: EventsCoordinator?
|
||||
|
||||
var hud: ACHud?
|
||||
|
||||
var vehicle: VehicleDto
|
||||
@ -41,6 +39,8 @@ class EventsViewModel: ACHudContainer {
|
||||
var showPasteAlert: Bool = false
|
||||
var pastedEvent: VehicleEventDto?
|
||||
|
||||
var onUpdate: (VehicleDto) -> Void
|
||||
|
||||
var isPasteAvailable: Bool {
|
||||
UIPasteboard.general.data(forPasteboardType: UTType.vehicleEvent.identifier) != nil
|
||||
}
|
||||
@ -53,12 +53,14 @@ class EventsViewModel: ACHudContainer {
|
||||
init(apiService: ApiServiceProtocol,
|
||||
storageService: StorageServiceProtocol,
|
||||
settingsService: SettingsServiceProtocol,
|
||||
vehicle: VehicleDto) {
|
||||
vehicle: VehicleDto,
|
||||
onUpdate: @escaping (VehicleDto) -> Void) {
|
||||
|
||||
self.apiService = apiService
|
||||
self.storageService = storageService
|
||||
self.settingsService = settingsService
|
||||
self.vehicle = vehicle
|
||||
self.onUpdate = onUpdate
|
||||
|
||||
updateEvents()
|
||||
}
|
||||
@ -82,6 +84,28 @@ class EventsViewModel: ACHudContainer {
|
||||
user: event.addedBy
|
||||
)
|
||||
}
|
||||
|
||||
onUpdate(vehicle)
|
||||
}
|
||||
|
||||
func onEventUpdated(_ event: VehicleEventDto) {
|
||||
|
||||
Task {
|
||||
await eventOperation {
|
||||
try await self.storageService.edit(event: event, for: self.vehicle.getNumber())
|
||||
} apiOperation: {
|
||||
try await self.apiService.edit(event: event)
|
||||
}
|
||||
|
||||
updateEvents()
|
||||
}
|
||||
}
|
||||
|
||||
func onNewEvent(_ event: VehicleEventDto) {
|
||||
|
||||
Task {
|
||||
await addEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
func addEvent(_ event: VehicleEventDto) async {
|
||||
@ -95,14 +119,6 @@ class EventsViewModel: ACHudContainer {
|
||||
updateEvents()
|
||||
}
|
||||
|
||||
func addNewEvent() async {
|
||||
|
||||
let emptyEvent = VehicleEventDto(lat: 0, lon: 0, addedBy: settingsService.user.email)
|
||||
if let newEvent = await coordinator?.editEvent(event: emptyEvent) {
|
||||
await addEvent(newEvent)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteEvent(_ event: EventModel) async {
|
||||
|
||||
await eventOperation {
|
||||
@ -114,22 +130,6 @@ class EventsViewModel: ACHudContainer {
|
||||
updateEvents()
|
||||
}
|
||||
|
||||
func editEvent(_ event: EventModel) async {
|
||||
guard let eventDto = vehicle.events.first(where: { $0.id == event.id }) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let updatedEvent = await coordinator?.editEvent(event: eventDto) {
|
||||
await eventOperation {
|
||||
try await self.storageService.edit(event: updatedEvent, for: self.vehicle.getNumber())
|
||||
} apiOperation: {
|
||||
try await self.apiService.edit(event: updatedEvent)
|
||||
}
|
||||
|
||||
updateEvents()
|
||||
}
|
||||
}
|
||||
|
||||
func eventOperation(_ storageOperation: @escaping VehicleOperation, apiOperation: @escaping VehicleOperation) async {
|
||||
|
||||
if vehicle.unrecognized {
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
//
|
||||
// FiltersCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 06.10.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import AutoCatCore
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
class FiltersCoordinator {
|
||||
|
||||
let viewController: UINavigationController
|
||||
let filter: Filter
|
||||
|
||||
init(navController: UINavigationController, filter: Filter) {
|
||||
|
||||
self.viewController = navController
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
func start() async -> Filter? {
|
||||
let viewModel = FiltersViewModel(
|
||||
apiService: ServiceContainer.shared.resolve(ApiServiceProtocol.self),
|
||||
filter: filter
|
||||
)
|
||||
let controller = CustomHostingController(rootView: FiltersScreen(viewModel: viewModel))
|
||||
viewController.pushViewController(controller, animated: true)
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.filterResult
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,14 @@ struct FiltersScreen: View {
|
||||
|
||||
@State var viewModel: FiltersViewModel
|
||||
|
||||
init(filter: Filter, onUpdate: @escaping (Filter) -> Void) {
|
||||
self.viewModel = FiltersViewModel(
|
||||
apiService: ServiceContainer.shared.resolve(ApiServiceProtocol.self),
|
||||
filter: filter,
|
||||
onUpdate: onUpdate
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Main filters") {
|
||||
@ -118,14 +126,3 @@ struct FiltersScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#Preview {
|
||||
FiltersScreen(viewModel: .init(
|
||||
apiService: MockApiServiceProtocol(),
|
||||
filter: Filter()
|
||||
))
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -26,8 +26,6 @@ class FiltersViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
@ObservationIgnored var filterResult: Filter?
|
||||
|
||||
var models: [StringOption] = [.any]
|
||||
var brands: [StringOption] = [.any]
|
||||
var colors: [StringOption] = [.any]
|
||||
@ -35,10 +33,16 @@ class FiltersViewModel {
|
||||
|
||||
@ObservationIgnored var currentBrand: StringOption = .any
|
||||
|
||||
init(apiService: ApiServiceProtocol, filter: Filter) {
|
||||
let onUpdate: (Filter) -> Void
|
||||
|
||||
init(
|
||||
apiService: ApiServiceProtocol,
|
||||
filter: Filter,
|
||||
onUpdate: @escaping (Filter) -> Void
|
||||
) {
|
||||
self.apiService = apiService
|
||||
self.filter = filter
|
||||
self.onUpdate = onUpdate
|
||||
}
|
||||
|
||||
func loadData() async {
|
||||
@ -61,7 +65,7 @@ class FiltersViewModel {
|
||||
}
|
||||
|
||||
func applyFilters() {
|
||||
filterResult = filter
|
||||
onUpdate(filter)
|
||||
}
|
||||
|
||||
func nullifyTime(of date: Date?) -> Date? {
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
//
|
||||
// GalleryCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 07.12.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class GalleryCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController
|
||||
let photos: [VehiclePhotoDto]
|
||||
|
||||
init(navController: UINavigationController, photos: [VehiclePhotoDto]) {
|
||||
|
||||
self.viewController = navController
|
||||
self.photos = photos
|
||||
}
|
||||
|
||||
func start() async throws {
|
||||
|
||||
let viewModel = GalleryViewModel(photos: photos)
|
||||
let controller = UIHostingController(rootView: GalleryScreen(viewModel: viewModel))
|
||||
viewController.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
@ -11,14 +11,14 @@ import AutoCatCore
|
||||
|
||||
struct GalleryScreen: View {
|
||||
|
||||
@State var viewModel: GalleryViewModel
|
||||
let photos: [VehiclePhotoDto]
|
||||
|
||||
@State var galleryModel: ACImageSliderModel?
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 2) {
|
||||
ForEach(viewModel.photos) { photo in
|
||||
ForEach(photos) { photo in
|
||||
ZStack {
|
||||
AsyncImage(url: URL(string: photo.url)) { phase in
|
||||
switch phase {
|
||||
@ -43,7 +43,7 @@ struct GalleryScreen: View {
|
||||
}
|
||||
|
||||
galleryModel = ACImageSliderModel(
|
||||
urls: viewModel.photos.compactMap { URL(string: $0.url) },
|
||||
urls: photos.compactMap { URL(string: $0.url) },
|
||||
selected: url
|
||||
)
|
||||
}
|
||||
@ -66,7 +66,3 @@ struct GalleryScreen: View {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
GalleryScreen(viewModel: .init(photos: []))
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
//
|
||||
// GalleryViewModel.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 07.12.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
class GalleryViewModel {
|
||||
|
||||
let photos: [VehiclePhotoDto]
|
||||
|
||||
init(photos: [VehiclePhotoDto]) {
|
||||
self.photos = photos
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,6 @@ import AutoCatCore
|
||||
@MainActor
|
||||
final class HistoryCoordinator {
|
||||
|
||||
var navController: UINavigationController?
|
||||
|
||||
func start() -> (UIViewController, HistoryViewModel) {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
@ -24,22 +22,9 @@ final class HistoryCoordinator {
|
||||
vehicleService: resolver.resolve(VehicleServiceProtocol.self)
|
||||
)
|
||||
|
||||
viewModel.coordinator = self
|
||||
|
||||
let view = HistoryScreen(viewModel: viewModel)
|
||||
let controller = UIHostingController(rootView: view)
|
||||
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
self.navController = navController
|
||||
|
||||
return (navController, viewModel)
|
||||
}
|
||||
|
||||
func openReport(vehicle: VehicleDto) async {
|
||||
|
||||
let coordinator = ReportCoordinator(controller: navController,
|
||||
vehicle: vehicle,
|
||||
isPersistent: true)
|
||||
_ = try? await coordinator.start()
|
||||
return (controller, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,69 +16,89 @@ struct HistoryScreen: View {
|
||||
@State var exportSheetPresented = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(viewModel.vehicleSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { vehicle in
|
||||
VehicleCellView(vehicle: vehicle)
|
||||
.onTapGesture {
|
||||
Task { await viewModel.openReport(vehicle: vehicle) }
|
||||
}
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: vehicle)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: vehicle, useLabels: true)
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(viewModel.vehicleSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { vehicle in
|
||||
NavigationLink(value: vehicle) {
|
||||
vehicleCell(vehicle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.hud($viewModel.hud)
|
||||
.listStyle(.plain)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehiclesCount))
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "square.and.arrow.up") {
|
||||
exportSheetPresented = true
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.hud($viewModel.hud)
|
||||
.listStyle(.plain)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehiclesCount))
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "square.and.arrow.up") {
|
||||
exportSheetPresented = true
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "line.horizontal.3.decrease") {
|
||||
filterSheetPresented = true
|
||||
}
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button("", systemImage: "line.horizontal.3.decrease") {
|
||||
filterSheetPresented = true
|
||||
.confirmationDialog("Filter check history", isPresented: $filterSheetPresented, titleVisibility: .visible) {
|
||||
ForEach(HistoryFilter.allCases) { filter in
|
||||
Button(filter.title) {
|
||||
viewModel.filter = filter
|
||||
viewModel.applyFilters()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Filter check history", isPresented: $filterSheetPresented, titleVisibility: .visible) {
|
||||
ForEach(HistoryFilter.allCases) { filter in
|
||||
Button(filter.title) {
|
||||
viewModel.filter = filter
|
||||
viewModel.applyFilters()
|
||||
}
|
||||
}
|
||||
}
|
||||
.confirmationDialog("Export history as", isPresented: $exportSheetPresented, titleVisibility: .visible) {
|
||||
.confirmationDialog("Export history as", isPresented: $exportSheetPresented, titleVisibility: .visible) {
|
||||
|
||||
ShareLink(item: viewModel.vehiclesArchive, preview: SharePreview(VehiclesArchive.fileName)) {
|
||||
Text("CSV table")
|
||||
}
|
||||
ShareLink(item: viewModel.vehiclesArchive, preview: SharePreview(VehiclesArchive.fileName)) {
|
||||
Text("CSV table")
|
||||
}
|
||||
|
||||
if let dbUrl = viewModel.dbFileURL {
|
||||
ShareLink(item: dbUrl) {
|
||||
Text("Database file")
|
||||
if let dbUrl = viewModel.dbFileURL {
|
||||
ShareLink(item: dbUrl) {
|
||||
Text("Database file")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: VehicleDto.self) { vehicle in
|
||||
ReportScreen(
|
||||
vehicle: vehicle,
|
||||
isPersistent: true,
|
||||
onUpdate: viewModel.onVehicleChanged
|
||||
)
|
||||
}
|
||||
.navigationDestination(item: $viewModel.vehicleToOpen) { vehicle in
|
||||
ReportScreen(
|
||||
vehicle: vehicle,
|
||||
isPersistent: true,
|
||||
onUpdate: viewModel.onVehicleChanged
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func vehicleCell(_ vehicle: VehicleDto) -> some View {
|
||||
VehicleCellView(vehicle: vehicle)
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: vehicle)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: vehicle, useLabels: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeActions(for vehicle: VehicleDto, useLabels: Bool = false) -> some View {
|
||||
|
||||
@ -95,7 +115,3 @@ struct HistoryScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// HistoryScreen(viewModel: .init())
|
||||
//}
|
||||
|
||||
@ -23,7 +23,6 @@ final class HistoryViewModel: ACHudContainer {
|
||||
let apiService: ApiServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
let vehicleService: VehicleServiceProtocol
|
||||
var coordinator: HistoryCoordinator?
|
||||
|
||||
var hud: ACHud?
|
||||
|
||||
@ -31,6 +30,8 @@ final class HistoryViewModel: ACHudContainer {
|
||||
var vehiclesFiltered: [VehicleDto] = []
|
||||
var vehicleSections: [DateSection<VehicleDto>] = []
|
||||
|
||||
var vehicleToOpen: VehicleDto?
|
||||
|
||||
var searchText: String = "" {
|
||||
didSet {
|
||||
if searchText != oldValue {
|
||||
@ -75,11 +76,6 @@ final class HistoryViewModel: ACHudContainer {
|
||||
applyFilters()
|
||||
}
|
||||
|
||||
func openReport(vehicle: VehicleDto) async {
|
||||
await coordinator?.openReport(vehicle: vehicle)
|
||||
await loadVehicles()
|
||||
}
|
||||
|
||||
func applyFilters() {
|
||||
vehiclesFiltered = filterType(vehicles)
|
||||
vehiclesFiltered = filterSearch(vehiclesFiltered)
|
||||
@ -133,7 +129,7 @@ final class HistoryViewModel: ACHudContainer {
|
||||
if errors.isEmpty {
|
||||
hud = nil
|
||||
if !vehicle.unrecognized {
|
||||
await openReport(vehicle: vehicle)
|
||||
vehicleToOpen = vehicle
|
||||
}
|
||||
} else {
|
||||
showErrors(errors)
|
||||
@ -162,4 +158,11 @@ final class HistoryViewModel: ACHudContainer {
|
||||
func checkRecord(number: String, event: VehicleEventDto?) async {
|
||||
await checkVehicle(number: number, type: .record(event))
|
||||
}
|
||||
|
||||
func onVehicleChanged(_ vehicle: VehicleDto) {
|
||||
|
||||
Task {
|
||||
await loadVehicles()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
//
|
||||
// LocationEditCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 27.07.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
final class LocationEditCoordinator {
|
||||
|
||||
let viewController: UINavigationController
|
||||
|
||||
init(navController: UINavigationController) {
|
||||
self.viewController = navController
|
||||
}
|
||||
|
||||
func start(event: VehicleEventDto) async -> VehicleEventDto? {
|
||||
|
||||
let viewModel = LocationEditViewModel(event: event)
|
||||
viewModel.coordinator = self
|
||||
|
||||
let screen = LocationEditScreen(viewModel: viewModel)
|
||||
let controller = CustomHostingController(rootView: screen)
|
||||
viewController.pushViewController(controller, animated: true)
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.result
|
||||
}
|
||||
|
||||
func openLocationPicker(event: VehicleEventDto) async -> VehicleEventDto? {
|
||||
|
||||
let coordinator = LocationPickerCoordinator(navController: viewController,
|
||||
event: event)
|
||||
return try? await coordinator.start()
|
||||
}
|
||||
}
|
||||
@ -11,18 +11,30 @@ import AutoCatCore
|
||||
|
||||
struct LocationEditScreen: View {
|
||||
|
||||
enum Screen: String {
|
||||
|
||||
case locationPicker
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@State var viewModel: LocationEditViewModel
|
||||
|
||||
init(event: VehicleEventDto, onUpdate: @escaping (VehicleEventDto) -> Void) {
|
||||
|
||||
self.viewModel = LocationEditViewModel(
|
||||
event: event,
|
||||
onUpdate: onUpdate
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
DatePicker("Date", selection: $viewModel.date)
|
||||
.datePickerStyle(.compact)
|
||||
TextRowView(title: "Location", value: viewModel.event.location)
|
||||
.onTapGesture {
|
||||
Task { await viewModel.pickLocation() }
|
||||
}
|
||||
NavigationLink(value: Screen.locationPicker) {
|
||||
TextRowView(title: "Location", value: viewModel.event.location)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Edit event")
|
||||
.toolbar {
|
||||
@ -33,13 +45,8 @@ struct LocationEditScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Screen.self) { _ in
|
||||
LocationPickerScreen(event: viewModel.event, onUpdate: viewModel.onEventUpdated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
var event = VehicleEventDto(lat: 25.54984, lon: 36.34857, addedBy: "")
|
||||
event.address = "Ул. Ленина, 123"
|
||||
|
||||
return LocationEditScreen(viewModel: .init(event: event))
|
||||
}
|
||||
|
||||
@ -13,26 +13,23 @@ import SwiftUI
|
||||
@Observable
|
||||
final class LocationEditViewModel {
|
||||
|
||||
weak var coordinator: LocationEditCoordinator?
|
||||
|
||||
var event: VehicleEventDto
|
||||
var date: Date
|
||||
|
||||
var result: VehicleEventDto?
|
||||
var onUpdate: (VehicleEventDto) -> Void
|
||||
|
||||
init(event: VehicleEventDto) {
|
||||
init(event: VehicleEventDto, onUpdate: @escaping (VehicleEventDto) -> Void) {
|
||||
self.event = event
|
||||
self.date = Date(timeIntervalSince1970: event.date)
|
||||
self.onUpdate = onUpdate
|
||||
}
|
||||
|
||||
func done() {
|
||||
event.date = date.timeIntervalSince1970
|
||||
result = event
|
||||
onUpdate(event)
|
||||
}
|
||||
|
||||
func pickLocation() async {
|
||||
if let newEvent = await coordinator?.openLocationPicker(event: event) {
|
||||
event = newEvent
|
||||
}
|
||||
func onEventUpdated(_ event: VehicleEventDto) {
|
||||
self.event = event
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
//
|
||||
// LocationPickerCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 27.07.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
final class LocationPickerCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController?
|
||||
let event: VehicleEventDto
|
||||
|
||||
init(navController: UINavigationController?, event: VehicleEventDto) {
|
||||
self.viewController = navController
|
||||
self.event = event
|
||||
}
|
||||
|
||||
func start() async throws -> VehicleEventDto? {
|
||||
|
||||
let viewModel = LocationPickerViewModel(
|
||||
locationService: ServiceContainer.shared.resolve(LocationServiceProtocol.self),
|
||||
event: event
|
||||
)
|
||||
let screen = LocationPickerScreen(viewModel: viewModel)
|
||||
let controller = CustomHostingController(rootView: screen)
|
||||
viewController?.pushViewController(controller, animated: true)
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.result
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,15 @@ struct LocationPickerScreen: View {
|
||||
|
||||
@State var viewModel: LocationPickerViewModel
|
||||
|
||||
init(event: VehicleEventDto, onUpdate: @escaping (VehicleEventDto) -> Void) {
|
||||
|
||||
self.viewModel = LocationPickerViewModel(
|
||||
locationService: ServiceContainer.shared.resolve(LocationServiceProtocol.self),
|
||||
event: event,
|
||||
onUpdate: onUpdate
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Map(initialPosition: viewModel.position)
|
||||
@ -45,16 +54,3 @@ struct LocationPickerScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
var event = VehicleEventDto(lat: 47.250049, lon: 39.711821, addedBy: nil)
|
||||
event.address = "Ул. Ленина, 123"
|
||||
|
||||
let viewModel = LocationPickerViewModel(
|
||||
locationService: ServiceContainer.shared.resolve(LocationServiceProtocol.self),
|
||||
event: event
|
||||
)
|
||||
|
||||
return LocationPickerScreen(viewModel: viewModel)
|
||||
}
|
||||
|
||||
@ -20,12 +20,17 @@ final class LocationPickerViewModel {
|
||||
var event: VehicleEventDto
|
||||
var position: MapCameraPosition
|
||||
|
||||
var result: VehicleEventDto?
|
||||
var onUpdate: (VehicleEventDto) -> Void
|
||||
|
||||
init(locationService: LocationServiceProtocol, event: VehicleEventDto) {
|
||||
init(
|
||||
locationService: LocationServiceProtocol,
|
||||
event: VehicleEventDto,
|
||||
onUpdate: @escaping (VehicleEventDto) -> Void
|
||||
) {
|
||||
|
||||
self.locationService = locationService
|
||||
self.event = event
|
||||
self.onUpdate = onUpdate
|
||||
|
||||
if event.latitude == 0 && event.longitude == 0 {
|
||||
self.position = .userLocation(fallback: .automatic)
|
||||
@ -43,6 +48,6 @@ final class LocationPickerViewModel {
|
||||
}
|
||||
|
||||
func done() {
|
||||
result = event
|
||||
onUpdate(event)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
//
|
||||
// MapCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 13.04.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
enum MapInput {
|
||||
|
||||
case event(VehicleEventDto)
|
||||
case filter(Filter)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class MapCoordinator {
|
||||
|
||||
let navController: UINavigationController
|
||||
|
||||
init(navController: UINavigationController) {
|
||||
|
||||
self.navController = navController
|
||||
}
|
||||
|
||||
func start(mapInput: MapInput) {
|
||||
let resolver = ServiceContainer.shared
|
||||
|
||||
let viewModel = MapViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
mapInput: mapInput
|
||||
)
|
||||
|
||||
let controller = UIHostingController(
|
||||
rootView: MapScreen(viewModel: viewModel)
|
||||
)
|
||||
|
||||
//controller.modalPresentationStyle = .fullScreen
|
||||
controller.hidesBottomBarWhenPushed = true
|
||||
navController.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
15
AutoCat/Screens/MapScreen/MapInput.swift
Normal file
15
AutoCat/Screens/MapScreen/MapInput.swift
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// MapInput.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 18.04.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import AutoCatCore
|
||||
|
||||
enum MapInput {
|
||||
|
||||
case event(VehicleEventDto)
|
||||
case filter(Filter)
|
||||
}
|
||||
@ -9,11 +9,20 @@
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
import ClusterMapSwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct MapScreen: View {
|
||||
|
||||
@State var viewModel: MapViewModel
|
||||
|
||||
init(mapInput: MapInput) {
|
||||
|
||||
self.viewModel = MapViewModel(
|
||||
apiService: ServiceContainer.shared.resolve(ApiServiceProtocol.self),
|
||||
mapInput: mapInput
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Map {
|
||||
ForEach(viewModel.markers) {
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
//
|
||||
// NotesCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 24.06.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class NotesCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController?
|
||||
let vehicle: VehicleDto
|
||||
|
||||
init(navController: UINavigationController, vehicle: VehicleDto) {
|
||||
|
||||
self.viewController = navController
|
||||
self.vehicle = vehicle
|
||||
}
|
||||
|
||||
func start() async throws -> VehicleDto {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = NotesViewModel(
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
vehicle: vehicle
|
||||
)
|
||||
let controller = CustomHostingController(rootView: NotesScreen(viewModel: viewModel))
|
||||
viewController?.pushViewController(controller, animated: true)
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.vehicle
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,17 @@ struct NotesScreen: View {
|
||||
@State var selectedNoteId = ""
|
||||
@State var noteText = ""
|
||||
|
||||
init(vehicle: VehicleDto, onUpdate: @escaping (VehicleDto) -> Void) {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
self.viewModel = NotesViewModel(
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
vehicle: vehicle,
|
||||
onUpdate: onUpdate
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
List(viewModel.vehicle.notes) { note in
|
||||
@ -85,26 +96,3 @@ struct NotesScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#Preview {
|
||||
|
||||
var vehicle = VehicleDto()
|
||||
|
||||
vehicle.notes = [
|
||||
.init(text: "qwe", user: ""),
|
||||
.init(text: "asdf", user: ""),
|
||||
.init(text: "zxcv", user: "")
|
||||
]
|
||||
|
||||
let vm = NotesViewModel(
|
||||
storageService: MockStorageServiceProtocol(),
|
||||
apiService: MockApiServiceProtocol(),
|
||||
vehicle: vehicle
|
||||
)
|
||||
|
||||
return NotesScreen(viewModel: vm)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -21,13 +21,19 @@ class NotesViewModel: ACHudContainer {
|
||||
var vehicle: VehicleDto
|
||||
var hud: ACHud?
|
||||
|
||||
init(storageService: StorageServiceProtocol,
|
||||
apiService: ApiServiceProtocol,
|
||||
vehicle: VehicleDto) {
|
||||
var onUpdate: (VehicleDto) -> Void
|
||||
|
||||
init(
|
||||
storageService: StorageServiceProtocol,
|
||||
apiService: ApiServiceProtocol,
|
||||
vehicle: VehicleDto,
|
||||
onUpdate: @escaping (VehicleDto) -> Void
|
||||
) {
|
||||
|
||||
self.storageService = storageService
|
||||
self.apiService = apiService
|
||||
self.vehicle = vehicle
|
||||
self.onUpdate = onUpdate
|
||||
}
|
||||
|
||||
func addNote(text: String) async {
|
||||
@ -67,6 +73,7 @@ class NotesViewModel: ACHudContainer {
|
||||
await wrapWithToast(showProgress: false) { [weak self] in
|
||||
guard let self else { throw GenericError.somethingWentWrong }
|
||||
vehicle = try await storageOp()
|
||||
onUpdate(vehicle)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -76,6 +83,7 @@ class NotesViewModel: ACHudContainer {
|
||||
let vehicle = try await apiOp()
|
||||
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
||||
self.vehicle = vehicle
|
||||
onUpdate(vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
//
|
||||
// OsagoCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 14.07.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
class OsagoCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController?
|
||||
let contracts: [OsagoDto]
|
||||
|
||||
init(navController: UINavigationController, contracts: [OsagoDto]) {
|
||||
|
||||
self.viewController = navController
|
||||
self.contracts = contracts
|
||||
}
|
||||
|
||||
func start() async throws {
|
||||
|
||||
let controller = await UIHostingController(rootView: OsagoScreen(contracts: contracts))
|
||||
await viewController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
//
|
||||
// OwnersCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 14.07.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
class OwnersCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController?
|
||||
let ownerships: [VehicleOwnershipPeriodDto]
|
||||
|
||||
init(navController: UINavigationController, ownerships: [VehicleOwnershipPeriodDto]) {
|
||||
|
||||
self.viewController = navController
|
||||
self.ownerships = ownerships
|
||||
}
|
||||
|
||||
func start() async throws {
|
||||
|
||||
let controller = await UIHostingController(rootView: OwnersScreen(ownerships: ownerships))
|
||||
await viewController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,6 @@ import AutoCatCore
|
||||
@MainActor
|
||||
final class RecordsCoordinator {
|
||||
|
||||
var navController = UINavigationController()
|
||||
|
||||
func start(output: RecordScreenOutput?) -> UIViewController {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
@ -24,20 +22,9 @@ final class RecordsCoordinator {
|
||||
recordPlayer: resolver.resolve(RecordPlayerServiceProtocol.self)
|
||||
)
|
||||
|
||||
viewModel.coordinator = self
|
||||
viewModel.output = output
|
||||
|
||||
let view = RecordsScreen(viewModel: viewModel)
|
||||
let controller = UIHostingController(rootView: view)
|
||||
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
self.navController = navController
|
||||
return navController
|
||||
}
|
||||
|
||||
func showOnMap(event: VehicleEventDto) {
|
||||
|
||||
let coordinator = MapCoordinator(navController: navController)
|
||||
coordinator.start(mapInput: .event(event))
|
||||
return UIHostingController(rootView: view)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,59 +18,65 @@ struct RecordsScreen: View {
|
||||
@State var selectedRecordId: String = ""
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(viewModel.recordSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { model in
|
||||
AudioRecordView(
|
||||
record: .init(
|
||||
dto: model,
|
||||
isPlaying: model.id == viewModel.playingRecord?.id
|
||||
),
|
||||
progress: viewModel.progress
|
||||
) {
|
||||
viewModel.onPlayTapped(record: model)
|
||||
}
|
||||
.listRowInsets(EdgeInsets())
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: model)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: model, useLabels: true)
|
||||
NavigationStack {
|
||||
List {
|
||||
ForEach(viewModel.recordSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { model in
|
||||
AudioRecordView(
|
||||
record: .init(
|
||||
dto: model,
|
||||
isPlaying: model.id == viewModel.playingRecord?.id
|
||||
),
|
||||
progress: viewModel.progress
|
||||
) {
|
||||
viewModel.onPlayTapped(record: model)
|
||||
}
|
||||
.listRowInsets(EdgeInsets())
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: model)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: model, useLabels: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.inset)
|
||||
.hud($viewModel.hud)
|
||||
.navigationTitle("Voice records")
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button {
|
||||
Task { await viewModel.startRecording() }
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
.alert("Recording...", isPresented: $viewModel.showRecordingAlert) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
Task { await viewModel.cancelRecording() }
|
||||
.listStyle(.inset)
|
||||
.hud($viewModel.hud)
|
||||
.navigationTitle("Voice records")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
Button {
|
||||
Task { await viewModel.startRecording() }
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
}
|
||||
Button("Done") {
|
||||
Task { await viewModel.stopRecording() }
|
||||
.alert("Recording...", isPresented: $viewModel.showRecordingAlert) {
|
||||
Button("Cancel", role: .cancel) {
|
||||
Task { await viewModel.cancelRecording() }
|
||||
}
|
||||
Button("Done") {
|
||||
Task { await viewModel.stopRecording() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.noteAlert(
|
||||
title: String(localized: "Edit plate number"),
|
||||
body: $numberText,
|
||||
isPresented: $showEditAlert
|
||||
) { text in
|
||||
Task { await viewModel.editRecord(id: selectedRecordId, number: numberText) }
|
||||
.noteAlert(
|
||||
title: String(localized: "Edit plate number"),
|
||||
body: $numberText,
|
||||
isPresented: $showEditAlert
|
||||
) { text in
|
||||
Task { await viewModel.editRecord(id: selectedRecordId, number: numberText) }
|
||||
}
|
||||
.navigationDestination(for: VehicleEventDto.self) { event in
|
||||
MapScreen(mapInput: .event(event))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,10 +115,8 @@ struct RecordsScreen: View {
|
||||
Label("Show recognized text", systemImage: "textformat")
|
||||
}
|
||||
|
||||
if record.event != nil {
|
||||
Button {
|
||||
viewModel.showOnMap(record)
|
||||
} label: {
|
||||
if let event = record.event {
|
||||
NavigationLink(value: event) {
|
||||
Label("Show on map", systemImage: "map")
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ final class RecordsViewModel: ACHudContainer {
|
||||
let recordService: VehicleRecordServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
let recordPlayer: RecordPlayerServiceProtocol
|
||||
var coordinator: RecordsCoordinator?
|
||||
weak var output: RecordScreenOutput?
|
||||
|
||||
var hud: ACHud?
|
||||
@ -112,14 +111,6 @@ final class RecordsViewModel: ACHudContainer {
|
||||
hud = .message(record.rawText)
|
||||
}
|
||||
|
||||
func showOnMap(_ record: AudioRecordDto) {
|
||||
guard let event = record.event else {
|
||||
return
|
||||
}
|
||||
|
||||
coordinator?.showOnMap(event: event)
|
||||
}
|
||||
|
||||
func check(_ record: AudioRecordDto) {
|
||||
guard let number = record.number else {
|
||||
return
|
||||
|
||||
@ -1,107 +0,0 @@
|
||||
//
|
||||
// ReportCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 16.11.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class ReportCoordinator {
|
||||
|
||||
let viewController: UIViewController?
|
||||
let vehicle: VehicleDto
|
||||
let isPersistent: Bool
|
||||
|
||||
weak var navController: UINavigationController?
|
||||
|
||||
init(controller: UIViewController?, vehicle: VehicleDto, isPersistent: Bool) {
|
||||
|
||||
self.viewController = controller
|
||||
self.vehicle = vehicle
|
||||
self.isPersistent = isPersistent
|
||||
}
|
||||
|
||||
func start() async -> VehicleDto {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = ReportViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
settingsService: resolver.resolve(SettingsServiceProtocol.self),
|
||||
vehicle: vehicle,
|
||||
isPersistent: isPersistent
|
||||
)
|
||||
viewModel.coordinator = self
|
||||
let controller = CustomHostingController(rootView: ReportScreen(viewModel: viewModel))
|
||||
let newNavController = UINavigationController(rootViewController: controller)
|
||||
viewController?.showDetailViewController(newNavController, sender: self)
|
||||
navController = controller.navigationController
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.vehicle
|
||||
}
|
||||
|
||||
func openEvents(vehicle: VehicleDto) async -> VehicleDto? {
|
||||
guard let navController else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let coordinator = EventsCoordinator(navController: navController)
|
||||
return await coordinator.start(vehicle: vehicle)
|
||||
}
|
||||
|
||||
func openOsago(contracts: [OsagoDto]) {
|
||||
guard let navController else { return }
|
||||
|
||||
Task {
|
||||
let coordinator = OsagoCoordinator(navController: navController, contracts: contracts)
|
||||
try? await coordinator.start()
|
||||
}
|
||||
}
|
||||
|
||||
func openOwners(ownerships: [VehicleOwnershipPeriodDto]) {
|
||||
guard let navController else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
let coordiantor = OwnersCoordinator(navController: navController, ownerships: ownerships)
|
||||
try? await coordiantor.start()
|
||||
}
|
||||
}
|
||||
|
||||
func openNotes(vehicle: VehicleDto) async -> VehicleDto? {
|
||||
guard let navController else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let coordinator = NotesCoordinator(navController: navController, vehicle: vehicle)
|
||||
return try? await coordinator.start()
|
||||
}
|
||||
|
||||
func openAds(_ ads: [VehicleAdDto]) {
|
||||
guard let navController else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
let coordinator = AdsCoordinator(navController: navController, ads: ads)
|
||||
try? await coordinator.start()
|
||||
}
|
||||
}
|
||||
|
||||
func openPhotos(_ photos: [VehiclePhotoDto]) {
|
||||
guard let navController else {
|
||||
return
|
||||
}
|
||||
|
||||
Task {
|
||||
let coordinator = GalleryCoordinator(navController: navController, photos: photos)
|
||||
try? await coordinator.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,31 @@ import AutoCatCore
|
||||
|
||||
struct ReportScreen: View {
|
||||
|
||||
enum Screen: String {
|
||||
|
||||
case events
|
||||
case osago
|
||||
case owners
|
||||
case notes
|
||||
case ads
|
||||
case photos
|
||||
}
|
||||
|
||||
@State var viewModel: ReportViewModel
|
||||
|
||||
init(vehicle: VehicleDto, isPersistent: Bool, onUpdate: @escaping (VehicleDto) -> Void) {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
self.viewModel = ReportViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
settingsService: resolver.resolve(SettingsServiceProtocol.self),
|
||||
vehicle: vehicle,
|
||||
isPersistent: isPersistent,
|
||||
onUpdate: onUpdate
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
@ -52,22 +75,34 @@ struct ReportScreen: View {
|
||||
}
|
||||
|
||||
Section("History") {
|
||||
LabeledContent("Events", value: String(viewModel.vehicle.events.count))
|
||||
.navigationLink(onTap: viewModel.openEvents)
|
||||
LabeledContent("OSAGO", value: String(viewModel.vehicle.osagoContracts.count))
|
||||
.navigationLink(isActive: !viewModel.vehicle.osagoContracts.isEmpty,
|
||||
onTap: viewModel.openOsago)
|
||||
LabeledContent("Owners", value: String(viewModel.vehicle.ownershipPeriods.count))
|
||||
.navigationLink(isActive: !viewModel.vehicle.ownershipPeriods.isEmpty,
|
||||
onTap: viewModel.openOwners)
|
||||
LabeledContent("Photos", value: String(viewModel.vehicle.photos.count))
|
||||
.navigationLink(isActive: !viewModel.vehicle.photos.isEmpty,
|
||||
onTap: viewModel.openPhotoGallery)
|
||||
LabeledContent("Ads", value: String(viewModel.vehicle.ads.count))
|
||||
.navigationLink(isActive: !viewModel.vehicle.ads.isEmpty,
|
||||
onTap: viewModel.openAds)
|
||||
LabeledContent("Notes", value: String(viewModel.vehicle.notes.count))
|
||||
.navigationLink(onTap: viewModel.openNotes)
|
||||
NavigationLink(value: Screen.events) {
|
||||
LabeledContent("Events", value: String(viewModel.vehicle.events.count))
|
||||
}
|
||||
|
||||
NavigationLink(value: Screen.osago) {
|
||||
LabeledContent("OSAGO", value: String(viewModel.vehicle.osagoContracts.count))
|
||||
}
|
||||
.disabled(viewModel.vehicle.osagoContracts.isEmpty)
|
||||
|
||||
NavigationLink(value: Screen.owners) {
|
||||
LabeledContent("Owners", value: String(viewModel.vehicle.ownershipPeriods.count))
|
||||
}
|
||||
.disabled(viewModel.vehicle.ownershipPeriods.isEmpty)
|
||||
|
||||
NavigationLink(value: Screen.photos) {
|
||||
LabeledContent("Photos", value: String(viewModel.vehicle.photos.count))
|
||||
}
|
||||
.disabled(viewModel.vehicle.photos.isEmpty)
|
||||
|
||||
NavigationLink(value: Screen.ads) {
|
||||
LabeledContent("Ads", value: String(viewModel.vehicle.ads.count))
|
||||
}
|
||||
.disabled(viewModel.vehicle.ads.isEmpty)
|
||||
|
||||
|
||||
NavigationLink(value: Screen.notes) {
|
||||
LabeledContent("Notes", value: String(viewModel.vehicle.notes.count))
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.showDebugInfo {
|
||||
@ -95,6 +130,22 @@ struct ReportScreen: View {
|
||||
ShareLink(item: link)
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Screen.self) { screen in
|
||||
switch screen {
|
||||
case .events:
|
||||
EventsScreen(vehicle: viewModel.vehicle, onUpdate: viewModel.onVehicleChanged)
|
||||
case .osago:
|
||||
OsagoScreen(contracts: viewModel.vehicle.osagoContracts)
|
||||
case .owners:
|
||||
OwnersScreen(ownerships: viewModel.vehicle.ownershipPeriods)
|
||||
case .notes:
|
||||
NotesScreen(vehicle: viewModel.vehicle, onUpdate: viewModel.onVehicleChanged)
|
||||
case .ads:
|
||||
AdsScreen(ads: viewModel.vehicle.ads)
|
||||
case .photos:
|
||||
GalleryScreen(photos: viewModel.vehicle.photos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@ -122,17 +173,3 @@ struct ReportScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#Preview {
|
||||
ReportScreen(viewModel: .init(
|
||||
apiService: MockApiServiceProtocol(),
|
||||
storageService: MockStorageServiceProtocol(),
|
||||
settingsService: MockSettingsServiceProtocol(),
|
||||
vehicle: .preview,
|
||||
isPersistent: false
|
||||
))
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -17,12 +17,12 @@ class ReportViewModel: ACHudContainer {
|
||||
let storageService: StorageServiceProtocol
|
||||
let settingsService: SettingsServiceProtocol
|
||||
|
||||
var coordinator: ReportCoordinator?
|
||||
|
||||
let isPersistent: Bool
|
||||
var vehicle: VehicleDto
|
||||
var hud: ACHud?
|
||||
|
||||
let onUpdate: (VehicleDto) -> Void
|
||||
|
||||
var plateNumber: String {
|
||||
if vehicle.outdated, let current = vehicle.currentNumber {
|
||||
"\(vehicle.number) (\(current))"
|
||||
@ -59,13 +59,15 @@ class ReportViewModel: ACHudContainer {
|
||||
storageService: StorageServiceProtocol,
|
||||
settingsService: SettingsServiceProtocol,
|
||||
vehicle: VehicleDto,
|
||||
isPersistent: Bool) {
|
||||
isPersistent: Bool,
|
||||
onUpdate: @escaping (VehicleDto) -> Void) {
|
||||
|
||||
self.apiService = apiService
|
||||
self.storageService = storageService
|
||||
self.settingsService = settingsService
|
||||
self.vehicle = vehicle
|
||||
self.isPersistent = isPersistent
|
||||
self.onUpdate = onUpdate
|
||||
}
|
||||
|
||||
func onAppear() async {
|
||||
@ -95,40 +97,12 @@ class ReportViewModel: ACHudContainer {
|
||||
guard let self else { throw GenericError.somethingWentWrong }
|
||||
vehicle = try await apiService.checkVehicleGb(by: vehicle.getNumber())
|
||||
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
||||
onUpdate(vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Open detail screens
|
||||
|
||||
func openEvents() {
|
||||
Task {
|
||||
if let vehicle = await coordinator?.openEvents(vehicle: vehicle) {
|
||||
self.vehicle = vehicle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openOsago() {
|
||||
coordinator?.openOsago(contracts: vehicle.osagoContracts)
|
||||
}
|
||||
|
||||
func openOwners() {
|
||||
coordinator?.openOwners(ownerships: vehicle.ownershipPeriods)
|
||||
}
|
||||
|
||||
func openPhotoGallery() {
|
||||
coordinator?.openPhotos(vehicle.photos)
|
||||
}
|
||||
|
||||
func openNotes() {
|
||||
Task {
|
||||
if let vehicle = await coordinator?.openNotes(vehicle: vehicle) {
|
||||
self.vehicle = vehicle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openAds() {
|
||||
coordinator?.openAds(vehicle.ads)
|
||||
func onVehicleChanged(_ vehicle: VehicleDto) {
|
||||
self.vehicle = vehicle
|
||||
onUpdate(vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,8 +13,6 @@ import AutoCatCore
|
||||
@MainActor
|
||||
final class SearchCoordinator {
|
||||
|
||||
var navController = UINavigationController()
|
||||
|
||||
func start() -> UIViewController {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
@ -22,47 +20,8 @@ final class SearchCoordinator {
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
vehicleService: resolver.resolve(VehicleServiceProtocol.self)
|
||||
)
|
||||
viewModel.coordinator = self
|
||||
|
||||
let view = SearchScreen(viewModel: viewModel)
|
||||
let controller = UIHostingController(rootView: view)
|
||||
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
self.navController = navController
|
||||
return navController
|
||||
}
|
||||
|
||||
func openReport(vehicle: VehicleDto) async -> VehicleDto? {
|
||||
|
||||
let coordinator = ReportCoordinator(controller: navController,
|
||||
vehicle: vehicle,
|
||||
isPersistent: false)
|
||||
return await coordinator.start()
|
||||
}
|
||||
|
||||
func openFilterDetail(filter: Filter) async -> Filter? {
|
||||
|
||||
let coordinator = FiltersCoordinator(navController: navController, filter: filter)
|
||||
return await coordinator.start()
|
||||
}
|
||||
|
||||
func showOnMap(filter: Filter) {
|
||||
|
||||
let coordinator = MapCoordinator(navController: navController)
|
||||
coordinator.start(mapInput: .filter(filter))
|
||||
}
|
||||
|
||||
func export(url: URL) {
|
||||
guard let currentController = navController.visibleViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
let controller = UIDocumentPickerViewController(forExporting: [url])
|
||||
currentController.present(controller, animated: true)
|
||||
#else
|
||||
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||
currentController.present(activityController, animated: true)
|
||||
#endif
|
||||
return UIHostingController(rootView: view)
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,32 +11,56 @@ import AutoCatCore
|
||||
|
||||
struct SearchScreen: View {
|
||||
|
||||
enum Screen: Hashable {
|
||||
|
||||
case report(VehicleDto)
|
||||
case filter(Filter)
|
||||
case map(Filter)
|
||||
}
|
||||
|
||||
@State var viewModel: SearchViewModel
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
vehicles
|
||||
if viewModel.hasMoreData && !viewModel.vehicleSections.isEmpty {
|
||||
progressCell
|
||||
NavigationStack {
|
||||
List {
|
||||
vehicles
|
||||
if viewModel.hasMoreData && !viewModel.vehicleSections.isEmpty {
|
||||
progressCell
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.hud($viewModel.hud)
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehiclesCount))
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.refreshable {
|
||||
Task { await viewModel.reloadData() }
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
toolbarMenu
|
||||
.listStyle(.plain)
|
||||
.hud($viewModel.hud)
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
.disableAutocorrection(true)
|
||||
.autocapitalization(.none)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehiclesCount))
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.refreshable {
|
||||
Task { await viewModel.reloadData() }
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
toolbarMenu
|
||||
}
|
||||
}
|
||||
.navigationDestination(for: Screen.self) { screen in
|
||||
switch screen {
|
||||
case .report(let vehicle):
|
||||
ReportScreen(
|
||||
vehicle: vehicle,
|
||||
isPersistent: false,
|
||||
onUpdate: viewModel.onVehicleChanged
|
||||
)
|
||||
case .filter(let filter):
|
||||
FiltersScreen(filter: filter, onUpdate: viewModel.onFilterChanged)
|
||||
case .map(let filter):
|
||||
MapScreen(mapInput: .filter(filter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,21 +69,24 @@ struct SearchScreen: View {
|
||||
ForEach(viewModel.vehicleSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { vehicle in
|
||||
VehicleCellView(vehicle: vehicle)
|
||||
.onTapGesture {
|
||||
Task { await viewModel.openReport(vehicle: vehicle) }
|
||||
}
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: vehicle)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: vehicle, useLabels: true)
|
||||
}
|
||||
NavigationLink(value: Screen.report(vehicle)) {
|
||||
vehicleCell(vehicle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func vehicleCell(_ vehicle: VehicleDto) -> some View {
|
||||
VehicleCellView(vehicle: vehicle)
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: vehicle)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: vehicle, useLabels: true)
|
||||
}
|
||||
}
|
||||
|
||||
var progressCell: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
@ -74,11 +101,11 @@ struct SearchScreen: View {
|
||||
|
||||
var toolbarMenu: some View {
|
||||
Menu("", systemImage: "ellipsis") {
|
||||
Button("Filter results", systemImage: "line.horizontal.3.decrease") {
|
||||
Task { await viewModel.openFilterDetail() }
|
||||
NavigationLink(value: Screen.filter(viewModel.filter)) {
|
||||
Label("Filter results", systemImage: "line.horizontal.3.decrease")
|
||||
}
|
||||
Button("Show on map", systemImage: "map") {
|
||||
viewModel.showOnMap()
|
||||
NavigationLink(value: Screen.map(viewModel.filter)) {
|
||||
Label("Show on map", systemImage: "map")
|
||||
}
|
||||
ShareLink(item: viewModel.vehiclesArchive, preview: SharePreview(VehiclesArchive.fileName))
|
||||
//ShareLink(items: [viewModel.vehiclesArchive])
|
||||
@ -95,7 +122,3 @@ struct SearchScreen: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// SearchScreen(viewModel: .init())
|
||||
//}
|
||||
|
||||
@ -16,7 +16,6 @@ final class SearchViewModel: ACHudContainer {
|
||||
|
||||
let apiService: ApiServiceProtocol
|
||||
let vehicleService: VehicleServiceProtocol
|
||||
var coordinator: SearchCoordinator?
|
||||
|
||||
var hud: ACHud?
|
||||
|
||||
@ -119,57 +118,6 @@ final class SearchViewModel: ACHudContainer {
|
||||
}
|
||||
}
|
||||
|
||||
func openReport(vehicle: VehicleDto) async {
|
||||
guard let updatedVehicle = await coordinator?.openReport(vehicle: vehicle) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let index = vehicles.firstIndex(where: { $0.number == updatedVehicle.number }) {
|
||||
vehicles[index] = updatedVehicle
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
}
|
||||
|
||||
func openFilterDetail() async {
|
||||
guard let updatedFilter = await coordinator?.openFilterDetail(filter: filter) else {
|
||||
return
|
||||
}
|
||||
|
||||
filter = updatedFilter
|
||||
resetData()
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else { return }
|
||||
try await loadSearchResults(filter: filter)
|
||||
}
|
||||
}
|
||||
|
||||
func showOnMap() {
|
||||
coordinator?.showOnMap(filter: filter)
|
||||
}
|
||||
|
||||
func exportSearchResults() async {
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let resp = try await apiService.getVehicles(with: filter, pageToken: nil, pageSize: 0)
|
||||
|
||||
let newLine = "\r\n"
|
||||
var csvString = VehicleDto.csvHeader + newLine
|
||||
|
||||
for vehicle in resp.items {
|
||||
csvString.append(vehicle.csvLine)
|
||||
csvString.append(newLine)
|
||||
}
|
||||
|
||||
let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv")
|
||||
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
|
||||
|
||||
coordinator?.export(url: tmpUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func updateVehicle(_ vehicle: VehicleDto) async {
|
||||
do {
|
||||
hud = .progress
|
||||
@ -189,4 +137,28 @@ final class SearchViewModel: ACHudContainer {
|
||||
hud = .error(error)
|
||||
}
|
||||
}
|
||||
|
||||
func onVehicleChanged(_ vehicle: VehicleDto) {
|
||||
|
||||
if let index = vehicles.firstIndex(where: { $0.number == vehicle.number }) {
|
||||
vehicles[index] = vehicle
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
}
|
||||
|
||||
func onFilterChanged(_ filter: Filter) {
|
||||
Task {
|
||||
await updateWithFilter(filter)
|
||||
}
|
||||
}
|
||||
|
||||
func updateWithFilter(_ filter: Filter) async {
|
||||
|
||||
self.filter = filter
|
||||
resetData()
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else { return }
|
||||
try await loadSearchResults(filter: filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,8 +24,7 @@ class SettingsCoordinator {
|
||||
|
||||
let viewModel = SettingsViewModel(settingsService: ServiceContainer.shared.resolve(SettingsServiceProtocol.self))
|
||||
viewModel.coordinator = self
|
||||
let controller = UIHostingController(rootView: SettingsScreen(viewModel: viewModel))
|
||||
return UINavigationController(rootViewController: controller)
|
||||
return UIHostingController(rootView: SettingsScreen(viewModel: viewModel))
|
||||
}
|
||||
|
||||
func openAuthScreen() {
|
||||
|
||||
@ -16,85 +16,79 @@ struct SettingsScreen: View {
|
||||
@State var googleLoginSheetOpened = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
TextRowView(title: "Version", value: Bundle.main.fullVersion)
|
||||
}
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section {
|
||||
TextRowView(title: "Version", value: Bundle.main.fullVersion)
|
||||
}
|
||||
|
||||
Section("Profile") {
|
||||
SimpleTextRowView(title: "AutoCat Account", value: viewModel.autocatEmail)
|
||||
SimpleTextRowView(title: "Google",
|
||||
value: viewModel.googleEmail ?? "Log In",
|
||||
showArrow: true)
|
||||
.onTapGesture {
|
||||
if viewModel.googleAuthorized {
|
||||
googleSheetOpened = true
|
||||
} else {
|
||||
googleLoginSheetOpened = true
|
||||
Section("Profile") {
|
||||
SimpleTextRowView(title: "AutoCat Account", value: viewModel.autocatEmail)
|
||||
SimpleTextRowView(title: "Google",
|
||||
value: viewModel.googleEmail ?? "Log In",
|
||||
showArrow: true)
|
||||
.onTapGesture {
|
||||
if viewModel.googleAuthorized {
|
||||
googleSheetOpened = true
|
||||
} else {
|
||||
googleLoginSheetOpened = true
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.canSignOut {
|
||||
Button("Sign Out", action: viewModel.signOut)
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.canSignOut {
|
||||
Button("Sign Out", action: viewModel.signOut)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Server", selection: $viewModel.settingService.backend) {
|
||||
ForEach(Constants.Backend.allCases, id: \.self) { backend in
|
||||
Text(backend.name)
|
||||
Section {
|
||||
Picker("Server", selection: $viewModel.settingService.backend) {
|
||||
ForEach(Constants.Backend.allCases, id: \.self) { backend in
|
||||
Text(backend.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section("Plate number recognition") {
|
||||
ToggleRowView(title: "Alternative order",
|
||||
description: "Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'",
|
||||
toggle: $viewModel.settingService.recognizeAlternativeOrder)
|
||||
ToggleRowView(title: "Shortened numbers",
|
||||
description: "If enabled, app will try to recognize shortened plate numbers (without region) and add default region",
|
||||
toggle: $viewModel.settingService.recognizeShortenedNumbers)
|
||||
if viewModel.settingService.recognizeShortenedNumbers {
|
||||
LabeledContent("Default region") {
|
||||
TextField("", text: $viewModel.settingService.defaultRegion)
|
||||
.frame(width: 50)
|
||||
.multilineTextAlignment(.trailing)
|
||||
Section("Plate number recognition") {
|
||||
ToggleRowView(title: "Alternative order",
|
||||
description: "Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'",
|
||||
toggle: $viewModel.settingService.recognizeAlternativeOrder)
|
||||
ToggleRowView(title: "Shortened numbers",
|
||||
description: "If enabled, app will try to recognize shortened plate numbers (without region) and add default region",
|
||||
toggle: $viewModel.settingService.recognizeShortenedNumbers)
|
||||
if viewModel.settingService.recognizeShortenedNumbers {
|
||||
LabeledContent("Default region") {
|
||||
TextField("", text: $viewModel.settingService.defaultRegion)
|
||||
.frame(width: 50)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
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",
|
||||
toggle: $viewModel.settingService.recordBeep)
|
||||
}
|
||||
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",
|
||||
toggle: $viewModel.settingService.recordBeep)
|
||||
}
|
||||
|
||||
Section("Debug") {
|
||||
ToggleRowView(title: "Show debug info",
|
||||
description: nil,
|
||||
toggle: $viewModel.settingService.showDebugInfo)
|
||||
Section("Debug") {
|
||||
ToggleRowView(title: "Show debug info",
|
||||
description: nil,
|
||||
toggle: $viewModel.settingService.showDebugInfo)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.confirmationDialog(viewModel.googleUsername ?? "",
|
||||
isPresented: $googleSheetOpened,
|
||||
titleVisibility: .visible) {
|
||||
Button("Sign Out", role: .destructive) {
|
||||
viewModel.googleSignout()
|
||||
}
|
||||
} message: {
|
||||
if let email = viewModel.googleEmail {
|
||||
Text("You are currently signed in with email \(email). It will help to gather more data about vehicles.")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $googleLoginSheetOpened) {
|
||||
GoogleAuthScreen()
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.confirmationDialog(viewModel.googleUsername ?? "",
|
||||
isPresented: $googleSheetOpened,
|
||||
titleVisibility: .visible) {
|
||||
Button("Sign Out", role: .destructive) {
|
||||
viewModel.googleSignout()
|
||||
}
|
||||
} message: {
|
||||
if let email = viewModel.googleEmail {
|
||||
Text("You are currently signed in with email \(email). It will help to gather more data about vehicles.")
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $googleLoginSheetOpened) {
|
||||
GoogleAuthScreen()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
#Preview {
|
||||
SettingsScreen(viewModel: .init(settingsService: MockSettingsServiceProtocol()))
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -37,7 +37,7 @@ struct NavigationLinkModifier: ViewModifier {
|
||||
|
||||
extension View {
|
||||
|
||||
func navigationLink(isActive: Bool = true, onTap: (() -> Void)?) -> some View {
|
||||
func navigationLink(isActive: Bool = true, onTap: (() -> Void)? = nil) -> some View {
|
||||
modifier(NavigationLinkModifier(onTap: onTap, isActive: isActive))
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
|
||||
@propertyWrapper
|
||||
public struct NullifyDate: Sendable {
|
||||
public struct NullifyDate: Sendable, Equatable, Hashable {
|
||||
|
||||
public var wrappedValue: Date? {
|
||||
didSet {
|
||||
|
||||
@ -8,13 +8,13 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum DebugInfoStatus: Int, Sendable, Decodable, Equatable {
|
||||
public enum DebugInfoStatus: Int, Sendable, Decodable, Equatable, Hashable {
|
||||
case success = 0
|
||||
case error = 1
|
||||
case warning = 2
|
||||
}
|
||||
|
||||
public struct DebugInfoDto: Decodable, Sendable, Equatable {
|
||||
public struct DebugInfoDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var autocod: DebugInfoEntryDto?
|
||||
public var vin01vin: DebugInfoEntryDto?
|
||||
@ -23,7 +23,7 @@ public struct DebugInfoDto: Decodable, Sendable, Equatable {
|
||||
public var nomerogram: DebugInfoEntryDto?
|
||||
}
|
||||
|
||||
public struct DebugInfoEntryDto: Decodable, Sendable, Equatable {
|
||||
public struct DebugInfoEntryDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var fields: Int64 = 0
|
||||
public var error: String?
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct OsagoDto: Decodable, Sendable, Equatable {
|
||||
public struct OsagoDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var date: TimeInterval = 0
|
||||
public var number: String = ""
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleBrandDto: Decodable, Sendable, Equatable {
|
||||
public struct VehicleBrandDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var name: VehicleNameDto?
|
||||
public var logo: String?
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleDto: Sendable, Equatable {
|
||||
public struct VehicleDto: Sendable, Equatable, Hashable {
|
||||
|
||||
public var brand: VehicleBrandDto?
|
||||
public var model: VehicleModelDto?
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleEngineDto: Decodable, Sendable, Equatable {
|
||||
public struct VehicleEngineDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var number: String?
|
||||
public var volume: Int? = 0
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleEventDto: Codable, Sendable, Equatable {
|
||||
public struct VehicleEventDto: Codable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var id: String = UUID().uuidString
|
||||
public var date: TimeInterval = Date().timeIntervalSince1970
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleModelDto: Decodable, Sendable, Equatable {
|
||||
public struct VehicleModelDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var name: VehicleNameDto?
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleNameDto: Decodable, Sendable, Equatable {
|
||||
public struct VehicleNameDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var original: String?
|
||||
public var normalized: String?
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleNoteDto: Codable, Sendable, Identifiable, Equatable {
|
||||
public struct VehicleNoteDto: Codable, Sendable, Identifiable, Equatable, Hashable {
|
||||
|
||||
public var id: String = UUID().uuidString
|
||||
public var user: String = ""
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehicleOwnershipPeriodDto: Decodable, Sendable, Equatable {
|
||||
public struct VehicleOwnershipPeriodDto: Decodable, Sendable, Equatable, Hashable {
|
||||
|
||||
public var lastOperation: String = ""
|
||||
public var ownerType: String = ""
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct VehiclePhotoDto: Decodable, Sendable, Equatable, Identifiable {
|
||||
public struct VehiclePhotoDto: Decodable, Sendable, Equatable, Identifiable, Hashable {
|
||||
|
||||
public let id = UUID()
|
||||
public var brand: String?
|
||||
|
||||
@ -38,7 +38,7 @@ public enum SortOrder: String, CustomStringConvertible, CaseIterable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum SearchScope: Int, CaseIterable, Sendable {
|
||||
public enum SearchScope: Int, CaseIterable, Sendable, Hashable {
|
||||
|
||||
case plateNumber = 0
|
||||
case vin = 1
|
||||
@ -88,7 +88,7 @@ public enum StringOption: Hashable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct Filter: Sendable {
|
||||
public struct Filter: Sendable, Hashable {
|
||||
public var searchString = ""
|
||||
public var brand: StringOption = .any
|
||||
public var model: StringOption = .any
|
||||
|
||||
@ -60,6 +60,18 @@ public final class VehiclesArchive {
|
||||
vehicles = result.items
|
||||
}
|
||||
}
|
||||
|
||||
func makeCsvData() async throws -> Data {
|
||||
|
||||
try await loadVehiclesIfNeeded()
|
||||
let csvString = try makeCsvString()
|
||||
|
||||
if let data = csvString.data(using: .utf8){
|
||||
return data
|
||||
} else {
|
||||
throw VehiclesArchiveError.filedCreateCsv
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension VehiclesArchive: Transferable {
|
||||
@ -72,15 +84,16 @@ extension VehiclesArchive: Transferable {
|
||||
|
||||
DataRepresentation(exportedContentType: .commaSeparatedText) { archive in
|
||||
|
||||
try await archive.loadVehiclesIfNeeded()
|
||||
let csvString = try archive.makeCsvString()
|
||||
|
||||
if let data = csvString.data(using: .utf8){
|
||||
return data
|
||||
} else {
|
||||
throw VehiclesArchiveError.filedCreateCsv
|
||||
}
|
||||
try await archive.makeCsvData()
|
||||
}
|
||||
.suggestedFileName(fileName)
|
||||
|
||||
FileRepresentation(exportedContentType: .commaSeparatedText) { archive in
|
||||
|
||||
let data = try await archive.makeCsvData()
|
||||
let url = FileManager.default.tmpUrl(name: "search", ext: "csv")
|
||||
try data.write(to: url)
|
||||
return SentTransferredFile(url, allowAccessingOriginalFile: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user