Store GPS coordinates when check plate number or recording audio
This commit is contained in:
parent
e8212e0184
commit
c39971637d
@ -33,6 +33,7 @@
|
|||||||
7A11474723FF2AA500B424AF /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474623FF2AA500B424AF /* User.swift */; };
|
7A11474723FF2AA500B424AF /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474623FF2AA500B424AF /* User.swift */; };
|
||||||
7A11474923FF2B2D00B424AF /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.swift */; };
|
7A11474923FF2B2D00B424AF /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.swift */; };
|
||||||
7A11474B23FF368B00B424AF /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474A23FF368B00B424AF /* Settings.swift */; };
|
7A11474B23FF368B00B424AF /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474A23FF368B00B424AF /* Settings.swift */; };
|
||||||
|
7A15051224DB3E3000F39631 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A15051124DB3E3000F39631 /* AnyEncodable.swift */; };
|
||||||
7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADC6249D43210035F39E /* RegionsController.swift */; };
|
7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADC6249D43210035F39E /* RegionsController.swift */; };
|
||||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF2249F8B650035F39E /* RecordsController.swift */; };
|
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF2249F8B650035F39E /* RecordsController.swift */; };
|
||||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */; };
|
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */; };
|
||||||
@ -84,6 +85,7 @@
|
|||||||
7AB562BA249C9E9B00473D53 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB562B9249C9E9B00473D53 /* Region.swift */; };
|
7AB562BA249C9E9B00473D53 /* Region.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB562B9249C9E9B00473D53 /* Region.swift */; };
|
||||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
|
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
|
||||||
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8D2435D1A000258F61 /* CustomButton.swift */; };
|
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8D2435D1A000258F61 /* CustomButton.swift */; };
|
||||||
|
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; };
|
||||||
7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFE727240455E200910EB7 /* SettingsController.swift */; };
|
7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFE727240455E200910EB7 /* SettingsController.swift */; };
|
||||||
7AF58D2F24029C5200CE01A0 /* MagazineLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D2E24029C5200CE01A0 /* MagazineLayout */; };
|
7AF58D2F24029C5200CE01A0 /* MagazineLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D2E24029C5200CE01A0 /* MagazineLayout */; };
|
||||||
7AF58D3124029E1000CE01A0 /* VehicleHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF58D3024029E1000CE01A0 /* VehicleHeaderCell.swift */; };
|
7AF58D3124029E1000CE01A0 /* VehicleHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF58D3024029E1000CE01A0 /* VehicleHeaderCell.swift */; };
|
||||||
@ -112,6 +114,7 @@
|
|||||||
7A11474823FF2B2D00B424AF /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
7A11474823FF2B2D00B424AF /* Response.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
|
||||||
7A11474A23FF368B00B424AF /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
7A11474A23FF368B00B424AF /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||||
7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = "<group>"; };
|
7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = "<group>"; };
|
||||||
|
7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||||
7A27ADC6249D43210035F39E /* RegionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsController.swift; sourceTree = "<group>"; };
|
7A27ADC6249D43210035F39E /* RegionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsController.swift; sourceTree = "<group>"; };
|
||||||
7A27ADF2249F8B650035F39E /* RecordsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsController.swift; sourceTree = "<group>"; };
|
7A27ADF2249F8B650035F39E /* RecordsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsController.swift; sourceTree = "<group>"; };
|
||||||
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = "<group>"; };
|
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = "<group>"; };
|
||||||
@ -162,6 +165,7 @@
|
|||||||
7AB562B9249C9E9B00473D53 /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = "<group>"; };
|
7AB562B9249C9E9B00473D53 /* Region.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Region.swift; sourceTree = "<group>"; };
|
||||||
7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
|
7AB67E8B2435C38700258F61 /* CustomTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTextField.swift; sourceTree = "<group>"; };
|
||||||
7AB67E8D2435D1A000258F61 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; };
|
7AB67E8D2435D1A000258F61 /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; };
|
||||||
|
7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = "<group>"; };
|
||||||
7AEFE727240455E200910EB7 /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
|
7AEFE727240455E200910EB7 /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
|
||||||
7AF58D3024029E1000CE01A0 /* VehicleHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleHeaderCell.swift; sourceTree = "<group>"; };
|
7AF58D3024029E1000CE01A0 /* VehicleHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleHeaderCell.swift; sourceTree = "<group>"; };
|
||||||
7AF58D57240309CA00CE01A0 /* VehicleTextParamCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleTextParamCell.swift; sourceTree = "<group>"; };
|
7AF58D57240309CA00CE01A0 /* VehicleTextParamCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleTextParamCell.swift; sourceTree = "<group>"; };
|
||||||
@ -258,6 +262,7 @@
|
|||||||
7A64AE6E2469DFB600ABE48E /* ATGMediaBrowser */,
|
7A64AE6E2469DFB600ABE48E /* ATGMediaBrowser */,
|
||||||
7A6DD90724329144009DE740 /* CenterTextLayer.swift */,
|
7A6DD90724329144009DE740 /* CenterTextLayer.swift */,
|
||||||
7A96AE32246C095700297C33 /* Base64FS.swift */,
|
7A96AE32246C095700297C33 /* Base64FS.swift */,
|
||||||
|
7A15051124DB3E3000F39631 /* AnyEncodable.swift */,
|
||||||
);
|
);
|
||||||
path = ThirdParty;
|
path = ThirdParty;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -311,6 +316,7 @@
|
|||||||
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */,
|
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */,
|
||||||
7A27ADF824A09CAD0035F39E /* CocoaError.swift */,
|
7A27ADF824A09CAD0035F39E /* CocoaError.swift */,
|
||||||
7A659B5A24A3768A0043A0F2 /* Substrings.swift */,
|
7A659B5A24A3768A0043A0F2 /* Substrings.swift */,
|
||||||
|
7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -500,6 +506,7 @@
|
|||||||
7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */,
|
7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */,
|
||||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
|
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
|
||||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */,
|
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */,
|
||||||
|
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */,
|
||||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
|
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
|
||||||
7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */,
|
7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */,
|
||||||
7A6DD90E24337930009DE740 /* PlateNumber.swift in Sources */,
|
7A6DD90E24337930009DE740 /* PlateNumber.swift in Sources */,
|
||||||
@ -522,6 +529,7 @@
|
|||||||
7A7547DD2403180A004E8406 /* SectionHeader.swift in Sources */,
|
7A7547DD2403180A004E8406 /* SectionHeader.swift in Sources */,
|
||||||
7AF58D58240309CA00CE01A0 /* VehicleTextParamCell.swift in Sources */,
|
7AF58D58240309CA00CE01A0 /* VehicleTextParamCell.swift in Sources */,
|
||||||
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */,
|
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */,
|
||||||
|
7A15051224DB3E3000F39631 /* AnyEncodable.swift in Sources */,
|
||||||
7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */,
|
7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */,
|
||||||
7A11474723FF2AA500B424AF /* User.swift in Sources */,
|
7A11474723FF2AA500B424AF /* User.swift in Sources */,
|
||||||
7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */,
|
7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */,
|
||||||
@ -691,7 +699,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 21;
|
CURRENT_PROJECT_VERSION = 22;
|
||||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
@ -713,7 +721,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 21;
|
CURRENT_PROJECT_VERSION = 22;
|
||||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
|||||||
@ -24,8 +24,8 @@
|
|||||||
"repositoryURL": "https://github.com/onevcat/Kingfisher",
|
"repositoryURL": "https://github.com/onevcat/Kingfisher",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "46bf251fee8ed426921c647790f08ca8ad0105a9",
|
"revision": "1339ebea9498ef6c3fc75cc195d7163d7c7167f9",
|
||||||
"version": "5.13.1"
|
"version": "5.14.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -33,8 +33,8 @@
|
|||||||
"repositoryURL": "https://github.com/airbnb/MagazineLayout",
|
"repositoryURL": "https://github.com/airbnb/MagazineLayout",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "bbbe1456c34c1abb527d05ff9da3ff2a54584d78",
|
"revision": "12dd2cc84b7f17c4f46c7d95cde64d521c588ee8",
|
||||||
"version": "1.5.5"
|
"version": "1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -78,8 +78,8 @@
|
|||||||
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "b3e888b4972d9bc76495dd74d30a8c7fad4b9395",
|
"revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa",
|
||||||
"version": "5.0.1"
|
"version": "5.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -50,64 +50,16 @@
|
|||||||
<BreakpointProxy
|
<BreakpointProxy
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||||
<BreakpointContent
|
<BreakpointContent
|
||||||
uuid = "0F1972A7-94E8-40E5-834C-B873D2578DC7"
|
uuid = "339C709C-134A-4B97-9F9C-949886D4AD65"
|
||||||
shouldBeEnabled = "Yes"
|
shouldBeEnabled = "No"
|
||||||
ignoreCount = "0"
|
ignoreCount = "0"
|
||||||
continueAfterRunningActions = "No"
|
continueAfterRunningActions = "No"
|
||||||
filePath = "AutoCat/Controllers/RecordsController.swift"
|
filePath = "AutoCat/Controllers/SearchController.swift"
|
||||||
startingColumnNumber = "9223372036854775807"
|
startingColumnNumber = "9223372036854775807"
|
||||||
endingColumnNumber = "9223372036854775807"
|
endingColumnNumber = "9223372036854775807"
|
||||||
startingLineNumber = "150"
|
startingLineNumber = "94"
|
||||||
endingLineNumber = "150"
|
endingLineNumber = "94"
|
||||||
landmarkName = "onAddVoiceRecord(_:)"
|
landmarkName = "onFilter(_:)"
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "79DDC3DB-613E-40E9-AD33-AEBAA5775C62"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "AutoCat/Controllers/RecordsController.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "144"
|
|
||||||
endingLineNumber = "144"
|
|
||||||
landmarkName = "onAddVoiceRecord(_:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "664BF878-7362-4887-BFD4-6938FCAB4750"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "AutoCat/Utils/Location.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "46"
|
|
||||||
endingLineNumber = "46"
|
|
||||||
landmarkName = "locationManager(_:didFailWithError:)"
|
|
||||||
landmarkType = "7">
|
|
||||||
</BreakpointContent>
|
|
||||||
</BreakpointProxy>
|
|
||||||
<BreakpointProxy
|
|
||||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
||||||
<BreakpointContent
|
|
||||||
uuid = "3661DA4A-8777-45F2-A6FD-0F1D3F487FF8"
|
|
||||||
shouldBeEnabled = "Yes"
|
|
||||||
ignoreCount = "0"
|
|
||||||
continueAfterRunningActions = "No"
|
|
||||||
filePath = "AutoCat/Utils/Location.swift"
|
|
||||||
startingColumnNumber = "9223372036854775807"
|
|
||||||
endingColumnNumber = "9223372036854775807"
|
|
||||||
startingLineNumber = "40"
|
|
||||||
endingLineNumber = "40"
|
|
||||||
landmarkName = "locationManager(_:didUpdateLocations:)"
|
|
||||||
landmarkType = "7">
|
landmarkType = "7">
|
||||||
</BreakpointContent>
|
</BreakpointContent>
|
||||||
</BreakpointProxy>
|
</BreakpointProxy>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ extension OSLog {
|
|||||||
enum QuickAction {
|
enum QuickAction {
|
||||||
case none
|
case none
|
||||||
case check
|
case check
|
||||||
case checkNumber(String)
|
case checkNumber(String, VehicleEvent?)
|
||||||
case addVoiceRecord
|
case addVoiceRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
let config = Realm.Configuration(
|
let config = Realm.Configuration(
|
||||||
schemaVersion: 10,
|
schemaVersion: 13,
|
||||||
migrationBlock: { migration, oldSchemaVersion in
|
migrationBlock: { migration, oldSchemaVersion in
|
||||||
if oldSchemaVersion <= 3 {
|
if oldSchemaVersion <= 3 {
|
||||||
var numbers: [String] = []
|
var numbers: [String] = []
|
||||||
|
|||||||
@ -8,5 +8,7 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.personal-information.location</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pme-aR-UNJ">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pme-aR-UNJ">
|
||||||
<device id="retina4_7" orientation="portrait" appearance="dark"/>
|
<device id="retina4_7" orientation="portrait" appearance="dark"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||||
@ -40,9 +40,9 @@
|
|||||||
<constraint firstAttribute="height" constant="48" id="Tk0-8G-9hP"/>
|
<constraint firstAttribute="height" constant="48" id="Tk0-8G-9hP"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</imageView>
|
</imageView>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ysk-FW-p2W">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ysk-FW-p2W">
|
||||||
<rect key="frame" x="80" y="26" width="215" height="33.5"/>
|
<rect key="frame" x="80" y="29.5" width="215" height="26.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
@ -64,10 +64,10 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="VehicleTextParamCell" id="3Qa-Fn-qe7" customClass="VehicleTextParamCell" customModule="AutoCat" customModuleProvider="target">
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="VehicleTextParamCell" id="3Qa-Fn-qe7" customClass="VehicleTextParamCell" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="118" width="145" height="42.5"/>
|
<rect key="frame" x="0.0" y="118" width="145.5" height="42.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="fmb-JM-Fcr">
|
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="fmb-JM-Fcr">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="145" height="42.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="145.5" height="42.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="023-Q6-kPk">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="023-Q6-kPk">
|
||||||
@ -76,14 +76,23 @@
|
|||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ptE-4B-jxt">
|
<textField opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Value" textAlignment="right" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="xoM-AA-KQ5" customClass="CustomTextField" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="76.5" y="8" width="52.5" height="26.5"/>
|
<rect key="frame" x="76.5" y="8" width="53" height="27"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
|
||||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||||
</label>
|
<textInputTraits key="textInputTraits"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="boolean" keyPath="editable" value="NO"/>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
|
||||||
|
<real key="value" value="0.0"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||||
|
<real key="value" value="0.0"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
|
</textField>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WAm-et-t5P">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WAm-et-t5P">
|
||||||
<rect key="frame" x="0.0" y="41.5" width="145" height="1"/>
|
<rect key="frame" x="0.0" y="41.5" width="145.5" height="1"/>
|
||||||
<color key="backgroundColor" systemColor="separatorColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" systemColor="separatorColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.28999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="1" id="Mpt-xA-af2"/>
|
<constraint firstAttribute="height" constant="1" id="Mpt-xA-af2"/>
|
||||||
@ -91,14 +100,14 @@
|
|||||||
</view>
|
</view>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="trailing" secondItem="ptE-4B-jxt" secondAttribute="trailing" constant="16" id="4EF-1X-hrJ"/>
|
|
||||||
<constraint firstAttribute="bottom" secondItem="WAm-et-t5P" secondAttribute="bottom" id="91A-1k-B4C"/>
|
<constraint firstAttribute="bottom" secondItem="WAm-et-t5P" secondAttribute="bottom" id="91A-1k-B4C"/>
|
||||||
<constraint firstItem="ptE-4B-jxt" firstAttribute="leading" secondItem="023-Q6-kPk" secondAttribute="trailing" constant="8" id="BPP-fC-Q8I"/>
|
|
||||||
<constraint firstItem="023-Q6-kPk" firstAttribute="centerY" secondItem="ptE-4B-jxt" secondAttribute="centerY" id="Ce2-tJ-wbq"/>
|
|
||||||
<constraint firstItem="WAm-et-t5P" firstAttribute="leading" secondItem="fmb-JM-Fcr" secondAttribute="leading" id="Qr4-Eb-9YL"/>
|
<constraint firstItem="WAm-et-t5P" firstAttribute="leading" secondItem="fmb-JM-Fcr" secondAttribute="leading" id="Qr4-Eb-9YL"/>
|
||||||
|
<constraint firstItem="xoM-AA-KQ5" firstAttribute="centerY" secondItem="fmb-JM-Fcr" secondAttribute="centerY" id="TRj-nX-R3d"/>
|
||||||
|
<constraint firstItem="xoM-AA-KQ5" firstAttribute="leading" secondItem="023-Q6-kPk" secondAttribute="trailing" constant="8" id="Vip-dt-0kM"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="023-Q6-kPk" secondAttribute="bottom" constant="8" id="asT-vZ-W1W"/>
|
<constraint firstAttribute="bottom" secondItem="023-Q6-kPk" secondAttribute="bottom" constant="8" id="asT-vZ-W1W"/>
|
||||||
<constraint firstItem="023-Q6-kPk" firstAttribute="top" secondItem="fmb-JM-Fcr" secondAttribute="top" constant="8" id="i2x-Tv-BGw"/>
|
<constraint firstItem="023-Q6-kPk" firstAttribute="top" secondItem="fmb-JM-Fcr" secondAttribute="top" constant="8" id="i2x-Tv-BGw"/>
|
||||||
<constraint firstItem="023-Q6-kPk" firstAttribute="leading" secondItem="fmb-JM-Fcr" secondAttribute="leading" constant="16" id="j9m-4c-c7c"/>
|
<constraint firstItem="023-Q6-kPk" firstAttribute="leading" secondItem="fmb-JM-Fcr" secondAttribute="leading" constant="16" id="j9m-4c-c7c"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="xoM-AA-KQ5" secondAttribute="trailing" constant="16" id="oAu-y7-Ano"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="WAm-et-t5P" secondAttribute="trailing" id="zil-58-Wao"/>
|
<constraint firstAttribute="trailing" secondItem="WAm-et-t5P" secondAttribute="trailing" id="zil-58-Wao"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</collectionViewCellContentView>
|
</collectionViewCellContentView>
|
||||||
@ -106,11 +115,11 @@
|
|||||||
<connections>
|
<connections>
|
||||||
<outlet property="dividerHeightConstraint" destination="Mpt-xA-af2" id="bYb-0D-Lz0"/>
|
<outlet property="dividerHeightConstraint" destination="Mpt-xA-af2" id="bYb-0D-Lz0"/>
|
||||||
<outlet property="paramName" destination="023-Q6-kPk" id="pGj-6y-fbP"/>
|
<outlet property="paramName" destination="023-Q6-kPk" id="pGj-6y-fbP"/>
|
||||||
<outlet property="paramValue" destination="ptE-4B-jxt" id="4YD-Va-YLb"/>
|
<outlet property="paramValue" destination="xoM-AA-KQ5" id="2Q2-ig-dIW"/>
|
||||||
</connections>
|
</connections>
|
||||||
</collectionViewCell>
|
</collectionViewCell>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="VehiclePhotoCell" id="X77-i3-DVY" customClass="VehiclePhotoCell" customModule="AutoCat" customModuleProvider="target">
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="VehiclePhotoCell" id="X77-i3-DVY" customClass="VehiclePhotoCell" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="155" y="95" width="58" height="88.5"/>
|
<rect key="frame" x="155.5" y="95" width="58" height="88.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="AMu-PZ-gGR">
|
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="AMu-PZ-gGR">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="58" height="88.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="58" height="88.5"/>
|
||||||
@ -223,14 +232,14 @@
|
|||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="VehicleCell" id="VEP-QD-i6y" customClass="VehicleCell" customModule="AutoCat" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="VehicleCell" id="VEP-QD-i6y" customClass="VehicleCell" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="28" width="375" height="85"/>
|
<rect key="frame" x="0.0" y="28" width="375" height="85.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VEP-QD-i6y" id="8hH-8I-XLB">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VEP-QD-i6y" id="8hH-8I-XLB">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="85"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="85.5"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Kia (JF) Optima" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AQY-7N-q8D">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Kia (JF) Optima" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AQY-7N-q8D">
|
||||||
<rect key="frame" x="8" y="8" width="124" height="21"/>
|
<rect key="frame" x="8" y="8" width="124" height="21.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@ -242,7 +251,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cvf-vM-QnT" customClass="PlateView" customModule="AutoCat" customModuleProvider="target">
|
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cvf-vM-QnT" customClass="PlateView" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="8" y="37" width="317" height="40"/>
|
<rect key="frame" x="8" y="37.5" width="317" height="40"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="40" id="Xoz-Iw-PCU"/>
|
<constraint firstAttribute="height" constant="40" id="Xoz-Iw-PCU"/>
|
||||||
@ -495,6 +504,14 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="250" height="50.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="250" height="50.5"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
|
||||||
<textInputTraits key="textInputTraits" returnKeyType="done"/>
|
<textInputTraits key="textInputTraits" returnKeyType="done"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
|
||||||
|
<real key="value" value="1"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||||
|
<real key="value" value="6"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
</textField>
|
</textField>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ync-fd-xQI" customClass="CustomButton" customModule="AutoCat" customModuleProvider="target">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ync-fd-xQI" customClass="CustomButton" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="66.5" width="250" height="48"/>
|
<rect key="frame" x="0.0" y="66.5" width="250" height="48"/>
|
||||||
@ -515,7 +532,7 @@
|
|||||||
<constraint firstAttribute="width" constant="250" id="lqO-Yy-NyQ"/>
|
<constraint firstAttribute="width" constant="250" id="lqO-Yy-NyQ"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</stackView>
|
</stackView>
|
||||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="JKr-UE-x8f">
|
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="JKr-UE-x8f">
|
||||||
<rect key="frame" x="0.0" y="206.5" width="375" height="411.5"/>
|
<rect key="frame" x="0.0" y="206.5" width="375" height="411.5"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
@ -677,6 +694,14 @@
|
|||||||
</constraints>
|
</constraints>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<textInputTraits key="textInputTraits"/>
|
<textInputTraits key="textInputTraits"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
|
||||||
|
<real key="value" value="1"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||||
|
<real key="value" value="6"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
</textField>
|
</textField>
|
||||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Password" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="G1p-Hz-8yn" customClass="CustomTextField" customModule="AutoCat" customModuleProvider="target">
|
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Password" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="G1p-Hz-8yn" customClass="CustomTextField" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="50" width="262.5" height="34"/>
|
<rect key="frame" x="0.0" y="50" width="262.5" height="34"/>
|
||||||
@ -685,6 +710,14 @@
|
|||||||
</constraints>
|
</constraints>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||||
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
|
<textInputTraits key="textInputTraits" secureTextEntry="YES"/>
|
||||||
|
<userDefinedRuntimeAttributes>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
|
||||||
|
<real key="value" value="1"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
<userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
|
||||||
|
<real key="value" value="6"/>
|
||||||
|
</userDefinedRuntimeAttribute>
|
||||||
|
</userDefinedRuntimeAttributes>
|
||||||
</textField>
|
</textField>
|
||||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ltG-B1-UBj" customClass="CustomButton" customModule="AutoCat" customModuleProvider="target">
|
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ltG-B1-UBj" customClass="CustomButton" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="100" width="262.5" height="40"/>
|
<rect key="frame" x="0.0" y="100" width="262.5" height="40"/>
|
||||||
@ -830,11 +863,11 @@
|
|||||||
<image name="doc.on.doc" catalog="system" width="117" height="128"/>
|
<image name="doc.on.doc" catalog="system" width="117" height="128"/>
|
||||||
<image name="line.horizontal.3.decrease" catalog="system" width="128" height="73"/>
|
<image name="line.horizontal.3.decrease" catalog="system" width="128" height="73"/>
|
||||||
<image name="play.fill" catalog="system" width="116" height="128"/>
|
<image name="play.fill" catalog="system" width="116" height="128"/>
|
||||||
<image name="record" width="128" height="128"/>
|
<image name="record" width="31" height="31"/>
|
||||||
<image name="record-compact" width="23" height="23"/>
|
<image name="record-compact" width="23" height="23"/>
|
||||||
<image name="search" width="128" height="128"/>
|
<image name="search" width="23" height="23"/>
|
||||||
<image name="search-compact" width="17" height="17"/>
|
<image name="search-compact" width="17" height="17"/>
|
||||||
<image name="settings" width="128" height="128"/>
|
<image name="settings" width="25" height="25"/>
|
||||||
<image name="settings-compact" width="18" height="18"/>
|
<image name="settings-compact" width="18" height="18"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import MagazineLayout
|
|||||||
|
|
||||||
class VehicleTextParamCell: MagazineLayoutCollectionViewCell {
|
class VehicleTextParamCell: MagazineLayoutCollectionViewCell {
|
||||||
@IBOutlet weak var paramName: UILabel!
|
@IBOutlet weak var paramName: UILabel!
|
||||||
@IBOutlet weak var paramValue: UILabel!
|
@IBOutlet weak var paramValue: UITextField!
|
||||||
@IBOutlet weak var dividerHeightConstraint: NSLayoutConstraint!
|
@IBOutlet weak var dividerHeightConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class AuthController: UIViewController {
|
|||||||
IHProgressHUD.show()
|
IHProgressHUD.show()
|
||||||
Api.login(username: name, password: pass)
|
Api.login(username: name, password: pass)
|
||||||
.observeOn(MainScheduler.instance)
|
.observeOn(MainScheduler.instance)
|
||||||
.subscribe(onNext: self.goToMainScreen(user:), onError: self.displayError(error:))
|
.subscribe(onSuccess: self.goToMainScreen(user:), onError: self.displayError(error:))
|
||||||
.disposed(by: self.bag)
|
.disposed(by: self.bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class AuthController: UIViewController {
|
|||||||
IHProgressHUD.show()
|
IHProgressHUD.show()
|
||||||
Api.signup(username: name, password: pass)
|
Api.signup(username: name, password: pass)
|
||||||
.observeOn(MainScheduler.instance)
|
.observeOn(MainScheduler.instance)
|
||||||
.subscribe(onNext: self.goToMainScreen(user:), onError: self.displayError(error:))
|
.subscribe(onSuccess: self.goToMainScreen(user:), onError: self.displayError(error:))
|
||||||
.disposed(by: self.bag)
|
.disposed(by: self.bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,11 +15,14 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
let bag = DisposeBag()
|
let bag = DisposeBag()
|
||||||
let maskFieldDelegate = MaskedTextFieldDelegate()
|
let maskFieldDelegate = MaskedTextFieldDelegate()
|
||||||
|
|
||||||
|
// MARK: - Lifecycle
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
guard let realm = try? Realm() else { return }
|
guard let realm = try? Realm() else { return }
|
||||||
|
|
||||||
|
self.hideKeyboardWhenTappedAround()
|
||||||
self.maskFieldDelegate.primaryMaskFormat = "[A][000][AA] [009]"
|
self.maskFieldDelegate.primaryMaskFormat = "[A][000][AA] [009]"
|
||||||
self.maskFieldDelegate.listener = self
|
self.maskFieldDelegate.listener = self
|
||||||
self.number.delegate = self.maskFieldDelegate
|
self.number.delegate = self.maskFieldDelegate
|
||||||
@ -70,6 +73,8 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
self.handleQuickActions()
|
self.handleQuickActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
func handleQuickActions() {
|
func handleQuickActions() {
|
||||||
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
|
||||||
@ -78,9 +83,9 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
ad.quickAction = .none
|
ad.quickAction = .none
|
||||||
self.number.becomeFirstResponder()
|
self.number.becomeFirstResponder()
|
||||||
break
|
break
|
||||||
case .checkNumber(let number):
|
case .checkNumber(let number, let event):
|
||||||
ad.quickAction = .none
|
ad.quickAction = .none
|
||||||
self.check(number: number)
|
self.check(number: number, event: event)
|
||||||
break
|
break
|
||||||
case .addVoiceRecord:
|
case .addVoiceRecord:
|
||||||
self.tabBarController?.selectedIndex = 1
|
self.tabBarController?.selectedIndex = 1
|
||||||
@ -90,21 +95,25 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Checking new number
|
||||||
|
|
||||||
@IBAction func checkTapped(_ sender: UIButton) {
|
@IBAction func checkTapped(_ sender: UIButton) {
|
||||||
guard let number = self.number.text else { return }
|
guard let number = self.number.text else { return }
|
||||||
|
|
||||||
let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
|
let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
|
||||||
self.check(number: numberNormalized)
|
self.check(number: numberNormalized, event: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func check(number: String) {
|
func check(number: String, event: VehicleEvent?) {
|
||||||
self.number.resignFirstResponder()
|
self.number.resignFirstResponder()
|
||||||
self.number.text = nil
|
self.number.text = nil
|
||||||
self.check.isEnabled = false
|
self.check.isEnabled = false
|
||||||
IHProgressHUD.show()
|
IHProgressHUD.show()
|
||||||
Api.checkVehicle(by: number)
|
Api.checkVehicle(by: number)
|
||||||
.observeOn(MainScheduler.instance)
|
.observeOn(MainScheduler.instance)
|
||||||
.subscribe(onNext: onReceivedVehicle(_:), onError: { err in
|
.subscribe(onSuccess: { vehicle in
|
||||||
|
self.onReceivedVehicle(vehicle, event: event)
|
||||||
|
}, onError: { err in
|
||||||
if let realm = try? Realm() {
|
if let realm = try? Realm() {
|
||||||
try? realm.write {
|
try? realm.write {
|
||||||
realm.add(Vehicle(number), update: .all)
|
realm.add(Vehicle(number), update: .all)
|
||||||
@ -115,23 +124,32 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
}).disposed(by: self.bag)
|
}).disposed(by: self.bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textField(_ textField: UITextField, didFillMandatoryCharacters complete: Bool, didExtractValue value: String) {
|
func save(vehicle: Vehicle) {
|
||||||
self.check.isEnabled = complete
|
|
||||||
}
|
|
||||||
|
|
||||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
|
||||||
if self.check.isEnabled {
|
|
||||||
self.checkTapped(self.check)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func onReceivedVehicle(_ vehicle: Vehicle) {
|
|
||||||
if let realm = try? Realm() {
|
if let realm = try? Realm() {
|
||||||
try? realm.write {
|
try? realm.write {
|
||||||
realm.add(vehicle, update: .all)
|
realm.add(vehicle, update: .all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEvent() -> Single<VehicleEvent> {
|
||||||
|
if let event = LocationManager.lastEvent, (Date().timeIntervalSince1970 - event.date) < 30 {
|
||||||
|
print("Using last event")
|
||||||
|
return Single<VehicleEvent>.just(event)
|
||||||
|
} else {
|
||||||
|
print("requesting new event")
|
||||||
|
return LocationManager.requestCurrentLocation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onReceivedVehicle(_ vehicle: Vehicle, event: VehicleEvent? = nil) {
|
||||||
|
self.save(vehicle: vehicle)
|
||||||
|
|
||||||
|
let eventSingle = event == nil ? self.getEvent() : Single.just(event!)
|
||||||
|
eventSingle
|
||||||
|
.flatMap { Api.add(event: $0, to: vehicle.number) }
|
||||||
|
.subscribe(onSuccess: self.save(vehicle:), onError: { print("Error adding event: \($0)") })
|
||||||
|
.disposed(by: self.bag)
|
||||||
|
|
||||||
self.updateDetailController(with: vehicle)
|
self.updateDetailController(with: vehicle)
|
||||||
IHProgressHUD.dismiss()
|
IHProgressHUD.dismiss()
|
||||||
@ -158,6 +176,25 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UITextFieldDelegate
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, didFillMandatoryCharacters complete: Bool, didExtractValue value: String) {
|
||||||
|
self.check.isEnabled = complete
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
|
if self.check.isEnabled {
|
||||||
|
self.checkTapped(self.check)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
|
LocationManager.requestCurrentLocation().subscribe().disposed(by: self.bag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
guard let vehicle: Vehicle = try? self.history.rx.model(at: indexPath) else { return nil }
|
guard let vehicle: Vehicle = try? self.history.rx.model(at: indexPath) else { return nil }
|
||||||
|
|
||||||
@ -165,7 +202,9 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
IHProgressHUD.show()
|
IHProgressHUD.show()
|
||||||
Api.checkVehicle(by: vehicle.number, force: true)
|
Api.checkVehicle(by: vehicle.number, force: true)
|
||||||
.observeOn(MainScheduler.instance)
|
.observeOn(MainScheduler.instance)
|
||||||
.subscribe(onNext: self.onReceivedVehicle(_:), onError: { err in
|
.subscribe(onSuccess: { vehicle in
|
||||||
|
self.onReceivedVehicle(vehicle, event: nil)
|
||||||
|
}, onError: { err in
|
||||||
IHProgressHUD.showError(withStatus: err.localizedDescription)
|
IHProgressHUD.showError(withStatus: err.localizedDescription)
|
||||||
print(err.localizedDescription)
|
print(err.localizedDescription)
|
||||||
}).disposed(by: self.bag)
|
}).disposed(by: self.bag)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class FiltersController: FormViewController {
|
|||||||
row.value = self.filter.brand ?? "Any"
|
row.value = self.filter.brand ?? "Any"
|
||||||
row.selectorTitle = "Brands"
|
row.selectorTitle = "Brands"
|
||||||
row.optionsProvider = .lazy({ form, completion in
|
row.optionsProvider = .lazy({ form, completion in
|
||||||
Api.getBrands().observeOn(MainScheduler.instance).subscribe(onNext: { brands in
|
Api.getBrands().observeOn(MainScheduler.instance).subscribe(onSuccess: { brands in
|
||||||
completion(["Any"] + brands)
|
completion(["Any"] + brands)
|
||||||
}, onError: { error in
|
}, onError: { error in
|
||||||
print("Get brands error: ", error)
|
print("Get brands error: ", error)
|
||||||
@ -39,7 +39,7 @@ class FiltersController: FormViewController {
|
|||||||
completion(["Any"])
|
completion(["Any"])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Api.getModels(of: brand).observeOn(MainScheduler.instance).subscribe(onNext: { models in
|
Api.getModels(of: brand).observeOn(MainScheduler.instance).subscribe(onSuccess: { models in
|
||||||
completion(["Any"] + models)
|
completion(["Any"] + models)
|
||||||
}, onError: { error in
|
}, onError: { error in
|
||||||
print("Get models error: ", error)
|
print("Get models error: ", error)
|
||||||
@ -53,7 +53,7 @@ class FiltersController: FormViewController {
|
|||||||
row.title = "Color"
|
row.title = "Color"
|
||||||
row.value = self.filter.color ?? "Any"
|
row.value = self.filter.color ?? "Any"
|
||||||
row.optionsProvider = .lazy({ form, completion in
|
row.optionsProvider = .lazy({ form, completion in
|
||||||
Api.getColors().observeOn(MainScheduler.instance).subscribe(onNext: { colors in
|
Api.getColors().observeOn(MainScheduler.instance).subscribe(onSuccess: { colors in
|
||||||
completion(["Any"] + colors)
|
completion(["Any"] + colors)
|
||||||
}, onError: { error in
|
}, onError: { error in
|
||||||
print("Get colors error: ", error)
|
print("Get colors error: ", error)
|
||||||
|
|||||||
@ -123,10 +123,12 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
.observeOn(MainScheduler.instance)
|
.observeOn(MainScheduler.instance)
|
||||||
.flatMap(self.makeStartSoundIfNeeded)
|
.flatMap(self.makeStartSoundIfNeeded)
|
||||||
.flatMap {
|
.flatMap {
|
||||||
alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert)
|
DispatchQueue.main.async {
|
||||||
alert!.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recordDisposable?.dispose() }))
|
alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert)
|
||||||
alert!.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() }))
|
alert!.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recordDisposable?.dispose() }))
|
||||||
self.present(alert!, animated: true)
|
alert!.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() }))
|
||||||
|
self.present(alert!, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
|
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
|
||||||
@ -141,13 +143,20 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
return AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: text), raw: text, duration: duration, event: event)
|
return AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: text), raw: text, duration: duration, event: event)
|
||||||
}
|
}
|
||||||
.subscribe(onSuccess: { record in
|
.subscribe(onSuccess: { record in
|
||||||
|
print(record)
|
||||||
let realm = try? Realm()
|
let realm = try? Realm()
|
||||||
try? realm?.write {
|
try? realm?.write {
|
||||||
realm?.add(record)
|
realm?.add(record)
|
||||||
}
|
}
|
||||||
alert?.dismiss(animated: true)
|
alert?.dismiss(animated: true)
|
||||||
}) { error in
|
}) { error in
|
||||||
IHProgressHUD.showError(withStatus: error.localizedDescription)
|
if let alert = alert {
|
||||||
|
alert.dismiss(animated: true) {
|
||||||
|
IHProgressHUD.show(error: error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IHProgressHUD.show(error: error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +230,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
|
|
||||||
let check = UIContextualAction(style: .normal, title: "Check") { action, view, completion in
|
let check = UIContextualAction(style: .normal, title: "Check") { action, view, completion in
|
||||||
if let number = record.number {
|
if let number = record.number {
|
||||||
self.check(number: number)
|
self.check(number: number, event: record.event)
|
||||||
}
|
}
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
@ -276,9 +285,9 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
self.present(sheet, animated: true, completion: nil)
|
self.present(sheet, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func check(number: String) {
|
func check(number: String, event: VehicleEvent?) {
|
||||||
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
ad.quickAction = .checkNumber(number)
|
ad.quickAction = .checkNumber(number, event)
|
||||||
self.tabBarController?.selectedIndex = 0
|
self.tabBarController?.selectedIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class RegionsController: UIViewController, UISearchResultsUpdating, UITableViewD
|
|||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
Api.getRegions().observeOn(MainScheduler.instance).subscribe(onNext: { regions in
|
Api.getRegions().observeOn(MainScheduler.instance).subscribe(onSuccess: { regions in
|
||||||
self.regions = regions
|
self.regions = regions
|
||||||
self.regionsFiltered = regions
|
self.regionsFiltered = regions
|
||||||
self.updateTableView()
|
self.updateTableView()
|
||||||
|
|||||||
@ -1,22 +1,44 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
extension NSError {
|
||||||
|
var displayMessage: (title: String, body: String) {
|
||||||
|
if let description = self.userInfo[NSLocalizedDescriptionKey] as? String {
|
||||||
|
return (title: "Error", body: description)
|
||||||
|
} else if let failure = self.userInfo[NSLocalizedFailureErrorKey] as? String, let reason = self.localizedFailureReason {
|
||||||
|
if let recovery = self.localizedRecoverySuggestion {
|
||||||
|
return (title: failure, body: reason + "\n" + recovery)
|
||||||
|
} else {
|
||||||
|
return (title: failure, body: reason)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (title: "Error", body: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension CocoaError {
|
extension CocoaError {
|
||||||
static func error(_ description: String) -> NSError {
|
static func error(_ description: String) -> NSError {
|
||||||
return error(Code(rawValue: 0), userInfo: [NSLocalizedDescriptionKey: description], url: nil) as NSError
|
return error(Code(rawValue: 0), userInfo: [NSLocalizedDescriptionKey: description], url: nil) as NSError
|
||||||
}
|
}
|
||||||
|
|
||||||
static func error(_ description: String, suggestion: String) -> NSError {
|
static func error(_ error: String, reason: String, suggestion: String? = nil) -> NSError {
|
||||||
let info = [
|
var info = [
|
||||||
NSLocalizedDescriptionKey: description,
|
NSLocalizedFailureErrorKey: error,
|
||||||
NSLocalizedRecoverySuggestionErrorKey: suggestion
|
NSLocalizedFailureReasonErrorKey: reason
|
||||||
]
|
]
|
||||||
return error(Code(rawValue: 0), userInfo: info, url: nil) as NSError
|
|
||||||
|
if let suggestion = suggestion {
|
||||||
|
info[NSLocalizedRecoverySuggestionErrorKey] = suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.error(Code(rawValue: 0), userInfo: info, url: nil) as NSError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIViewController {
|
extension UIViewController {
|
||||||
func show(error: NSError) {
|
func show(error: Error) {
|
||||||
let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .alert)
|
let msg = (error as NSError).displayMessage
|
||||||
|
let alert = UIAlertController(title: msg.title, message: msg.body, preferredStyle: .alert)
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
|
||||||
self.present(alert, animated: true)
|
self.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
@ -27,3 +49,10 @@ extension UIViewController {
|
|||||||
self.present(alert, animated: true)
|
self.present(alert, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension IHProgressHUD {
|
||||||
|
static func show(error: Error) {
|
||||||
|
let msg = (error as NSError).displayMessage
|
||||||
|
self.showError(withStatus: msg.title + "\n" + msg.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
13
AutoCat/Extensions/UIViewControllerExt.swift
Normal file
13
AutoCat/Extensions/UIViewControllerExt.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import UIKit
|
||||||
|
|
||||||
|
extension UIViewController {
|
||||||
|
func hideKeyboardWhenTappedAround() {
|
||||||
|
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
|
||||||
|
tap.cancelsTouchesInView = false
|
||||||
|
view.addGestureRecognizer(tap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func dismissKeyboard() {
|
||||||
|
view.endEditing(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,6 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
|
||||||
<string>Access is needed for storing locations of vehicles</string>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@ -44,6 +42,8 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
|
<string>Access is needed for storing locations of vehicles</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Access is needed for voice recordings</string>
|
<string>Access is needed for voice recordings</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
|||||||
@ -70,6 +70,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
|||||||
@objc dynamic var addedBy: String = ""
|
@objc dynamic var addedBy: String = ""
|
||||||
let photos = List<VehiclePhoto>()
|
let photos = List<VehiclePhoto>()
|
||||||
let ownershipPeriods = List<VehicleOwnershipPeriod>()
|
let ownershipPeriods = List<VehicleOwnershipPeriod>()
|
||||||
|
let events = List<VehicleEvent>()
|
||||||
|
|
||||||
var identity: String { number }
|
var identity: String { number }
|
||||||
|
|
||||||
@ -91,6 +92,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
|||||||
case addedBy
|
case addedBy
|
||||||
case photos
|
case photos
|
||||||
case ownershipPeriods
|
case ownershipPeriods
|
||||||
|
case events
|
||||||
}
|
}
|
||||||
|
|
||||||
required init(from decoder: Decoder) throws {
|
required init(from decoder: Decoder) throws {
|
||||||
@ -98,7 +100,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
|||||||
self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
|
self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
|
||||||
self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
|
self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
|
||||||
self.color = try container.decodeIfPresent(String.self, forKey: .color)
|
self.color = try container.decodeIfPresent(String.self, forKey: .color)
|
||||||
self.year = try container.decode(Int.self, forKey: .year)
|
self.year = try container.decodeIfPresent(Int.self, forKey: .year) ?? 0
|
||||||
self.category = try container.decodeIfPresent(String.self, forKey: .category)
|
self.category = try container.decodeIfPresent(String.self, forKey: .category)
|
||||||
self.engine = try container.decodeIfPresent(VehicleEngine.self, forKey: .engine)
|
self.engine = try container.decodeIfPresent(VehicleEngine.self, forKey: .engine)
|
||||||
self.number = try container.decode(String.self, forKey: .number)
|
self.number = try container.decode(String.self, forKey: .number)
|
||||||
@ -118,6 +120,10 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
|||||||
if let ownersipsArray = try container.decodeIfPresent([VehicleOwnershipPeriod].self, forKey: .ownershipPeriods) {
|
if let ownersipsArray = try container.decodeIfPresent([VehicleOwnershipPeriod].self, forKey: .ownershipPeriods) {
|
||||||
self.ownershipPeriods.append(objectsIn: ownersipsArray)
|
self.ownershipPeriods.append(objectsIn: ownersipsArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let eventsArray = try container.decodeIfPresent([VehicleEvent].self, forKey: .events) {
|
||||||
|
self.events.append(objectsIn: eventsArray)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import RealmSwift
|
import RealmSwift
|
||||||
|
|
||||||
class VehicleEvent: Object {
|
class VehicleEvent: Object, Codable {
|
||||||
@objc dynamic var date: Date = Date()
|
@objc dynamic var date: TimeInterval = Date().timeIntervalSince1970
|
||||||
@objc dynamic var latitude: Double = 0
|
@objc dynamic var latitude: Double = 0
|
||||||
@objc dynamic var longitude: Double = 0
|
@objc dynamic var longitude: Double = 0
|
||||||
@objc dynamic var speed: Double = 0
|
@objc dynamic var speed: Double = 0
|
||||||
@objc dynamic var direction: Double = 0
|
@objc dynamic var direction: Double = 0
|
||||||
|
@objc dynamic var address: String? = nil
|
||||||
|
|
||||||
init(lat: Double, lon: Double, speed: Double, dir: Double) {
|
init(lat: Double, lon: Double, speed: Double, dir: Double) {
|
||||||
self.latitude = lat
|
self.latitude = lat
|
||||||
@ -18,4 +19,8 @@ class VehicleEvent: Object {
|
|||||||
required init() {
|
required init() {
|
||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override class func ignoredProperties() -> [String] {
|
||||||
|
return ["address"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
273
AutoCat/ThirdParty/AnyEncodable.swift
vendored
Normal file
273
AutoCat/ThirdParty/AnyEncodable.swift
vendored
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
#if canImport(Foundation)
|
||||||
|
import Foundation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type-erased `Encodable` value.
|
||||||
|
|
||||||
|
The `AnyEncodable` type forwards encoding responsibilities
|
||||||
|
to an underlying value, hiding its specific underlying type.
|
||||||
|
|
||||||
|
You can encode mixed-type values in dictionaries
|
||||||
|
and other collections that require `Encodable` conformance
|
||||||
|
by declaring their contained type to be `AnyEncodable`:
|
||||||
|
|
||||||
|
let dictionary: [String: AnyEncodable] = [
|
||||||
|
"boolean": true,
|
||||||
|
"integer": 1,
|
||||||
|
"double": 3.141592653589793,
|
||||||
|
"string": "string",
|
||||||
|
"array": [1, 2, 3],
|
||||||
|
"nested": [
|
||||||
|
"a": "alpha",
|
||||||
|
"b": "bravo",
|
||||||
|
"c": "charlie"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let json = try! encoder.encode(dictionary)
|
||||||
|
*/
|
||||||
|
#if swift(>=5.1)
|
||||||
|
@frozen public struct AnyEncodable: Encodable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
public struct AnyEncodable: Encodable {
|
||||||
|
public let value: Any
|
||||||
|
|
||||||
|
public init<T>(_ value: T?) {
|
||||||
|
self.value = value ?? ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if swift(>=4.2)
|
||||||
|
@usableFromInline
|
||||||
|
protocol _AnyEncodable {
|
||||||
|
var value: Any { get }
|
||||||
|
init<T>(_ value: T?)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
protocol _AnyEncodable {
|
||||||
|
var value: Any { get }
|
||||||
|
init<T>(_ value: T?)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extension AnyEncodable: _AnyEncodable {}
|
||||||
|
|
||||||
|
// MARK: - Encodable
|
||||||
|
|
||||||
|
extension _AnyEncodable {
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||||
|
case let number as NSNumber:
|
||||||
|
try encode(nsnumber: number, into: &container)
|
||||||
|
#endif
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case is NSNull:
|
||||||
|
try container.encodeNil()
|
||||||
|
#endif
|
||||||
|
case is Void:
|
||||||
|
try container.encodeNil()
|
||||||
|
case let bool as Bool:
|
||||||
|
try container.encode(bool)
|
||||||
|
case let int as Int:
|
||||||
|
try container.encode(int)
|
||||||
|
case let int8 as Int8:
|
||||||
|
try container.encode(int8)
|
||||||
|
case let int16 as Int16:
|
||||||
|
try container.encode(int16)
|
||||||
|
case let int32 as Int32:
|
||||||
|
try container.encode(int32)
|
||||||
|
case let int64 as Int64:
|
||||||
|
try container.encode(int64)
|
||||||
|
case let uint as UInt:
|
||||||
|
try container.encode(uint)
|
||||||
|
case let uint8 as UInt8:
|
||||||
|
try container.encode(uint8)
|
||||||
|
case let uint16 as UInt16:
|
||||||
|
try container.encode(uint16)
|
||||||
|
case let uint32 as UInt32:
|
||||||
|
try container.encode(uint32)
|
||||||
|
case let uint64 as UInt64:
|
||||||
|
try container.encode(uint64)
|
||||||
|
case let float as Float:
|
||||||
|
try container.encode(float)
|
||||||
|
case let double as Double:
|
||||||
|
try container.encode(double)
|
||||||
|
case let string as String:
|
||||||
|
try container.encode(string)
|
||||||
|
#if canImport(Foundation)
|
||||||
|
case let date as Date:
|
||||||
|
try container.encode(date)
|
||||||
|
case let url as URL:
|
||||||
|
try container.encode(url)
|
||||||
|
#endif
|
||||||
|
case let array as [Any?]:
|
||||||
|
try container.encode(array.map { AnyEncodable($0) })
|
||||||
|
case let dictionary as [String: Any?]:
|
||||||
|
try container.encode(dictionary.mapValues { AnyEncodable($0) })
|
||||||
|
case let enc as Encodable:
|
||||||
|
//try container.encode(enc)
|
||||||
|
try enc.encode(to: encoder)
|
||||||
|
default:
|
||||||
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyEncodable value cannot be encoded")
|
||||||
|
throw EncodingError.invalidValue(value, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
|
||||||
|
private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws {
|
||||||
|
switch CFNumberGetType(nsnumber) {
|
||||||
|
case .charType:
|
||||||
|
try container.encode(nsnumber.boolValue)
|
||||||
|
case .sInt8Type:
|
||||||
|
try container.encode(nsnumber.int8Value)
|
||||||
|
case .sInt16Type:
|
||||||
|
try container.encode(nsnumber.int16Value)
|
||||||
|
case .sInt32Type:
|
||||||
|
try container.encode(nsnumber.int32Value)
|
||||||
|
case .sInt64Type:
|
||||||
|
try container.encode(nsnumber.int64Value)
|
||||||
|
case .shortType:
|
||||||
|
try container.encode(nsnumber.uint16Value)
|
||||||
|
case .longType:
|
||||||
|
try container.encode(nsnumber.uint32Value)
|
||||||
|
case .longLongType:
|
||||||
|
try container.encode(nsnumber.uint64Value)
|
||||||
|
case .intType, .nsIntegerType, .cfIndexType:
|
||||||
|
try container.encode(nsnumber.intValue)
|
||||||
|
case .floatType, .float32Type:
|
||||||
|
try container.encode(nsnumber.floatValue)
|
||||||
|
case .doubleType, .float64Type, .cgFloatType:
|
||||||
|
try container.encode(nsnumber.doubleValue)
|
||||||
|
#if swift(>=5.0)
|
||||||
|
@unknown default:
|
||||||
|
let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "NSNumber cannot be encoded because its type is not handled")
|
||||||
|
throw EncodingError.invalidValue(nsnumber, context)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: Equatable {
|
||||||
|
public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool {
|
||||||
|
switch (lhs.value, rhs.value) {
|
||||||
|
case is (Void, Void):
|
||||||
|
return true
|
||||||
|
case let (lhs as Bool, rhs as Bool):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int, rhs as Int):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int8, rhs as Int8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int16, rhs as Int16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int32, rhs as Int32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Int64, rhs as Int64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt, rhs as UInt):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt8, rhs as UInt8):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt16, rhs as UInt16):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt32, rhs as UInt32):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as UInt64, rhs as UInt64):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Float, rhs as Float):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as Double, rhs as Double):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as String, rhs as String):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [String: AnyEncodable], rhs as [String: AnyEncodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
case let (lhs as [AnyEncodable], rhs as [AnyEncodable]):
|
||||||
|
return lhs == rhs
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch value {
|
||||||
|
case is Void:
|
||||||
|
return String(describing: nil as Any?)
|
||||||
|
case let value as CustomStringConvertible:
|
||||||
|
return value.description
|
||||||
|
default:
|
||||||
|
return String(describing: value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: CustomDebugStringConvertible {
|
||||||
|
public var debugDescription: String {
|
||||||
|
switch value {
|
||||||
|
case let value as CustomDebugStringConvertible:
|
||||||
|
return "AnyEncodable(\(value.debugDescription))"
|
||||||
|
default:
|
||||||
|
return "AnyEncodable(\(description))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AnyEncodable: ExpressibleByNilLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByBooleanLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByIntegerLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByFloatLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByStringLiteral {}
|
||||||
|
#if swift(>=5.0)
|
||||||
|
extension AnyEncodable: ExpressibleByStringInterpolation {}
|
||||||
|
#endif
|
||||||
|
extension AnyEncodable: ExpressibleByArrayLiteral {}
|
||||||
|
extension AnyEncodable: ExpressibleByDictionaryLiteral {}
|
||||||
|
|
||||||
|
extension _AnyEncodable {
|
||||||
|
public init(nilLiteral _: ()) {
|
||||||
|
self.init(nil as Any?)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(booleanLiteral value: Bool) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(integerLiteral value: Int) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(floatLiteral value: Double) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(extendedGraphemeClusterLiteral value: String) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(stringLiteral value: String) {
|
||||||
|
self.init(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(arrayLiteral elements: Any...) {
|
||||||
|
self.init(elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(dictionaryLiteral elements: (AnyHashable, Any)...) {
|
||||||
|
self.init([AnyHashable: Any](elements, uniquingKeysWith: { first, _ in first }))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,11 +6,11 @@ class Api {
|
|||||||
return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion])
|
return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion])
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func createRequest(api: String, method: String, body: [String: String]? = nil) -> URLRequest? {
|
private static func createRequest<B,P>(api: String, method: String, body: B? = nil, params: [String:P]? = nil) -> URLRequest? where B: Encodable, P: LosslessStringConvertible {
|
||||||
guard var urlComponents = URLComponents(string: Constants.baseUrl + api) else { return nil }
|
guard var urlComponents = URLComponents(string: Constants.baseUrl + api) else { return nil }
|
||||||
|
|
||||||
if let body = body, method.uppercased() == "GET" {
|
if let params = params, method.uppercased() == "GET" {
|
||||||
urlComponents.queryItems = body.map { URLQueryItem(name: $0, value: String($1)) }
|
urlComponents.queryItems = params.map { URLQueryItem(name: $0, value: String($1)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = URLRequest(url: urlComponents.url!)
|
var request = URLRequest(url: urlComponents.url!)
|
||||||
@ -20,21 +20,29 @@ class Api {
|
|||||||
request.addValue("Bearer " + Settings.shared.user.token, forHTTPHeaderField: "Authorization")
|
request.addValue("Bearer " + Settings.shared.user.token, forHTTPHeaderField: "Authorization")
|
||||||
|
|
||||||
if let body = body, method.uppercased() != "GET" {
|
if let body = body, method.uppercased() != "GET" {
|
||||||
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = .prettyPrinted
|
||||||
|
if let data = try? encoder.encode(body) {
|
||||||
|
request.httpBody = data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func makeRequest<T>(api: String, method: String = "GET", body: [String: String]? = nil) -> Observable<T> where T: Decodable {
|
private static func makeRequest<T,B,P>(api: String, method: String = "GET", body: B?, params: [String:P]? = nil) -> Single<T> where T: Decodable, B: Encodable, P: LosslessStringConvertible {
|
||||||
guard let request = self.createRequest(api: api, method: method, body: body) else {
|
guard let request = self.createRequest(api: api, method: method, body: body, params: params) else {
|
||||||
return Observable.error(self.genError("Error creating request", suggestion: ""))
|
return Single.error(self.genError("Error creating request", suggestion: ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
return URLSession.shared.rx.data(request: request).map { data in
|
return URLSession.shared.rx.data(request: request).asSingle().map { data in
|
||||||
// let str = String(data: data, encoding: .utf8)
|
// let str = String(data: data, encoding: .utf8)
|
||||||
// print("================================")
|
// print("================================")
|
||||||
// print(str?.replacingOccurrences(of: "\\\"", with: "\""))
|
// if let string = str?.replacingOccurrences(of: "\\\"", with: "\"")
|
||||||
|
// .replacingOccurrences(of: "\\'", with: "'")
|
||||||
|
// .replacingOccurrences(of: "\\n", with: "") {
|
||||||
|
// print(string)
|
||||||
|
// }
|
||||||
// print("================================")
|
// print("================================")
|
||||||
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
|
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
|
||||||
if resp.success {
|
if resp.success {
|
||||||
@ -45,7 +53,27 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func refreshFbToken() -> Observable<Void> {
|
private static func makeGetRequest<T,P>(api: String, params:[String: P]? = nil) -> Single<T> where T: Decodable, P: LosslessStringConvertible {
|
||||||
|
// Kind of hack to satisfy compiler
|
||||||
|
return self.makeRequest(api: api, method: "GET", body: nil as Int?, params: params)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func makeEmptyGetRequest<T>(api: String) -> Single<T> where T: Decodable {
|
||||||
|
// Same hack as before
|
||||||
|
return self.makeRequest(api: api, method: "GET", body: nil as Int?, params: nil as [String:Int]?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func makeEmptyBodyRequest<T>(api: String, method: String = "POST") -> Single<T> where T: Decodable {
|
||||||
|
// Same hack as before
|
||||||
|
return self.makeRequest(api: api, method: method, body: nil as Int?, params: nil as [String:Int]?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func makeBodyRequest<T,B>(api: String, body: B?, method: String = "POST") -> Single<T> where T: Decodable, B: Encodable {
|
||||||
|
// Same hack as before
|
||||||
|
return self.makeRequest(api: api, method: method, body: body, params: nil as [String:Int]?)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func refreshFbToken() -> Single<Void> {
|
||||||
guard let token = Settings.shared.user.googleIdToken, let refreshToken = Settings.shared.user.googleRefreshToken, let jwt = JWT(string: token), jwt.expired else {
|
guard let token = Settings.shared.user.googleIdToken, let refreshToken = Settings.shared.user.googleRefreshToken, let jwt = JWT(string: token), jwt.expired else {
|
||||||
return .just(())
|
return .just(())
|
||||||
}
|
}
|
||||||
@ -65,7 +93,7 @@ class Api {
|
|||||||
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
||||||
request.addValue(Constants.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
request.addValue(Constants.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
||||||
request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
|
request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
|
||||||
return URLSession.shared.rx.json(request: request).map { resp in
|
return URLSession.shared.rx.json(request: request).asSingle().map { resp in
|
||||||
guard let json = resp as? [String: Any] else { return }
|
guard let json = resp as? [String: Any] else { return }
|
||||||
if let newToken = json["id_token"] as? String {
|
if let newToken = json["id_token"] as? String {
|
||||||
Settings.shared.user.googleIdToken = newToken
|
Settings.shared.user.googleIdToken = newToken
|
||||||
@ -83,28 +111,28 @@ class Api {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func login(username: String, password: String) -> Observable<User> {
|
public static func login(username: String, password: String) -> Single<User> {
|
||||||
let body = [
|
let body = [
|
||||||
"login": username,
|
"login": username,
|
||||||
"password": password
|
"password": password
|
||||||
]
|
]
|
||||||
return self.makeRequest(api: "user/login", method: "POST", body: body)
|
return self.makeBodyRequest(api: "user/login", body: body)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func signup(username: String, password: String) -> Observable<User> {
|
public static func signup(username: String, password: String) -> Single<User> {
|
||||||
let body = [
|
let body = [
|
||||||
"login": username,
|
"login": username,
|
||||||
"password": password
|
"password": password
|
||||||
]
|
]
|
||||||
return self.makeRequest(api: "user/signup", method: "POST", body: body)
|
return self.makeBodyRequest(api: "user/signup", body: body)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getVehicles(with filter: Filter) -> Observable<[Vehicle]> {
|
public static func getVehicles(with filter: Filter) -> Single<[Vehicle]> {
|
||||||
return self.makeRequest(api: "vehicles", method: "GET", body: filter.queryDictionary())
|
return self.makeGetRequest(api: "vehicles", params: filter.queryDictionary())
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func checkVehicle(by number: String, force: Bool = false) -> Observable<Vehicle> {
|
public static func checkVehicle(by number: String, force: Bool = false) -> Single<Vehicle> {
|
||||||
return self.refreshFbToken().flatMap { () -> Observable<Vehicle> in
|
return self.refreshFbToken().flatMap { () -> Single<Vehicle> in
|
||||||
var body = [
|
var body = [
|
||||||
"number": number,
|
"number": number,
|
||||||
"forceUpdate": String(force)
|
"forceUpdate": String(force)
|
||||||
@ -112,26 +140,34 @@ class Api {
|
|||||||
if let token = Settings.shared.user.googleIdToken {
|
if let token = Settings.shared.user.googleIdToken {
|
||||||
body["googleIdToken"] = token
|
body["googleIdToken"] = token
|
||||||
}
|
}
|
||||||
return self.makeRequest(api: "vehicles/check", method: "POST", body: body).map { (vehicle: Vehicle) -> Vehicle in
|
return self.makeBodyRequest(api: "vehicles/check", body: body).map { (vehicle: Vehicle) -> Vehicle in
|
||||||
vehicle.addedDate = Date().timeIntervalSince1970*1000
|
vehicle.addedDate = Date().timeIntervalSince1970*1000
|
||||||
return vehicle
|
return vehicle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getBrands() -> Observable<[String]> {
|
public static func getBrands() -> Single<[String]> {
|
||||||
return self.makeRequest(api: "vehicles/brands")
|
return self.makeEmptyGetRequest(api: "vehicles/brands")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getModels(of brand: String) -> Observable<[String]> {
|
public static func getModels(of brand: String) -> Single<[String]> {
|
||||||
return self.makeRequest(api: "vehicles/models", body: ["brand": brand])
|
return self.makeGetRequest(api: "vehicles/models", params: ["brand": brand])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getColors() -> Observable<[String]> {
|
public static func getColors() -> Single<[String]> {
|
||||||
return self.makeRequest(api: "vehicles/colors")
|
return self.makeEmptyGetRequest(api: "vehicles/colors")
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getRegions() -> Observable<[Region]> {
|
public static func getRegions() -> Single<[Region]> {
|
||||||
return self.makeRequest(api: "vehicles/regions")
|
return self.makeEmptyGetRequest(api: "vehicles/regions")
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func add(event: VehicleEvent, to number: String) -> Single<Vehicle> {
|
||||||
|
let body = ["number": AnyEncodable(number), "event": AnyEncodable(event)]
|
||||||
|
return self.makeBodyRequest(api: "events", body: body).map { (vehicle: Vehicle) -> Vehicle in
|
||||||
|
vehicle.addedDate = Date().timeIntervalSince1970*1000
|
||||||
|
return vehicle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import Foundation
|
|||||||
enum Constants {
|
enum Constants {
|
||||||
static var baseUrl: String {
|
static var baseUrl: String {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
//return "http://127.0.0.1:3000/"
|
//return "http://192.168.1.67:3000/"
|
||||||
return "https://vps.aliencat.pro:8443/"
|
return "https://vps.aliencat.pro:8443/"
|
||||||
#else
|
#else
|
||||||
return "https://vps.aliencat.pro:8443/"
|
return "https://vps.aliencat.pro:8443/"
|
||||||
|
|||||||
@ -107,7 +107,7 @@ class JWT {
|
|||||||
let bodyData = try JSONSerialization.data(withJSONObject: bodyDict, options: [])
|
let bodyData = try JSONSerialization.data(withJSONObject: bodyDict, options: [])
|
||||||
|
|
||||||
guard let body = String(data: bodyData, encoding: .utf8) else {
|
guard let body = String(data: bodyData, encoding: .utf8) else {
|
||||||
throw CocoaError.error("Error", suggestion: "Error generating JWT for sharing report via link")
|
throw CocoaError.error("Error", reason: "Failed to generate JWT for sharing report via link")
|
||||||
}
|
}
|
||||||
let twoParts = Base64FS.encodeString(str: header).trimmingCharacters(in: CharacterSet(charactersIn: "=")) + "." + Base64FS.encodeString(str: body).trimmingCharacters(in: CharacterSet(charactersIn: "="))
|
let twoParts = Base64FS.encodeString(str: header).trimmingCharacters(in: CharacterSet(charactersIn: "=")) + "." + Base64FS.encodeString(str: body).trimmingCharacters(in: CharacterSet(charactersIn: "="))
|
||||||
let signature = twoParts.hmac(algorithm: .SHA256, key: Constants.reportLinkTokenSecret)
|
let signature = twoParts.hmac(algorithm: .SHA256, key: Constants.reportLinkTokenSecret)
|
||||||
|
|||||||
@ -5,8 +5,11 @@ import CoreLocation
|
|||||||
|
|
||||||
class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {
|
class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {
|
||||||
|
|
||||||
let authSubject = PublishSubject<CLAuthorizationStatus>()
|
private let generalErrors: [CLError.Code] = [.locationUnknown, .denied, .network, .headingFailure, .rangingUnavailable, .rangingFailure]
|
||||||
let locationSubject = PublishSubject<CLLocation>()
|
private let geocodingErrors: [CLError.Code] = [.geocodeCanceled, .geocodeFoundNoResult, .geocodeFoundPartialResult]
|
||||||
|
|
||||||
|
private(set) var authSubject = PublishSubject<CLAuthorizationStatus>()
|
||||||
|
private(set) var locationSubject = PublishSubject<CLLocation>()
|
||||||
|
|
||||||
init(locationManager: ParentObject) {
|
init(locationManager: ParentObject) {
|
||||||
super.init(parentObject: locationManager, delegateProxy: RxLocationManagerDelegateProxy.self)
|
super.init(parentObject: locationManager, delegateProxy: RxLocationManagerDelegateProxy.self)
|
||||||
@ -16,6 +19,17 @@ class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocatio
|
|||||||
print("deinit")
|
print("deinit")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func generateAuthObservable() -> Observable<CLAuthorizationStatus> {
|
||||||
|
// self.authSubject = PublishSubject<CLAuthorizationStatus>()
|
||||||
|
// return self.authSubject
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func generateLocationObservable() -> Observable<CLLocation> {
|
||||||
|
// print("generateLocationObservable")
|
||||||
|
// self.locationSubject = PublishSubject<CLLocation>()
|
||||||
|
// return self.locationSubject
|
||||||
|
// }
|
||||||
|
|
||||||
// MARK: - DelegateProxyType
|
// MARK: - DelegateProxyType
|
||||||
|
|
||||||
static func registerKnownImplementations() {
|
static func registerKnownImplementations() {
|
||||||
@ -43,39 +57,19 @@ class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||||
print(error)
|
guard let err = error as? CLError else { return }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
if self.generalErrors.contains(err.code) {
|
||||||
extension Reactive where Base: CLLocationManager {
|
// Pass general errors to all existing subjects
|
||||||
var delegate: DelegateProxy<CLLocationManager, CLLocationManagerDelegate> {
|
self.authSubject.onError(error)
|
||||||
return RxLocationManagerDelegateProxy.proxy(for: base)
|
self.locationSubject.onError(error)
|
||||||
}
|
} else if self.geocodingErrors.contains(err.code) {
|
||||||
|
// TODO: pass error to geocoding subject
|
||||||
var didChangeAuthorization: ControlEvent<CLAuthorizationStatus> {
|
} else {
|
||||||
let sel = #selector((CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)! as (CLLocationManagerDelegate) -> (CLLocationManager, CLAuthorizationStatus) -> Void))
|
print("Unexpected CoreLocation error: \(error)")
|
||||||
let source: Observable<CLAuthorizationStatus> = delegate.methodInvoked(sel)
|
|
||||||
.map { arg in
|
|
||||||
let status = CLAuthorizationStatus(rawValue: arg[1] as! Int32)
|
|
||||||
return status!
|
|
||||||
}
|
|
||||||
return ControlEvent(events: source)
|
|
||||||
}
|
|
||||||
|
|
||||||
var didUpdateLocations: Observable<VehicleEvent> {
|
|
||||||
let sel = #selector((CLLocationManagerDelegate.locationManager(_:didUpdateLocations:)! as (CLLocationManagerDelegate) -> (CLLocationManager, [CLLocation]) -> Void))
|
|
||||||
return delegate.methodInvoked(sel)
|
|
||||||
.map { args in
|
|
||||||
if let locations = args[1] as? [CLLocation], let location = locations.first {
|
|
||||||
return VehicleEvent(lat: location.coordinate.latitude, lon: location.coordinate.longitude, speed: location.speed, dir: location.course)
|
|
||||||
} else {
|
|
||||||
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Update location error"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
class LocationManager {
|
class LocationManager {
|
||||||
private static let manager: CLLocationManager = {
|
private static let manager: CLLocationManager = {
|
||||||
@ -84,6 +78,7 @@ class LocationManager {
|
|||||||
return mgr
|
return mgr
|
||||||
}()
|
}()
|
||||||
private static let bag = DisposeBag()
|
private static let bag = DisposeBag()
|
||||||
|
private(set) static var lastEvent: VehicleEvent?
|
||||||
|
|
||||||
private static func checkPermissions() -> Single<Void> {
|
private static func checkPermissions() -> Single<Void> {
|
||||||
return Single<Void>.create { observer in
|
return Single<Void>.create { observer in
|
||||||
@ -93,7 +88,8 @@ class LocationManager {
|
|||||||
break
|
break
|
||||||
case .notDetermined:
|
case .notDetermined:
|
||||||
self.manager.requestWhenInUseAuthorization()
|
self.manager.requestWhenInUseAuthorization()
|
||||||
_ = RxLocationManagerDelegateProxy.proxy(for: self.manager).authSubject.skip(1).first().subscribe(onSuccess: { result in
|
let proxy = RxLocationManagerDelegateProxy.proxy(for: self.manager)
|
||||||
|
_ = proxy.authSubject.skip(1).first().subscribe(onSuccess: { result in
|
||||||
if let status = result, status == .authorizedWhenInUse {
|
if let status = result, status == .authorizedWhenInUse {
|
||||||
observer(.success(()))
|
observer(.success(()))
|
||||||
} else {
|
} else {
|
||||||
@ -110,14 +106,24 @@ class LocationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static func requestLocation() -> Single<VehicleEvent> {
|
private static func requestLocation() -> Single<VehicleEvent> {
|
||||||
let single = RxLocationManagerDelegateProxy.proxy(for: self.manager).locationSubject.take(1).asSingle().map { location in
|
let proxy = RxLocationManagerDelegateProxy.proxy(for: self.manager)
|
||||||
return VehicleEvent(lat: location.coordinate.latitude, lon: location.coordinate.longitude, speed: location.speed, dir: location.course)
|
let single = proxy.locationSubject.take(1).asSingle().map { location -> VehicleEvent in
|
||||||
|
let event = VehicleEvent(lat: location.coordinate.latitude, lon: location.coordinate.longitude, speed: location.speed, dir: location.course)
|
||||||
|
self.lastEvent = event
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
self.manager.requestLocation()
|
self.manager.requestLocation()
|
||||||
return single
|
return single
|
||||||
}
|
}
|
||||||
|
|
||||||
static func requestCurrentLocation() -> Single<VehicleEvent> {
|
static func requestCurrentLocation() -> Single<VehicleEvent> {
|
||||||
return self.checkPermissions().flatMap(self.requestLocation)
|
return self.checkPermissions().flatMap(self.requestLocation).do(onSuccess: { event in
|
||||||
|
print("Get location success")
|
||||||
|
}, onSubscribed: {
|
||||||
|
print("Get location subscribed")
|
||||||
|
}, onDispose: {
|
||||||
|
print("Get location dispose")
|
||||||
|
self.manager.stopUpdatingLocation()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,19 @@ class Recorder {
|
|||||||
init() {
|
init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func microphoneAvailable() -> Bool {
|
||||||
|
// FIXME:
|
||||||
|
// This is primarily for mac catalyst app.
|
||||||
|
// On iOS this will always return true (as there is always at least one microphone on any iOS device)
|
||||||
|
let session = AVAudioSession.sharedInstance()
|
||||||
|
// #if targetEnvironment(macCatalyst)
|
||||||
|
// for input in session.availableInputs! {
|
||||||
|
// print(input.portType == .headsetMic)
|
||||||
|
// }
|
||||||
|
// #endif
|
||||||
|
return session.availableInputs?.contains(where: { $0.portType == .builtInMic }) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
func requestPermissions() -> Single<Void> {
|
func requestPermissions() -> Single<Void> {
|
||||||
return Single<Void>.create { observer in
|
return Single<Void>.create { observer in
|
||||||
AVAudioSession.sharedInstance().requestRecordPermission { allowed in
|
AVAudioSession.sharedInstance().requestRecordPermission { allowed in
|
||||||
@ -36,25 +49,25 @@ class Recorder {
|
|||||||
observer(.success(()))
|
observer(.success(()))
|
||||||
break
|
break
|
||||||
case .denied:
|
case .denied:
|
||||||
let error = CocoaError.error("Access denied", suggestion: "Please give permission to use speech recognition in system settings")
|
let error = CocoaError.error("Access error", reason: "Access to speech recognition is denied", suggestion: "Please give permission to use speech recognition in system settings")
|
||||||
observer(.error(error))
|
observer(.error(error))
|
||||||
break
|
break
|
||||||
case .restricted:
|
case .restricted:
|
||||||
let error = CocoaError.error("Access restricted", suggestion: "Speech recognition is restricted on this device")
|
let error = CocoaError.error("Access error", reason: "Speech recognition is restricted on this device")
|
||||||
observer(.error(error))
|
observer(.error(error))
|
||||||
break
|
break
|
||||||
case .notDetermined:
|
case .notDetermined:
|
||||||
let error = CocoaError.error("Access error", suggestion: "Speech recognition status is not yet determined")
|
let error = CocoaError.error("Access error", reason: "Speech recognition status is not yet determined")
|
||||||
observer(.error(error))
|
observer(.error(error))
|
||||||
break
|
break
|
||||||
@unknown default:
|
@unknown default:
|
||||||
let error = CocoaError.error("Access error", suggestion: "Unknown error accessing speech recognizer")
|
let error = CocoaError.error("Access error", reason: "Unknown error accessing speech recognizer")
|
||||||
observer(.error(error))
|
observer(.error(error))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let error = CocoaError.error("Access denied", suggestion: "Please give permission to use microphone in system settings")
|
let error = CocoaError.error("Access error", reason: "Access to microphone is denied", suggestion: "Please give permission to use microphone in system settings")
|
||||||
observer(.error(error))
|
observer(.error(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,9 +77,13 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func startRecording(to file: URL) -> Single<String> {
|
func startRecording(to file: URL) -> Single<String> {
|
||||||
|
guard self.microphoneAvailable() else {
|
||||||
|
return Single.error(CocoaError.error("Recording error", reason: "Microphone not found"))
|
||||||
|
}
|
||||||
|
|
||||||
return Single<String>.create { observer in
|
return Single<String>.create { observer in
|
||||||
guard let aac = AVAudioFormat(settings: self.recordingSettings) else {
|
guard let aac = AVAudioFormat(settings: self.recordingSettings) else {
|
||||||
observer(.error(CocoaError.error("Recording error", suggestion: "Format not supported")))
|
observer(.error(CocoaError.error("Recording error", reason: "Format not supported")))
|
||||||
return Disposables.create()
|
return Disposables.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,32 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class CustomTextField: UITextField {
|
class CustomTextField: UITextField, UITextFieldDelegate {
|
||||||
|
|
||||||
|
@IBInspectable var editable: Bool = true
|
||||||
|
|
||||||
|
@IBInspectable var borderWidth: CGFloat = 1 {
|
||||||
|
didSet {
|
||||||
|
self.layer.borderWidth = self.borderWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBInspectable var cornerRadius: CGFloat = 6 {
|
||||||
|
didSet {
|
||||||
|
self.layer.cornerRadius = self.cornerRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
super.init(coder: coder)
|
super.init(coder: coder)
|
||||||
|
self.delegate = self
|
||||||
//self.borderStyle = .none
|
|
||||||
self.layer.borderWidth = 1
|
|
||||||
self.layer.cornerRadius = 6
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
self.layer.borderColor = UIColor.secondaryLabel.cgColor
|
self.layer.borderColor = UIColor.secondaryLabel.cgColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||||
|
return self.editable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user