Compare commits

..

10 Commits

38 changed files with 905 additions and 455 deletions

View File

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

View File

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

View File

@ -8,7 +8,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent
uuid = "676638C8-1CC5-4C04-98B0-1C0D6CB28B76"
shouldBeEnabled = "Yes"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "UITableViewAlertForLayoutOutsideViewHierarchy"
@ -27,5 +27,68 @@
</Locations>
</BreakpointContent>
</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>
</Bucket>

View File

@ -15,6 +15,13 @@ extension UISearchController {
searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
searchController.searchBar.keyboardType = .webSearch
if #available(iOS 16, *) {
searchController.scopeBarActivation = .onTextEntry
} else {
searchController.automaticallyShowsScopeBar = true
}
return searchController
}
@ -37,4 +44,14 @@ extension UISearchController {
searchBar.smartInsertDeleteType = .no
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 {
let config = Realm.Configuration(
schemaVersion: 38,
schemaVersion: 40,
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 {
migration.enumerateObjects(ofType: "Vehicle") { old, new in
if let oldDebugInfo = old?["debugInfo"] as? DynamicObject, let newDebugInfo = new?["debugInfo"] as? DynamicObject {

View File

@ -173,17 +173,17 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<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">
<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"/>
<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"/>
<subviews>
<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>
<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>
<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"/>
@ -192,7 +192,7 @@
<nil key="highlightedColor"/>
</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">
<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"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
@ -200,7 +200,7 @@
</subviews>
</stackView>
<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>
</subviews>
</stackView>

View File

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

View File

@ -2,7 +2,6 @@ import UIKit
import RealmSwift
import RxSwift
import SwiftDate
import RxRealm
import PKHUD
import CoreLocation
import AutoCatCore
@ -100,7 +99,7 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
}
HUD.hide()
self.showErrors(errors)
} onError: { error in
} onFailure: { error in
HUD.hide()
self.show(error: error)
//HUD.show(error: error)
@ -138,7 +137,12 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
let csvString = try self.historyDataSource.makeCsv()
let tmpUrl = FileManager.default.tmpUrl(name: "history", ext: "csv")
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
#if targetEnvironment(macCatalyst)
self.save(file: tmpUrl)
#else
self.shareFile(tmpUrl)
#endif
} catch {
self.show(error: error)
}
@ -199,6 +203,16 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
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
func checkTapped(number: String) {
@ -221,7 +235,7 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
}
HUD.hide()
self.showErrors(errors)
} onError: { error in
} onFailure: { error in
HUD.hide()
self.show(error: error)
}
@ -324,7 +338,7 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
}
HUD.hide()
self.showErrors(errors)
} onError: { error in
} onFailure: { error in
HUD.hide()
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))
if action != .doNotSend {
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) }
.observeOn(MainScheduler.instance)
.catchError { .just((event: nil, error: $0)) }
.observe(on: MainScheduler.instance)
.catch { .just((event: nil, error: $0)) }
}
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
try self.save(vehicle: vehicle)
return (vehicle: vehicle, error: nil)
}
.catchError { error in
.catch { error in
let realm = try Realm()
if let existingVehicle = realm.object(ofType: Vehicle.self, forPrimaryKey: number) {
return .just((vehicle: existingVehicle, error: error))
@ -419,12 +433,12 @@ class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpd
} else {
if let event = eventResult.event {
return Api.add(event: event, to: vehicleResult.vehicle.getNumber())
.observeOn(MainScheduler.instance)
.observe(on: MainScheduler.instance)
.map {
try self.save(vehicle: $0)
return (vehicle: $0, errors: errors)
}
.catchError { error in
.catch { error in
errors.append(error)
return .just((vehicle: vehicleResult.vehicle, errors: errors))
}

View File

@ -145,42 +145,77 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
// MARK: - UITableViewDelegate
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
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
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])
return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [copy, share, edit, openMenu, delete])
}
}
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
self.copyEvent(index: indexPath.row)
self.copyEvent(event: event)
completion(true)
}
copy.image = UIImage(systemName: "doc.on.doc")
copy.backgroundColor = .systemBlue
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")
let edit = UIContextualAction(style: .normal, title: NSLocalizedString("Edit", comment: "")) { action, view, completion in
self.editEvent(index: indexPath.row)
self.editEvent(event: event)
completion(true)
}
edit.image = UIImage(systemName: "pencil")
@ -206,31 +241,19 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
// MARK: - Event actions
func deleteEvent(index: Int, completion: ((Bool) -> Void)? = nil) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return
}
let event = vehicle.events[index]
func deleteEvent(event: VehicleEvent, completion: ((Bool) -> Void)? = nil) {
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)
completion?(result)
}, onError: { error in
}, onFailure: { error in
completion?(false)
HUD.show(error: error)
print(error)
}).disposed(by: self.bag)
}
func editEvent(index: Int) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return
}
let event = vehicle.events[index]
func editEvent(event: VehicleEvent) {
let sb = UIStoryboard(name: "Main", bundle: nil)
let controller = sb.instantiateViewController(identifier: "LocationEditController") as LocationEditController
controller.title = NSLocalizedString("Edit event", comment: "")
@ -241,8 +264,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.navigationController?.popViewController(animated: true, completion: {
HUD.show(.progress)
Api.edit(event: newEvent)
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onError:
.observe(on: MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onFailure:
{ error in
HUD.show(error: error)
})
@ -252,14 +275,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.navigationController?.pushViewController(controller, animated: true)
}
func copyEvent(index: Int) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
return
}
func copyEvent(event: VehicleEvent) {
var items: [String: Any] = [:]
let event = vehicle.events[index]
if let url = event.getMapLink() {
items[kUTTypeURL as String] = url
@ -278,17 +295,29 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.setupBarButtonItems()
}
func shareEvent(index: Int) {
guard let vehicle = self.vehicle else {
HUD.flash(.labeledError(title: nil, subtitle: "Unknown vehicle"))
func shareEvent(event: VehicleEvent) {
guard let url = event.getMapLink() else {
return
}
let event = vehicle.events[index]
if let url = event.getMapLink() {
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) {
@ -304,8 +333,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
self.navigationController?.popViewController(animated: true, completion: {
HUD.show(.progress)
Api.add(event: newEvent, to: vehicle.getNumber())
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onError:
.observe(on: MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onFailure:
{ error in
HUD.show(error: error)
})
@ -334,8 +363,8 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele
HUD.show(.progress)
event.id = UUID().uuidString
Api.add(event: event, to: vehicle.getNumber())
.observeOn(MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onError:
.observe(on: MainScheduler.instance)
.subscribe(onSuccess: { self.update(vehicle: $0) }, onFailure:
{ error in
HUD.show(error: error)
})

View File

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

View File

@ -1,9 +1,24 @@
import UIKit
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 {
public var onCheck: ((String) -> Void)?
// MARK: - Views
private lazy var keyboardView: PNKeyboard = {
let keyboard = PNKeyboard(target: self.plateView)
@ -21,21 +36,26 @@ class NewNumberController: UIViewController {
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 = {
let button = ACButton(title: NSLocalizedString("Check", comment: ""), onTap: check)
button.isEnabled = false
button.contentEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 8)
button.accessibilityIdentifier = "checkButton"
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return button
}()
private lazy var stackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [plateView, checkButton])
stack.axis = .horizontal
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private lazy var stackView: UIStackView = .horizontal([
plateView, vinField, checkButton
])
private let titleLabel: UILabel = {
let label = UILabel()
@ -46,13 +66,35 @@ class NewNumberController: UIViewController {
return label
}()
private lazy var mainStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [titleLabel, stackView, keyboardView])
stack.axis = .vertical
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private lazy var mainStackView: UIStackView = .vertical([
titleLabel,
stackView,
//settingsStackView,
keyboardView
])
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() {
super.viewDidLoad()
@ -79,6 +121,27 @@ class NewNumberController: UIViewController {
func onNumberChanged() {
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 {

View File

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

View File

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

View File

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

View File

@ -4,6 +4,8 @@ import LinkPresentation
import RealmSwift
import Eureka
import AutoCatCore
import SwiftEntryKit
import MobileCoreServices
class ReportController: FormViewController, MediaBrowserViewControllerDataSource, MediaBrowserViewControllerDelegate, UIActivityItemSource {
@ -13,6 +15,10 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
private var reportImageUrl: URL?
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? {
didSet {
if isViewLoaded && self.view.window != nil {
@ -154,17 +160,43 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
func setupCopyBehaviour() {
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
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.delaysTouchesBegan = true
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) {
if let row = self.form.rowBy(tag: tag) as? LabelRow {
row.value = value
@ -184,8 +216,8 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
self.update(row: "Year", with: String(self.vehicle?.year ?? 0))
self.update(row: "Color", with: self.vehicle?.color ?? "<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: "Japanese", with: self.stringFromBool(self.vehicle?.isJapanese.value, yes: NSLocalizedString("Yes", comment: ""), no: NSLocalizedString("No", 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, yes: NSLocalizedString("Yes", comment: ""), no: NSLocalizedString("No", comment: "")))
var num = self.vehicle?.getNumber() ?? "<unknown>"
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: "EngineNumber", with: self.vehicle?.engine?.number ?? "<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: "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: "OSAGO", with: String(self.vehicle?.osagoContracts.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
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)
controller.popoverPresentationController?.barButtonItem = sender
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(shareTextAndImage)
sheet.addAction(shareLink)
sheet.addAction(copyLink)
sheet.addAction(cancel)
self.present(sheet, animated: true, completion: nil)
}

View File

@ -6,7 +6,7 @@ import PKHUD
import ExceptionCatcher
import AutoCatCore
class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDelegate, UIScrollViewDelegate {
class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDelegate, UIScrollViewDelegate, UISearchBarDelegate {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var showMapButton: UIBarButtonItem?
@ -20,7 +20,9 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
private lazy var searchController: UISearchController = .default
.placeholder(NSLocalizedString("Search plate numbers", comment: ""))
.resultsUpdater(self)
.searchBarDelegate(self)
.makeDumb()
.scopeButtons(SearchScope.allCases.map(\.title))
private var refreshControl = UIRefreshControl()
private var datasource: RxSectionedDataSource<Vehicle,VehicleCell>!
@ -72,9 +74,9 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
}
return Api.getVehicles(with: filter, pageToken: self.datasource.pageToken)
.do(onError: { print($0) })
.catchErrorJustReturn(PagedResponse<Vehicle>())
.catchAndReturn(PagedResponse<Vehicle>())
}
.observeOn(MainScheduler.instance)
.observe(on: MainScheduler.instance)
.do(onNext: {
if let count = $0.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.needReset = true
self.filter.scope = SearchScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
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
@available(iOS 14.0, *)
@ -183,6 +196,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
self.filter = controller.filter
self.datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
self.filter.needReset = true
self.filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
self.filterRelay.accept(self.filter)
}
self.navigationController?.pushViewController(controller, animated: true)
@ -203,7 +217,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
showProgress()
Api.getVehicles(with: filter, pageSize: 0)
.observeOn(MainScheduler.instance)
.observe(on: MainScheduler.instance)
.subscribe(onSuccess: { resp in
self.hideProgress()
@ -226,7 +240,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
} catch {
HUD.show(error: error)
}
}, onError: { error in
}, onFailure: { error in
self.hideProgress()
HUD.show(error: error)
})
@ -278,7 +292,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
func update(vehicle: Vehicle, at indexPath: IndexPath) {
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()
do {
let realm = try Realm()
@ -295,7 +309,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
let frozenVehicle = newVehicle.realm != nil ? newVehicle.clone() : newVehicle
self.datasource.set(item: frozenVehicle, at: indexPath)
self.updateDetailController(with: frozenVehicle)
} onError: { err in
} onFailure: { err in
HUD.show(error: err)
}.disposed(by: self.bag)
}

View File

@ -15,7 +15,7 @@ extension AVAudioSession {
do {
try self.setCategory(category, mode: .default, options: [])
} catch {
observer(.error(error))
observer(.failure(error))
}
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)
y += cellHeight
var position = "Unknown"
if let rightWheel = self.isRightWheel.value {
if let rightWheel = self.isRightWheel {
position = rightWheel ? "Right" : "Left"
}
self.drawCell(y: y, width: w, height: cellHeight, title: "Steering wheel position", value: position, context: ctx)
y += cellHeight
var japanese = "<Unknown>"
if let isJapanese = self.isJapanese.value {
if let isJapanese = self.isJapanese {
japanese = isJapanese ? "Yes" : "No"
}
self.drawCell(y: y, width: w, height: cellHeight, title: "Japanese", value: japanese, lineMargin: 0, context: ctx)
@ -115,11 +115,11 @@ extension Vehicle {
y += cellHeight
self.drawCell(y: y, width: w, height: cellHeight, title: "Fuel type", value: self.engine?.fuelType ?? "<unknown>", context: ctx)
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
self.drawCell(y: y, width: w, height: cellHeight, title: "Power (HP)", value: String(self.engine?.powerHp ?? 0), context: ctx)
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
"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 category = self.category { text += "Category: \(category)\n" }
var position = "Unknown"
if let rightWheel = self.isRightWheel.value {
if let rightWheel = self.isRightWheel {
position = rightWheel ? "Right" : "Left"
}
var japanese = "<Unknown>"
if let isJapanese = self.isJapanese.value {
if let isJapanese = self.isJapanese {
japanese = isJapanese ? "Yes" : "No"
}
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">
<plist version="1.0">
<dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>yandexmaps</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>

View File

@ -163,7 +163,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
guard let rootController = self.window?.rootViewController else { return }
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 controller = sb.instantiateViewController(identifier: "ReportController") as ReportController
controller.vehicle = vehicle
@ -172,7 +172,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
controller.navigationItem.leftBarButtonItem = BlockBarButtonItem(barButtonSystemItem: .close) { _ in nav.dismiss(animated: true) }
rootController.present(nav, animated: true)
HUD.hide()
} onError: { error in
} onFailure: { error in
HUD.show(error: error)
}
}

View File

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

View File

@ -45,25 +45,25 @@ class Recorder {
break
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")
observer(.error(error))
observer(.failure(error))
break
case .restricted:
let error = CocoaError.error("Access error", reason: "Speech recognition is restricted on this device")
observer(.error(error))
observer(.failure(error))
break
case .notDetermined:
let error = CocoaError.error("Access error", reason: "Speech recognition status is not yet determined")
observer(.error(error))
observer(.failure(error))
break
@unknown default:
let error = CocoaError.error("Access error", reason: "Unknown error accessing speech recognizer")
observer(.error(error))
observer(.failure(error))
break
}
}
} else {
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
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()
}
ExtAudioFileCreateWithURL(file as CFURL, kAudioFileM4AType, aac.streamDescription, nil, AudioFileFlags.eraseFile.rawValue, &self.fileRef)
guard let fileRef = self.fileRef else {
observer(.error(CocoaError.error(CocoaError.Code.fileWriteUnknown)))
observer(.failure(CocoaError.error(CocoaError.Code.fileWriteUnknown)))
return Disposables.create()
}
@ -121,7 +121,7 @@ class Recorder {
self.engine.prepare()
try self.engine.start()
} catch {
observer(.error(error))
observer(.failure(error))
}
return Disposables.create {

View File

@ -15,28 +15,57 @@ enum PNButtonType {
case done
}
enum PNKeyboardType {
case plateNumber
case vin
}
class PNButton: UIButton {
private(set) var type: PNButtonType
private var keyboardType: PNKeyboardType = .plateNumber
private var rectLayer = CAShapeLayer()
private var bgColor = UIColor(named: "KeyBackground")
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.keyboardType = keyboardType
super.init(frame: .zero)
self.setup()
self.titleLabel?.font = UIFont(name: "RoadNumbers", size: 36)
self.titleLabel?.font = letterFont
let title = String(Constants.pnLettersMap[letter] ?? letter)
self.setTitle(title, for: .normal)
self.setTitleColor(.label, for: .normal)
}
init(digit: Int) {
init(digit: Int, keyboardType: PNKeyboardType = .plateNumber) {
self.type = .symbol(String(digit))
self.keyboardType = keyboardType
super.init(frame: .zero)
self.setup()
self.titleLabel?.font = UIFont(name: "RoadNumbersCyr-Regular", size: 30)
self.titleLabel?.font = digitFont
let character = Character(String(digit))
let title = String(Constants.pnLettersMap[character] ?? character)
self.setTitle(title, for: .normal)
@ -98,20 +127,52 @@ class PNButton: UIButton {
}
class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
private weak var target: UIKeyInput?
public weak var target: UIKeyInput?
weak var delegate: PNKeyboardDelegate?
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.insets = insets
self.type = type
super.init(frame: .zero)
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
let blurEffectView = UIVisualEffectView(effect: UIBlurEffect())
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
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()
}
@ -120,14 +181,53 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
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() {
let letters: [PNButton] = Constants.pnLettersMap.keys.sorted().map { letter in
let button = PNButton(letter: letter)
let letterButtons: [PNButton] = letters.sorted().map { letter in
let button = PNButton(letter: letter, keyboardType: type)
button.delegate = self
return button
}
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
return button
}
@ -139,14 +239,8 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
done.setBacgroundColor(color: .systemBlue)
done.tintColor = .white
let letterRows = [
self.createLetterStack([letters[0], letters[1], letters[2]]),
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)
let rows = letterRows(from: letterButtons)
let lettersStack = UIStackView(arrangedSubviews: rows)
lettersStack.axis = .vertical
lettersStack.distribution = .fillEqually
//lettersStack.spacing = 8
@ -163,18 +257,15 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
digitsStack.distribution = .fillEqually
//digitsStack.spacing = 8
let mainStack = UIStackView(arrangedSubviews: [lettersStack, digitsStack])
mainStack.spacing = 16
mainStack.distribution = .fillEqually
mainStack.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(mainStack)
mainStack.setArrangedSubviews([lettersStack, digitsStack])
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)
])
lettersWidthConstraint = lettersStack.widthAnchor.constraint(equalToConstant: 0)
digitsWidthConstraint = digitsStack.widthAnchor.constraint(equalToConstant: 0)
lettersWidthConstraint?.priority = .defaultLow
digitsWidthConstraint?.priority = .defaultLow
NSLayoutConstraint.activate([lettersWidthConstraint, digitsWidthConstraint].compactMap { $0 })
}
func createLetterStack(_ views: [UIView]) -> UIStackView {

View File

@ -403,3 +403,5 @@
/* No comment provided by engineer. */
"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 {
@objc public dynamic var path: String = ""
@objc public dynamic var number: String?
@objc public dynamic var rawText: String = ""
@objc private dynamic var addedDate: TimeInterval = 0
@objc public dynamic var duration: TimeInterval = 0
@objc public dynamic var event: VehicleEvent?
@Persisted public var path: String = ""
@Persisted public var number: String?
@Persisted public var rawText: String = ""
@Persisted private var addedDate: TimeInterval = 0
@Persisted public var duration: TimeInterval = 0
@Persisted public var event: VehicleEvent?
public var identifier: TimeInterval = 0
public var id: TimeInterval {
@ -33,7 +33,7 @@ public class AudioRecord: Object, Identifiable, Cloneable {
self.addedDate = Date().timeIntervalSince1970
}
required init() {
required override init() {
super.init()
}

View File

@ -8,17 +8,17 @@ public enum DebugInfoStatus: Int {
}
public class DebugInfo: Object, Decodable {
@objc public dynamic var autocod: DebugInfoEntry!
@objc public dynamic var vin01vin: DebugInfoEntry!
@objc public dynamic var vin01base: DebugInfoEntry!
@objc public dynamic var vin01history: DebugInfoEntry!
@objc public dynamic var nomerogram: DebugInfoEntry!
@Persisted public var autocod: DebugInfoEntry!
@Persisted public var vin01vin: DebugInfoEntry!
@Persisted public var vin01base: DebugInfoEntry!
@Persisted public var vin01history: DebugInfoEntry!
@Persisted public var nomerogram: DebugInfoEntry!
}
public class DebugInfoEntry: Object, Decodable {
@objc public dynamic var fields: Int64 = 0
@objc public dynamic var error: String?
@objc public dynamic var status: Int = 0
@Persisted public var fields: Int64 = 0
@Persisted public var error: String?
@Persisted public var status: Int = 0
public var statusEnum: DebugInfoStatus {
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 var searchString = ""
public var brand: String?
@ -55,6 +78,7 @@ public struct Filter {
public var fromLocationDate: Date?
public var toLocationDate: Date?
public var needReset: Bool = false
public var scope: SearchScope = .plateNumber
public init() {
}
@ -74,6 +98,7 @@ public struct Filter {
self.toDateUpdated = nil
self.fromLocationDate = nil
self.toLocationDate = nil
self.scope = .plateNumber
}
public func queryDictionary() -> [String: String] {
@ -122,6 +147,8 @@ public struct Filter {
dict["toLocationDate"] = String(toLocationDate.timeIntervalSince1970)
}
dict["scope"] = scope.stringValue
return dict
}
}

View File

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

View File

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

View File

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

View File

@ -4,12 +4,12 @@ import RxSwift
import CoreLocation
public class VehicleEvent: Object, Codable, Cloneable {
@objc public dynamic var id: String = UUID().uuidString
@objc public dynamic var date: TimeInterval = Date().timeIntervalSince1970
@objc public dynamic var latitude: Double = 0
@objc public dynamic var longitude: Double = 0
@objc public dynamic var address: String? = nil
@objc public dynamic var addedBy: String? = nil
@Persisted public var id: String = UUID().uuidString
@Persisted public var date: TimeInterval = Date().timeIntervalSince1970
@Persisted public var latitude: Double = 0
@Persisted public var longitude: Double = 0
@Persisted public var address: String? = nil
@Persisted public var addedBy: String? = nil
public var number: String?
public var coordinate: CLLocationCoordinate2D {
@ -22,7 +22,7 @@ public class VehicleEvent: Object, Codable, Cloneable {
self.addedBy = Settings.shared.user.email
}
required init() {
required override init() {
super.init()
}
@ -78,4 +78,20 @@ public class VehicleEvent: Object, Codable, Cloneable {
self.number = copy.number
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
public class VehicleNote: Object, Codable, Cloneable {
@objc public dynamic var id: String = UUID().uuidString
@objc public dynamic var user: String = ""
@objc public dynamic var date: TimeInterval = Date().timeIntervalSince1970
@objc public dynamic var text: String = ""
@Persisted public var id: String = UUID().uuidString
@Persisted public var user: String = ""
@Persisted public var date: TimeInterval = Date().timeIntervalSince1970
@Persisted public var text: String = ""
// MARK: - Cloneable
@ -16,7 +16,7 @@ public class VehicleNote: Object, Codable, Cloneable {
self.text = copy.text
}
required init() {
required override init() {
super.init()
}

View File

@ -46,7 +46,7 @@ public class Api {
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)
// print("================================")
// if let string = str?.replacingOccurrences(of: "\\\"", with: "\"")
@ -55,6 +55,15 @@ public class Api {
// print(string)
// }
// 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 {
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
if resp.success {
@ -163,7 +172,7 @@ public class Api {
Settings.shared.user.firebaseRefreshToken = newRefreshToken
print("Refresh token: \(newRefreshToken)")
}
}.catchError { err in
}.catch { err in
print(err)
return .just(())
}

View File

@ -15,6 +15,10 @@ public enum Constants {
"А": "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 googleTokenURL = "https://oauth2.googleapis.com/token"
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) {
observer(.success(()))
} 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:
observer(.error(CLError(.denied)))
observer(.failure(CLError(.denied)))
break
default:
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Location permission error"])))
break
}
@ -139,11 +139,11 @@ public class RxLocationManager {
let location = CLLocation(latitude: latitude, longitude: longitude)
geocoder.reverseGeocodeLocation(location) { placemarks, error in
if let error = error {
observer(.error(error))
observer(.failure(error))
} else if let placemark = placemarks?.first, let name = placemark.name {
observer(.success(name))
} else {
observer(.error(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Reverse geolocation error"])))
observer(.failure(NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Reverse geolocation error"])))
}
}