Compare commits

..

10 Commits

38 changed files with 905 additions and 455 deletions

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 52; objectVersion = 54;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -25,6 +25,15 @@
7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471523FDEB2A00B424AF /* MainSplitController.swift */; }; 7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471523FDEB2A00B424AF /* MainSplitController.swift */; };
7A11471823FDEBFA00B424AF /* ReportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471723FDEBFA00B424AF /* ReportController.swift */; }; 7A11471823FDEBFA00B424AF /* ReportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471723FDEBFA00B424AF /* ReportController.swift */; };
7A11471A23FE839000B424AF /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471923FE839000B424AF /* AuthController.swift */; }; 7A11471A23FE839000B424AF /* AuthController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11471923FE839000B424AF /* AuthController.swift */; };
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A17CE492A2E820300626A6E /* UIStackView.swift */; };
7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */; };
7A1CF80329A41C62007962DA /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80229A41C62007962DA /* Realm */; };
7A1CF80529A41C66007962DA /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80429A41C66007962DA /* RealmSwift */; };
7A1CF80829A41D58007962DA /* RxBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80729A41D58007962DA /* RxBlocking */; };
7A1CF80A29A41D58007962DA /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80929A41D58007962DA /* RxCocoa */; };
7A1CF80C29A41D58007962DA /* RxRelay in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80B29A41D58007962DA /* RxRelay */; };
7A1CF80E29A41D58007962DA /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80D29A41D58007962DA /* RxSwift */; };
7A1CF81629A42117007962DA /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1CF81529A42117007962DA /* Realm.swift */; };
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */; }; 7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */; };
7A21112A24FC3D7E003BBF6F /* AudioEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */; }; 7A21112A24FC3D7E003BBF6F /* AudioEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */; };
7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADC6249D43210035F39E /* RegionsController.swift */; }; 7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADC6249D43210035F39E /* RegionsController.swift */; };
@ -76,9 +85,6 @@
7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; }; 7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; };
7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */; }; 7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A99406326E4BFAE002E9CB6 /* VehicleNoteCell.swift */; };
7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */; }; 7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FEEC72529AB23001CA50E /* RxRealmDataSource.swift */; };
7AA54C1C26CD977A00F2BF28 /* RxCocoa in Frameworks */ = {isa = PBXBuildFile; productRef = 7AA54C1B26CD977A00F2BF28 /* RxCocoa */; };
7AA54C1E26CD977A00F2BF28 /* RxSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7AA54C1D26CD977A00F2BF28 /* RxSwift */; settings = {ATTRIBUTES = (Required, ); }; };
7AA54C2026CD977A00F2BF28 /* RxRealm in Frameworks */ = {isa = PBXBuildFile; productRef = 7AA54C1F26CD977A00F2BF28 /* RxRealm */; };
7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D332402A91C00CE01A0 /* Kingfisher */; }; 7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D332402A91C00CE01A0 /* Kingfisher */; };
7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; }; 7AA7BC3525A5DFB80053A5D5 /* ExceptionCatcher in Frameworks */ = {isa = PBXBuildFile; productRef = 7A813DC02508C4D900CC93B9 /* ExceptionCatcher */; };
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; }; 7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABDE1C2532F3EB0041AFC6 /* PKHUD */; };
@ -128,8 +134,6 @@
7AF6D21F2677C1680086EA64 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.swift */; }; 7AF6D21F2677C1680086EA64 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.swift */; };
7AF6D2202677C1680086EA64 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A333813249A532400D878F1 /* Filter.swift */; }; 7AF6D2202677C1680086EA64 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A333813249A532400D878F1 /* Filter.swift */; };
7AF6D2212677C1680086EA64 /* PagedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841A913FABBB0AB20DEF4FC /* PagedResponse.swift */; }; 7AF6D2212677C1680086EA64 /* PagedResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6841A913FABBB0AB20DEF4FC /* PagedResponse.swift */; };
7AF6D2232677C2B40086EA64 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF6D2222677C2B40086EA64 /* Realm */; };
7AF6D2252677C2B40086EA64 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF6D2242677C2B40086EA64 /* RealmSwift */; };
7AF6D2282677C2DC0086EA64 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE30246B2FE400297C33 /* Constants.swift */; }; 7AF6D2282677C2DC0086EA64 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE30246B2FE400297C33 /* Constants.swift */; };
7AF6D22A2677C3AD0086EA64 /* Exportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8424D26109F78002F6B31 /* Exportable.swift */; }; 7AF6D22A2677C3AD0086EA64 /* Exportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8424D26109F78002F6B31 /* Exportable.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -196,6 +200,9 @@
7A11474A23FF368B00B424AF /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; }; 7A11474A23FF368B00B424AF /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = "<group>"; }; 7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = "<group>"; };
7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.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>"; };
7A1CF81529A42117007962DA /* Realm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Realm.swift; sourceTree = "<group>"; };
7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBarButtonItem.swift; sourceTree = "<group>"; }; 7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBarButtonItem.swift; sourceTree = "<group>"; };
7A21112924FC3D7E003BBF6F /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = "<group>"; }; 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngine.swift; sourceTree = "<group>"; };
7A27ADC6249D43210035F39E /* RegionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsController.swift; sourceTree = "<group>"; }; 7A27ADC6249D43210035F39E /* RegionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsController.swift; sourceTree = "<group>"; };
@ -323,12 +330,13 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
7AA54C1E26CD977A00F2BF28 /* RxSwift in Frameworks */, 7A1CF80C29A41D58007962DA /* RxRelay in Frameworks */,
7AF6D2252677C2B40086EA64 /* RealmSwift in Frameworks */, 7A1CF80529A41C66007962DA /* RealmSwift in Frameworks */,
7AA54C1C26CD977A00F2BF28 /* RxCocoa in Frameworks */, 7A1CF80829A41D58007962DA /* RxBlocking in Frameworks */,
7AF6D2232677C2B40086EA64 /* Realm in Frameworks */, 7A1CF80A29A41D58007962DA /* RxCocoa in Frameworks */,
7A1CF80329A41C62007962DA /* Realm in Frameworks */,
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */, 7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */,
7AA54C2026CD977A00F2BF28 /* RxRealm in Frameworks */, 7A1CF80E29A41D58007962DA /* RxSwift in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -607,6 +615,8 @@
7AC355582969746600889457 /* UIControl.swift */, 7AC355582969746600889457 /* UIControl.swift */,
7AC3555A296995B200889457 /* UIEdgeInsets.swift */, 7AC3555A296995B200889457 /* UIEdgeInsets.swift */,
7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */, 7A91894E29A2BD8700519C74 /* GestureRecognizers.swift */,
7A17CE492A2E820300626A6E /* UIStackView.swift */,
7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -659,6 +669,7 @@
children = ( children = (
7A27ADF824A09CAD0035F39E /* CocoaError.swift */, 7A27ADF824A09CAD0035F39E /* CocoaError.swift */,
7AE8424D26109F78002F6B31 /* Exportable.swift */, 7AE8424D26109F78002F6B31 /* Exportable.swift */,
7A1CF81529A42117007962DA /* Realm.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -735,12 +746,13 @@
); );
name = AutoCatCore; name = AutoCatCore;
packageProductDependencies = ( packageProductDependencies = (
7AF6D2222677C2B40086EA64 /* Realm */,
7AF6D2242677C2B40086EA64 /* RealmSwift */,
7AABB1F1267E9CC800D7AB32 /* SwiftDate */, 7AABB1F1267E9CC800D7AB32 /* SwiftDate */,
7AA54C1B26CD977A00F2BF28 /* RxCocoa */, 7A1CF80229A41C62007962DA /* Realm */,
7AA54C1D26CD977A00F2BF28 /* RxSwift */, 7A1CF80429A41C66007962DA /* RealmSwift */,
7AA54C1F26CD977A00F2BF28 /* RxRealm */, 7A1CF80729A41D58007962DA /* RxBlocking */,
7A1CF80929A41D58007962DA /* RxCocoa */,
7A1CF80B29A41D58007962DA /* RxRelay */,
7A1CF80D29A41D58007962DA /* RxSwift */,
); );
productName = AutoCatCore; productName = AutoCatCore;
productReference = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */; productReference = 7AF6D1EF2677C03B0086EA64 /* AutoCatCore.framework */;
@ -779,15 +791,14 @@
); );
mainGroup = 7A1146F423FDE7E500B424AF; mainGroup = 7A1146F423FDE7E500B424AF;
packageReferences = ( packageReferences = (
7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */,
7A11472423FEA1F400B424AF /* XCRemoteSwiftPackageReference "realm-cocoa" */,
7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */,
7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */, 7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */,
7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */, 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */,
7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */, 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */,
7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */, 7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */,
7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */, 7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */,
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */, 7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */,
7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */,
7A1CF80629A41D58007962DA /* XCRemoteSwiftPackageReference "RxSwift" */,
); );
productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */; productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -860,6 +871,7 @@
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */, 7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
7A0420B62568650C00034941 /* DkbmController.swift in Sources */, 7A0420B62568650C00034941 /* DkbmController.swift in Sources */,
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */, 7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */,
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */,
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */, 7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */, 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */,
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */, 7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
@ -911,6 +923,7 @@
7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */, 7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */,
7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */, 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */,
7A7547E024032CB6004E8406 /* VehiclePhotoCell.swift in Sources */, 7A7547E024032CB6004E8406 /* VehiclePhotoCell.swift in Sources */,
7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */,
6841A85D4B60DB71D1E68DA0 /* ImageGrid.swift in Sources */, 6841A85D4B60DB71D1E68DA0 /* ImageGrid.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -947,6 +960,7 @@
7A0B663729984201006F5189 /* DateCache.swift in Sources */, 7A0B663729984201006F5189 /* DateCache.swift in Sources */,
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */, 7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
7AF6D2152677C1680086EA64 /* Settings.swift in Sources */, 7AF6D2152677C1680086EA64 /* Settings.swift in Sources */,
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */, 7A761C052677F1BC0005F28F /* CocoaError.swift in Sources */,
7AF6D2132677C15A0086EA64 /* AudioRecord.swift in Sources */, 7AF6D2132677C15A0086EA64 /* AudioRecord.swift in Sources */,
7AF6D21B2677C1680086EA64 /* Vehicle.swift in Sources */, 7AF6D21B2677C1680086EA64 /* Vehicle.swift in Sources */,
@ -1144,15 +1158,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 111; CURRENT_PROJECT_VERSION = 117;
DEVELOPMENT_TEAM = 46DTTB8X4S; DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist; INFOPLIST_FILE = AutoCat/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.0; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCat; PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCat;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
@ -1169,15 +1186,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 111; CURRENT_PROJECT_VERSION = 117;
DEVELOPMENT_TEAM = 46DTTB8X4S; DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist; INFOPLIST_FILE = AutoCat/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 13.0; IPHONEOS_DEPLOYMENT_TARGET = 13.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.0; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 13.0;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCat; PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCat;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES; SUPPORTS_MACCATALYST = YES;
@ -1337,20 +1357,20 @@
minimumVersion = 6.1.0; minimumVersion = 6.1.0;
}; };
}; };
7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */ = { 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/realm-swift.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 10.36.0;
};
};
7A1CF80629A41D58007962DA /* XCRemoteSwiftPackageReference "RxSwift" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/ReactiveX/RxSwift.git"; repositoryURL = "https://github.com/ReactiveX/RxSwift.git";
requirement = { requirement = {
kind = upToNextMajorVersion; kind = upToNextMajorVersion;
minimumVersion = 5.0.1; minimumVersion = 6.0.0;
};
};
7A11472423FEA1F400B424AF /* XCRemoteSwiftPackageReference "realm-cocoa" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/realm/realm-cocoa";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.0.0;
}; };
}; };
7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */ = { 7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */ = {
@ -1361,14 +1381,6 @@
minimumVersion = 5.0.0; minimumVersion = 5.0.0;
}; };
}; };
7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/RxSwiftCommunity/RxRealm";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.0.0;
};
};
7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */ = { 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sindresorhus/ExceptionCatcher"; repositoryURL = "https://github.com/sindresorhus/ExceptionCatcher";
@ -1404,6 +1416,36 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
7A1CF80229A41C62007962DA /* Realm */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */;
productName = Realm;
};
7A1CF80429A41C66007962DA /* RealmSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1CF7FD29A41C2F007962DA /* XCRemoteSwiftPackageReference "realm-swift" */;
productName = RealmSwift;
};
7A1CF80729A41D58007962DA /* RxBlocking */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1CF80629A41D58007962DA /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxBlocking;
};
7A1CF80929A41D58007962DA /* RxCocoa */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1CF80629A41D58007962DA /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxCocoa;
};
7A1CF80B29A41D58007962DA /* RxRelay */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1CF80629A41D58007962DA /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxRelay;
};
7A1CF80D29A41D58007962DA /* RxSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 7A1CF80629A41D58007962DA /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxSwift;
};
7A35177A27E23F8800DC538C /* Eureka */ = { 7A35177A27E23F8800DC538C /* Eureka */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */; package = 7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */;
@ -1414,21 +1456,6 @@
package = 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */; package = 7A813DBF2508C4D900CC93B9 /* XCRemoteSwiftPackageReference "ExceptionCatcher" */;
productName = ExceptionCatcher; productName = ExceptionCatcher;
}; };
7AA54C1B26CD977A00F2BF28 /* RxCocoa */ = {
isa = XCSwiftPackageProductDependency;
package = 7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxCocoa;
};
7AA54C1D26CD977A00F2BF28 /* RxSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */;
productName = RxSwift;
};
7AA54C1F26CD977A00F2BF28 /* RxRealm */ = {
isa = XCSwiftPackageProductDependency;
package = 7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */;
productName = RxRealm;
};
7AABB1F1267E9CC800D7AB32 /* SwiftDate */ = { 7AABB1F1267E9CC800D7AB32 /* SwiftDate */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */; package = 7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */;
@ -1449,16 +1476,6 @@
package = 7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */; package = 7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher; productName = Kingfisher;
}; };
7AF6D2222677C2B40086EA64 /* Realm */ = {
isa = XCSwiftPackageProductDependency;
package = 7A11472423FEA1F400B424AF /* XCRemoteSwiftPackageReference "realm-cocoa" */;
productName = Realm;
};
7AF6D2242677C2B40086EA64 /* RealmSwift */ = {
isa = XCSwiftPackageProductDependency;
package = 7A11472423FEA1F400B424AF /* XCRemoteSwiftPackageReference "realm-cocoa" */;
productName = RealmSwift;
};
/* End XCSwiftPackageProductDependency section */ /* End XCSwiftPackageProductDependency section */
}; };
rootObject = 7A1146F523FDE7E500B424AF /* Project object */; rootObject = 7A1146F523FDE7E500B424AF /* Project object */;

