Full migration to SwiftUI

This commit is contained in:
Selim Mustafaev 2025-04-29 18:24:10 +03:00
parent 544f81be5e
commit 72ce7ec798
44 changed files with 811 additions and 951 deletions

View File

@ -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 = "<group>"; };
7A1090EB24A4E3E100B4F0B2 /* CellProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellProgressView.swift; sourceTree = "<group>"; };
7A1146FD23FDE7E500B424AF /* AutoCat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AutoCat.app; sourceTree = BUILT_PRODUCTS_DIR; };
7A11470023FDE7E500B424AF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7A11470223FDE7E500B424AF /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
7A11470723FDE7E500B424AF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
7A11470923FDE7E600B424AF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
7A11470C23FDE7E600B424AF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
7A11470E23FDE7E600B424AF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7A11474323FF06CA00B424AF /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = "<group>"; };
7A11474623FF2AA500B424AF /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
@ -292,7 +282,6 @@
7A131FD42D37B76A00DC7755 /* HistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = "<group>"; };
7A1441652C297EDE00E79018 /* NotesScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesScreen.swift; sourceTree = "<group>"; };
7A1441672C297EFD00E79018 /* NotesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotesViewModel.swift; sourceTree = "<group>"; };
7A14416D2C297F7C00E79018 /* Coordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinator.swift; sourceTree = "<group>"; };
7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
7A17CE492A2E820300626A6E /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = "<group>"; };
@ -337,12 +326,10 @@
7A60D24C2C5A9D4900D13F7B /* LocationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = "<group>"; };
7A60D24E2C5A9DA800D13F7B /* LocationServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServiceProtocol.swift; sourceTree = "<group>"; };
7A60D2502C5A9E4200D13F7B /* GeocoderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeocoderProtocol.swift; sourceTree = "<group>"; };
7A61FF8325759DE700D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = "<group>"; };
7A61FF8A2575A2CD00D905D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
7A61FF8D2575A2F900D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
7A61FF902575A5B300D905D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
7A61FF932575A5B600D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
7A61FF962576C16400D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Main.strings; sourceTree = "<group>"; };
7A61FFA1257D3CFC00D905D5 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = en; path = en.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
7A61FFA4257D3D0200D905D5 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
7A64A2022C19DA1000284124 /* VehicleDto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleDto.swift; sourceTree = "<group>"; };
@ -375,12 +362,10 @@
7A71580B2C44453200852088 /* AdsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsScreen.swift; sourceTree = "<group>"; };
7A7158112C444A6400852088 /* AdsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsViewModel.swift; sourceTree = "<group>"; };
7A71EF562D0A26B200943129 /* EventModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventModel.swift; sourceTree = "<group>"; };
7A761C0A267E8FF90005F28F /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
7A7AA2C32DA2A3CB00276D83 /* LocationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationError.swift; sourceTree = "<group>"; };
7A7DADAB2D99738300F52F6C /* AudioRecordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecordView.swift; sourceTree = "<group>"; };
7A809F382D66755B00CF1B3C /* Error+Canceled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Canceled.swift"; sourceTree = "<group>"; };
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 = "<group>"; };
7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleVersion.swift; sourceTree = "<group>"; };
7A8AB76725A0DC8200ECF2C1 /* DebugInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugInfo.swift; sourceTree = "<group>"; };
7A912F362D381B7400002938 /* LicensePlateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensePlateView.swift; sourceTree = "<group>"; };
@ -438,13 +423,12 @@
7AC355582969746600889457 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
7AC3555A296995B200889457 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = "<group>"; };
7AC44B812DB390B900ADC026 /* MainTabScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabScreen.swift; sourceTree = "<group>"; };
7AC44B832DB3EF7A00ADC026 /* HistorySplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistorySplitScreen.swift; sourceTree = "<group>"; };
7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideTabBarModifier.swift; sourceTree = "<group>"; };
7AC44B872DB438F200ADC026 /* UIView+layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+layout.swift"; sourceTree = "<group>"; };
7AC44B892DB4395300ADC026 /* SearchSplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSplitScreen.swift; sourceTree = "<group>"; };
7AC76D7A270083AE0084DB27 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
7AC8B2752D6A01C700190706 /* UISearchTextField+Dumb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchTextField+Dumb.swift"; sourceTree = "<group>"; };
7ADF6C92250B954900F237B2 /* Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigation.swift; sourceTree = "<group>"; };
7AD176AC2DC110830023049D /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = "<group>"; };
7ADCBC562DB51739002522C0 /* AutoCatApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCatApp.swift; sourceTree = "<group>"; };
7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNKeyboard.swift; sourceTree = "<group>"; };
7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = "<group>"; };
7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = "<group>"; };
@ -452,12 +436,13 @@
7ADFC9582DAD1C3D001A43E3 /* GoogleAuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthViewModel.swift; sourceTree = "<group>"; };
7ADFC95A2DAD1F45001A43E3 /* WebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebView.swift; sourceTree = "<group>"; };
7AE24C5E251F1B4E00758E39 /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = "<group>"; };
7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = "<group>"; };
7AE8424D26109F78002F6B31 /* Exportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Exportable.swift; sourceTree = "<group>"; };
7AE8CBB22DBA1475005EF1AB /* UIDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIDevice.swift; sourceTree = "<group>"; };
7AE8CBB42DBA3B55005EF1AB /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
7AE8CBB62DBA3E4E005EF1AB /* MainSplitScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitScreen.swift; sourceTree = "<group>"; };
7AEAA2A02DAD9C00009954F0 /* TokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenResponse.swift; sourceTree = "<group>"; };
7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthScreen.swift; sourceTree = "<group>"; };
7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = "<group>"; };
7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthCoordinator.swift; sourceTree = "<group>"; };
7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACButtonView.swift; sourceTree = "<group>"; };
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 = "<group>"; };
@ -468,6 +453,7 @@
7AFBE8C92C3081C7003C491D /* ACProgressHud+Modifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ACProgressHud+Modifiers.swift"; sourceTree = "<group>"; };
7AFBE8CB2C3085C6003C491D /* ACProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACProgressView.swift; sourceTree = "<group>"; };
7AFBE8CD2C308B53003C491D /* ACMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACMessageView.swift; sourceTree = "<group>"; };
7AFFF79F2DBAAFF300EE2DEE /* SideBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarItem.swift; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -642,8 +625,8 @@
7A11474223FF06B600B424AF /* Utils */ = {
isa = PBXGroup;
children = (
7A14416D2C297F7C00E79018 /* Coordinator.swift */,
7AB4E4652D58A16C0006D052 /* GenericError.swift */,
7AE8CBB42DBA3B55005EF1AB /* Router.swift */,
);
path = Utils;
sourceTree = "<group>";
@ -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 = "<group>";
@ -1085,12 +1064,14 @@
path = Extensions;
sourceTree = "<group>";
};
7AC44B802DB390A500ADC026 /* MainTabScreen */ = {
7AC44B802DB390A500ADC026 /* MainScreen */ = {
isa = PBXGroup;
children = (
7AC44B812DB390B900ADC026 /* MainTabScreen.swift */,
7AE8CBB62DBA3E4E005EF1AB /* MainSplitScreen.swift */,
7AFFF79F2DBAAFF300EE2DEE /* SideBarItem.swift */,
);
path = MainTabScreen;
path = MainScreen;
sourceTree = "<group>";
};
7ADFC9552DAD026C001A43E3 /* GoogleAuthScreen */ = {
@ -1108,7 +1089,6 @@
children = (
7AF231922DA1C28100AE5EB3 /* AuthScreen.swift */,
7AF231942DA1C29300AE5EB3 /* AuthViewModel.swift */,
7AF231962DA1C30000AE5EB3 /* AuthCoordinator.swift */,
);
path = AuthScreen;
sourceTree = "<group>";
@ -1174,7 +1154,7 @@
7AB4902A2D6B1446002F39C6 /* ACKeyboardButton.swift */,
7A589E0E2D6B6E8E00EF3FBE /* NumberEditView.swift */,
7AF231982DA27C1B00AE5EB3 /* ACButtonView.swift */,
7AC44B852DB40A5C00ADC026 /* HideTabBarModifier.swift */,
7AD176AC2DC110830023049D /* Environment.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
@ -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 = "<group>";
};
7A11470B23FDE7E600B424AF /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
7A11470C23FDE7E600B424AF /* Base */,
7A61FF8325759DE700D905D5 /* ru */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
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;

View File

@ -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<UISceneSession>) {
// 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.
}
}

