From 72ce7ec79834c75e1ac709dea4ef7ee7d7e4f71f Mon Sep 17 00:00:00 2001 From: Selim Mustafaev Date: Tue, 29 Apr 2025 18:24:10 +0300 Subject: [PATCH] Full migration to SwiftUI --- AutoCat.xcodeproj/project.pbxproj | 102 +++------ AutoCat/AppDelegate.swift | 31 --- AutoCat/AutoCatApp.swift | 86 ++++++++ AutoCat/Base.lproj/LaunchScreen.storyboard | 25 --- AutoCat/Base.lproj/Main.storyboard | 8 - AutoCat/Extensions/Error.swift | 32 --- AutoCat/Extensions/Navigation.swift | 32 --- AutoCat/Extensions/ResizeImage.swift | 43 ---- AutoCat/Extensions/UIDevice.swift | 17 ++ AutoCat/Extensions/UIViewControllerExt.swift | 26 --- AutoCat/Info.plist | 47 ++-- AutoCat/SceneDelegate.swift | 167 -------------- .../Screens/AuthScreen/AuthCoordinator.swift | 42 ---- AutoCat/Screens/AuthScreen/AuthScreen.swift | 13 ++ .../Screens/AuthScreen/AuthViewModel.swift | 3 - .../Screens/EventsScreen/EventsScreen.swift | 1 + .../EventsScreen/EventsViewModel.swift | 13 +- .../Screens/FiltersScreen/FiltersScreen.swift | 2 +- .../Screens/HistoryScreen/HistoryScreen.swift | 33 ++- .../HistoryScreen/HistorySplitScreen.swift | 29 --- .../HistoryScreen/HistoryViewModel.swift | 37 +++- .../Screens/MainScreen/MainSplitScreen.swift | 179 +++++++++++++++ .../Screens/MainScreen/MainTabScreen.swift | 121 ++++++++++ AutoCat/Screens/MainScreen/SideBarItem.swift | 56 +++++ .../Screens/MainTabScreen/MainTabScreen.swift | 117 ---------- AutoCat/Screens/MapScreen/MapScreen.swift | 2 +- .../Screens/RecordsScreen/RecordsScreen.swift | 17 +- .../Screens/ReportScreen/ReportScreen.swift | 206 +++++++++--------- .../Screens/SearchScreen/SearchScreen.swift | 9 +- .../SettingsScreen/SettingsViewModel.swift | 14 -- AutoCat/SwiftUI/ACKeyboardButton.swift | 9 +- AutoCat/SwiftUI/ACKeyboardView.swift | 24 +- .../ACProgressHud+Modifiers.swift | 29 ++- AutoCat/SwiftUI/Environment.swift | 14 ++ AutoCat/SwiftUI/HideTabBarModifier.swift | 32 --- AutoCat/SwiftUI/NumberEditView.swift | 9 +- AutoCat/Utils/Coordinator.swift | 27 --- AutoCat/Utils/Router.swift | 29 +++ AutoCat/Views/PNKeyboard.swift | 8 +- AutoCat/ru.lproj/LaunchScreen.strings | 1 - AutoCat/ru.lproj/Localizable.strings | 6 + AutoCat/ru.lproj/Main.strings | 51 ----- AutoCatCore/Models/PlateNumber.swift | 10 +- AutoCatCore/Utils/Constants.swift | 3 +- 44 files changed, 811 insertions(+), 951 deletions(-) delete mode 100644 AutoCat/AppDelegate.swift create mode 100644 AutoCat/AutoCatApp.swift delete mode 100644 AutoCat/Base.lproj/LaunchScreen.storyboard delete mode 100644 AutoCat/Base.lproj/Main.storyboard delete mode 100644 AutoCat/Extensions/Error.swift delete mode 100644 AutoCat/Extensions/Navigation.swift delete mode 100644 AutoCat/Extensions/ResizeImage.swift create mode 100644 AutoCat/Extensions/UIDevice.swift delete mode 100644 AutoCat/Extensions/UIViewControllerExt.swift delete mode 100644 AutoCat/SceneDelegate.swift delete mode 100644 AutoCat/Screens/AuthScreen/AuthCoordinator.swift delete mode 100644 AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift create mode 100644 AutoCat/Screens/MainScreen/MainSplitScreen.swift create mode 100644 AutoCat/Screens/MainScreen/MainTabScreen.swift create mode 100644 AutoCat/Screens/MainScreen/SideBarItem.swift delete mode 100644 AutoCat/Screens/MainTabScreen/MainTabScreen.swift create mode 100644 AutoCat/SwiftUI/Environment.swift delete mode 100644 AutoCat/SwiftUI/HideTabBarModifier.swift delete mode 100644 AutoCat/Utils/Coordinator.swift create mode 100644 AutoCat/Utils/Router.swift delete mode 100644 AutoCat/ru.lproj/LaunchScreen.strings delete mode 100644 AutoCat/ru.lproj/Main.strings diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 5ac90ae..2122ec0 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -17,16 +17,11 @@ 7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022762C557EC400B84627 /* LocationPickerScreen.swift */; }; 7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1022782C557ED600B84627 /* LocationPickerViewModel.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 */; }; - 7A11470823FDE7E500B424AF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A11470623FDE7E500B424AF /* Main.storyboard */; }; 7A11470A23FDE7E600B424AF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7A11470923FDE7E600B424AF /* Assets.xcassets */; }; - 7A11470D23FDE7E600B424AF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7A11470B23FDE7E600B424AF /* LaunchScreen.storyboard */; }; 7A131FD32D37B75500DC7755 /* HistoryScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A131FD22D37B75500DC7755 /* HistoryScreen.swift */; }; 7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */; }; 7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441652C297EDE00E79018 /* NotesScreen.swift */; }; 7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1441672C297EFD00E79018 /* NotesViewModel.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 */; }; 7A1CF81629A42117007962DA /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1CF81529A42117007962DA /* Realm.swift */; }; @@ -102,14 +97,12 @@ 7A761C07267E8E7F0005F28F /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A15051124DB3E3000F39631 /* AnyEncodable.swift */; }; 7A761C08267E8EA20005F28F /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A43F9F7246C8A6200BA5B49 /* JWT.swift */; }; 7A761C09267E8EE40005F28F /* Base64FS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE32246C095700297C33 /* Base64FS.swift */; }; - 7A761C0B267E8FF90005F28F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A761C0A267E8FF90005F28F /* Error.swift */; }; 7A7AA2C42DA2A3CB00276D83 /* LocationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AA2C32DA2A3CB00276D83 /* LocationError.swift */; }; 7A7AA2C72DA2A45600276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C62DA2A45600276D83 /* RealmSwift */; }; 7A7AA2CA2DA2C85100276D83 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; }; 7A7AA2CB2DA2C85100276D83 /* RealmSwift in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 7A7AA2C92DA2C85100276D83 /* RealmSwift */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */; }; 7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A809F382D66755B00CF1B3C /* Error+Canceled.swift */; }; - 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; }; 7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */; }; 7A912F372D381B7400002938 /* LicensePlateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A912F362D381B7400002938 /* LicensePlateView.swift */; }; 7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */; }; @@ -165,15 +158,14 @@ 7AC355592969746600889457 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC355582969746600889457 /* UIControl.swift */; }; 7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3555A296995B200889457 /* UIEdgeInsets.swift */; }; 7AC44B822DB390B900ADC026 /* MainTabScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B812DB390B900ADC026 /* MainTabScreen.swift */; }; - 7AC44B842DB3EF7A00ADC026 /* HistorySplitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */; }; - 7AC44B862DB40A5C00ADC026 /* HideTabBarModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */; }; 7AC44B882DB438F200ADC026 /* UIView+layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B872DB438F200ADC026 /* UIView+layout.swift */; }; 7AC44B8A2DB4395300ADC026 /* SearchSplitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC44B892DB4395300ADC026 /* SearchSplitScreen.swift */; }; 7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC76D7A270083AE0084DB27 /* TextView.swift */; }; 7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */; }; 7ACBB91E2CB9B155005A5168 /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7ACBB91D2CB9B155005A5168 /* Mockable */; }; 7ACBB9202CB9B16C005A5168 /* Mockable in Frameworks */ = {isa = PBXBuildFile; productRef = 7ACBB91F2CB9B16C005A5168 /* Mockable */; }; - 7ADF6C93250B954900F237B2 /* Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C92250B954900F237B2 /* Navigation.swift */; }; + 7AD176AD2DC110830023049D /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD176AC2DC110830023049D /* Environment.swift */; }; + 7ADCBC572DB51739002522C0 /* AutoCatApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCBC562DB51739002522C0 /* AutoCatApp.swift */; }; 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */; }; 7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; }; 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; }; @@ -181,11 +173,12 @@ 7ADFC9592DAD1C3D001A43E3 /* GoogleAuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADFC9582DAD1C3D001A43E3 /* GoogleAuthViewModel.swift */; }; 7ADFC95B2DAD1F45001A43E3 /* WebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADFC95A2DAD1F45001A43E3 /* WebView.swift */; }; 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE24C5E251F1B4E00758E39 /* Buttons.swift */; }; - 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; }; + 7AE8CBB32DBA1475005EF1AB /* UIDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8CBB22DBA1475005EF1AB /* UIDevice.swift */; }; + 7AE8CBB52DBA3B55005EF1AB /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8CBB42DBA3B55005EF1AB /* Router.swift */; }; + 7AE8CBB72DBA3E4E005EF1AB /* MainSplitScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8CBB62DBA3E4E005EF1AB /* MainSplitScreen.swift */; }; 7AEAA2A12DAD9C00009954F0 /* TokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEAA2A02DAD9C00009954F0 /* TokenResponse.swift */; }; 7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */; }; 7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */; }; - 7AF231972DA1C30000AE5EB3 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */; }; 7AF231992DA27C1B00AE5EB3 /* ACButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */; }; 7AF6D2042677C03B0086EA64 /* AutoCatCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; }; 7AF6D2052677C03B0086EA64 /* AutoCatCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -212,6 +205,7 @@ 7AFBE8CA2C3081C7003C491D /* ACProgressHud+Modifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */; }; 7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8CB2C3085C6003C491D /* ACProgressView.swift */; }; 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE8CD2C308B53003C491D /* ACMessageView.swift */; }; + 7AFFF7A02DBAAFF300EE2DEE /* SideBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFF79F2DBAAFF300EE2DEE /* SideBarItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -278,11 +272,7 @@ 7A1022782C557ED600B84627 /* LocationPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationPickerViewModel.swift; sourceTree = ""; }; 7A1090EB24A4E3E100B4F0B2 /* CellProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellProgressView.swift; sourceTree = ""; }; 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 = ""; }; - 7A11470223FDE7E500B424AF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 7A11470723FDE7E500B424AF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 7A11470923FDE7E600B424AF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 7A11470C23FDE7E600B424AF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 7A11470E23FDE7E600B424AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7A11474323FF06CA00B424AF /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = ""; }; 7A11474623FF2AA500B424AF /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; @@ -292,7 +282,6 @@ 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = ""; }; 7A1441652C297EDE00E79018 /* NotesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesScreen.swift; sourceTree = ""; }; 7A1441672C297EFD00E79018 /* NotesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesViewModel.swift; sourceTree = ""; }; - 7A14416D2C297F7C00E79018 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = ""; }; 7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; 7A17CE492A2E820300626A6E /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; 7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = ""; }; @@ -337,12 +326,10 @@ 7A60D24C2C5A9D4900D13F7B /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = ""; }; 7A60D24E2C5A9DA800D13F7B /* LocationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServiceProtocol.swift; sourceTree = ""; }; 7A60D2502C5A9E4200D13F7B /* GeocoderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeocoderProtocol.swift; sourceTree = ""; }; - 7A61FF8325759DE700D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; 7A61FF8A2575A2CD00D905D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 7A61FF8D2575A2F900D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 7A61FF902575A5B300D905D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = ""; }; 7A61FF932575A5B600D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; - 7A61FF962576C16400D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = ""; }; 7A61FFA1257D3CFC00D905D5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = ""; }; 7A61FFA4257D3D0200D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = ""; }; 7A64A2022C19DA1000284124 /* VehicleDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleDto.swift; sourceTree = ""; }; @@ -375,12 +362,10 @@ 7A71580B2C44453200852088 /* AdsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsScreen.swift; sourceTree = ""; }; 7A7158112C444A6400852088 /* AdsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsViewModel.swift; sourceTree = ""; }; 7A71EF562D0A26B200943129 /* EventModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventModel.swift; sourceTree = ""; }; - 7A761C0A267E8FF90005F28F /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 7A7AA2C32DA2A3CB00276D83 /* LocationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationError.swift; sourceTree = ""; }; 7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordView.swift; sourceTree = ""; }; 7A809F382D66755B00CF1B3C /* Error+Canceled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Canceled.swift"; sourceTree = ""; }; 7A813DBD2506A57100CC93B9 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/AuthenticationServices.framework; sourceTree = DEVELOPER_DIR; }; - 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizeImage.swift; sourceTree = ""; }; 7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleVersion.swift; sourceTree = ""; }; 7A8AB76725A0DC8200ECF2C1 /* DebugInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugInfo.swift; sourceTree = ""; }; 7A912F362D381B7400002938 /* LicensePlateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensePlateView.swift; sourceTree = ""; }; @@ -438,13 +423,12 @@ 7AC355582969746600889457 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; }; 7AC3555A296995B200889457 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = ""; }; 7AC44B812DB390B900ADC026 /* MainTabScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabScreen.swift; sourceTree = ""; }; - 7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistorySplitScreen.swift; sourceTree = ""; }; - 7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideTabBarModifier.swift; sourceTree = ""; }; 7AC44B872DB438F200ADC026 /* UIView+layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+layout.swift"; sourceTree = ""; }; 7AC44B892DB4395300ADC026 /* SearchSplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSplitScreen.swift; sourceTree = ""; }; 7AC76D7A270083AE0084DB27 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; 7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchTextField+Dumb.swift"; sourceTree = ""; }; - 7ADF6C92250B954900F237B2 /* Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigation.swift; sourceTree = ""; }; + 7AD176AC2DC110830023049D /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; + 7ADCBC562DB51739002522C0 /* AutoCatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCatApp.swift; sourceTree = ""; }; 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNKeyboard.swift; sourceTree = ""; }; 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = ""; }; 7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = ""; }; @@ -452,12 +436,13 @@ 7ADFC9582DAD1C3D001A43E3 /* GoogleAuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthViewModel.swift; sourceTree = ""; }; 7ADFC95A2DAD1F45001A43E3 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = ""; }; 7AE24C5E251F1B4E00758E39 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; - 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = ""; }; 7AE8424D26109F78002F6B31 /* Exportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exportable.swift; sourceTree = ""; }; + 7AE8CBB22DBA1475005EF1AB /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = ""; }; + 7AE8CBB42DBA3B55005EF1AB /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; + 7AE8CBB62DBA3E4E005EF1AB /* MainSplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitScreen.swift; sourceTree = ""; }; 7AEAA2A02DAD9C00009954F0 /* TokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenResponse.swift; sourceTree = ""; }; 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthScreen.swift; sourceTree = ""; }; 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = ""; }; - 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = ""; }; 7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACButtonView.swift; sourceTree = ""; }; 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AutoCatCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AF6D1F12677C03B0086EA64 /* AutoCatCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutoCatCore.h; sourceTree = ""; }; @@ -468,6 +453,7 @@ 7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACProgressHud+Modifiers.swift"; sourceTree = ""; }; 7AFBE8CB2C3085C6003C491D /* ACProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressView.swift; sourceTree = ""; }; 7AFBE8CD2C308B53003C491D /* ACMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACMessageView.swift; sourceTree = ""; }; + 7AFFF79F2DBAAFF300EE2DEE /* SideBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -611,14 +597,11 @@ 7A6DD901242BF48D009DE740 /* Views */, 7A64AE6B2469DC6900ABE48E /* AutoCat.entitlements */, 7A11470E23FDE7E600B424AF /* Info.plist */, - 7A11470023FDE7E500B424AF /* AppDelegate.swift */, - 7A11470223FDE7E500B424AF /* SceneDelegate.swift */, 7A11470923FDE7E600B424AF /* Assets.xcassets */, 7A61FF8F2575A5B300D905D5 /* InfoPlist.strings */, - 7A11470B23FDE7E600B424AF /* LaunchScreen.storyboard */, 7A61FF892575A2CD00D905D5 /* Localizable.strings */, 7A61FFA2257D3CFC00D905D5 /* Localizable.stringsdict */, - 7A11470623FDE7E500B424AF /* Main.storyboard */, + 7ADCBC562DB51739002522C0 /* AutoCatApp.swift */, ); path = AutoCat; sourceTree = ""; @@ -642,8 +625,8 @@ 7A11474223FF06B600B424AF /* Utils */ = { isa = PBXGroup; children = ( - 7A14416D2C297F7C00E79018 /* Coordinator.swift */, 7AB4E4652D58A16C0006D052 /* GenericError.swift */, + 7AE8CBB42DBA3B55005EF1AB /* Router.swift */, ); path = Utils; sourceTree = ""; @@ -680,7 +663,6 @@ 7A131FD12D37B74100DC7755 /* HistoryScreen */ = { isa = PBXGroup; children = ( - 7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */, 7A131FD22D37B75500DC7755 /* HistoryScreen.swift */, 7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */, 7A4955812D58CCF900912E66 /* HistoryFilter.swift */, @@ -691,7 +673,7 @@ 7A1441632C297E9800E79018 /* Screens */ = { isa = PBXGroup; children = ( - 7AC44B802DB390A500ADC026 /* MainTabScreen */, + 7AC44B802DB390A500ADC026 /* MainScreen */, 7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */, 7A386A3E2DABDBFF0051676A /* MapScreen */, 7AF231912DA1C26C00AE5EB3 /* AuthScreen */, @@ -758,11 +740,8 @@ 7AE24C5E251F1B4E00758E39 /* Buttons.swift */, 7A3F07AA24360DC800E59687 /* Dated.swift */, 7ADF6CA02512244400F237B2 /* MapExt.swift */, - 7ADF6C92250B954900F237B2 /* Navigation.swift */, - 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */, - 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */, - 7A761C0A267E8FF90005F28F /* Error.swift */, 7AC76D7A270083AE0084DB27 /* TextView.swift */, + 7AE8CBB22DBA1475005EF1AB /* UIDevice.swift */, ); path = Extensions; sourceTree = ""; @@ -1085,12 +1064,14 @@ path = Extensions; sourceTree = ""; }; - 7AC44B802DB390A500ADC026 /* MainTabScreen */ = { + 7AC44B802DB390A500ADC026 /* MainScreen */ = { isa = PBXGroup; children = ( 7AC44B812DB390B900ADC026 /* MainTabScreen.swift */, + 7AE8CBB62DBA3E4E005EF1AB /* MainSplitScreen.swift */, + 7AFFF79F2DBAAFF300EE2DEE /* SideBarItem.swift */, ); - path = MainTabScreen; + path = MainScreen; sourceTree = ""; }; 7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */ = { @@ -1108,7 +1089,6 @@ children = ( 7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */, 7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */, - 7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */, ); path = AuthScreen; sourceTree = ""; @@ -1174,7 +1154,7 @@ 7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */, 7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */, 7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */, - 7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */, + 7AD176AC2DC110830023049D /* Environment.swift */, ); path = SwiftUI; sourceTree = ""; @@ -1371,11 +1351,9 @@ 7A61FF912575A5B300D905D5 /* InfoPlist.strings in Resources */, 7A61FFA0257D3CFC00D905D5 /* Localizable.stringsdict in Resources */, 7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */, - 7A11470D23FDE7E600B424AF /* LaunchScreen.storyboard in Resources */, 7A61FF8B2575A2CD00D905D5 /* Localizable.strings in Resources */, 7A6DD90A24329541009DE740 /* RoadNumbers2.0.otf in Resources */, 7A11470A23FDE7E600B424AF /* Assets.xcassets in Resources */, - 7A11470823FDE7E500B424AF /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1408,13 +1386,12 @@ buildActionMask = 2147483647; files = ( 7A961C6C2C4C3C8600CE2211 /* TextRowView.swift in Sources */, + 7AD176AD2DC110830023049D /* Environment.swift in Sources */, 7A1022772C557EC400B84627 /* LocationPickerScreen.swift in Sources */, 7A4322932CB2CCAA00085CF6 /* FiltersViewModel.swift in Sources */, 7A5D7E0C2C71EB25002C17E7 /* ToggleRowView.swift in Sources */, 7A1441662C297EDE00E79018 /* NotesScreen.swift in Sources */, - 7A11470123FDE7E500B424AF /* AppDelegate.swift in Sources */, 7A1E78FF2CE91A740004B740 /* Vehicle.swift in Sources */, - 7A14416E2C297F7C00E79018 /* Coordinator.swift in Sources */, 7A6DD90824329144009DE740 /* CenterTextLayer.swift in Sources */, 7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */, 7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */, @@ -1424,10 +1401,12 @@ 7AC8B2762D6A01C700190706 /* UISearchTextField+Dumb.swift in Sources */, 7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */, 7A2E11292CCE395300E5CA17 /* OptionalDatePicker.swift in Sources */, - 7A761C0B267E8FF90005F28F /* Error.swift in Sources */, 7AC3555029696D5A00889457 /* NewNumberController.swift in Sources */, 7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */, 7AF860702CBAA24500954D2F /* NavigationLink.swift in Sources */, + 7ADCBC572DB51739002522C0 /* AutoCatApp.swift in Sources */, + 7AFFF7A02DBAAFF300EE2DEE /* SideBarItem.swift in Sources */, + 7AE8CBB32DBA1475005EF1AB /* UIDevice.swift in Sources */, 7A386A482DABE0D00051676A /* MapMarkerModel.swift in Sources */, 7A386A402DABDC190051676A /* MapScreen.swift in Sources */, 7AB9FE282D08C2F4005DE374 /* EventsViewModel.swift in Sources */, @@ -1435,15 +1414,12 @@ 7A5911EE2D63226F00EC51BA /* SearchScreen.swift in Sources */, 7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */, 7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */, - 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */, 7AC44B882DB438F200ADC026 /* UIView+layout.swift in Sources */, 7A6C65222D999325001240C2 /* AudioRecordViewModel.swift in Sources */, 7A06E0AE2C7065C7005731AC /* SettingsViewModel.swift in Sources */, 7AB4E42C2D397D8E0006D052 /* VehicleCellView.swift in Sources */, 7A961C6E2C4C3C9E00CE2211 /* LinkRowView.swift in Sources */, - 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */, 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */, - 7AC44B842DB3EF7A00ADC026 /* HistorySplitScreen.swift in Sources */, 7AC44B822DB390B900ADC026 /* MainTabScreen.swift in Sources */, 7A7158122C444A6400852088 /* AdsViewModel.swift in Sources */, 7AF231952DA1C29300AE5EB3 /* AuthViewModel.swift in Sources */, @@ -1462,7 +1438,6 @@ 7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */, 7A2C96122C3B155B00AE46B5 /* NoteAlertModifier.swift in Sources */, 7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */, - 7AF231972DA1C30000AE5EB3 /* AuthCoordinator.swift in Sources */, 7A7DADAC2D99738300F52F6C /* AudioRecordView.swift in Sources */, 7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */, 7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */, @@ -1470,12 +1445,12 @@ 7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */, 7A1022722C554A1300B84627 /* CustomHostingController.swift in Sources */, 7A1022792C557ED600B84627 /* LocationPickerViewModel.swift in Sources */, - 7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */, 7AF231932DA1C28100AE5EB3 /* AuthScreen.swift in Sources */, 7A1E78F82CE900440004B740 /* ReportViewModel.swift in Sources */, 7A10226E2C551EE000B84627 /* LocationEditViewModel.swift in Sources */, 7ADFC95B2DAD1F45001A43E3 /* WebView.swift in Sources */, 7AB4902B2D6B1446002F39C6 /* ACKeyboardButton.swift in Sources */, + 7AE8CBB72DBA3E4E005EF1AB /* MainSplitScreen.swift in Sources */, 7AFBE8CE2C308B53003C491D /* ACMessageView.swift in Sources */, 7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */, 7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */, @@ -1493,16 +1468,15 @@ 7A4322912CB2CC8A00085CF6 /* FiltersScreen.swift in Sources */, 7ABD1B472D044A3200B43213 /* GalleryScreen.swift in Sources */, 7A71580C2C44453200852088 /* AdsScreen.swift in Sources */, + 7AE8CBB52DBA3B55005EF1AB /* Router.swift in Sources */, 7AADD4452DB2D4D60027FD7B /* MapInput.swift in Sources */, 7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */, 7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */, 7A131FD32D37B75500DC7755 /* HistoryScreen.swift in Sources */, 7AB9FE222D08C2A5005DE374 /* EventsScreen.swift in Sources */, - 7ADF6C93250B954900F237B2 /* Navigation.swift in Sources */, 7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */, 7A589E0F2D6B6E8E00EF3FBE /* NumberEditView.swift in Sources */, 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */, - 7AC44B862DB40A5C00ADC026 /* HideTabBarModifier.swift in Sources */, 7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */, 7A9519802D80B6C100E69883 /* RecordsScreen.swift in Sources */, 7A131FD52D37B76A00DC7755 /* HistoryViewModel.swift in Sources */, @@ -1630,24 +1604,6 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - 7A11470623FDE7E500B424AF /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 7A11470723FDE7E500B424AF /* Base */, - 7A61FF962576C16400D905D5 /* ru */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 7A11470B23FDE7E600B424AF /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 7A11470C23FDE7E600B424AF /* Base */, - 7A61FF8325759DE700D905D5 /* ru */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; 7A61FF892575A2CD00D905D5 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( @@ -1801,7 +1757,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 157; + CURRENT_PROJECT_VERSION = 158; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AutoCat; @@ -1828,7 +1784,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 157; + CURRENT_PROJECT_VERSION = 158; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = AutoCat; diff --git a/AutoCat/AppDelegate.swift b/AutoCat/AppDelegate.swift deleted file mode 100644 index 84cc9a4..0000000 --- a/AutoCat/AppDelegate.swift +++ /dev/null @@ -1,31 +0,0 @@ -import UIKit -import PKHUD -import AutoCatCore - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - - HUD.dimsBackground = true - HUD.allowsInteraction = false - - return true - } - - // MARK: UISceneSession Lifecycle - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } -} - diff --git a/AutoCat/AutoCatApp.swift b/AutoCat/AutoCatApp.swift new file mode 100644 index 0000000..d199390 --- /dev/null +++ b/AutoCat/AutoCatApp.swift @@ -0,0 +1,86 @@ +// +// AutoCatApp.swift +// AutoCat +// +// Created by Selim Mustafaev on 20.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import AutoCatCore +import SwiftLocation +import CoreLocation + +@main +struct AutoCatApp: App { + + @State var diReady: Bool = false + @State var settingsService: SettingsServiceProtocol? + + var body: some Scene { + WindowGroup { + if diReady { + if settingsService?.user.token.isEmpty == false { + mainScreen + } else { + AuthScreen() + } + } else { + Text("") + .task { + try! await registerServices() + diReady = true + } + } + } + } + + @ViewBuilder + var mainScreen: some View { + if UIDevice.isIPhone { + MainTabScreen() + } else { + MainSplitScreen() + } + } + + func registerServices() async throws { + + let container = ServiceContainer.shared + + let settingsService = await SettingsService() + self.settingsService = settingsService + + container.register(SettingsServiceProtocol.self, instance: settingsService) + + let apiService = ApiService(settingsService: settingsService) + container.register(ApiServiceProtocol.self, instance: apiService) + + let locationService = LocationService( + geocoder: CLGeocoder(), + locationManager: Location(), + settingsService: settingsService + ) + + container.register(LocationServiceProtocol.self, instance: locationService) + + let storageService = try await StorageService(settingsService: settingsService) + container.register(StorageServiceProtocol.self, instance: storageService) + + let vehicleService = VehicleService(apiService: apiService, + storageService: storageService, + locationService: locationService) + container.register(VehicleServiceProtocol.self, instance: vehicleService) + + let audioRecordService = AudioRecordService() + container.register(AudioRecordServiceProtocol.self, instance: audioRecordService) + + let vehicleRecordService = VehicleRecordService( + recordService: audioRecordService, + locationService: locationService, + settingsService: settingsService + ) + container.register(VehicleRecordServiceProtocol.self, instance: vehicleRecordService) + container.register(RecordPlayerServiceProtocol.self, instance: RecordPlayerService()) + } +} diff --git a/AutoCat/Base.lproj/LaunchScreen.storyboard b/AutoCat/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e932..0000000 --- a/AutoCat/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AutoCat/Base.lproj/Main.storyboard b/AutoCat/Base.lproj/Main.storyboard deleted file mode 100644 index d1c633e..0000000 --- a/AutoCat/Base.lproj/Main.storyboard +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/AutoCat/Extensions/Error.swift b/AutoCat/Extensions/Error.swift deleted file mode 100644 index 922deb3..0000000 --- a/AutoCat/Extensions/Error.swift +++ /dev/null @@ -1,32 +0,0 @@ -import UIKit -import PKHUD -import AutoCatCore - -extension UIViewController { - - func show(error: Error) { - self.show(error: error, animated: true, completion: nil) - } - - func show(error: Error, animated: Bool = true, completion: (() -> Void)? = nil) { - let msg = (error as NSError).displayMessage - let alert = UIAlertController(title: msg.title, message: msg.body, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in - completion?() - })) - self.present(alert, animated: animated) - } - - func showAlert(title: String, message: String) { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - self.present(alert, animated: true) - } -} - -extension HUD { - static func show(error: Error) { - let msg = (error as NSError).displayMessage - self.flash(.labeledError(title: msg.title, subtitle: msg.body), delay: 2.0) - } -} diff --git a/AutoCat/Extensions/Navigation.swift b/AutoCat/Extensions/Navigation.swift deleted file mode 100644 index 35f9eb0..0000000 --- a/AutoCat/Extensions/Navigation.swift +++ /dev/null @@ -1,32 +0,0 @@ -import UIKit - -extension UINavigationController { - public func pushViewController( - _ viewController: UIViewController, - animated: Bool, - completion: @escaping () -> Void) - { - pushViewController(viewController, animated: animated) - - guard animated, let coordinator = transitionCoordinator else { - DispatchQueue.main.async { completion() } - return - } - - coordinator.animate(alongsideTransition: nil) { _ in completion() } - } - - func popViewController( - animated: Bool, - completion: @escaping () -> Void) - { - popViewController(animated: animated) - - guard animated, let coordinator = transitionCoordinator else { - DispatchQueue.main.async { completion() } - return - } - - coordinator.animate(alongsideTransition: nil) { _ in completion() } - } -} diff --git a/AutoCat/Extensions/ResizeImage.swift b/AutoCat/Extensions/ResizeImage.swift deleted file mode 100644 index 045ef05..0000000 --- a/AutoCat/Extensions/ResizeImage.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import UIKit - -extension UIImage { - class func resize(image: UIImage, targetSize: CGSize) -> UIImage { - let size = image.size - - let widthRatio = targetSize.width / image.size.width - let heightRatio = targetSize.height / image.size.height - - var newSize: CGSize - if widthRatio > heightRatio { - newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) - } else { - newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) - } - - let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) - - UIGraphicsBeginImageContextWithOptions(newSize, false, 0) - image.draw(in: rect) - let newImage = UIGraphicsGetImageFromCurrentImageContext() - UIGraphicsEndImageContext() - - return newImage! - } - - class func scale(image: UIImage, by scale: CGFloat) -> UIImage? { - let size = image.size - let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) - return UIImage.resize(image: image, targetSize: scaledSize) - } - - func cutHeight(to newHeight: CGFloat) -> UIImage { - let newSize = CGSize(width: self.size.width, height: newHeight) - let rect = CGRect(origin: .zero, size: newSize) - - let renderer = UIGraphicsImageRenderer(bounds: rect) - return renderer.image { _ in - self.draw(in: CGRect(origin: .zero, size: self.size)) - } - } -} diff --git a/AutoCat/Extensions/UIDevice.swift b/AutoCat/Extensions/UIDevice.swift new file mode 100644 index 0000000..74d0aa5 --- /dev/null +++ b/AutoCat/Extensions/UIDevice.swift @@ -0,0 +1,17 @@ +// +// UIDevice.swift +// AutoCat +// +// Created by Selim Mustafaev on 24.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import UIKit + +extension UIDevice { + + static var isIPhone: Bool { + + current.userInterfaceIdiom == .phone + } +} diff --git a/AutoCat/Extensions/UIViewControllerExt.swift b/AutoCat/Extensions/UIViewControllerExt.swift deleted file mode 100644 index b5cfbf2..0000000 --- a/AutoCat/Extensions/UIViewControllerExt.swift +++ /dev/null @@ -1,26 +0,0 @@ -import UIKit - -extension UIViewController { - func hideKeyboardWhenTappedAround() { - let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard)) - tap.cancelsTouchesInView = false - view.addGestureRecognizer(tap) - } - - @objc func dismissKeyboard() { - view.endEditing(true) - } -} - -extension UIView { - var parentViewController: UIViewController? { - var parentResponder: UIResponder? = self - while parentResponder != nil { - parentResponder = parentResponder!.next - if let viewController = parentResponder as? UIViewController { - return viewController - } - } - return nil - } -} diff --git a/AutoCat/Info.plist b/AutoCat/Info.plist index 849bcbd..b4aa811 100644 --- a/AutoCat/Info.plist +++ b/AutoCat/Info.plist @@ -2,25 +2,6 @@ - UTExportedTypeDeclarations - - - UTTypeConformsTo - - public.json - - UTTypeIdentifier - pro.aliencat.vehicle.event - UTTypeTagSpecification - - - - LSApplicationQueriesSchemes - - yandexmaps - - ITSAppUsesNonExemptEncryption - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -37,8 +18,14 @@ 1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + LSApplicationCategoryType public.app-category.utilities + LSApplicationQueriesSchemes + + yandexmaps + LSRequiresIPhoneOS NSAppTransportSecurity @@ -93,14 +80,15 @@ Default Configuration UISceneDelegateClassName $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - UILaunchStoryboardName - LaunchScreen + UILaunchScreen + + UIImageName + + UIRequiredDeviceCapabilities armv7 @@ -118,5 +106,18 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UTExportedTypeDeclarations + + + UTTypeConformsTo + + public.json + + UTTypeIdentifier + pro.aliencat.vehicle.event + UTTypeTagSpecification + + + diff --git a/AutoCat/SceneDelegate.swift b/AutoCat/SceneDelegate.swift deleted file mode 100644 index c5f161f..0000000 --- a/AutoCat/SceneDelegate.swift +++ /dev/null @@ -1,167 +0,0 @@ -import UIKit -import os.log -import AVFoundation -import PKHUD -import AutoCatCore -import SwiftLocation -import CoreLocation -import SwiftUI - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - - var number: String? - - if let activity = connectionOptions.userActivities.first { - - if let url = activity.webpageURL { - if let param = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first, let token = param.value { - if let jwt = JWT(string: token) { - number = jwt.payload.plateNumber - } - } - } - } - - Task { - try? await registerServices() - setupRootController(scene: scene, openReport: number) - } - } - - func registerServices() async throws { - - let container = ServiceContainer.shared - - let settingsService = await SettingsService() - - container.register(SettingsServiceProtocol.self, instance: settingsService) - - let apiService = ApiService(settingsService: settingsService) - container.register(ApiServiceProtocol.self, instance: apiService) - - let locationService = LocationService( - geocoder: CLGeocoder(), - locationManager: Location(), - settingsService: settingsService - ) - - container.register(LocationServiceProtocol.self, instance: locationService) - - let storageService = try await StorageService(settingsService: settingsService) - container.register(StorageServiceProtocol.self, instance: storageService) - - let vehicleService = VehicleService(apiService: apiService, - storageService: storageService, - locationService: locationService) - container.register(VehicleServiceProtocol.self, instance: vehicleService) - - let audioRecordService = AudioRecordService() - container.register(AudioRecordServiceProtocol.self, instance: audioRecordService) - - let vehicleRecordService = VehicleRecordService( - recordService: audioRecordService, - locationService: locationService, - settingsService: settingsService - ) - container.register(VehicleRecordServiceProtocol.self, instance: vehicleRecordService) - container.register(RecordPlayerServiceProtocol.self, instance: RecordPlayerService()) - } - - func setupRootController(scene: UIScene, openReport number: String?) { - guard let windowScene = (scene as? UIWindowScene) else { - return - } - - self.window = UIWindow(windowScene: windowScene) - let storyboard = UIStoryboard(name: "Main", bundle: nil) - - let settingsService = ServiceContainer.shared.resolve(SettingsServiceProtocol.self) - - if settingsService.user.token.isEmpty { - let coordinator = AuthCoordinator(window: self.window) - self.window?.rootViewController = coordinator.start() - } else { - //self.window?.rootViewController = storyboard.instantiateViewController(identifier: "MainSplitController") - - self.window?.rootViewController = UIHostingController(rootView: MainTabScreen()) - - if let number { - Task { await openReport(with: number) } - } - } - - self.window?.makeKeyAndVisible() - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { - - } - - func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { - - if let url = userActivity.webpageURL { - if let param = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first, let token = param.value { - if let jwt = JWT(string: token) { - Task { await self.openReport(with: jwt.payload.plateNumber) } - } - } - } - } - - func openReport(with number: String) async { - guard let rootController = self.window?.rootViewController else { return } - - let apiService: ApiServiceProtocol = ServiceContainer.shared.resolve(ApiServiceProtocol.self) - - do { - HUD.show(.progress) - let vehicle = try await apiService.getReport(for: number) - - Task { - 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() - } catch { - HUD.show(error: error) - } - } -} diff --git a/AutoCat/Screens/AuthScreen/AuthCoordinator.swift b/AutoCat/Screens/AuthScreen/AuthCoordinator.swift deleted file mode 100644 index 974ed7d..0000000 --- a/AutoCat/Screens/AuthScreen/AuthCoordinator.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// AuthCoordinator.swift -// AutoCat -// -// Created by Selim Mustafaev on 05.04.2025. -// Copyright © 2025 Selim Mustafaev. All rights reserved. -// - -import UIKit -import SwiftUI -import AutoCatCore - -@MainActor -final class AuthCoordinator { - - let window: UIWindow? - - init(window: UIWindow?) { - - self.window = window - } - - func start() -> UIViewController { - - let resolver = ServiceContainer.shared - let viewModel = AuthViewModel( - apiService: resolver.resolve(ApiServiceProtocol.self), - storageService: resolver.resolve(StorageServiceProtocol.self), - settingsService: resolver.resolve(SettingsServiceProtocol.self) - ) - - viewModel.coordinator = self - - let view = AuthScreen(viewModel: viewModel) - return UIHostingController(rootView: view) - } - - func openMainScreen() { - - window?.rootViewController = UIHostingController(rootView: MainTabScreen()) - } -} diff --git a/AutoCat/Screens/AuthScreen/AuthScreen.swift b/AutoCat/Screens/AuthScreen/AuthScreen.swift index 7dd7213..b6ce6b5 100644 --- a/AutoCat/Screens/AuthScreen/AuthScreen.swift +++ b/AutoCat/Screens/AuthScreen/AuthScreen.swift @@ -7,11 +7,22 @@ // import SwiftUI +import AutoCatCore struct AuthScreen: View { @State var viewModel: AuthViewModel + init() { + + let resolver = ServiceContainer.shared + self.viewModel = AuthViewModel( + apiService: resolver.resolve(ApiServiceProtocol.self), + storageService: resolver.resolve(StorageServiceProtocol.self), + settingsService: resolver.resolve(SettingsServiceProtocol.self) + ) + } + var body: some View { ZStack { VStack(spacing: 16) { @@ -20,6 +31,8 @@ struct AuthScreen: View { .keyboardType(.emailAddress) SecureField("Password", text: $viewModel.password, prompt: Text("Password")) } + .disableAutocorrection(true) + .autocapitalization(.none) .padding(8) .background { RoundedRectangle(cornerRadius: 8) diff --git a/AutoCat/Screens/AuthScreen/AuthViewModel.swift b/AutoCat/Screens/AuthScreen/AuthViewModel.swift index 9f4ecdb..0503d09 100644 --- a/AutoCat/Screens/AuthScreen/AuthViewModel.swift +++ b/AutoCat/Screens/AuthScreen/AuthViewModel.swift @@ -16,7 +16,6 @@ final class AuthViewModel: ACHudContainer { let apiService: ApiServiceProtocol let storageService: StorageServiceProtocol var settingsService: SettingsServiceProtocol - var coordinator: AuthCoordinator? var hud: ACHud? @@ -49,7 +48,6 @@ final class AuthViewModel: ACHudContainer { guard let self else { return } let user = try await apiService.login(email: email, password: password) try await saveUser(user) - coordinator?.openMainScreen() } } @@ -58,7 +56,6 @@ final class AuthViewModel: ACHudContainer { guard let self else { return } let user = try await apiService.signUp(email: email, password: password) try await saveUser(user) - coordinator?.openMainScreen() } } diff --git a/AutoCat/Screens/EventsScreen/EventsScreen.swift b/AutoCat/Screens/EventsScreen/EventsScreen.swift index 6859490..81c80ec 100644 --- a/AutoCat/Screens/EventsScreen/EventsScreen.swift +++ b/AutoCat/Screens/EventsScreen/EventsScreen.swift @@ -42,6 +42,7 @@ struct EventsScreen: View { list .zIndex(viewModel.mode == .list ? 1 : 0) } + .onAppear(perform: viewModel.onAppear) .hud($viewModel.hud) .navigationTitle(viewModel.vehicle.number) .toolbar { diff --git a/AutoCat/Screens/EventsScreen/EventsViewModel.swift b/AutoCat/Screens/EventsScreen/EventsViewModel.swift index 2a0018e..472f14e 100644 --- a/AutoCat/Screens/EventsScreen/EventsViewModel.swift +++ b/AutoCat/Screens/EventsScreen/EventsViewModel.swift @@ -61,11 +61,14 @@ class EventsViewModel: ACHudContainer { self.settingsService = settingsService self.vehicle = vehicle self.onUpdate = onUpdate - - updateEvents() } - func updateEvents() { + func onAppear() { + + updateEvents(initialLoad: true) + } + + func updateEvents(initialLoad: Bool = false) { let email = settingsService.user.email @@ -85,7 +88,9 @@ class EventsViewModel: ACHudContainer { ) } - onUpdate(vehicle) + if !initialLoad { + onUpdate(vehicle) + } } func onEventUpdated(_ event: VehicleEventDto) { diff --git a/AutoCat/Screens/FiltersScreen/FiltersScreen.swift b/AutoCat/Screens/FiltersScreen/FiltersScreen.swift index 49f6855..ffd36a3 100644 --- a/AutoCat/Screens/FiltersScreen/FiltersScreen.swift +++ b/AutoCat/Screens/FiltersScreen/FiltersScreen.swift @@ -134,6 +134,6 @@ struct FiltersScreen: View { .task { await viewModel.loadData() } - .hideTabBar() + .toolbar(.hidden, for: .tabBar) } } diff --git a/AutoCat/Screens/HistoryScreen/HistoryScreen.swift b/AutoCat/Screens/HistoryScreen/HistoryScreen.swift index c387a20..8588c7f 100644 --- a/AutoCat/Screens/HistoryScreen/HistoryScreen.swift +++ b/AutoCat/Screens/HistoryScreen/HistoryScreen.swift @@ -9,23 +9,28 @@ import SwiftUI import AutoCatCore +enum HistoryRoute: Hashable { + + case localReport(VehicleDto) + case sharedReport(VehicleDto) +} + struct HistoryScreen: View { @State var viewModel: HistoryViewModel @State var filterSheetPresented = false @State var exportSheetPresented = false - init(viewModel: HistoryViewModel) { - - self.viewModel = viewModel - } - var body: some View { List(selection: $viewModel.selectedVehicleId) { ForEach(viewModel.vehicleSections) { section in Section(header: Text(section.header)) { ForEach(section.elements) { vehicle in - NavigationLink(value: vehicle.id) { + if UIDevice.isIPhone { + NavigationLink(value: HistoryRoute.localReport(vehicle)) { + vehicleCell(vehicle) + } + } else { vehicleCell(vehicle) } } @@ -76,6 +81,22 @@ struct HistoryScreen: View { } } } + .navigationDestination(for: HistoryRoute.self) { route in + switch route { + case .localReport(let vehicle): + ReportScreen( + vehicle: vehicle, + isPersistent: true, + onUpdate: viewModel.onVehicleChanged + ) + case .sharedReport(let vehicle): + ReportScreen( + vehicle: vehicle, + isPersistent: false, + onUpdate: { _ in } + ) + } + } } func vehicleCell(_ vehicle: VehicleDto) -> some View { diff --git a/AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift b/AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift deleted file mode 100644 index f9ede1a..0000000 --- a/AutoCat/Screens/HistoryScreen/HistorySplitScreen.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// HistorySplitScreen.swift -// AutoCat -// -// Created by Selim Mustafaev on 19.04.2025. -// Copyright © 2025 Selim Mustafaev. All rights reserved. -// - -import SwiftUI -import AutoCatCore - -struct HistorySplitScreen: View { - - var viewModel: HistoryViewModel - - var body: some View { - NavigationSplitView { - HistoryScreen(viewModel: viewModel) - } detail: { - if let vehicle = viewModel.selectedVehicle { - ReportScreen(vehicle: vehicle, isPersistent: true, onUpdate: viewModel.onVehicleChanged) - .id(vehicle.id) - } else { - Text("No vehicle selected") - } - } - .navigationSplitViewStyle(.balanced) - } -} diff --git a/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift b/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift index b15b198..62534eb 100644 --- a/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift +++ b/AutoCat/Screens/HistoryScreen/HistoryViewModel.swift @@ -30,6 +30,7 @@ final class HistoryViewModel: ACHudContainer { var vehiclesFiltered: [VehicleDto] = [] var vehicleSections: [DateSection] = [] + var vehicleToOpen: VehicleDto? var selectedVehicleId: VehicleDto.ID? var selectedVehicle: VehicleDto? { @@ -74,6 +75,9 @@ final class HistoryViewModel: ACHudContainer { } func onAppear() async { + guard vehicles.isEmpty else { + return + } async let loadTask: () = loadVehicles() async let dbUrlTask = storageService.dbFileURL @@ -141,8 +145,11 @@ final class HistoryViewModel: ACHudContainer { if errors.isEmpty { hud = nil if !vehicle.unrecognized { - // TODO: Fix programmatic navigation - //selectedVehicleId = vehicle.id + if UIDevice.isIPhone { + Router.shared.openLocalReport(vehicle: vehicle) + } else { + selectedVehicleId = vehicle.id + } } } else { showErrors(errors) @@ -178,4 +185,30 @@ final class HistoryViewModel: ACHudContainer { await loadVehicles() } } + + func onOpenUrl(_ url: URL) { + guard let param = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems?.first, + let token = param.value, + let jwt = JWT(string: token) + else { + return + } + + Task { + await openReport(number: jwt.payload.plateNumber) + } + } + + func openReport(number: String) async { + await wrapWithToast { [weak self] in + guard let self else { return } + let vehicle = try await apiService.getReport(for: number) + + if UIDevice.isIPhone { + Router.shared.openSharedReport(vehicle: vehicle) + } else { + vehicleToOpen = vehicle + } + } + } } diff --git a/AutoCat/Screens/MainScreen/MainSplitScreen.swift b/AutoCat/Screens/MainScreen/MainSplitScreen.swift new file mode 100644 index 0000000..b16767f --- /dev/null +++ b/AutoCat/Screens/MainScreen/MainSplitScreen.swift @@ -0,0 +1,179 @@ +// +// MainSplitScreen.swift +// AutoCat +// +// Created by Selim Mustafaev on 24.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import AutoCatCore + +struct MainSplitScreen: View { + + @State private var selection: SideBarItem? = .history + @State private var columnVisibility: NavigationSplitViewVisibility = .all + + @State var historyViewModel: HistoryViewModel + @State var recordsViewModel: RecordsViewModel + @State var searchViewModel: SearchViewModel + + @State var checkNumberSheetOpened = false + + init() { + let resolver = ServiceContainer.shared + + historyViewModel = HistoryViewModel( + apiService: resolver.resolve(ApiServiceProtocol.self), + storageService: resolver.resolve(StorageServiceProtocol.self), + vehicleService: resolver.resolve(VehicleServiceProtocol.self) + ) + + recordsViewModel = RecordsViewModel( + recordService: resolver.resolve(VehicleRecordServiceProtocol.self), + storageService: resolver.resolve(StorageServiceProtocol.self), + recordPlayer: resolver.resolve(RecordPlayerServiceProtocol.self) + ) + + searchViewModel = SearchViewModel( + apiService: resolver.resolve(ApiServiceProtocol.self), + vehicleService: resolver.resolve(VehicleServiceProtocol.self) + ) + + recordsViewModel.onCheckRecord = checkRecord + } + + var body: some View { + ZStack { + threeColumnSplitView + .opacity(selection?.needThreeColumns == true ? 1 : 0) + twoColumnSplitView + .opacity(selection?.needThreeColumns == true ? 0 : 1) + } + .navigationSplitViewStyle(.balanced) + .sheet(isPresented: $checkNumberSheetOpened) { + numberEditSheet + } + .sheet(item: $historyViewModel.vehicleToOpen) { vehicle in + NavigationStack { + ReportScreen( + vehicle: vehicle, + isPersistent: false, + onUpdate: { _ in } + ) + } + } + .onOpenURL { url in + selection = .history + historyViewModel.onOpenUrl(url) + } + } + + var numberEditSheet: some View { + NumberEditView { number in + checkNumberSheetOpened = false + selection = .history + Task { await historyViewModel.checkNewNumber(number) } + } + .presentationDetents([.height(350)]) + } + + var twoColumnSplitView: some View { + + NavigationSplitView(columnVisibility: $columnVisibility) { + sidebar + } detail: { + twoColumnDetail + } + } + + var threeColumnSplitView: some View { + + NavigationSplitView(columnVisibility: $columnVisibility) { + sidebar + } content: { + content + } detail: { + threeColumnDetail + } + } + + var sidebar: some View { + List(SideBarItem.allSideBarItems, selection: $selection) { tab in + NavigationLink(value: tab) { + Label(tab.title, systemImage: tab.imageName) + } + } + .navigationSplitViewColumnWidth(250) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button("Check new number", systemImage: "plus") { + checkNumberSheetOpened = true + } + } + } + .toolbar(.visible, for: .navigationBar) + } + + @ViewBuilder + var content: some View { + Group { + switch selection { + case .history: + HistoryScreen(viewModel: historyViewModel) + case .search: + SearchScreen(viewModel: searchViewModel) + default: + EmptyView() + } + } + .navigationSplitViewColumnWidth(350) + } + + @ViewBuilder + var twoColumnDetail: some View { + switch selection { + case .records: + RecordsScreen(selectedTab: $selection, viewModel: recordsViewModel) + case .settings: + SettingsScreen() + case .history, .search, .plus, .none: + EmptyView() + } + } + + @ViewBuilder + var threeColumnDetail: some View { + switch selection { + case .history: + if let vehicle = historyViewModel.selectedVehicle { + NavigationStack { + ReportScreen( + vehicle: vehicle, + isPersistent: true, + onUpdate: historyViewModel.onVehicleChanged + ) + .id(vehicle.id) + } + } + case .search: + if let vehicle = searchViewModel.selectedVehicle { + NavigationStack { + ReportScreen( + vehicle: vehicle, + isPersistent: false, + onUpdate: searchViewModel.onVehicleChanged + ) + .id(vehicle.id) + } + } + case .settings, .records, .plus, .none: + EmptyView() + } + } + + func checkRecord(number: String, event: VehicleEventDto?) { + + Task { await historyViewModel.checkRecord(number: number, event: event) } + } +} diff --git a/AutoCat/Screens/MainScreen/MainTabScreen.swift b/AutoCat/Screens/MainScreen/MainTabScreen.swift new file mode 100644 index 0000000..8afcb90 --- /dev/null +++ b/AutoCat/Screens/MainScreen/MainTabScreen.swift @@ -0,0 +1,121 @@ +// +// MainTabScreen.swift +// AutoCat +// +// Created by Selim Mustafaev on 19.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import UIKit +import SwiftEntryKit +import AutoCatCore + +struct MainTabScreen: View { + + @State private var selection: SideBarItem? = .history + @State private var oldSelection: SideBarItem? = .history + + @State var historyViewModel: HistoryViewModel + @State var recordsViewModel: RecordsViewModel + @State var searchViewModel: SearchViewModel + + @State var router = Router.shared + @State var checkNumberSheetOpened = false + + init() { + let resolver = ServiceContainer.shared + + historyViewModel = HistoryViewModel( + apiService: resolver.resolve(ApiServiceProtocol.self), + storageService: resolver.resolve(StorageServiceProtocol.self), + vehicleService: resolver.resolve(VehicleServiceProtocol.self) + ) + + recordsViewModel = RecordsViewModel( + recordService: resolver.resolve(VehicleRecordServiceProtocol.self), + storageService: resolver.resolve(StorageServiceProtocol.self), + recordPlayer: resolver.resolve(RecordPlayerServiceProtocol.self) + ) + + searchViewModel = SearchViewModel( + apiService: resolver.resolve(ApiServiceProtocol.self), + vehicleService: resolver.resolve(VehicleServiceProtocol.self) + ) + + recordsViewModel.onCheckRecord = checkRecord + } + + var body: some View { + nativeTabView + .sheet(isPresented: $checkNumberSheetOpened) { + numberEditSheet + } + .onOpenURL { url in + selection = .history + historyViewModel.onOpenUrl(url) + } + } + + var nativeTabView: some View { + TabView(selection: $selection) { + NavigationStack(path: $router.historyPath) { + HistoryScreen(viewModel: historyViewModel) + } + .tabItem { + Label("History", systemImage: "clock.arrow.circlepath") + } + .tag(SideBarItem.history) + .onAppear { oldSelection = selection } + + RecordsScreen(selectedTab: $selection, viewModel: recordsViewModel) + .tabItem { + Label("Records", systemImage: "waveform") + } + .tag(SideBarItem.records) + .onAppear { oldSelection = selection } + + Text("") + .tabItem { + Label("", systemImage: "plus") + } + .tag(SideBarItem.plus) + .onAppear { + DispatchQueue.main.async { + checkNumberSheetOpened = true + selection = oldSelection + } + } + + NavigationStack { + SearchScreen(viewModel: searchViewModel) + } + .tabItem { + Label("Search", systemImage: "magnifyingglass") + } + .tag(SideBarItem.search) + .onAppear { oldSelection = selection } + + SettingsScreen() + .tabItem { + Label("Settings", systemImage: "gear") + } + .tag(SideBarItem.settings) + .onAppear { oldSelection = selection } + } + } + + var numberEditSheet: some View { + NumberEditView { number in + checkNumberSheetOpened = false + selection = .history + Task { await historyViewModel.checkNewNumber(number) } + } + .presentationDetents([.height(350)]) + } + + func checkRecord(number: String, event: VehicleEventDto?) { + + Task { await historyViewModel.checkRecord(number: number, event: event) } + } +} diff --git a/AutoCat/Screens/MainScreen/SideBarItem.swift b/AutoCat/Screens/MainScreen/SideBarItem.swift new file mode 100644 index 0000000..cd2fe68 --- /dev/null +++ b/AutoCat/Screens/MainScreen/SideBarItem.swift @@ -0,0 +1,56 @@ +// +// SideBarItem.swift +// AutoCat +// +// Created by Selim Mustafaev on 24.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +enum SideBarItem: Int, CaseIterable, Identifiable, Hashable, Equatable { + + case history + case records + case plus + case search + case settings + + var id: Int { + rawValue + } + + var title: LocalizedStringKey { + switch self { + case .history: "History" + case .records: "Records" + case .plus: "" + case .search: "Search" + case .settings: "Settings" + } + } + + var imageName: String { + switch self { + case .history: "clock.arrow.circlepath" + case .records: "waveform" + case .plus: "plus" + case .search: "magnifyingglass" + case .settings: "gear" + } + } + + var needThreeColumns: Bool { + switch self { + case .history, .search: + true + case .records, .settings, .plus: + false + } + } + + static var allSideBarItems: [SideBarItem] { + + SideBarItem.allCases.filter { $0 != .plus } + } +} diff --git a/AutoCat/Screens/MainTabScreen/MainTabScreen.swift b/AutoCat/Screens/MainTabScreen/MainTabScreen.swift deleted file mode 100644 index e800153..0000000 --- a/AutoCat/Screens/MainTabScreen/MainTabScreen.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// MainTabScreen.swift -// AutoCat -// -// Created by Selim Mustafaev on 19.04.2025. -// Copyright © 2025 Selim Mustafaev. All rights reserved. -// - -import SwiftUI -import UIKit -import SwiftEntryKit -import AutoCatCore - -struct MainTabScreen: View { - - @State private var selection = 0 - - let historyViewModel: HistoryViewModel - let recordsViewModel: RecordsViewModel - - init() { - let resolver = ServiceContainer.shared - - historyViewModel = HistoryViewModel( - apiService: resolver.resolve(ApiServiceProtocol.self), - storageService: resolver.resolve(StorageServiceProtocol.self), - vehicleService: resolver.resolve(VehicleServiceProtocol.self) - ) - - recordsViewModel = RecordsViewModel( - recordService: resolver.resolve(VehicleRecordServiceProtocol.self), - storageService: resolver.resolve(StorageServiceProtocol.self), - recordPlayer: resolver.resolve(RecordPlayerServiceProtocol.self) - ) - - recordsViewModel.onCheckRecord = checkRecord - } - - var body: some View { - TabView(selection: $selection) { - HistorySplitScreen(viewModel: historyViewModel) - .tabItem { - Label("History", systemImage: "clock.arrow.circlepath") - } - .tag(0) - - RecordsScreen(viewModel: recordsViewModel) - .tabItem { - Label("Records", image: "record") - } - .tag(1) - -#if os(iOS) - Text("") - .tabItem { - Label("", systemImage: "plus") - } - .tag(2) -#endif - - SearchSplitScreen() - .tabItem { - Label("Search", systemImage: "magnifyingglass") - } - .tag(3) - - SettingsScreen() - .tabItem { - Label("Settings", systemImage: "gear") - } - .tag(4) - } -#if os(iOS) - .onChange(of: selection) { oldValue, newValue in - if newValue == 2 { - self.selection = oldValue - showCheckPuller() - } - } -#endif - } - - func showCheckPuller() { - - var attributes = EKAttributes.bottomToast - attributes.displayDuration = .infinity - attributes.entryBackground = .color(color: .init(.secondarySystemBackground)) - attributes.screenBackground = .color(color: EKColor(UIColor(white: 0, alpha: 0.7))) - attributes.roundCorners = .top(radius: 24) - attributes.screenInteraction = .dismiss - attributes.scroll = .disabled - attributes.entryInteraction = .absorbTouches - attributes.entranceAnimation = .init(translate: .init(duration: 0.2)) - attributes.exitAnimation = .init(translate: .init(duration: 0.2)) - - let newNumberController = NewNumberController() - newNumberController.onCheck = { number in - SwiftEntryKit.dismiss { - selection = 0 - Task { await historyViewModel.checkNewNumber(number) } - } - } - - SwiftEntryKit.display(entry: newNumberController, using: attributes) - - // User probably just saw a vehicle and is about to start entering plate number - // Requesting current location ASAP while we still close to initial location - let locationService = ServiceContainer.shared.resolve(LocationServiceProtocol.self) - Task { try? await locationService.requestCurrentLocation() } - } - - func checkRecord(number: String, event: VehicleEventDto?) { - - selection = 0 - Task { await historyViewModel.checkRecord(number: number, event: event) } - } -} diff --git a/AutoCat/Screens/MapScreen/MapScreen.swift b/AutoCat/Screens/MapScreen/MapScreen.swift index 4f553a0..ae90ca6 100644 --- a/AutoCat/Screens/MapScreen/MapScreen.swift +++ b/AutoCat/Screens/MapScreen/MapScreen.swift @@ -61,6 +61,6 @@ struct MapScreen: View { } } } - .hideTabBar() + .toolbar(.hidden, for: .tabBar) } } diff --git a/AutoCat/Screens/RecordsScreen/RecordsScreen.swift b/AutoCat/Screens/RecordsScreen/RecordsScreen.swift index 8730797..1baaf1e 100644 --- a/AutoCat/Screens/RecordsScreen/RecordsScreen.swift +++ b/AutoCat/Screens/RecordsScreen/RecordsScreen.swift @@ -11,27 +11,13 @@ import AutoCatCore struct RecordsScreen: View { + @Binding var selectedTab: SideBarItem? @State var viewModel: RecordsViewModel @State var showEditAlert = false @State var numberText = "" @State var selectedRecordId: String = "" - init() { - - let resolver = ServiceContainer.shared - self.viewModel = RecordsViewModel( - recordService: resolver.resolve(VehicleRecordServiceProtocol.self), - storageService: resolver.resolve(StorageServiceProtocol.self), - recordPlayer: resolver.resolve(RecordPlayerServiceProtocol.self) - ) - } - - init(viewModel: RecordsViewModel) { - - self.viewModel = viewModel - } - var body: some View { NavigationStack { List { @@ -138,6 +124,7 @@ struct RecordsScreen: View { if record.number != nil { Button { + selectedTab = .history viewModel.check(record) } label: { Label("Check", systemImage: "eye") diff --git a/AutoCat/Screens/ReportScreen/ReportScreen.swift b/AutoCat/Screens/ReportScreen/ReportScreen.swift index 5fc9eb9..3b79bf8 100644 --- a/AutoCat/Screens/ReportScreen/ReportScreen.swift +++ b/AutoCat/Screens/ReportScreen/ReportScreen.swift @@ -36,119 +36,119 @@ struct ReportScreen: View { ) } + + var body: some View { - NavigationStack { - Form { - Section { - LabeledContent { - Text(viewModel.vehicle.brand?.name?.original ?? "") - } label: { - AsyncImage(url: URL(string: viewModel.vehicle.brand?.logo ?? "")) { phase in - phase.image? - .resizable() - .aspectRatio(contentMode: .fit) - .frame(height: 32) - } - } - } - - Section("General") { - LabeledContent("Year", value: String(viewModel.vehicle.year)) - LabeledContent("Color", value: viewModel.vehicle.color ?? "") - LabeledContent("Category", value: viewModel.vehicle.category ?? "") - LabeledContent("Steering wheel position", value: viewModel.steerignWheelPosition) - LabeledContent("Japanese", value: viewModel.isJapanese) - } - - Section("Identifiers") { - LabeledContent("Plate number", value: viewModel.plateNumber) - LabeledContent("VIN", value: viewModel.vehicle.vin1 ?? "") - LabeledContent("STS", value: viewModel.vehicle.sts ?? "") - LabeledContent("PTS", value: viewModel.vehicle.pts ?? "") - } - - Section("Engine") { - LabeledContent("Number", value: viewModel.vehicle.engine?.number ?? "") - LabeledContent("Fuel type", value: viewModel.vehicle.engine?.fuelType ?? "") - LabeledContent("Volume (cm³)", value: String(viewModel.vehicle.engine?.volume ?? 0)) - LabeledContent("Power (HP)", value: String(viewModel.vehicle.engine?.powerHp ?? 0)) - LabeledContent("Power (kw)", value: String(viewModel.vehicle.engine?.powerKw ?? 0)) - } - - Section("History") { - 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 { - Section("Debug info") { - makeDebugInfoCell(title: "Avtocod", model: viewModel.vehicle.debugInfo?.autocod) - makeDebugInfoCell(title: "Vin01 (VIN)", model: viewModel.vehicle.debugInfo?.vin01vin) - makeDebugInfoCell(title: "Vin01 (base)", model: viewModel.vehicle.debugInfo?.vin01base) - makeDebugInfoCell(title: "Vin01 (history)", model: viewModel.vehicle.debugInfo?.vin01history) - makeDebugInfoCell(title: "Nomerogram", model: viewModel.vehicle.debugInfo?.nomerogram) - } - } - - Section { - Button("Check GB") { - Task { await viewModel.checkGB() } + Form { + Section { + LabeledContent { + Text(viewModel.vehicle.brand?.name?.original ?? "") + } label: { + AsyncImage(url: URL(string: viewModel.vehicle.brand?.logo ?? "")) { phase in + phase.image? + .resizable() + .aspectRatio(contentMode: .fit) + .frame(height: 32) } } } - .onAppear { - Task { await viewModel.onAppear() } + + Section("General") { + LabeledContent("Year", value: String(viewModel.vehicle.year)) + LabeledContent("Color", value: viewModel.vehicle.color ?? "") + LabeledContent("Category", value: viewModel.vehicle.category ?? "") + LabeledContent("Steering wheel position", value: viewModel.steerignWheelPosition) + LabeledContent("Japanese", value: viewModel.isJapanese) } - .hud($viewModel.hud) - .toolbar { - if let link = viewModel.shareLink { - ShareLink(item: link) + + Section("Identifiers") { + LabeledContent("Plate number", value: viewModel.plateNumber) + LabeledContent("VIN", value: viewModel.vehicle.vin1 ?? "") + LabeledContent("STS", value: viewModel.vehicle.sts ?? "") + LabeledContent("PTS", value: viewModel.vehicle.pts ?? "") + } + + Section("Engine") { + LabeledContent("Number", value: viewModel.vehicle.engine?.number ?? "") + LabeledContent("Fuel type", value: viewModel.vehicle.engine?.fuelType ?? "") + LabeledContent("Volume (cm³)", value: String(viewModel.vehicle.engine?.volume ?? 0)) + LabeledContent("Power (HP)", value: String(viewModel.vehicle.engine?.powerHp ?? 0)) + LabeledContent("Power (kw)", value: String(viewModel.vehicle.engine?.powerKw ?? 0)) + } + + Section("History") { + 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)) } } - .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) + + if viewModel.showDebugInfo { + Section("Debug info") { + makeDebugInfoCell(title: "Avtocod", model: viewModel.vehicle.debugInfo?.autocod) + makeDebugInfoCell(title: "Vin01 (VIN)", model: viewModel.vehicle.debugInfo?.vin01vin) + makeDebugInfoCell(title: "Vin01 (base)", model: viewModel.vehicle.debugInfo?.vin01base) + makeDebugInfoCell(title: "Vin01 (history)", model: viewModel.vehicle.debugInfo?.vin01history) + makeDebugInfoCell(title: "Nomerogram", model: viewModel.vehicle.debugInfo?.nomerogram) + } + } + + Section { + Button("Check GB") { + Task { await viewModel.checkGB() } } } - .hideTabBar() } + .onAppear { + Task { await viewModel.onAppear() } + } + .hud($viewModel.hud) + .toolbar { + if let link = viewModel.shareLink { + 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) + } + } + .toolbar(.hidden, for: .tabBar) } @ViewBuilder diff --git a/AutoCat/Screens/SearchScreen/SearchScreen.swift b/AutoCat/Screens/SearchScreen/SearchScreen.swift index 1493904..27e0aed 100644 --- a/AutoCat/Screens/SearchScreen/SearchScreen.swift +++ b/AutoCat/Screens/SearchScreen/SearchScreen.swift @@ -59,13 +59,20 @@ struct SearchScreen: View { MapScreen(mapInput: .filter(filter)) } } + .navigationDestination(for: VehicleDto.self) { vehicle in + return ReportScreen(vehicle: vehicle, isPersistent: false, onUpdate: viewModel.onVehicleChanged) + } } var vehicles: some View { ForEach(viewModel.vehicleSections) { section in Section(header: Text(section.header)) { ForEach(section.elements) { vehicle in - NavigationLink(value: vehicle.id) { + if UIDevice.isIPhone { + NavigationLink(value: vehicle) { + vehicleCell(vehicle) + } + } else { vehicleCell(vehicle) } } diff --git a/AutoCat/Screens/SettingsScreen/SettingsViewModel.swift b/AutoCat/Screens/SettingsScreen/SettingsViewModel.swift index 9396da5..87f65ba 100644 --- a/AutoCat/Screens/SettingsScreen/SettingsViewModel.swift +++ b/AutoCat/Screens/SettingsScreen/SettingsViewModel.swift @@ -6,7 +6,6 @@ // Copyright © 2024 Selim Mustafaev. All rights reserved. // -import UIKit import SwiftUI import AutoCatCore @@ -49,19 +48,6 @@ class SettingsViewModel { func signOut() { settingService.user.token = "" - - let window = UIApplication.shared - .connectedScenes - .filter({$0.activationState == .foregroundActive}) - .map({$0 as? UIWindowScene}) - .compactMap({$0}) - .first?.windows - .filter({$0.isKeyWindow}).first - - if let window { - let coordinator = AuthCoordinator(window: window) - window.rootViewController = coordinator.start() - } } func googleSignout() { diff --git a/AutoCat/SwiftUI/ACKeyboardButton.swift b/AutoCat/SwiftUI/ACKeyboardButton.swift index 0110531..2a6f78b 100644 --- a/AutoCat/SwiftUI/ACKeyboardButton.swift +++ b/AutoCat/SwiftUI/ACKeyboardButton.swift @@ -7,6 +7,7 @@ // import SwiftUI +import AutoCatCore struct ACKeyboardButton: View { @@ -37,7 +38,7 @@ struct ACKeyboardButton: View { var title: some View { switch type { case .symbol(let s): - Text(s) + Text(String(Constants.pnLettersMap[s] ?? s)) .font(.custom("RoadNumbers", size: 36 - 2*sizeDelta)) .offset(y: sizeDelta/2) case .backspace: @@ -60,11 +61,7 @@ struct ACKeyboardButton: View { return 0 } - if let character = title.first { - return character.isNumber ? 3 : 0 - } else { - return 0 - } + return title.isNumber ? 3 : 0 } } diff --git a/AutoCat/SwiftUI/ACKeyboardView.swift b/AutoCat/SwiftUI/ACKeyboardView.swift index 12c9fb5..0645dc7 100644 --- a/AutoCat/SwiftUI/ACKeyboardView.swift +++ b/AutoCat/SwiftUI/ACKeyboardView.swift @@ -16,24 +16,24 @@ struct ACKeyboardView: View { HStack(spacing: 16) { Grid(horizontalSpacing: 0, verticalSpacing: 0) { GridRow { - ACKeyboardButton(type: .symbol("A"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("B"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("E"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("А"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("В"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("Е"), onTap: buttonPressed) } GridRow { - ACKeyboardButton(type: .symbol("K"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("M"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("H"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("К"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("М"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("Н"), onTap: buttonPressed) } GridRow { - ACKeyboardButton(type: .symbol("O"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("P"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("C"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("О"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("Р"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("С"), onTap: buttonPressed) } GridRow { - ACKeyboardButton(type: .symbol("T"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("Y"), onTap: buttonPressed) - ACKeyboardButton(type: .symbol("X"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("Т"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("У"), onTap: buttonPressed) + ACKeyboardButton(type: .symbol("Х"), onTap: buttonPressed) } } Grid(horizontalSpacing: 0, verticalSpacing: 0) { diff --git a/AutoCat/SwiftUI/ACProgressHud/ACProgressHud+Modifiers.swift b/AutoCat/SwiftUI/ACProgressHud/ACProgressHud+Modifiers.swift index 1fca688..21ef165 100644 --- a/AutoCat/SwiftUI/ACProgressHud/ACProgressHud+Modifiers.swift +++ b/AutoCat/SwiftUI/ACProgressHud/ACProgressHud+Modifiers.swift @@ -13,19 +13,24 @@ struct ACProgressHudModifier: ViewModifier { @Binding var item: ACHud? func body(content: Content) -> some View { + + if let item { + ZStack { + content + makeHud(for: item) + } + } else { + content + } - content - .fullScreenCover(item: $item) { item in - if #available(iOS 16.4, *) { - makeHud(for: item) - .presentationBackground(.clear) - } else { - makeHud(for: item) - } - } - .transaction { transaction in - transaction.disablesAnimations = true - } +// content +// .fullScreenCover(item: $item) { item in +// makeHud(for: item) +// .presentationBackground(.clear) +// } +// .transaction { transaction in +// transaction.disablesAnimations = true +// } } @ViewBuilder diff --git a/AutoCat/SwiftUI/Environment.swift b/AutoCat/SwiftUI/Environment.swift new file mode 100644 index 0000000..151b355 --- /dev/null +++ b/AutoCat/SwiftUI/Environment.swift @@ -0,0 +1,14 @@ +// +// Environment.swift +// AutoCat +// +// Created by Selim Mustafaev on 29.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI + +extension EnvironmentValues { + + @Entry var selectedTab: SideBarItem = .history +} diff --git a/AutoCat/SwiftUI/HideTabBarModifier.swift b/AutoCat/SwiftUI/HideTabBarModifier.swift deleted file mode 100644 index 9440d77..0000000 --- a/AutoCat/SwiftUI/HideTabBarModifier.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// HideTabBarModifier.swift -// AutoCat -// -// Created by Selim Mustafaev on 19.04.2025. -// Copyright © 2025 Selim Mustafaev. All rights reserved. -// - -import SwiftUI - -struct HideTabBarModifier: ViewModifier { - - @Environment(\.horizontalSizeClass) var horizontalSizeClass - - func body(content: Content) -> some View { - - if horizontalSizeClass == .compact { - content - .toolbar(.hidden, for: .tabBar) - } else { - content - } - } -} - -extension View { - - public func hideTabBar() -> some View { - - modifier(HideTabBarModifier()) - } -} diff --git a/AutoCat/SwiftUI/NumberEditView.swift b/AutoCat/SwiftUI/NumberEditView.swift index 9f33c2c..59a38cd 100644 --- a/AutoCat/SwiftUI/NumberEditView.swift +++ b/AutoCat/SwiftUI/NumberEditView.swift @@ -17,13 +17,17 @@ struct NumberEditView: View { var body: some View { VStack(spacing: 16) { + Text("Check number") + .font(.headline) + //.padding(.top, 24) + HStack(spacing: 16) { LicensePlateView(number: number, foreground: .primary) .layoutPriority(1) .frame(height: 50) Button { - + onCheck(number.asString()) } label: { Text("Check") .foregroundColor(.primary) @@ -35,6 +39,7 @@ struct NumberEditView: View { } .layoutPriority(0) .frame(height: 50) + .opacity(number.isValid ? 1 : 0.5) } ACKeyboardView(buttonPressed: buttonPressed) @@ -46,7 +51,7 @@ struct NumberEditView: View { func buttonPressed(type: PNButtonType) { switch type { case .symbol(let s): - number.insertText(s) + number.insertText(String(s)) case .backspace: number.deleteBackward() case .done: diff --git a/AutoCat/Utils/Coordinator.swift b/AutoCat/Utils/Coordinator.swift deleted file mode 100644 index 10b216d..0000000 --- a/AutoCat/Utils/Coordinator.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Coordinator.swift -// AutoCat -// -// Created by Selim Mustafaev on 24.06.2024. -// Copyright © 2024 Selim Mustafaev. All rights reserved. -// - -import UIKit - -protocol Coordinator { - - associatedtype ResultType - //associatedtype Controller: UIViewController - - var viewController: UIViewController? { get } - - @discardableResult - func start() async throws -> ResultType -} - -extension Coordinator { - - var viewController: UIViewController? { - nil - } -} diff --git a/AutoCat/Utils/Router.swift b/AutoCat/Utils/Router.swift new file mode 100644 index 0000000..750a7a5 --- /dev/null +++ b/AutoCat/Utils/Router.swift @@ -0,0 +1,29 @@ +// +// Router.swift +// AutoCat +// +// Created by Selim Mustafaev on 24.04.2025. +// Copyright © 2025 Selim Mustafaev. All rights reserved. +// + +import SwiftUI +import AutoCatCore + +@MainActor +@Observable +final class Router { + + static let shared = Router() + + var historyPath = NavigationPath() + + func openLocalReport(vehicle: VehicleDto) { + + historyPath.append(HistoryRoute.localReport(vehicle)) + } + + func openSharedReport(vehicle: VehicleDto) { + + historyPath.append(HistoryRoute.sharedReport(vehicle)) + } +} diff --git a/AutoCat/Views/PNKeyboard.swift b/AutoCat/Views/PNKeyboard.swift index 1612bc5..732e6ba 100644 --- a/AutoCat/Views/PNKeyboard.swift +++ b/AutoCat/Views/PNKeyboard.swift @@ -12,7 +12,7 @@ protocol PNButtonDelegate: AnyObject { } enum PNButtonType { - case symbol(String) + case symbol(Character) case backspace case done } @@ -52,7 +52,7 @@ class PNButton: UIButton { } init(letter: Character, keyboardType: PNKeyboardType = .plateNumber) { - self.type = .symbol(String(letter)) + self.type = .symbol(letter) self.keyboardType = keyboardType super.init(frame: .zero) self.setup() @@ -63,7 +63,7 @@ class PNButton: UIButton { } init(digit: Int, keyboardType: PNKeyboardType = .plateNumber) { - self.type = .symbol(String(digit)) + self.type = .symbol(String(digit).first!) self.keyboardType = keyboardType super.init(frame: .zero) self.setup() @@ -284,7 +284,7 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate { switch button.type { case .symbol(let s): - self.target?.insertText(s) + self.target?.insertText(String(s)) case .backspace: self.target?.deleteBackward() case .done: diff --git a/AutoCat/ru.lproj/LaunchScreen.strings b/AutoCat/ru.lproj/LaunchScreen.strings deleted file mode 100644 index 8b13789..0000000 --- a/AutoCat/ru.lproj/LaunchScreen.strings +++ /dev/null @@ -1 +0,0 @@ - diff --git a/AutoCat/ru.lproj/Localizable.strings b/AutoCat/ru.lproj/Localizable.strings index 26a8f64..f1c9e5e 100644 --- a/AutoCat/ru.lproj/Localizable.strings +++ b/AutoCat/ru.lproj/Localizable.strings @@ -427,3 +427,9 @@ "Password" = "Пароль"; "Sign up" = "Регистрация"; + +"Records" = "Аудио записи"; + +"Search" = "Поиск"; + +"Settings" = "Настройки"; diff --git a/AutoCat/ru.lproj/Main.strings b/AutoCat/ru.lproj/Main.strings deleted file mode 100644 index abf9569..0000000 --- a/AutoCat/ru.lproj/Main.strings +++ /dev/null @@ -1,51 +0,0 @@ -/* Class = "UIBarButtonItem"; title = "Back"; ObjectID = "7QQ-Qi-qEw"; */ -"7QQ-Qi-qEw.title" = "Назад"; - -/* Class = "UILabel"; text = "00:00"; ObjectID = "bFQ-eU-5YJ"; */ -"bFQ-eU-5YJ.text" = "00:00"; - -/* Class = "UIViewController"; title = "Events"; ObjectID = "DmF-8j-ae3"; */ -"DmF-8j-ae3.title" = "События"; - -/* Class = "UITextField"; placeholder = "Password"; ObjectID = "G1p-Hz-8yn"; */ -"G1p-Hz-8yn.placeholder" = "Пароль"; - -/* Class = "UITabBarItem"; title = "Search"; ObjectID = "gDG-z8-R0t"; */ -"gDG-z8-R0t.title" = "Поиск"; - -/* Class = "UIBarButtonItem"; title = "Close"; ObjectID = "hCi-F4-z7b"; */ -"hCi-F4-z7b.title" = "Закрыть"; - -/* Class = "UIButton"; normalTitle = "Sign up"; ObjectID = "hRD-Ha-MrP"; */ -"hRD-Ha-MrP.normalTitle" = "Регистрация"; - -/* Class = "UIButton"; normalTitle = "Log in"; ObjectID = "ltG-B1-UBj"; */ -"ltG-B1-UBj.normalTitle" = "Вход"; - -/* Class = "UINavigationItem"; title = "Voice records"; ObjectID = "lu2-xz-pMr"; */ -"lu2-xz-pMr.title" = "Голосовые записи"; - -/* Class = "UITabBarItem"; title = "Records"; ObjectID = "lxF-EY-z8V"; */ -"lxF-EY-z8V.title" = "Аудио"; - -/* Class = "UITabBarItem"; title = "History"; ObjectID = "QJd-35-4OB"; */ -"QJd-35-4OB.title" = "История"; - -/* Class = "UILabel"; text = "or"; ObjectID = "SXb-1Q-TxY"; */ -"SXb-1Q-TxY.text" = "или"; - -/* Class = "UINavigationItem"; title = "Filters"; ObjectID = "U4X-4i-ZJm"; */ -"U4X-4i-ZJm.title" = "Фильтры"; - -/* Class = "UITextField"; placeholder = "Email"; ObjectID = "Uey-88-eZL"; */ -"Uey-88-eZL.placeholder" = "Email"; - -/* Class = "UITabBarItem"; title = "Add"; ObjectID = "X0w-PF-Dn8"; */ -"X0w-PF-Dn8.title" = "Добавить"; - -/* Class = "UITabBarItem"; title = "Settings"; ObjectID = "zEL-ph-E2f"; */ -"zEL-ph-E2f.title" = "Настройки"; - -/* Class = "UIBarButtonItem"; title = "Close"; ObjectID = "ZHH-OZ-vHc"; */ -"ZHH-OZ-vHc.title" = "Закрыть"; - diff --git a/AutoCatCore/Models/PlateNumber.swift b/AutoCatCore/Models/PlateNumber.swift index f788ef4..0fefa46 100644 --- a/AutoCatCore/Models/PlateNumber.swift +++ b/AutoCatCore/Models/PlateNumber.swift @@ -1,6 +1,6 @@ import Foundation -public class PlateNumber { +public struct PlateNumber { private var number: String = "" private var numberEnglish: String = "" @@ -13,7 +13,7 @@ public class PlateNumber { } public func asString() -> String { - return self.number + return self.number.filter { !$0.isWhitespace }.uppercased() } public func mainPart() -> String { @@ -32,7 +32,7 @@ public class PlateNumber { number.count >= 8 } - public func setNumber(_ number: String) { + public mutating func setNumber(_ number: String) { self.number = number self.numberEnglish = String(self.number.map { Constants.pnLettersMap[$0] ?? $0 }) } @@ -45,7 +45,7 @@ public class PlateNumber { // Returns true if number was changed @discardableResult - public func insertText(_ text: String) -> Bool { + public mutating func insertText(_ text: String) -> Bool { guard number.count < maxLength, text.count == 1 else { return false } @@ -63,7 +63,7 @@ public class PlateNumber { } @discardableResult - public func deleteBackward() -> Bool { + public mutating func deleteBackward() -> Bool { guard !number.isEmpty else { return false } diff --git a/AutoCatCore/Utils/Constants.swift b/AutoCatCore/Utils/Constants.swift index 8051d74..4159f19 100644 --- a/AutoCatCore/Utils/Constants.swift +++ b/AutoCatCore/Utils/Constants.swift @@ -19,7 +19,8 @@ public enum Constants { public var baseUrl: String { #if DEBUG - "http://127.0.0.1:3000/" + //"http://127.0.0.1:3000/" + "https://charon.aliencat.pro:8444/" #else rawValue #endif