View File

@ -36,31 +36,22 @@
"version" : "5.4.0" "version" : "5.4.0"
} }
}, },
{
"identity" : "realm-cocoa",
"kind" : "remoteSourceControl",
"location" : "https://github.com/realm/realm-cocoa",
"state" : {
"revision" : "2dce752b48c3265c63ab04a8c66ddfdf9185f847",
"version" : "5.5.2"
}
},
{ {
"identity" : "realm-core", "identity" : "realm-core",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/realm/realm-core", "location" : "https://github.com/realm/realm-core.git",
"state" : { "state" : {
"revision" : "66d79b3c5213fb14d491c1b22193077b488d49a6", "revision" : "dd91f5f967c4ae89c37e24ab2a0315c31106648f",
"version" : "6.2.4" "version" : "13.6.0"
} }
}, },
{ {
"identity" : "rxrealm", "identity" : "realm-swift",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/RxSwiftCommunity/RxRealm", "location" : "https://github.com/realm/realm-swift.git",
"state" : { "state" : {
"revision" : "4dcae40562b5a086dd4711fa8a596be0436dc474", "revision" : "8ac6fe1aa5d0fb0100062d80863416a4d70de8ca",
"version" : "3.1.0" "version" : "10.37.0"
} }
}, },
{ {
@ -68,8 +59,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveX/RxSwift.git", "location" : "https://github.com/ReactiveX/RxSwift.git",
"state" : { "state" : {
"revision" : "254617dd7fae0c45319ba5fbea435bf4d0e15b5d", "revision" : "b4307ba0b6425c0ba4178e138799946c3da594f8",
"version" : "5.1.2" "version" : "6.5.0"
} }
}, },
{ {

View File

@ -8,7 +8,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent <BreakpointContent
uuid = "676638C8-1CC5-4C04-98B0-1C0D6CB28B76" uuid = "676638C8-1CC5-4C04-98B0-1C0D6CB28B76"
shouldBeEnabled = "Yes" shouldBeEnabled = "No"
ignoreCount = "0" ignoreCount = "0"
continueAfterRunningActions = "No" continueAfterRunningActions = "No"
symbolName = "UITableViewAlertForLayoutOutsideViewHierarchy" symbolName = "UITableViewAlertForLayoutOutsideViewHierarchy"
@ -27,5 +27,68 @@
</Locations> </Locations>
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "2786565A-9610-4232-920E-0763816C4DBF"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../../../Library/Developer/Xcode/DerivedData/AutoCat-fhilwnlnsrpirleiajogdcyhyyey/SourcePackages/checkouts/Eureka/Source/Rows/DateInlineRow.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "configurePickerStyle(_:_:)"
landmarkType = "7">
<Locations>
<Location
uuid = "2786565A-9610-4232-920E-0763816C4DBF - f20dc4504decd67d"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "Eureka.DatePickerRowProtocol.configurePickerStyle(Eureka.DatePickerCell, __C.UIDatePickerMode) -&gt; ()"
moduleName = "AutoCat"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/selim/Library/Developer/Xcode/DerivedData/AutoCat-fhilwnlnsrpirleiajogdcyhyyey/SourcePackages/checkouts/Eureka/Source/Rows/DateInlineRow.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
offsetFromSymbolStart = "260">
</Location>
<Location
uuid = "2786565A-9610-4232-920E-0763816C4DBF - f20dc4504decd67d"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "Eureka.DatePickerRowProtocol.configurePickerStyle(Eureka.DatePickerCell, __C.UIDatePickerMode) -&gt; ()"
moduleName = "AutoCat"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/selim/Library/Developer/Xcode/DerivedData/AutoCat-fhilwnlnsrpirleiajogdcyhyyey/SourcePackages/checkouts/Eureka/Source/Rows/DateInlineRow.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
offsetFromSymbolStart = "256">
</Location>
<Location
uuid = "2786565A-9610-4232-920E-0763816C4DBF - f20dc4504decd67d"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "Eureka.DatePickerRowProtocol.configurePickerStyle(Eureka.DatePickerCell, __C.UIDatePickerMode) -&gt; ()"
moduleName = "AutoCat"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/selim/Library/Developer/Xcode/DerivedData/AutoCat-fhilwnlnsrpirleiajogdcyhyyey/SourcePackages/checkouts/Eureka/Source/Rows/DateInlineRow.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
offsetFromSymbolStart = "112">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
</Breakpoints> </Breakpoints>
</Bucket> </Bucket>

View File

@ -15,6 +15,13 @@ extension UISearchController {
searchController.obscuresBackgroundDuringPresentation = false searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.keyboardType = .webSearch searchController.searchBar.keyboardType = .webSearch
if #available(iOS 16, *) {
searchController.scopeBarActivation = .onTextEntry
} else {
searchController.automaticallyShowsScopeBar = true
}
return searchController return searchController
} }
@ -37,4 +44,14 @@ extension UISearchController {
searchBar.smartInsertDeleteType = .no searchBar.smartInsertDeleteType = .no
return self return self
} }
func scopeButtons(_ buttons: [String]) -> UISearchController {
searchBar.scopeButtonTitles = buttons
return self
}
func searchBarDelegate(_ delegate: UISearchBarDelegate) -> UISearchController {
searchBar.delegate = delegate
return self
}
} }

View File

@ -0,0 +1,42 @@
//
// UISegmentedControl.swift
// AutoCat
//
// Created by Selim Mustafaev on 06.06.2023.
// Copyright © 2023 Selim Mustafaev. All rights reserved.
//
import UIKit
extension UISegmentedControl {
static func segments(titles: [String]) -> UISegmentedControl {
let view = UISegmentedControl(items: titles)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}
static func segments(images: [UIImage?]) -> UISegmentedControl {
let view = UISegmentedControl(items: images.compactMap { $0 })
view.translatesAutoresizingMaskIntoConstraints = false
return view
}
func onValueChanged(_ closure: @escaping (Int) -> Void) -> UISegmentedControl {
addActionImpl(for: .valueChanged) { [weak self] in
guard let index = self?.selectedSegmentIndex else {
return
}
closure(index)
}
return self
}
func select(index: Int) -> UISegmentedControl {
selectedSegmentIndex = index
return self
}
}

View File

@ -0,0 +1,60 @@
//
// UIStackView.swift
// AutoCat
//
// Created by Selim Mustafaev on 05.06.2023.
// Copyright © 2023 Selim Mustafaev. All rights reserved.
//
import UIKit
extension UIStackView {
static func horizontal(_ views: [UIView]) -> UIStackView {
let stack = UIStackView(arrangedSubviews: views)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .horizontal
stack.spacing = 16
return stack
}
static func vertical(_ views: [UIView]) -> UIStackView {
let stack = UIStackView(arrangedSubviews: views)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
stack.spacing = 16
return stack
}
func spacing(_ spacing: CGFloat) -> UIStackView {
self.spacing = spacing
return self
}
func alignment(_ alignment: Alignment) -> UIStackView {
self.alignment = alignment
return self
}
func distribution(_ distribution: Distribution) -> UIStackView {
self.distribution = distribution
return self
}
func removeAllArrangedSubviews() {
arrangedSubviews.forEach {
self.removeArrangedSubview($0)
NSLayoutConstraint.deactivate($0.constraints)
$0.removeFromSuperview()
}
}
func addArrangedSubviews(_ views: [UIView]) {
views.forEach { addArrangedSubview($0) }
}
func setArrangedSubviews(_ views: [UIView]) {
removeAllArrangedSubviews()
addArrangedSubviews(views)
}
}

View File