86
AutoCat/AutoCatApp.swift Normal file
View File

@ -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())
}
}

View File

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="dark"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
</dependencies>
<scenes/>
</document>

View File

@ -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)
}
}

View File

@ -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() }
}
}

View File

@ -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))
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -2,25 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.json</string>
</array>
<key>UTTypeIdentifier</key>
<string>pro.aliencat.vehicle.event</string>
<key>UTTypeTagSpecification</key>
<dict/>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>yandexmaps</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
@ -37,8 +18,14 @@
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>yandexmaps</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
@ -93,14 +80,15 @@
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UILaunchScreen</key>
<dict>
<key>UIImageName</key>
<string></string>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
@ -118,5 +106,18 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.json</string>
</array>
<key>UTTypeIdentifier</key>
<string>pro.aliencat.vehicle.event</string>
<key>UTTypeTagSpecification</key>
<dict/>
</dict>
</array>
</dict>
</plist>

View File

@ -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<NumberPayload>(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<NumberPayload>(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)
}
}
}

View File

@ -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())
}
}

View File

@ -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)

View File

@ -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()
}
}

View File

@ -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 {

View File

@ -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) {

View File

@ -134,6 +134,6 @@ struct FiltersScreen: View {
.task {
await viewModel.loadData()
}
.hideTabBar()
.toolbar(.hidden, for: .tabBar)
}
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -30,6 +30,7 @@ final class HistoryViewModel: ACHudContainer {
var vehiclesFiltered: [VehicleDto] = []
var vehicleSections: [DateSection<VehicleDto>] = []
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<NumberPayload>(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
}
}
}
}

View File

@ -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) }
}
}

View File

@ -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) }
}
}

View File

@ -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 }
}
}

View File

@ -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) }
}
}

View File

@ -61,6 +61,6 @@ struct MapScreen: View {
}
}
}
.hideTabBar()
.toolbar(.hidden, for: .tabBar)
}
}

View File

@ -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")

View File

@ -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

View File

@ -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)
}
}

View File

@ -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() {

View File

@ -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
}
}

View File

@ -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) {

View File

@ -14,18 +14,23 @@ struct ACProgressHudModifier: ViewModifier {
func body(content: Content) -> some View {
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
if let item {
ZStack {
content
makeHud(for: item)
}
} else {
content
}
// content
// .fullScreenCover(item: $item) { item in
// makeHud(for: item)
// .presentationBackground(.clear)
// }
// .transaction { transaction in
// transaction.disablesAnimations = true
// }
}
@ViewBuilder

View File

@ -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
}

View File

@ -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())
}
}

View File

@ -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:

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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:

View File

@ -1 +0,0 @@

View File

@ -427,3 +427,9 @@
"Password" = "Пароль";
"Sign up" = "Регистрация";
"Records" = "Аудио записи";
"Search" = "Поиск";
"Settings" = "Настройки";

View File

@ -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" = "Закрыть";

View File

@ -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
}

View File

@ -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