@ -26,92 +26,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let config = Realm.Configuration( let config = Realm.Configuration(
schemaVersion: 38, schemaVersion: 40,
migrationBlock: { migration, oldSchemaVersion in migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion <= 3 {
var numbers: [String] = []
migration.enumerateObjects(ofType: "Vehicle") { old, new in
if let number = old?["number"] as? String {
if numbers.contains(number) {
migration.delete(old!)
} else {
numbers.append(number)
}
}
}
}
if oldSchemaVersion <= 14 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
new!["isRightWheel"] = RealmOptional<Bool>(old!["isRightWheel"] as? Bool)
}
}
if oldSchemaVersion <= 18 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
let addedDate = old!["addedDate"] as! TimeInterval
let events = old!.dynamicList("events")
new!["addedDate"] = addedDate/1000
let lastEvent = events.max { first, second in
let firstDate = first["date"] as! TimeInterval
let secondDate = second["date"] as! TimeInterval
return firstDate < secondDate
}
if let lastEvent = lastEvent {
new!["updatedDate"] = max(addedDate/1000, lastEvent["date"] as! TimeInterval)
} else {
new!["updatedDate"] = addedDate/1000
}
}
}
if oldSchemaVersion == 19 || oldSchemaVersion == 20 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
guard let addedDate = old?["addedDate"] as? TimeInterval else { return }
guard let updatedDate = old?["updatedDate"] as? TimeInterval else { return }
if addedDate > TimeInterval(Int32.max) {
new!["addedDate"] = addedDate/1000
}
if updatedDate > TimeInterval(Int32.max) {
new!["updatedDate"] = updatedDate/1000
}
}
}
if oldSchemaVersion <= 21 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
if let oldEngineVolume = (old?["engine"] as? MigrationObject)?["volume"] as? Int {
(new?["engine"] as? MigrationObject)?["volume"] = RealmOptional(oldEngineVolume)
} else {
(new?["engine"] as? MigrationObject)?["volume"] = RealmOptional(0)
}
}
}
if oldSchemaVersion <= 22 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
if let oldEnginePower = (old?["engine"] as? MigrationObject)?["powerKw"] as? Int {
(new?["engine"] as? MigrationObject)?["powerKw"] = RealmOptional(oldEnginePower)
} else {
(new?["engine"] as? MigrationObject)?["powerKw"] = RealmOptional(0)
}
}
}
if oldSchemaVersion <= 23 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
if let oldJapanese = old?["isJapanese"] as? Bool {
new?["isJapanese"] = RealmOptional(oldJapanese)
} else {
new?["isJapanese"] = RealmOptional<Bool>()
}
}
}
if oldSchemaVersion <= 31 { if oldSchemaVersion <= 31 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in migration.enumerateObjects(ofType: "Vehicle") { old, new in
if let oldDebugInfo = old?["debugInfo"] as? DynamicObject, let newDebugInfo = new?["debugInfo"] as? DynamicObject { if let oldDebugInfo = old?["debugInfo"] as? DynamicObject, let newDebugInfo = new?["debugInfo"] as? DynamicObject {

View File

@ -173,17 +173,17 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes> <prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="EventCell" id="QIb-Hv-tvk" customClass="EventCell" customModule="AutoCat" customModuleProvider="target"> <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="EventCell" id="QIb-Hv-tvk" customClass="EventCell" customModule="AutoCat" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="375" height="431.5"/> <rect key="frame" x="0.0" y="50" width="375" height="432"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QIb-Hv-tvk" id="Ypt-ch-fGT"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QIb-Hv-tvk" id="Ypt-ch-fGT">
<rect key="frame" x="0.0" y="0.0" width="375" height="431.5"/> <rect key="frame" x="0.0" y="0.0" width="375" height="432"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HP8-oO-yhP"> <stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HP8-oO-yhP">
<rect key="frame" x="16" y="8" width="343" height="415.5"/> <rect key="frame" x="16" y="8" width="343" height="416"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="k4Z-KM-byE"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="k4Z-KM-byE">
<rect key="frame" x="0.0" y="0.0" width="335" height="415.5"/> <rect key="frame" x="0.0" y="0.0" width="335" height="416"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xcQ-Wz-gJ0"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xcQ-Wz-gJ0">
<rect key="frame" x="0.0" y="0.0" width="335" height="201"/> <rect key="frame" x="0.0" y="0.0" width="335" height="201"/>
@ -192,7 +192,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1tQ-zM-6T9"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1tQ-zM-6T9">
<rect key="frame" x="0.0" y="209" width="335" height="206.5"/> <rect key="frame" x="0.0" y="209" width="335" height="207"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/> <fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" systemColor="secondaryLabelColor"/> <color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@ -200,7 +200,7 @@
</subviews> </subviews>
</stackView> </stackView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="750" verticalHuggingPriority="251" image="person" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="CFI-xa-eLs"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="750" verticalHuggingPriority="251" image="person" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="CFI-xa-eLs">
<rect key="frame" x="343" y="1.5" width="0.0" height="413"/> <rect key="frame" x="343" y="1.5" width="0.0" height="413.5"/>
</imageView> </imageView>
</subviews> </subviews>
</stackView> </stackView>

View File

@ -65,7 +65,7 @@ class AuthController: UIViewController, ASAuthorizationControllerDelegate, ASAut
authorizationController.performRequests() authorizationController.performRequests()
} }
func goToMainScreen(user: User) { func goToMainScreen(user: AutoCatCore.User) {
guard let realm = try? Realm() else { guard let realm = try? Realm() else {
HUD.flash(.labeledError(title: nil, subtitle: "Database error")) HUD.flash(.labeledError(title: nil, subtitle: "Database error"))
return return

View File

@ -2,7 +2,6 @@ import UIKit
import RealmSwift import RealmSwift
import RxSwift import RxSwift
import SwiftDate import SwiftDate
import RxRealm
import PKHUD import PKHUD
import CoreLocation import CoreLocation
import AutoCatCore import AutoCatCore
@ -100,7 +99,7 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
} }
HUD.hide() HUD.hide()
self.showErrors(errors) self.showErrors(errors)
} onError: { error in } onFailure: { error in
HUD.hide() HUD.hide()
self.show(error: error) self.show(error: error)
//HUD.show(error: error) //HUD.show(error: error)
@ -138,7 +137,12 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
let csvString = try self.historyDataSource.makeCsv() let csvString = try self.historyDataSource.makeCsv()
let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "csv") let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "csv")
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8) try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
#if targetEnvironment(macCatalyst)
self.save(file: tmpUrl)
#else
self.shareFile(tmpUrl) self.shareFile(tmpUrl)
#endif
} catch { } catch {
self.show(error: error) self.show(error: error)
} }
@ -199,6 +203,16 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
self.present(activityController, animated: true) self.present(activityController, animated: true)
} }
func save(file url: URL) {
if #available(iOS 14, *) {
let controller = UIDocumentPickerViewController(forExporting: [url])
self.present(controller, animated: true)
} else {
let controller = UIDocumentPickerViewController(url: url, in: .exportToService)
present(controller, animated: true)
}
}
// MARK: - Checking new number // MARK: - Checking new number
func checkTapped(number: String) { func checkTapped(number: String) {
@ -221,7 +235,7 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
} }
HUD.hide() HUD.hide()
self.showErrors(errors) self.showErrors(errors)
} onError: { error in } onFailure: { error in
HUD.hide() HUD.hide()
self.show(error: error) self.show(error: error)
} }
@ -324,7 +338,7 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
} }
HUD.hide() HUD.hide()
self.showErrors(errors) self.showErrors(errors)
} onError: { error in } onFailure: { error in
HUD.hide() HUD.hide()
self.show(error: error) self.show(error: error)
} }
@ -365,19 +379,19 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
var eventSingle: Single<(event: VehicleEvent?, error: Error?)> = .just((event: nil, error: nil)) var eventSingle: Single<(event: VehicleEvent?, error: Error?)> = .just((event: nil, error: nil))
if action != .doNotSend { if action != .doNotSend {
eventSingle = self.getEvent(for: action) eventSingle = self.getEvent(for: action)
.flatMap { event in event.findAddress().map{ event }.catchErrorJustReturn(event) } .flatMap { event in event.findAddress().map{ event }.catchAndReturn(event) }
.map { event -> (event: VehicleEvent?, error: Error?) in (event: event, error: nil) } .map { event -> (event: VehicleEvent?, error: Error?) in (event: event, error: nil) }
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.catchError { .just((event: nil, error: $0)) } .catch { .just((event: nil, error: $0)) }
} }
let checkSingle = Api.checkVehicle(by: number, notes: notes, events: events, force: force) let checkSingle = Api.checkVehicle(by: number, notes: notes, events: events, force: force)
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.map { (vehicle: Vehicle) -> (vehicle: Vehicle, error: Error?) in .map { (vehicle: Vehicle) -> (vehicle: Vehicle, error: Error?) in
try self.save(vehicle: vehicle) try self.save(vehicle: vehicle)
return (vehicle: vehicle, error: nil) return (vehicle: vehicle, error: nil)
} }
.catchError { error in .catch { error in
let realm = try Realm() let realm = try Realm()
if let existingVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: number) { if let existingVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: number) {
return .just((vehicle: existingVehicle, error: error)) return .just((vehicle: existingVehicle, error: error))
@ -419,12 +433,12 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
} else { } else {
if let event = eventResult.event { if let event = eventResult.event {
return Api.add(event: event, to: vehicleResult.vehicle.getNumber()) return Api.add(event: event, to: vehicleResult.vehicle.getNumber())
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.map { .map {
try self.save(vehicle: $0) try self.save(vehicle: $0)
return (vehicle: $0, errors: errors) return (vehicle: $0, errors: errors)
} }
.catchError { error in .catch { error in
errors.append(error) errors.append(error)
return .just((vehicle: vehicleResult.vehicle, errors: errors)) return .just((vehicle: vehicleResult.vehicle, errors: errors))
} }

View File

@ -145,42 +145,77 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
// MARK: - UITableViewDelegate // MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return nil
}
let event = vehicle.events[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
let copy = UIAction(title: NSLocalizedString("Copy", comment: ""), image: UIImage(systemName: "doc.on.doc")) { action in
self.copyEvent(index: indexPath.row) let copy = UIAction(title: NSLocalizedString("Copy", comment: ""),
image: UIImage(systemName: "doc.on.doc"),
handler: { _ in self.copyEvent(event: event) })
let share = UIAction(title: NSLocalizedString("Share", comment: ""),
image: UIImage(systemName: "square.and.arrow.up"),
handler: { _ in self.shareEvent(event: event) })
let edit = UIAction(title: NSLocalizedString("Edit", comment: ""),
image: UIImage(systemName: "pencil"),
handler: { _ in self.editEvent(event: event) })
let delete = UIAction(title: NSLocalizedString("Delete", comment: ""),
image: UIImage(systemName: "trash"),
attributes: .destructive,
handler: { _ in self.deleteEvent(event: event) })
let openApple = UIAction(title: NSLocalizedString("Apple Maps", comment: ""),
image: UIImage(systemName: "map"),
handler: { _ in self.openInAppleMaps(event: event) })
let openYandex = UIAction(title: NSLocalizedString("Yandex Maps", comment: ""),
image: UIImage(systemName: "map"),
handler: { _ in self.openInYandexMaps(event: event) })
let openMenu: UIMenuElement
if let yandexUrl = URL(string: "yandexmaps://"),
UIApplication.shared.canOpenURL(yandexUrl)
{
openMenu = UIMenu(title: NSLocalizedString("Open in ...", comment: ""),
children: [openApple, openYandex])
} else {
openApple.title = NSLocalizedString("Open in Apple Maps", comment: "")
openMenu = openApple
} }
let share = UIAction(title: NSLocalizedString("Share", comment: ""), image: UIImage(systemName: "square.and.arrow.up")) { action in return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [copy, share, edit, openMenu, delete])
self.shareEvent(index: indexPath.row)
}
let edit = UIAction(title: NSLocalizedString("Edit", comment: ""), image: UIImage(systemName: "pencil")) { action in
self.editEvent(index: indexPath.row)
}
let delete = UIAction(title: NSLocalizedString("Delete", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { action in
self.deleteEvent(index: indexPath.row)
}
return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [copy, share, edit, delete])
} }
} }
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return nil
}
let event = vehicle.events[indexPath.row]
let copy = UIContextualAction(style: .normal, title: NSLocalizedString("Copy", comment: "")) { action, view, completion in let copy = UIContextualAction(style: .normal, title: NSLocalizedString("Copy", comment: "")) { action, view, completion in
self.copyEvent(index: indexPath.row) self.copyEvent(event: event)
completion(true) completion(true)
} }
copy.image = UIImage(systemName: "doc.on.doc") copy.image = UIImage(systemName: "doc.on.doc")
copy.backgroundColor = .systemBlue copy.backgroundColor = .systemBlue
let delete = UIContextualAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { action, view, completion in let delete = UIContextualAction(style: .destructive, title: NSLocalizedString("Delete", comment: "")) { action, view, completion in
self.deleteEvent(index: indexPath.row, completion: completion) self.deleteEvent(event: event, completion: completion)
} }
delete.image = UIImage(systemName: "trash") delete.image = UIImage(systemName: "trash")
let edit = UIContextualAction(style: .normal, title: NSLocalizedString("Edit", comment: "")) { action, view, completion in let edit = UIContextualAction(style: .normal, title: NSLocalizedString("Edit", comment: "")) { action, view, completion in
self.editEvent(index: indexPath.row) self.editEvent(event: event)
completion(true) completion(true)
} }
edit.image = UIImage(systemName: "pencil") edit.image = UIImage(systemName: "pencil")
@ -206,31 +241,19 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
// MARK: - Event actions // MARK: - Event actions
func deleteEvent(index: Int, completion: ((Bool) -> Void)? = nil) { func deleteEvent(event: VehicleEvent, completion: ((Bool) -> Void)? = nil) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return
}
let event = vehicle.events[index]
HUD.show(.progress) HUD.show(.progress)
Api.remove(event: event.id).observeOn(MainScheduler.instance).subscribe(onSuccess: { vehicle in Api.remove(event: event.id).observe(on: MainScheduler.instance).subscribe(onSuccess: { vehicle in
let result = self.update(vehicle: vehicle) let result = self.update(vehicle: vehicle)
completion?(result) completion?(result)
}, onError: { error in }, onFailure: { error in
completion?(false) completion?(false)
HUD.show(error: error) HUD.show(error: error)
print(error) print(error)
}).disposed(by: self.bag) }).disposed(by: self.bag)
} }
func editEvent(index: Int) { func editEvent(event: VehicleEvent) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return
}
let event = vehicle.events[index]
let sb = UIStoryboard(name: "Main", bundle: nil) let sb = UIStoryboard(name: "Main", bundle: nil)
let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController
controller.title = NSLocalizedString("Edit event", comment: "") controller.title = NSLocalizedString("Edit event", comment: "")
@ -241,8 +264,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.navigationController?.popViewController(animated: true, completion: { self.navigationController?.popViewController(animated: true, completion: {
HUD.show(.progress) HUD.show(.progress)
Api.edit(event: newEvent) Api.edit(event: newEvent)
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onError: .subscribe(onSuccess: { self.update(vehicle: $0) }, onFailure:
{ error in { error in
HUD.show(error: error) HUD.show(error: error)
}) })
@ -252,14 +275,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.navigationController?.pushViewController(controller, animated: true) self.navigationController?.pushViewController(controller, animated: true)
} }
func copyEvent(index: Int) { func copyEvent(event: VehicleEvent) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return
}
var items: [String: Any] = [:] var items: [String: Any] = [:]
let event = vehicle.events[index]
if let url = event.getMapLink() { if let url = event.getMapLink() {
items[kUTTypeURL as String] = url items[kUTTypeURL as String] = url
@ -278,17 +295,29 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.setupBarButtonItems() self.setupBarButtonItems()
} }
func shareEvent(index: Int) { func shareEvent(event: VehicleEvent) {
guard let vehicle = self.vehicle else { guard let url = event.getMapLink() else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return return
} }
let event = vehicle.events[index] let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
if let url = event.getMapLink() { self.present(controller, animated: true)
let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil) }
self.present(controller, animated: true)
func openInAppleMaps(event: VehicleEvent) {
let coordinates = CLLocationCoordinate2D(latitude: event.latitude,
longitude: event.longitude)
let placemark = MKPlacemark(coordinate: coordinates)
let mapItem = MKMapItem(placemark: placemark)
mapItem.openInMaps()
}
func openInYandexMaps(event: VehicleEvent) {
guard let url = URL(string: "yandexmaps://maps.yandex.ru/?pt=\(event.longitude),\(event.latitude)&z=12") else {
return
} }
UIApplication.shared.open(url)
} }
@objc func addEvent(_ sender: UIBarButtonItem) { @objc func addEvent(_ sender: UIBarButtonItem) {
@ -304,8 +333,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.navigationController?.popViewController(animated: true, completion: { self.navigationController?.popViewController(animated: true, completion: {
HUD.show(.progress) HUD.show(.progress)
Api.add(event: newEvent, to: vehicle.getNumber()) Api.add(event: newEvent, to: vehicle.getNumber())
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onError: .subscribe(onSuccess: { self.update(vehicle: $0) }, onFailure:
{ error in { error in
HUD.show(error: error) HUD.show(error: error)
}) })
@ -334,8 +363,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
HUD.show(.progress) HUD.show(.progress)
event.id = UUID().uuidString event.id = UUID().uuidString
Api.add(event: event, to: vehicle.getNumber()) Api.add(event: event, to: vehicle.getNumber())
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onError: .subscribe(onSuccess: { self.update(vehicle: $0) }, onFailure:
{ error in { error in
HUD.show(error: error) HUD.show(error: error)
}) })

View File

@ -25,7 +25,7 @@ class GlobalEventsController: UIViewController {
HUD.show(.progress) HUD.show(.progress)
Api.events(with: self.filter) Api.events(with: self.filter)
.observeOn(MainScheduler.init()) .observe(on: MainScheduler.init())
.subscribe(onSuccess: { events in .subscribe(onSuccess: { events in
self.title = String.localizedStringWithFormat(NSLocalizedString("events found", comment: ""), events.count) self.title = String.localizedStringWithFormat(NSLocalizedString("events found", comment: ""), events.count)
let pins = events.map(EventPin.init(event:)) let pins = events.map(EventPin.init(event:))
@ -33,9 +33,9 @@ class GlobalEventsController: UIViewController {
self.map.addAnnotations(pins) self.map.addAnnotations(pins)
self.map.centerOnPins() self.map.centerOnPins()
HUD.hide() HUD.hide()
}) { error in }, onFailure: { error in
HUD.show(error: error) HUD.show(error: error)
} })
.disposed(by: self.bag) .disposed(by: self.bag)
} }

View File

@ -1,9 +1,24 @@
import UIKit import UIKit
import AutoCatCore import AutoCatCore
// MARK: - Types
enum NumberType: Int, CaseIterable {
case plateNumber = 0
case vin = 1
var title: String {
switch self {
case .plateNumber: return "GRZ"
case .vin: return "VIN"
}
}
}
class NewNumberController: UIViewController { class NewNumberController: UIViewController {
public var onCheck: ((String) -> Void)? // MARK: - Views
private lazy var keyboardView: PNKeyboard = { private lazy var keyboardView: PNKeyboard = {
let keyboard = PNKeyboard(target: self.plateView) let keyboard = PNKeyboard(target: self.plateView)
@ -21,21 +36,26 @@ class NewNumberController: UIViewController {
return view return view
}() }()
private lazy var vinField: UITextField = {
let view = UITextField()
view.translatesAutoresizingMaskIntoConstraints = false
view.borderStyle = .roundedRect
view.isHidden = true
return view
}()
private lazy var checkButton: ACButton = { private lazy var checkButton: ACButton = {
let button = ACButton(title: NSLocalizedString("Check", comment: ""), onTap: check) let button = ACButton(title: NSLocalizedString("Check", comment: ""), onTap: check)
button.isEnabled = false button.isEnabled = false
button.contentEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 8) button.contentEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 8)
button.accessibilityIdentifier = "checkButton" button.accessibilityIdentifier = "checkButton"
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return button return button
}() }()
private lazy var stackView: UIStackView = { private lazy var stackView: UIStackView = .horizontal([
let stack = UIStackView(arrangedSubviews: [plateView, checkButton]) plateView, vinField, checkButton
stack.axis = .horizontal ])
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private let titleLabel: UILabel = { private let titleLabel: UILabel = {
let label = UILabel() let label = UILabel()
@ -46,13 +66,35 @@ class NewNumberController: UIViewController {
return label return label
}() }()
private lazy var mainStackView: UIStackView = { private lazy var mainStackView: UIStackView = .vertical([
let stack = UIStackView(arrangedSubviews: [titleLabel, stackView, keyboardView]) titleLabel,
stack.axis = .vertical stackView,
stack.spacing = 16 //settingsStackView,
stack.translatesAutoresizingMaskIntoConstraints = false keyboardView
return stack ])
}()
private let locationSwitcherView: UISegmentedControl = .segments(images: [
UIImage(systemName: "location.fill"),
UIImage(systemName: "location"),
UIImage(systemName: "location.slash")
])
private lazy var numTypeSwitcherView: UISegmentedControl = .segments(titles: NumberType.allCases.map(\.title))
.select(index: NumberType.plateNumber.rawValue)
.onValueChanged(onNumberTypeChanged)
private lazy var settingsStackView: UIStackView = .horizontal([
numTypeSwitcherView,
locationSwitcherView
])
.distribution(.fillProportionally)
// MARK: - Variables
public var onCheck: ((String) -> Void)?
private var numberType: NumberType = .plateNumber
// MARK: - Lifecycle
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -79,6 +121,27 @@ class NewNumberController: UIViewController {
func onNumberChanged() { func onNumberChanged() {
checkButton.isEnabled = plateView.number?.isValid ?? false checkButton.isEnabled = plateView.number?.isValid ?? false
} }
func onNumberTypeChanged(_ index: Int) {
guard let type = NumberType(rawValue: index) else {
return
}
numberType = type
switch type {
case .plateNumber:
plateView.isHidden = false
vinField.isHidden = true
keyboardView.target = plateView
keyboardView.type = .plateNumber
case .vin:
plateView.isHidden = true
vinField.isHidden = false
keyboardView.target = vinField
keyboardView.type = .vin
}
}
} }
extension NewNumberController: PNKeyboardDelegate { extension NewNumberController: PNKeyboardDelegate {

View File

@ -138,11 +138,11 @@ class NotesController: UIViewController, UITableViewDataSource, UITableViewDeleg
HUD.show(.progress) HUD.show(.progress)
Api.add(notes: [note], to: vehicle.getNumber()) Api.add(notes: [note], to: vehicle.getNumber())
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { .subscribe(onSuccess: {
HUD.hide() HUD.hide()
self.update(vehicle: $0) self.update(vehicle: $0)
}, onError: { error in }, onFailure: { error in
HUD.hide() HUD.hide()
self.show(error: error) self.show(error: error)
}) })
@ -183,11 +183,11 @@ class NotesController: UIViewController, UITableViewDataSource, UITableViewDeleg
let newNote = note.clone() let newNote = note.clone()
newNote.text = noteText newNote.text = noteText
Api.edit(note: newNote) Api.edit(note: newNote)
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { .subscribe(onSuccess: {
HUD.hide() HUD.hide()
self.update(vehicle: $0) self.update(vehicle: $0)
}, onError: { error in }, onFailure: { error in
HUD.hide() HUD.hide()
self.show(error: error) self.show(error: error)
}) })
@ -217,12 +217,12 @@ class NotesController: UIViewController, UITableViewDataSource, UITableViewDeleg
HUD.show(.progress) HUD.show(.progress)
Api.remove(note: note.id) Api.remove(note: note.id)
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { vehicle in .subscribe(onSuccess: { vehicle in
HUD.hide() HUD.hide()
let result = self.update(vehicle: vehicle) let result = self.update(vehicle: vehicle)
completion?(result) completion?(result)
}, onError: { error in }, onFailure: { error in
completion?(false) completion?(false)
HUD.hide() HUD.hide()
self.show(error: error) self.show(error: error)

View File

@ -71,11 +71,11 @@ class OsagoAddController: FormViewController {
} }
HUD.show(.progress) HUD.show(.progress)
Api.checkOsago(number: number, vin: vin, date: date, token: token) Api.checkOsago(number: number, vin: vin, date: date, token: token)
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe { vehicle in .subscribe { vehicle in
HUD.hide() HUD.hide()
self.onDone?(vehicle) self.onDone?(vehicle)
} onError: { err in } onFailure: { err in
HUD.show(error: err) HUD.show(error: err)
} }
.disposed(by: self.bag) .disposed(by: self.bag)

View File

@ -2,7 +2,6 @@ import UIKit
import AVFoundation import AVFoundation
import RealmSwift import RealmSwift
import RxSwift import RxSwift
import RxRealm
import Intents import Intents
import CoreSpotlight import CoreSpotlight
import MobileCoreServices import MobileCoreServices
@ -97,10 +96,10 @@ class RecordsController: UIViewController, UITableViewDelegate {
let locationObservable = RxLocationManager.requestCurrentLocation() let locationObservable = RxLocationManager.requestCurrentLocation()
.map(Optional.init) .map(Optional.init)
.catchErrorJustReturn(nil) .catchAndReturn(nil)
let recordObservable: Single<String> = recorder.requestPermissions() let recordObservable: Single<String> = recorder.requestPermissions()
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.flatMap(self.makeStartSoundIfNeeded) .flatMap(self.makeStartSoundIfNeeded)
.flatMap { .flatMap {
#if targetEnvironment(macCatalyst) || targetEnvironment(simulator) #if targetEnvironment(macCatalyst) || targetEnvironment(simulator)
@ -141,7 +140,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
} }
alert?.dismiss(animated: true) alert?.dismiss(animated: true)
self.addButton.isEnabled = true self.addButton.isEnabled = true
}) { error in }, onFailure: { error in
if let alert = alert { if let alert = alert {
alert.dismiss(animated: true) { alert.dismiss(animated: true) {
HUD.show(error: error) HUD.show(error: error)
@ -150,7 +149,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
HUD.show(error: error) HUD.show(error: error)
} }
self.addButton.isEnabled = true self.addButton.isEnabled = true
} })
} }
func showRecordingAlert() -> UIAlertController { func showRecordingAlert() -> UIAlertController {

View File

@ -4,6 +4,8 @@ import LinkPresentation
import RealmSwift import RealmSwift
import Eureka import Eureka
import AutoCatCore import AutoCatCore
import SwiftEntryKit
import MobileCoreServices
class ReportController: FormViewController, MediaBrowserViewControllerDataSource, MediaBrowserViewControllerDelegate, UIActivityItemSource { class ReportController: FormViewController, MediaBrowserViewControllerDataSource, MediaBrowserViewControllerDelegate, UIActivityItemSource {
@ -13,6 +15,10 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
private var reportImageUrl: URL? private var reportImageUrl: URL?
private let logoPlaceholder = UIImage(named: "SteeringWheel") private let logoPlaceholder = UIImage(named: "SteeringWheel")
private let copyableTags = ["Model", "Year", "Color", "Category", "STP", "Japanese",
"PlateNumber", "VIN", "STS", "PTS",
"EngineNumber", "FuelType", "Volume", "PowerHP", "PowerKw"];
var vehicle: Vehicle? { var vehicle: Vehicle? {
didSet { didSet {
if isViewLoaded && self.view.window != nil { if isViewLoaded && self.view.window != nil {
@ -154,17 +160,43 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
func setupCopyBehaviour() { func setupCopyBehaviour() {
for row in form.allRows { for row in form.allRows {
if let labelRow = row as? LabelRow { if let labelRow = row as? LabelRow, copyableTags.contains(row.tag ?? "") {
let doubleTap = UITapGestureRecognizer { _ in let doubleTap = UITapGestureRecognizer { _ in
UIPasteboard.general.string = labelRow.value guard let text = labelRow.value else {
return
}
UIPasteboard.general.string = text
let generator = UIImpactFeedbackGenerator(style: .rigid)
generator.impactOccurred()
let toastMessage = NSLocalizedString("Copied: ", comment: "") + text
self.showToast(text: toastMessage)
} }
doubleTap.numberOfTapsRequired = 2 doubleTap.numberOfTapsRequired = 2
doubleTap.delaysTouchesBegan = true
labelRow.cell.addGestureRecognizer(doubleTap) labelRow.cell.addGestureRecognizer(doubleTap)
} }
} }
} }
func showToast(text: String) {
let style = EKProperty.LabelStyle(
font: .systemFont(ofSize: 14),
color: .white, //.black,
alignment: .center
)
let labelContent = EKProperty.LabelContent(
text: text,
style: style
)
let contentView = EKNoteMessageView(with: labelContent)
var attributes: EKAttributes = .bottomFloat //.toast
attributes.entryBackground = .visualEffect(style: EKAttributes.BackgroundStyle.BlurStyle(light: .dark, dark: .light)) //.color(color: .init(red: 0, green: 196, blue: 0))
SwiftEntryKit.display(entry: contentView, using: attributes)
}
func update(row tag: String, with value: String) { func update(row tag: String, with value: String) {
if let row = self.form.rowBy(tag: tag) as? LabelRow { if let row = self.form.rowBy(tag: tag) as? LabelRow {
row.value = value row.value = value
@ -184,8 +216,8 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
self.update(row: "Year", with: String(self.vehicle?.year ?? 0)) self.update(row: "Year", with: String(self.vehicle?.year ?? 0))
self.update(row: "Color", with: self.vehicle?.color ?? "<unknown>") self.update(row: "Color", with: self.vehicle?.color ?? "<unknown>")
self.update(row: "Category", with: self.vehicle?.category ?? "<unknown>") self.update(row: "Category", with: self.vehicle?.category ?? "<unknown>")
self.update(row: "STP", with: self.stringFromBool(self.vehicle?.isRightWheel.value, yes: NSLocalizedString("Right", comment: ""), no: NSLocalizedString("Left", comment: ""))) self.update(row: "STP", with: self.stringFromBool(self.vehicle?.isRightWheel, yes: NSLocalizedString("Right", comment: ""), no: NSLocalizedString("Left", comment: "")))
self.update(row: "Japanese", with: self.stringFromBool(self.vehicle?.isJapanese.value, yes: NSLocalizedString("Yes", comment: ""), no: NSLocalizedString("No", comment: ""))) self.update(row: "Japanese", with: self.stringFromBool(self.vehicle?.isJapanese, yes: NSLocalizedString("Yes", comment: ""), no: NSLocalizedString("No", comment: "")))
var num = self.vehicle?.getNumber() ?? "<unknown>" var num = self.vehicle?.getNumber() ?? "<unknown>"
if self.vehicle?.outdated ?? false, let current = self.vehicle?.currentNumber { if self.vehicle?.outdated ?? false, let current = self.vehicle?.currentNumber {
@ -198,9 +230,9 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
self.update(row: "PTS", with: self.vehicle?.pts ?? "<unknown>") self.update(row: "PTS", with: self.vehicle?.pts ?? "<unknown>")
self.update(row: "EngineNumber", with: self.vehicle?.engine?.number ?? "<unknown>") self.update(row: "EngineNumber", with: self.vehicle?.engine?.number ?? "<unknown>")
self.update(row: "FuelType", with: self.vehicle?.engine?.fuelType ?? "<unknown>") self.update(row: "FuelType", with: self.vehicle?.engine?.fuelType ?? "<unknown>")
self.update(row: "Volume", with: String(self.vehicle?.engine?.volume.value ?? 0)) self.update(row: "Volume", with: String(self.vehicle?.engine?.volume ?? 0))
self.update(row: "PowerHP", with: String(self.vehicle?.engine?.powerHp ?? 0)) self.update(row: "PowerHP", with: String(self.vehicle?.engine?.powerHp ?? 0))
self.update(row: "PowerKw", with: String(self.vehicle?.engine?.powerKw.value ?? 0)) self.update(row: "PowerKw", with: String(self.vehicle?.engine?.powerKw ?? 0))
self.update(row: "Events", with: String(self.vehicle?.events.count ?? 0)) self.update(row: "Events", with: String(self.vehicle?.events.count ?? 0))
self.update(row: "OSAGO", with: String(self.vehicle?.osagoContracts.count ?? 0)) self.update(row: "OSAGO", with: String(self.vehicle?.osagoContracts.count ?? 0))
self.update(row: "Owners", with: String(self.vehicle?.ownershipPeriods.count ?? 0)) self.update(row: "Owners", with: String(self.vehicle?.ownershipPeriods.count ?? 0))
@ -317,16 +349,29 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
let shareLink = UIAlertAction(title: NSLocalizedString("As link", comment: ""), style: .default) { _ in let shareLink = UIAlertAction(title: NSLocalizedString("As link", comment: ""), style: .default) { _ in
guard let vehicle = self.vehicle else { return } guard let vehicle = self.vehicle else { return }
if let jwt = try? JWT<EmptyPayload>.generate(for: vehicle.getNumber()), let url = URL(string: Constants.reportLinkBaseURL + "?token=" + jwt) { if let jwt = try? JWT<EmptyPayload>.generate(for: vehicle.getNumber()),
let url = URL(string: Constants.reportLinkBaseURL + "?token=" + jwt)
{
let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil) let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
controller.popoverPresentationController?.barButtonItem = sender controller.popoverPresentationController?.barButtonItem = sender
self.present(controller, animated: true) self.present(controller, animated: true)
} }
} }
let copyLink = UIAlertAction(title: NSLocalizedString("Copy link to report", comment: ""), style: .default) { _ in
guard let vehicle = self.vehicle else { return }
if let jwt = try? JWT<EmptyPayload>.generate(for: vehicle.getNumber()),
let url = URL(string: Constants.reportLinkBaseURL + "?token=" + jwt)
{
UIPasteboard.general.string = url.absoluteString
}
}
sheet.addAction(shareImage) sheet.addAction(shareImage)
sheet.addAction(shareTextAndImage) sheet.addAction(shareTextAndImage)
sheet.addAction(shareLink) sheet.addAction(shareLink)
sheet.addAction(copyLink)
sheet.addAction(cancel) sheet.addAction(cancel)
self.present(sheet, animated: true, completion: nil) self.present(sheet, animated: true, completion: nil)
} }

View File

@ -6,7 +6,7 @@ import PKHUD
import ExceptionCatcher import ExceptionCatcher
import AutoCatCore import AutoCatCore
class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDelegate, UIScrollViewDelegate { class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDelegate, UIScrollViewDelegate, UISearchBarDelegate {
@IBOutlet weak var tableView: UITableView! @IBOutlet weak var tableView: UITableView!
@IBOutlet weak var showMapButton: UIBarButtonItem? @IBOutlet weak var showMapButton: UIBarButtonItem?
@ -20,7 +20,9 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
private lazy var searchController: UISearchController = .default private lazy var searchController: UISearchController = .default
.placeholder(NSLocalizedString("Search plate numbers", comment: "")) .placeholder(NSLocalizedString("Search plate numbers", comment: ""))
.resultsUpdater(self) .resultsUpdater(self)
.searchBarDelegate(self)
.makeDumb() .makeDumb()
.scopeButtons(SearchScope.allCases.map(\.title))
private var refreshControl = UIRefreshControl() private var refreshControl = UIRefreshControl()
private var datasource: RxSectionedDataSource<Vehicle,VehicleCell>! private var datasource: RxSectionedDataSource<Vehicle,VehicleCell>!
@ -72,9 +74,9 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
} }
return Api.getVehicles(with: filter, pageToken: self.datasource.pageToken) return Api.getVehicles(with: filter, pageToken: self.datasource.pageToken)
.do(onError: { print($0) }) .do(onError: { print($0) })
.catchErrorJustReturn(PagedResponse<Vehicle>()) .catchAndReturn(PagedResponse<Vehicle>())
} }
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.do(onNext: { .do(onNext: {
if let count = $0.count { if let count = $0.count {
self.navigationItem.title = String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), count) self.navigationItem.title = String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), count)
@ -136,9 +138,20 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
self.filter.searchString = newQuery self.filter.searchString = newQuery
self.filter.needReset = true self.filter.needReset = true
self.filter.scope = SearchScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
self.filterRelay.accept(self.filter) self.filterRelay.accept(self.filter)
} }
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
guard let scope = SearchScope(rawValue: selectedScope) else {
return
}
filter.scope = scope
filterRelay.accept(filter)
}
// MARK: NavigationBar actions // MARK: NavigationBar actions
@available(iOS 14.0, *) @available(iOS 14.0, *)
@ -183,6 +196,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
self.filter = controller.filter self.filter = controller.filter
self.datasource.setSortParameter(self.filter.sortBy ?? .updatedDate) self.datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
self.filter.needReset = true self.filter.needReset = true
self.filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
self.filterRelay.accept(self.filter) self.filterRelay.accept(self.filter)
} }
self.navigationController?.pushViewController(controller, animated: true) self.navigationController?.pushViewController(controller, animated: true)
@ -203,7 +217,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
showProgress() showProgress()
Api.getVehicles(with: filter, pageSize: 0) Api.getVehicles(with: filter, pageSize: 0)
.observeOn(MainScheduler.instance) .observe(on: MainScheduler.instance)
.subscribe(onSuccess: { resp in .subscribe(onSuccess: { resp in
self.hideProgress() self.hideProgress()
@ -218,15 +232,15 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
do { do {
let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv") let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv")
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8) try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
self.save(file: tmpUrl) self.save(file: tmpUrl)
#else #else
self.share(file: tmpUrl) self.share(file: tmpUrl)
#endif #endif
} catch { } catch {
HUD.show(error: error) HUD.show(error: error)
} }
}, onError: { error in }, onFailure: { error in
self.hideProgress() self.hideProgress()
HUD.show(error: error) HUD.show(error: error)
}) })
@ -278,7 +292,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
func update(vehicle: Vehicle, at indexPath: IndexPath) { func update(vehicle: Vehicle, at indexPath: IndexPath) {
HUD.show(.progress) HUD.show(.progress)
Api.checkVehicle(by: vehicle.getNumber(), notes: Array(vehicle.notes), events: [], force: true).observeOn(MainScheduler.instance).subscribe { newVehicle in Api.checkVehicle(by: vehicle.getNumber(), notes: Array(vehicle.notes), events: [], force: true).observe(on: MainScheduler.instance).subscribe { newVehicle in
HUD.hide() HUD.hide()
do { do {
let realm = try Realm() let realm = try Realm()
@ -295,7 +309,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
let frozenVehicle = newVehicle.realm != nil ? newVehicle.clone() : newVehicle let frozenVehicle = newVehicle.realm != nil ? newVehicle.clone() : newVehicle
self.datasource.set(item: frozenVehicle, at: indexPath) self.datasource.set(item: frozenVehicle, at: indexPath)
self.updateDetailController(with: frozenVehicle) self.updateDetailController(with: frozenVehicle)
} onError: { err in } onFailure: { err in
HUD.show(error: err) HUD.show(error: err)
}.disposed(by: self.bag) }.disposed(by: self.bag)
} }

View File

@ -15,7 +15,7 @@ extension AVAudioSession {
do { do {
try self.setCategory(category, mode: .default, options: []) try self.setCategory(category, mode: .default, options: [])
} catch { } catch {
observer(.error(error)) observer(.failure(error))
} }
return Disposables.create() return Disposables.create()

View File

@ -82,13 +82,13 @@ extension Vehicle {
self.drawCell(y: y, width: w, height: cellHeight, title: "Category", value: self.category ?? "<unknown>", context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Category", value: self.category ?? "<unknown>", context: ctx)
y += cellHeight y += cellHeight
var position = "Unknown" var position = "Unknown"
if let rightWheel = self.isRightWheel.value { if let rightWheel = self.isRightWheel {
position = rightWheel ? "Right" : "Left" position = rightWheel ? "Right" : "Left"
} }
self.drawCell(y: y, width: w, height: cellHeight, title: "Steering wheel position", value: position, context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Steering wheel position", value: position, context: ctx)
y += cellHeight y += cellHeight
var japanese = "<Unknown>" var japanese = "<Unknown>"
if let isJapanese = self.isJapanese.value { if let isJapanese = self.isJapanese {
japanese = isJapanese ? "Yes" : "No" japanese = isJapanese ? "Yes" : "No"
} }
self.drawCell(y: y, width: w, height: cellHeight, title: "Japanese", value: japanese, lineMargin: 0, context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Japanese", value: japanese, lineMargin: 0, context: ctx)
@ -115,11 +115,11 @@ extension Vehicle {
y += cellHeight y += cellHeight
self.drawCell(y: y, width: w, height: cellHeight, title: "Fuel type", value: self.engine?.fuelType ?? "<unknown>", context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Fuel type", value: self.engine?.fuelType ?? "<unknown>", context: ctx)
y += cellHeight y += cellHeight
self.drawCell(y: y, width: w, height: cellHeight, title: "Volume (cm³)", value: String(self.engine?.volume.value ?? 0), context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Volume (cm³)", value: String(self.engine?.volume ?? 0), context: ctx)
y += cellHeight y += cellHeight
self.drawCell(y: y, width: w, height: cellHeight, title: "Power (HP)", value: String(self.engine?.powerHp ?? 0), context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Power (HP)", value: String(self.engine?.powerHp ?? 0), context: ctx)
y += cellHeight y += cellHeight
self.drawCell(y: y, width: w, height: cellHeight, title: "Power (kw)", value: String(self.engine?.powerKw.value ?? 0), context: ctx) self.drawCell(y: y, width: w, height: cellHeight, title: "Power (kw)", value: String(self.engine?.powerKw ?? 0), context: ctx)
y += cellHeight + 32 y += cellHeight + 32
"OWNERSHIP PERIODS (\(self.ownershipPeriods.count))".draw(with: CGRect(x: 15, y: y, width: w - 15, height: 24), options: .usesLineFragmentOrigin, attributes: headerAttributes, context: nil) "OWNERSHIP PERIODS (\(self.ownershipPeriods.count))".draw(with: CGRect(x: 15, y: y, width: w - 15, height: 24), options: .usesLineFragmentOrigin, attributes: headerAttributes, context: nil)
@ -216,11 +216,11 @@ extension Vehicle {
if let color = self.color { text += "Color: \(color)\n" } if let color = self.color { text += "Color: \(color)\n" }
if let category = self.category { text += "Category: \(category)\n" } if let category = self.category { text += "Category: \(category)\n" }
var position = "Unknown" var position = "Unknown"
if let rightWheel = self.isRightWheel.value { if let rightWheel = self.isRightWheel {
position = rightWheel ? "Right" : "Left" position = rightWheel ? "Right" : "Left"
} }
var japanese = "<Unknown>" var japanese = "<Unknown>"
if let isJapanese = self.isJapanese.value { if let isJapanese = self.isJapanese {
japanese = isJapanese ? "Yes" : "No" japanese = isJapanese ? "Yes" : "No"
} }
text += "Steering wheel position: \(position)\n" text += "Steering wheel position: \(position)\n"

View File

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>yandexmaps</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>

View File

@ -163,7 +163,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
guard let rootController = self.window?.rootViewController else { return } guard let rootController = self.window?.rootViewController else { return }
HUD.show(.progress) HUD.show(.progress)
_ = Api.getReport(for: number).observeOn(MainScheduler.instance).subscribe { vehicle in _ = Api.getReport(for: number).observe(on: MainScheduler.instance).subscribe { vehicle in
let sb = UIStoryboard(name: "Main", bundle: nil) let sb = UIStoryboard(name: "Main", bundle: nil)
let controller = sb.instantiateViewController(identifier: "ReportController") as ReportController let controller = sb.instantiateViewController(identifier: "ReportController") as ReportController
controller.vehicle = vehicle controller.vehicle = vehicle
@ -172,7 +172,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
controller.navigationItem.leftBarButtonItem = BlockBarButtonItem(barButtonSystemItem: .close) { _ in nav.dismiss(animated: true) } controller.navigationItem.leftBarButtonItem = BlockBarButtonItem(barButtonSystemItem: .close) { _ in nav.dismiss(animated: true) }
rootController.present(nav, animated: true) rootController.present(nav, animated: true)
HUD.hide() HUD.hide()
} onError: { error in } onFailure: { error in
HUD.show(error: error) HUD.show(error: error)
} }
} }

View File

@ -25,7 +25,7 @@ import UIKit
// MARK: - MediaBrowserViewControllerDataSource protocol // MARK: - MediaBrowserViewControllerDataSource protocol
/// Protocol to supply media browser contents. /// Protocol to supply media browser contents.
public protocol MediaBrowserViewControllerDataSource: class { public protocol MediaBrowserViewControllerDataSource: AnyObject {
/** /**
Completion block for passing requested media image with details. Completion block for passing requested media image with details.
@ -88,7 +88,7 @@ extension MediaBrowserViewControllerDataSource {
// MARK: - MediaBrowserViewControllerDelegate protocol // MARK: - MediaBrowserViewControllerDelegate protocol
public protocol MediaBrowserViewControllerDelegate: class { public protocol MediaBrowserViewControllerDelegate: AnyObject {
/** /**
Method invoked on scrolling to next/previous media items. Method invoked on scrolling to next/previous media items.

View File

@ -45,25 +45,25 @@ class Recorder {
break break
case .denied: case .denied:
let error = CocoaError.error("Access error", reason: "Access to speech recognition is denied", suggestion: "Please give permission to use speech recognition in system settings") let error = CocoaError.error("Access error", reason: "Access to speech recognition is denied", suggestion: "Please give permission to use speech recognition in system settings")
observer(.error(error)) observer(.failure(error))
break break
case .restricted: case .restricted:
let error = CocoaError.error("Access error", reason: "Speech recognition is restricted on this device") let error = CocoaError.error("Access error", reason: "Speech recognition is restricted on this device")
observer(.error(error)) observer(.failure(error))
break break
case .notDetermined: case .notDetermined:
let error = CocoaError.error("Access error", reason: "Speech recognition status is not yet determined") let error = CocoaError.error("Access error", reason: "Speech recognition status is not yet determined")
observer(.error(error)) observer(.failure(error))
break break
@unknown default: @unknown default:
let error = CocoaError.error("Access error", reason: "Unknown error accessing speech recognizer") let error = CocoaError.error("Access error", reason: "Unknown error accessing speech recognizer")
observer(.error(error)) observer(.failure(error))
break break
} }
} }
} else { } else {
let error = CocoaError.error("Access error", reason: "Access to microphone is denied", suggestion: "Please give permission to use microphone in system settings") let error = CocoaError.error("Access error", reason: "Access to microphone is denied", suggestion: "Please give permission to use microphone in system settings")
observer(.error(error)) observer(.failure(error))
} }
} }
@ -78,13 +78,13 @@ class Recorder {
return Single<String>.create { observer in return Single<String>.create { observer in
guard let aac = AVAudioFormat(settings: self.recordingSettings) else { guard let aac = AVAudioFormat(settings: self.recordingSettings) else {
observer(.error(CocoaError.error("Recording error", reason: "Format not supported"))) observer(.failure(CocoaError.error("Recording error", reason: "Format not supported")))
return Disposables.create() return Disposables.create()
} }
ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &self.fileRef) ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &self.fileRef)
guard let fileRef = self.fileRef else { guard let fileRef = self.fileRef else {
observer(.error(CocoaError.error(CocoaError.Code.fileWriteUnknown))) observer(.failure(CocoaError.error(CocoaError.Code.fileWriteUnknown)))
return Disposables.create() return Disposables.create()
} }
@ -121,7 +121,7 @@ class Recorder {
self.engine.prepare() self.engine.prepare()
try self.engine.start() try self.engine.start()
} catch { } catch {
observer(.error(error)) observer(.failure(error))
} }
return Disposables.create { return Disposables.create {

View File

@ -15,28 +15,57 @@ enum PNButtonType {
case done case done
} }
enum PNKeyboardType {
case plateNumber
case vin
}
class PNButton: UIButton { class PNButton: UIButton {
private(set) var type: PNButtonType private(set) var type: PNButtonType
private var keyboardType: PNKeyboardType = .plateNumber
private var rectLayer = CAShapeLayer() private var rectLayer = CAShapeLayer()
private var bgColor = UIColor(named: "KeyBackground") private var bgColor = UIColor(named: "KeyBackground")
weak var delegate: PNButtonDelegate? weak var delegate: PNButtonDelegate?
init(letter: Character) { var letterFont: UIFont? {
switch keyboardType {
case .plateNumber:
return UIFont(name: "RoadNumbers", size: 36)
case .vin:
return .systemFont(ofSize: 24)
}
}
var digitFont: UIFont? {
switch keyboardType {
case .plateNumber:
return UIFont(name: "RoadNumbersCyr-Regular", size: 30)
case .vin:
return .systemFont(ofSize: 24)
}
}
init(letter: Character, keyboardType: PNKeyboardType = .plateNumber) {
self.type = .symbol(String(letter)) self.type = .symbol(String(letter))
self.keyboardType = keyboardType
super.init(frame: .zero) super.init(frame: .zero)
self.setup() self.setup()
self.titleLabel?.font = UIFont(name: "RoadNumbers", size: 36) self.titleLabel?.font = letterFont
let title = String(Constants.pnLettersMap[letter] ?? letter) let title = String(Constants.pnLettersMap[letter] ?? letter)
self.setTitle(title, for: .normal) self.setTitle(title, for: .normal)
self.setTitleColor(.label, for: .normal) self.setTitleColor(.label, for: .normal)
} }
init(digit: Int) { init(digit: Int, keyboardType: PNKeyboardType = .plateNumber) {
self.type = .symbol(String(digit)) self.type = .symbol(String(digit))
self.keyboardType = keyboardType
super.init(frame: .zero) super.init(frame: .zero)
self.setup() self.setup()
self.titleLabel?.font = UIFont(name: "RoadNumbersCyr-Regular", size: 30) self.titleLabel?.font = digitFont
let character = Character(String(digit)) let character = Character(String(digit))
let title = String(Constants.pnLettersMap[character] ?? character) let title = String(Constants.pnLettersMap[character] ?? character)
self.setTitle(title, for: .normal) self.setTitle(title, for: .normal)
@ -98,20 +127,52 @@ class PNButton: UIButton {
} }
class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate { class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
private weak var target: UIKeyInput? public weak var target: UIKeyInput?
weak var delegate: PNKeyboardDelegate? weak var delegate: PNKeyboardDelegate?
private let insets: UIEdgeInsets private let insets: UIEdgeInsets
init(target: UIKeyInput, insets: UIEdgeInsets = .zero) { public var type: PNKeyboardType {
didSet {
setupButtons()
}
}
var letters: [Character] {
switch type {
case .plateNumber:
return Array(Constants.pnLettersMap.keys)
case .vin:
return Constants.vinLetters
}
}
private var lettersWidthConstraint: NSLayoutConstraint?
private var digitsWidthConstraint: NSLayoutConstraint?
var mainStack: UIStackView = .horizontal([])
//.distribution(.fillProportionally)
.spacing(16)
init(target: UIKeyInput, type: PNKeyboardType = .plateNumber, insets: UIEdgeInsets = .zero) {
self.target = target self.target = target
self.insets = insets self.insets = insets
self.type = type
super.init(frame: .zero) super.init(frame: .zero)
self.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let blurEffectView = UIVisualEffectView(effect: UIBlurEffect()) let blurEffectView = UIVisualEffectView(effect: UIBlurEffect())
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(blurEffectView) self.addSubview(blurEffectView)
self.addSubview(mainStack)
NSLayoutConstraint.activate([
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: insets.left),
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -insets.right),
mainStack.topAnchor.constraint(equalTo: self.topAnchor, constant: insets.top),
mainStack.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -insets.bottom)
])
self.setupButtons() self.setupButtons()
} }
@ -120,14 +181,53 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func layoutSubviews() {
super.layoutSubviews()
let lettersMult: CGFloat = type == .plateNumber ? 0.5 : 2.0/3.0
let digitsMult: CGFloat = type == .plateNumber ? 0.5 : 1.0/3.0
let availableWidth = frame.width - mainStack.spacing
let newLettersWidth = availableWidth*lettersMult
let newDigitsWidth = availableWidth*digitsMult
let needChangeConstraints = lettersWidthConstraint?.constant != newLettersWidth
|| digitsWidthConstraint?.constant != newDigitsWidth
if needChangeConstraints {
lettersWidthConstraint?.constant = newLettersWidth
digitsWidthConstraint?.constant = newDigitsWidth
}
}
func letterRows(from buttons: [PNButton]) -> [UIView] {
switch type {
case .plateNumber:
return [
createLetterStack([buttons[0], buttons[1], buttons[2]]),
createLetterStack([buttons[3], buttons[4], buttons[5]]),
createLetterStack([buttons[6], buttons[7], buttons[8]]),
createLetterStack([buttons[9], buttons[10], buttons[11]])
]
case .vin:
return [
createLetterStack([buttons[0], buttons[1], buttons[2], buttons[3], buttons[4], buttons[5]]),
createLetterStack([buttons[6], buttons[7], buttons[8], buttons[9], buttons[10], buttons[11]]),
createLetterStack([buttons[12], buttons[13], buttons[14], buttons[15], buttons[16], buttons[17]]),
createLetterStack([buttons[18], buttons[19], buttons[20], buttons[21], buttons[22]])
]
}
}
func setupButtons() { func setupButtons() {
let letters: [PNButton] = Constants.pnLettersMap.keys.sorted().map { letter in let letterButtons: [PNButton] = letters.sorted().map { letter in
let button = PNButton(letter: letter) let button = PNButton(letter: letter, keyboardType: type)
button.delegate = self button.delegate = self
return button return button
} }
let digits: [PNButton] = [1,2,3,4,5,6,7,8,9,0].map { digit in let digits: [PNButton] = [1,2,3,4,5,6,7,8,9,0].map { digit in
let button = PNButton(digit: digit) let button = PNButton(digit: digit, keyboardType: type)
button.delegate = self button.delegate = self
return button return button
} }
@ -139,14 +239,8 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
done.setBacgroundColor(color: .systemBlue) done.setBacgroundColor(color: .systemBlue)
done.tintColor = .white done.tintColor = .white
let letterRows = [ let rows = letterRows(from: letterButtons)
self.createLetterStack([letters[0], letters[1], letters[2]]), let lettersStack = UIStackView(arrangedSubviews: rows)
self.createLetterStack([letters[3], letters[4], letters[5]]),
self.createLetterStack([letters[6], letters[7], letters[8]]),
self.createLetterStack([letters[9], letters[10], letters[11]])
]
let lettersStack = UIStackView(arrangedSubviews: letterRows)
lettersStack.axis = .vertical lettersStack.axis = .vertical
lettersStack.distribution = .fillEqually lettersStack.distribution = .fillEqually
//lettersStack.spacing = 8 //lettersStack.spacing = 8
@ -163,18 +257,15 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
digitsStack.distribution = .fillEqually digitsStack.distribution = .fillEqually
//digitsStack.spacing = 8 //digitsStack.spacing = 8
let mainStack = UIStackView(arrangedSubviews: [lettersStack, digitsStack]) mainStack.setArrangedSubviews([lettersStack, digitsStack])
mainStack.spacing = 16
mainStack.distribution = .fillEqually
mainStack.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(mainStack)
NSLayoutConstraint.activate([ lettersWidthConstraint = lettersStack.widthAnchor.constraint(equalToConstant: 0)
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: insets.left), digitsWidthConstraint = digitsStack.widthAnchor.constraint(equalToConstant: 0)
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -insets.right),
mainStack.topAnchor.constraint(equalTo: self.topAnchor, constant: insets.top), lettersWidthConstraint?.priority = .defaultLow
mainStack.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -insets.bottom) digitsWidthConstraint?.priority = .defaultLow
])
NSLayoutConstraint.activate([lettersWidthConstraint, digitsWidthConstraint].compactMap { $0 })
} }
func createLetterStack(_ views: [UIView]) -> UIStackView { func createLetterStack(_ views: [UIView]) -> UIStackView {

View File

@ -403,3 +403,5 @@
/* No comment provided by engineer. */ /* No comment provided by engineer. */
"ZIP (or OKTMO) code" = "Индекс (или ОКТМО)"; "ZIP (or OKTMO) code" = "Индекс (или ОКТМО)";
/* No comment provided by engineer. */
"Copy link to report" = "Копировать ссылку на отчет";

View File

@ -0,0 +1,23 @@
//
// Realm.swift
// AutoCatCore
//
// Created by Selim Mustafaev on 21.02.2023.
// Copyright © 2023 Selim Mustafaev. All rights reserved.
//
import RealmSwift
extension Results {
public func toArray() -> [Element] {
Array(self)
}
}
extension List {
public func toArray() -> [Element] {
Array(self)
}
}

View File

@ -3,12 +3,12 @@ import RealmSwift
public class AudioRecord: Object, Identifiable, Cloneable { public class AudioRecord: Object, Identifiable, Cloneable {
@objc public dynamic var path: String = "" @Persisted public var path: String = ""
@objc public dynamic var number: String? @Persisted public var number: String?
@objc public dynamic var rawText: String = "" @Persisted public var rawText: String = ""
@objc private dynamic var addedDate: TimeInterval = 0 @Persisted private var addedDate: TimeInterval = 0
@objc public dynamic var duration: TimeInterval = 0 @Persisted public var duration: TimeInterval = 0
@objc public dynamic var event: VehicleEvent? @Persisted public var event: VehicleEvent?
public var identifier: TimeInterval = 0 public var identifier: TimeInterval = 0
public var id: TimeInterval { public var id: TimeInterval {
@ -33,7 +33,7 @@ public class AudioRecord: Object, Identifiable, Cloneable {
self.addedDate = Date().timeIntervalSince1970 self.addedDate = Date().timeIntervalSince1970
} }
required init() { required override init() {
super.init() super.init()
} }

View File

@ -8,17 +8,17 @@ public enum DebugInfoStatus: Int {
} }
public class DebugInfo: Object, Decodable { public class DebugInfo: Object, Decodable {
@objc public dynamic var autocod: DebugInfoEntry! @Persisted public var autocod: DebugInfoEntry!
@objc public dynamic var vin01vin: DebugInfoEntry! @Persisted public var vin01vin: DebugInfoEntry!
@objc public dynamic var vin01base: DebugInfoEntry! @Persisted public var vin01base: DebugInfoEntry!
@objc public dynamic var vin01history: DebugInfoEntry! @Persisted public var vin01history: DebugInfoEntry!
@objc public dynamic var nomerogram: DebugInfoEntry! @Persisted public var nomerogram: DebugInfoEntry!
} }
public class DebugInfoEntry: Object, Decodable { public class DebugInfoEntry: Object, Decodable {
@objc public dynamic var fields: Int64 = 0 @Persisted public var fields: Int64 = 0
@objc public dynamic var error: String? @Persisted public var error: String?
@objc public dynamic var status: Int = 0 @Persisted public var status: Int = 0
public var statusEnum: DebugInfoStatus { public var statusEnum: DebugInfoStatus {
get { DebugInfoStatus(rawValue: self.status)! } get { DebugInfoStatus(rawValue: self.status)! }

View File

@ -38,6 +38,29 @@ public enum SortOrder: String, CustomStringConvertible, CaseIterable {
} }
} }
public enum SearchScope: Int, CaseIterable {
case plateNumber = 0
case vin = 1
case notes = 2
public var title: String {
switch self {
case .plateNumber: return NSLocalizedString("Plate number", comment: "")
case .vin: return NSLocalizedString("VIN", comment: "")
case .notes: return NSLocalizedString("Notes", comment: "")
}
}
public var stringValue: String {
switch self {
case .plateNumber: return "plateNumber"
case .vin: return "vin"
case .notes: return "notes"
}
}
}
public struct Filter { public struct Filter {
public var searchString = "" public var searchString = ""
public var brand: String? public var brand: String?
@ -55,6 +78,7 @@ public struct Filter {
public var fromLocationDate: Date? public var fromLocationDate: Date?
public var toLocationDate: Date? public var toLocationDate: Date?
public var needReset: Bool = false public var needReset: Bool = false
public var scope: SearchScope = .plateNumber
public init() { public init() {
} }
@ -74,6 +98,7 @@ public struct Filter {
self.toDateUpdated = nil self.toDateUpdated = nil
self.fromLocationDate = nil self.fromLocationDate = nil
self.toLocationDate = nil self.toLocationDate = nil
self.scope = .plateNumber
} }
public func queryDictionary() -> [String: String] { public func queryDictionary() -> [String: String] {
@ -122,6 +147,8 @@ public struct Filter {
dict["toLocationDate"] = String(toLocationDate.timeIntervalSince1970) dict["toLocationDate"] = String(toLocationDate.timeIntervalSince1970)
} }
dict["scope"] = scope.stringValue
return dict return dict
} }
} }

View File

@ -2,16 +2,16 @@ import Foundation
import RealmSwift import RealmSwift
public class Osago: Object, Decodable, Cloneable { public class Osago: Object, Decodable, Cloneable {
@objc public dynamic var date: TimeInterval = 0 @Persisted public var date: TimeInterval = 0
@objc public dynamic var number: String = "" @Persisted public var number: String = ""
@objc public dynamic var vin: String? @Persisted public var vin: String?
@objc public dynamic var plateNumber: String? @Persisted public var plateNumber: String?
@objc public dynamic var name: String = "" @Persisted public var name: String = ""
@objc public dynamic var status: String = "" @Persisted public var status: String = ""
@objc public dynamic var restrictions: String = "" @Persisted public var restrictions: String = ""
@objc public dynamic var insurant: String? @Persisted public var insurant: String?
@objc public dynamic var owner: String? @Persisted public var owner: String?
@objc public dynamic var usageRegion: String? @Persisted public var usageRegion: String?
public required init(copy: Osago) { public required init(copy: Osago) {
self.date = copy.date self.date = copy.date
@ -26,7 +26,7 @@ public class Osago: Object, Decodable, Cloneable {
self.usageRegion = copy.usageRegion self.usageRegion = copy.usageRegion
} }
required init() { required override init() {
super.init() super.init()
} }
} }

View File

@ -2,50 +2,50 @@ import Foundation
import RealmSwift import RealmSwift
public class VehicleName: Object, Decodable, Cloneable { public class VehicleName: Object, Decodable, Cloneable {
@objc public dynamic var original: String? @Persisted public var original: String?
@objc public dynamic var normalized: String? @Persisted public var normalized: String?
public required init(copy: VehicleName) { public required init(copy: VehicleName) {
self.original = copy.original self.original = copy.original
self.normalized = copy.normalized self.normalized = copy.normalized
} }
required init() { required override init() {
} }
} }
public class VehicleBrand: Object, Decodable, Cloneable { public class VehicleBrand: Object, Decodable, Cloneable {
@objc public dynamic var name: VehicleName? @Persisted public var name: VehicleName?
@objc public dynamic var logo: String? @Persisted public var logo: String?
public required init(copy: VehicleBrand) { public required init(copy: VehicleBrand) {
self.name = copy.name?.clone() self.name = copy.name?.clone()
self.logo = copy.logo self.logo = copy.logo
} }
required init() { required override init() {
super.init() super.init()
} }
} }
public class VehicleModel: Object, Decodable, Cloneable { public class VehicleModel: Object, Decodable, Cloneable {
@objc dynamic var name: VehicleName? @Persisted var name: VehicleName?
public required init(copy: VehicleModel) { public required init(copy: VehicleModel) {
self.name = copy.name?.clone() self.name = copy.name?.clone()
} }
required init() { required override init() {
super.init() super.init()
} }
} }
public class VehicleEngine: Object, Decodable, Cloneable { public class VehicleEngine: Object, Decodable, Cloneable {
@objc public dynamic var number: String? @Persisted public var number: String?
public var volume: RealmOptional<Int> = RealmOptional(0) @Persisted public var volume: Int? = 0
@objc public dynamic var powerHp: Float = 0 @Persisted public var powerHp: Float? = 0
public var powerKw: RealmOptional<Float> = RealmOptional(0) @Persisted public var powerKw: Float? = 0
@objc public dynamic var fuelType: String? @Persisted public var fuelType: String?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case number, volume, powerHp, powerKw, fuelType case number, volume, powerHp, powerKw, fuelType
@ -54,13 +54,13 @@ public class VehicleEngine: Object, Decodable, Cloneable {
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.number = try container.decodeIfPresent(String.self, forKey: .number) self.number = try container.decodeIfPresent(String.self, forKey: .number)
self.volume = RealmOptional(try container.decodeIfPresent(Int.self, forKey: .volume)) self.volume = try container.decodeIfPresent(Int.self, forKey: .volume)
self.powerHp = try container.decode(Float.self, forKey: .powerHp) self.powerHp = try container.decodeIfPresent(Float.self, forKey: .powerHp)
self.powerKw = RealmOptional(try container.decodeIfPresent(Float.self, forKey: .powerKw)) self.powerKw = try container.decodeIfPresent(Float.self, forKey: .powerKw)
self.fuelType = try container.decodeIfPresent(String.self, forKey: .fuelType) self.fuelType = try container.decodeIfPresent(String.self, forKey: .fuelType)
} }
required init() { required override init() {
super.init() super.init()
} }
@ -74,10 +74,10 @@ public class VehicleEngine: Object, Decodable, Cloneable {
} }
public class VehiclePhoto: Object, Decodable, Cloneable { public class VehiclePhoto: Object, Decodable, Cloneable {
@objc public dynamic var brand: String? @Persisted public var brand: String?
@objc public dynamic var model: String? @Persisted public var model: String?
@objc public dynamic var date: TimeInterval = 0 @Persisted public var date: TimeInterval = 0
@objc public dynamic var url: String = "" @Persisted public var url: String = ""
public override var description: String { public override var description: String {
let formatter = DateFormatter() let formatter = DateFormatter()
@ -96,7 +96,7 @@ public class VehiclePhoto: Object, Decodable, Cloneable {
self.url = copy.url self.url = copy.url
} }
required init() { required override init() {
super.init() super.init()
} }
} }
@ -128,17 +128,17 @@ public enum SteeringWheelPosition: CustomStringConvertible {
} }
public class VehicleOwnershipPeriod: Object, Decodable, Cloneable { public class VehicleOwnershipPeriod: Object, Decodable, Cloneable {
@objc public dynamic var lastOperation: String = "" @Persisted public var lastOperation: String = ""
@objc public dynamic var ownerType: String = "" @Persisted public var ownerType: String = ""
@objc public dynamic var from: Int64 = 0 @Persisted public var from: Int64 = 0
@objc public dynamic var to: Int64 = 0 @Persisted public var to: Int64 = 0
@objc public dynamic var region: String? @Persisted public var region: String?
@objc public dynamic var registrationRegion: String? @Persisted public var registrationRegion: String?
@objc public dynamic var locality: String? @Persisted public var locality: String?
@objc public dynamic var code: String? @Persisted public var code: String?
@objc public dynamic var street: String? @Persisted public var street: String?
@objc public dynamic var building: String? @Persisted public var building: String?
@objc public dynamic var inn: String? @Persisted public var inn: String?
required public init(copy: VehicleOwnershipPeriod) { required public init(copy: VehicleOwnershipPeriod) {
self.lastOperation = copy.lastOperation self.lastOperation = copy.lastOperation
@ -154,37 +154,37 @@ public class VehicleOwnershipPeriod: Object, Decodable, Cloneable {
self.inn = copy.inn self.inn = copy.inn
} }
required init() { required override init() {
super.init() super.init()
} }
} }
public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportable { public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportable {
@objc public dynamic var brand: VehicleBrand? @Persisted public var brand: VehicleBrand?
@objc public dynamic var model: VehicleModel? @Persisted public var model: VehicleModel?
@objc public dynamic var color: String? @Persisted public var color: String?
@objc public dynamic var year: Int = 0 @Persisted public var year: Int = 0
@objc public dynamic var category: String? @Persisted public var category: String?
@objc public dynamic var engine: VehicleEngine? @Persisted public var engine: VehicleEngine?
@objc private dynamic var number: String = "" @Persisted private var number: String = ""
@objc public dynamic var currentNumber: String? @Persisted public var currentNumber: String?
@objc public dynamic var vin1: String? @Persisted public var vin1: String?
@objc public dynamic var vin2: String? @Persisted public var vin2: String?
@objc public dynamic var sts: String? @Persisted public var sts: String?
@objc public dynamic var pts: String? @Persisted public var pts: String?
public var isRightWheel = RealmOptional<Bool>() @Persisted public var isRightWheel: Bool?
public var isJapanese: RealmOptional<Bool> = RealmOptional<Bool>() @Persisted public var isJapanese: Bool?
@objc public dynamic var addedDate: TimeInterval = 0 @Persisted public var addedDate: TimeInterval = 0
@objc public dynamic var updatedDate: TimeInterval = 0 @Persisted public var updatedDate: TimeInterval = 0
@objc public dynamic var addedBy: String = "" @Persisted public var addedBy: String = ""
public var photos = List<VehiclePhoto>() @Persisted public var photos: List<VehiclePhoto>
public var ownershipPeriods = List<VehicleOwnershipPeriod>() @Persisted public var ownershipPeriods: List<VehicleOwnershipPeriod>
public var events = List<VehicleEvent>() @Persisted public var events: List<VehicleEvent>
public var osagoContracts = List<Osago>() @Persisted public var osagoContracts: List<Osago>
public var ads = List<VehicleAd>() @Persisted public var ads: List<VehicleAd>
public var notes = List<VehicleNote>() @Persisted public var notes: List<VehicleNote>
@objc public dynamic var debugInfo: DebugInfo? @Persisted public var debugInfo: DebugInfo?
@objc public dynamic var synchronized: Bool = true @Persisted public var synchronized: Bool = true
lazy var formatter: DateFormatter = { lazy var formatter: DateFormatter = {
let f = DateFormatter() let f = DateFormatter()
@ -221,6 +221,8 @@ public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportab
} }
required public init(from decoder: Decoder) throws { required public init(from decoder: Decoder) throws {
super.init()
let container = try decoder.container(keyedBy: CodingKeys.self) let container = try decoder.container(keyedBy: CodingKeys.self)
self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand) self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model) self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
@ -234,8 +236,8 @@ public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportab
self.vin2 = try container.decodeIfPresent(String.self, forKey: .vin2) self.vin2 = try container.decodeIfPresent(String.self, forKey: .vin2)
self.sts = try container.decodeIfPresent(String.self, forKey: .sts) self.sts = try container.decodeIfPresent(String.self, forKey: .sts)
self.pts = try container.decodeIfPresent(String.self, forKey: .pts) self.pts = try container.decodeIfPresent(String.self, forKey: .pts)
self.isRightWheel = RealmOptional(try container.decodeIfPresent(Bool.self, forKey: .isRightWheel)) self.isRightWheel = try container.decodeIfPresent(Bool.self, forKey: .isRightWheel)
self.isJapanese = RealmOptional(try container.decodeIfPresent(Bool.self, forKey: .isJapanese)) self.isJapanese = try container.decodeIfPresent(Bool.self, forKey: .isJapanese)
self.addedDate = (try container.decode(TimeInterval.self, forKey: .addedDate))/1000 self.addedDate = (try container.decode(TimeInterval.self, forKey: .addedDate))/1000
self.addedBy = try container.decode(String.self, forKey: .addedBy) self.addedBy = try container.decode(String.self, forKey: .addedBy)
self.debugInfo = try container.decodeIfPresent(DebugInfo.self, forKey: .debugInfo) self.debugInfo = try container.decodeIfPresent(DebugInfo.self, forKey: .debugInfo)
@ -269,11 +271,12 @@ public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportab
self.synchronized = true self.synchronized = true
} }
required init() { required override init() {
super.init() super.init()
} }
public init(_ number: String) { public init(_ number: String) {
super.init()
self.number = number self.number = number
self.addedDate = Date().timeIntervalSince1970 self.addedDate = Date().timeIntervalSince1970
self.updatedDate = self.addedDate self.updatedDate = self.addedDate
@ -375,7 +378,7 @@ public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportab
// MARK: - Exportable // MARK: - Exportable
public static var csvHeader: String { public static var csvHeader: String {
return "Plate Number, Model, Color, Year, Power (HP), Events, Owners, VIN, STS, PTS, Added Date, Updated date, Locations, Notes" return "Plate Number, Model, Color, Year, Power (HP), Events, Owners, VIN, STS, PTS, Engine number, Added Date, Updated date, Locations, Notes"
} }
public var csvLine: String { public var csvLine: String {
@ -396,7 +399,7 @@ public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportab
let number = self.currentNumber == nil ? self.number : "\(self.number) (\(self.currentNumber ?? ""))" let number = self.currentNumber == nil ? self.number : "\(self.number) (\(self.currentNumber ?? ""))"
return String(format: "%@, \"%@\", %@, %d, %f, %d, %d, %@, %@, %@, \"%@\", \"%@\", \"%@\", \"%@\"", return String(format: "%@, \"%@\", %@, %d, %f, %d, %d, %@, %@, %@, %@, \"%@\", \"%@\", \"%@\", \"%@\"",
number, number,
model, model,
self.color ?? "", self.color ?? "",
@ -407,6 +410,7 @@ public final class Vehicle: Object, Decodable, Identifiable, Cloneable, Exportab
self.vin1 ?? "", self.vin1 ?? "",
self.sts ?? "", self.sts ?? "",
self.pts ?? "", self.pts ?? "",
self.engine?.number ?? "",
added, added,
updated, updated,
eventsString, eventsString,

View File

@ -2,15 +2,15 @@ import Foundation
import RealmSwift import RealmSwift
public class VehicleAd: Object, Decodable, Cloneable { public class VehicleAd: Object, Decodable, Cloneable {
@objc public dynamic var id: Int = 0 @Persisted public var id: Int = 0
@objc public dynamic var url: String? @Persisted public var url: String?
@objc public dynamic var price: String? @Persisted public var price: String?
@objc public dynamic var date: TimeInterval = Date().timeIntervalSince1970 @Persisted public var date: TimeInterval = Date().timeIntervalSince1970
@objc public dynamic var mileage: String? @Persisted public var mileage: String?
@objc public dynamic var region: String? @Persisted public var region: String?
@objc public dynamic var city: String? @Persisted public var city: String?
@objc public dynamic var adDescription: String? @Persisted public var adDescription: String?
public var photos = List<String>() @Persisted public var photos: List<String>
public required init(copy: VehicleAd) { public required init(copy: VehicleAd) {
self.id = copy.id self.id = copy.id
@ -27,7 +27,7 @@ public class VehicleAd: Object, Decodable, Cloneable {
self.photos = photos self.photos = photos
} }
required init() { required override init() {
super.init() super.init()
} }
} }

View File

@ -4,12 +4,12 @@ import RxSwift
import CoreLocation import CoreLocation
public class VehicleEvent: Object, Codable, Cloneable { public class VehicleEvent: Object, Codable, Cloneable {
@objc public dynamic var id: String = UUID().uuidString @Persisted public var id: String = UUID().uuidString
@objc public dynamic var date: TimeInterval = Date().timeIntervalSince1970 @Persisted public var date: TimeInterval = Date().timeIntervalSince1970
@objc public dynamic var latitude: Double = 0 @Persisted public var latitude: Double = 0
@objc public dynamic var longitude: Double = 0 @Persisted public var longitude: Double = 0
@objc public dynamic var address: String? = nil @Persisted public var address: String? = nil
@objc public dynamic var addedBy: String? = nil @Persisted public var addedBy: String? = nil
public var number: String? public var number: String?
public var coordinate: CLLocationCoordinate2D { public var coordinate: CLLocationCoordinate2D {
@ -22,7 +22,7 @@ public class VehicleEvent: Object, Codable, Cloneable {
self.addedBy = Settings.shared.user.email self.addedBy = Settings.shared.user.email
} }
required init() { required override init() {
super.init() super.init()
} }
@ -78,4 +78,20 @@ public class VehicleEvent: Object, Codable, Cloneable {
self.number = copy.number self.number = copy.number
self.addedBy = copy.addedBy self.addedBy = copy.addedBy
} }
// MARK: - Codable
enum CodingKeys: String, CodingKey {
case id, date, latitude, longitude, address, addedBy
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(date, forKey: .date)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
try container.encodeIfPresent(address, forKey: .address)
try container.encodeIfPresent(addedBy, forKey: .addedBy)
}
} }

View File

@ -2,10 +2,10 @@ import Foundation
import RealmSwift import RealmSwift
public class VehicleNote: Object, Codable, Cloneable { public class VehicleNote: Object, Codable, Cloneable {
@objc public dynamic var id: String = UUID().uuidString @Persisted public var id: String = UUID().uuidString
@objc public dynamic var user: String = "" @Persisted public var user: String = ""
@objc public dynamic var date: TimeInterval = Date().timeIntervalSince1970 @Persisted public var date: TimeInterval = Date().timeIntervalSince1970
@objc public dynamic var text: String = "" @Persisted public var text: String = ""
// MARK: - Cloneable // MARK: - Cloneable
@ -16,7 +16,7 @@ public class VehicleNote: Object, Codable, Cloneable {
self.text = copy.text self.text = copy.text
} }
required init() { required override init() {
super.init() super.init()
} }

View File

@ -46,7 +46,7 @@ public class Api {
return Single.error(self.genError("Error creating request", suggestion: "")) return Single.error(self.genError("Error creating request", suggestion: ""))
} }
return self.session.rx.data(request: request).asSingle().map { data in return self.session.rx.response(request: request).asSingle().map { httpResp, data in
// let str = String(data: data, encoding: .utf8) // let str = String(data: data, encoding: .utf8)
// print("================================") // print("================================")
// if let string = str?.replacingOccurrences(of: "\\\"", with: "\"") // if let string = str?.replacingOccurrences(of: "\\\"", with: "\"")
@ -55,6 +55,15 @@ public class Api {
// print(string) // print(string)
// } // }
// print("================================") // print("================================")
if httpResp.statusCode == 401 {
throw genError("Unauthorized", suggestion: "", code: httpResp.statusCode)
}
if httpResp.statusCode < 200 || httpResp.statusCode >= 300 {
throw genError("HTTP error \(httpResp.statusCode)", suggestion: "", code: httpResp.statusCode)
}
do { do {
let resp = try JSONDecoder().decode(Response<T>.self, from: data) let resp = try JSONDecoder().decode(Response<T>.self, from: data)
if resp.success { if resp.success {
@ -163,7 +172,7 @@ public class Api {
Settings.shared.user.firebaseRefreshToken = newRefreshToken Settings.shared.user.firebaseRefreshToken = newRefreshToken
print("Refresh token: \(newRefreshToken)") print("Refresh token: \(newRefreshToken)")
} }
}.catchError { err in }.catch { err in
print(err) print(err)
return .just(()) return .just(())
} }

View File

@ -15,6 +15,10 @@ public enum Constants {
"А": "A", "В": "B", "Е": "E", "К": "K", "М": "M", "Н": "H", "О": "O", "Р": "P", "С": "C", "Т": "T", "У": "Y", "Х": "X" "А": "A", "В": "B", "Е": "E", "К": "K", "М": "M", "Н": "H", "О": "O", "Р": "P", "С": "C", "Т": "T", "У": "Y", "Х": "X"
] ]
public static let vinLetters: [Character] = [
"A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"
]
public static let googleAuthURL = "https://accounts.google.com/o/oauth2/v2/auth" public static let googleAuthURL = "https://accounts.google.com/o/oauth2/v2/auth"
public static let googleTokenURL = "https://oauth2.googleapis.com/token" public static let googleTokenURL = "https://oauth2.googleapis.com/token"
public static let googleRedirectURL = "com.googleusercontent.apps.994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb:/oauth2callback" public static let googleRedirectURL = "com.googleusercontent.apps.994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb:/oauth2callback"

View File

@ -89,14 +89,14 @@ public class RxLocationManager {
if let status = result, [.authorizedWhenInUse, .authorizedAlways].contains(status) { if let status = result, [.authorizedWhenInUse, .authorizedAlways].contains(status) {
observer(.success(())) observer(.success(()))
} else { } else {
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"]))) observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
} }
}, onError: { observer(.error($0)) }) }, onFailure: { observer(.failure($0)) })
case .denied: case .denied:
observer(.error(CLError(.denied))) observer(.failure(CLError(.denied)))
break break
default: default:
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"]))) observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
break break
} }
@ -139,11 +139,11 @@ public class RxLocationManager {
let location = CLLocation(latitude: latitude, longitude: longitude) let location = CLLocation(latitude: latitude, longitude: longitude)
geocoder.reverseGeocodeLocation(location) { placemarks, error in geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let error = error { if let error = error {
observer(.error(error)) observer(.failure(error))
} else if let placemark = placemarks?.first, let name = placemark.name { } else if let placemark = placemarks?.first, let name = placemark.name {
observer(.success(name)) observer(.success(name))
} else { } else {
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Reverse geolocation error"]))) observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Reverse geolocation error"])))
} }
} }