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 */; };
|
||||
7A11474923FF2B2D00B424AF /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.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 */; };
|
||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF2249F8B650035F39E /* RecordsController.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 */; };
|
||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.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 */; };
|
||||
7AF58D2F24029C5200CE01A0 /* MagazineLayout in Frameworks */ = {isa = PBXBuildFile; productRef = 7AF58D2E24029C5200CE01A0 /* MagazineLayout */; };
|
||||
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>"; };
|
||||
7A11474A23FF368B00B424AF /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
7A11474D23FFEE8800B424AF /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = "<group>"; };
|
||||
7A15051124DB3E3000F39631 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -258,6 +262,7 @@
|
||||
7A64AE6E2469DFB600ABE48E /* ATGMediaBrowser */,
|
||||
7A6DD90724329144009DE740 /* CenterTextLayer.swift */,
|
||||
7A96AE32246C095700297C33 /* Base64FS.swift */,
|
||||
7A15051124DB3E3000F39631 /* AnyEncodable.swift */,
|
||||
);
|
||||
path = ThirdParty;
|
||||
sourceTree = "<group>";
|
||||
@ -311,6 +316,7 @@
|
||||
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */,
|
||||
7A27ADF824A09CAD0035F39E /* CocoaError.swift */,
|
||||
7A659B5A24A3768A0043A0F2 /* Substrings.swift */,
|
||||
7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -500,6 +506,7 @@
|
||||
7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */,
|
||||
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
|
||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */,
|
||||
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */,
|
||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
|
||||
7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */,
|
||||
7A6DD90E24337930009DE740 /* PlateNumber.swift in Sources */,
|
||||
@ -522,6 +529,7 @@
|
||||
7A7547DD2403180A004E8406 /* SectionHeader.swift in Sources */,
|
||||
7AF58D58240309CA00CE01A0 /* VehicleTextParamCell.swift in Sources */,
|
||||
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */,
|
||||
7A15051224DB3E3000F39631 /* AnyEncodable.swift in Sources */,
|
||||
7A1090EA24A3A26300B4F0B2 /* AudioPlayer.swift in Sources */,
|
||||
7A11474723FF2AA500B424AF /* User.swift in Sources */,
|
||||
7A11471623FDEB2A00B424AF /* MainSplitController.swift in Sources */,
|
||||
@ -691,7 +699,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
CURRENT_PROJECT_VERSION = 22;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
@ -713,7 +721,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 21;
|
||||
CURRENT_PROJECT_VERSION = 22;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
|
||||
@ -24,8 +24,8 @@
|
||||
"repositoryURL": "https://github.com/onevcat/Kingfisher",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "46bf251fee8ed426921c647790f08ca8ad0105a9",
|
||||
"version": "5.13.1"
|
||||
"revision": "1339ebea9498ef6c3fc75cc195d7163d7c7167f9",
|
||||
"version": "5.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -33,8 +33,8 @@
|
||||
"repositoryURL": "https://github.com/airbnb/MagazineLayout",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "bbbe1456c34c1abb527d05ff9da3ff2a54584d78",
|
||||
"version": "1.5.5"
|
||||
"revision": "12dd2cc84b7f17c4f46c7d95cde64d521c588ee8",
|
||||
"version": "1.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -78,8 +78,8 @@
|
||||
"repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b3e888b4972d9bc76495dd74d30a8c7fad4b9395",
|
||||
"version": "5.0.1"
|
||||
"revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa",
|
||||
"version": "5.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -50,64 +50,16 @@
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
uuid = "0F1972A7-94E8-40E5-834C-B873D2578DC7"
|
||||
shouldBeEnabled = "Yes"
|
||||
uuid = "339C709C-134A-4B97-9F9C-949886D4AD65"
|
||||
shouldBeEnabled = "No"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "AutoCat/Controllers/RecordsController.swift"
|
||||
filePath = "AutoCat/Controllers/SearchController.swift"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "150"
|
||||
endingLineNumber = "150"
|
||||
landmarkName = "onAddVoiceRecord(_:)"
|
||||
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:)"
|
||||
startingLineNumber = "94"
|
||||
endingLineNumber = "94"
|
||||
landmarkName = "onFilter(_:)"
|
||||
landmarkType = "7">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
|
||||
@ -11,7 +11,7 @@ extension OSLog {
|
||||
enum QuickAction {
|
||||
case none
|
||||
case check
|
||||
case checkNumber(String)
|
||||
case checkNumber(String, VehicleEvent?)
|
||||
case addVoiceRecord
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
|
||||
let config = Realm.Configuration(
|
||||
schemaVersion: 10,
|
||||
schemaVersion: 13,
|
||||
migrationBlock: { migration, oldSchemaVersion in
|
||||
if oldSchemaVersion <= 3 {
|
||||
var numbers: [String] = []
|
||||
|
||||
@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.location</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?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"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
|
||||
@ -40,9 +40,9 @@
|
||||
<constraint firstAttribute="height" constant="48" id="Tk0-8G-9hP"/>
|
||||
</constraints>
|
||||
</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">
|
||||
<rect key="frame" x="80" y="26" width="215" height="33.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
|
||||
<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="29.5" width="215" height="26.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
@ -64,10 +64,10 @@
|
||||
</connections>
|
||||
</collectionViewCell>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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">
|
||||
@ -76,14 +76,23 @@
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</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">
|
||||
<rect key="frame" x="76.5" y="8" width="52.5" height="26.5"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<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="53" height="27"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle2"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="1" id="Mpt-xA-af2"/>
|
||||
@ -91,14 +100,14 @@
|
||||
</view>
|
||||
</subviews>
|
||||
<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 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="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 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 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"/>
|
||||
</constraints>
|
||||
</collectionViewCellContentView>
|
||||
@ -106,11 +115,11 @@
|
||||
<connections>
|
||||
<outlet property="dividerHeightConstraint" destination="Mpt-xA-af2" id="bYb-0D-Lz0"/>
|
||||
<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>
|
||||
</collectionViewCell>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@ -223,14 +232,14 @@
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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">
|
||||
<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"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@ -242,7 +251,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<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"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
|
||||
<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>
|
||||
<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"/>
|
||||
@ -515,7 +532,7 @@
|
||||
<constraint firstAttribute="width" constant="250" id="lqO-Yy-NyQ"/>
|
||||
</constraints>
|
||||
</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"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<prototypes>
|
||||
@ -677,6 +694,14 @@
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<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 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"/>
|
||||
@ -685,6 +710,14 @@
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="14"/>
|
||||
<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>
|
||||
<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"/>
|
||||
@ -830,11 +863,11 @@
|
||||
<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="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="search" width="128" height="128"/>
|
||||
<image name="search" width="23" height="23"/>
|
||||
<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"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -3,7 +3,7 @@ import MagazineLayout
|
||||
|
||||
class VehicleTextParamCell: MagazineLayoutCollectionViewCell {
|
||||
@IBOutlet weak var paramName: UILabel!
|
||||
@IBOutlet weak var paramValue: UILabel!
|
||||
@IBOutlet weak var paramValue: UITextField!
|
||||
@IBOutlet weak var dividerHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
override func awakeFromNib() {
|
||||
|
||||
@ -34,7 +34,7 @@ class AuthController: UIViewController {
|
||||
IHProgressHUD.show()
|
||||
Api.login(username: name, password: pass)
|
||||
.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)
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ class AuthController: UIViewController {
|
||||
IHProgressHUD.show()
|
||||
Api.signup(username: name, password: pass)
|
||||
.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)
|
||||
}
|
||||
|
||||
|
||||
@ -15,11 +15,14 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
||||
let bag = DisposeBag()
|
||||
let maskFieldDelegate = MaskedTextFieldDelegate()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
guard let realm = try? Realm() else { return }
|
||||
|
||||
self.hideKeyboardWhenTappedAround()
|
||||
self.maskFieldDelegate.primaryMaskFormat = "[A][000][AA] [009]"
|
||||
self.maskFieldDelegate.listener = self
|
||||
self.number.delegate = self.maskFieldDelegate
|
||||
@ -70,6 +73,8 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
||||
self.handleQuickActions()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func handleQuickActions() {
|
||||
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||
|
||||
@ -78,9 +83,9 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
||||
ad.quickAction = .none
|
||||
self.number.becomeFirstResponder()
|
||||
break
|
||||
case .checkNumber(let number):
|
||||
case .checkNumber(let number, let event):
|
||||
ad.quickAction = .none
|
||||
self.check(number: number)
|
||||
self.check(number: number, event: event)
|
||||
break
|
||||
case .addVoiceRecord:
|
||||
self.tabBarController?.selectedIndex = 1
|
||||
@ -90,21 +95,25 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Checking new number
|
||||
|
||||
@IBAction func checkTapped(_ sender: UIButton) {
|
||||
guard let number = self.number.text else { return }
|
||||
|
||||
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.text = nil
|
||||
self.check.isEnabled = false
|
||||
IHProgressHUD.show()
|
||||
Api.checkVehicle(by: number)
|
||||
.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() {
|
||||
try? realm.write {
|
||||
realm.add(Vehicle(number), update: .all)
|
||||
@ -115,23 +124,32 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
||||
}).disposed(by: self.bag)
|
||||
}
|
||||
|
||||
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 onReceivedVehicle(_ vehicle: Vehicle) {
|
||||
func save(vehicle: Vehicle) {
|
||||
if let realm = try? Realm() {
|
||||
try? realm.write {
|
||||
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)
|
||||
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? {
|
||||
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()
|
||||
Api.checkVehicle(by: vehicle.number, force: true)
|
||||
.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)
|
||||
print(err.localizedDescription)
|
||||
}).disposed(by: self.bag)
|
||||
|
||||
@ -20,7 +20,7 @@ class FiltersController: FormViewController {
|
||||
row.value = self.filter.brand ?? "Any"
|
||||
row.selectorTitle = "Brands"
|
||||
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)
|
||||
}, onError: { error in
|
||||
print("Get brands error: ", error)
|
||||
@ -39,7 +39,7 @@ class FiltersController: FormViewController {
|
||||
completion(["Any"])
|
||||
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)
|
||||
}, onError: { error in
|
||||
print("Get models error: ", error)
|
||||
@ -53,7 +53,7 @@ class FiltersController: FormViewController {
|
||||
row.title = "Color"
|
||||
row.value = self.filter.color ?? "Any"
|
||||
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)
|
||||
}, onError: { error in
|
||||
print("Get colors error: ", error)
|
||||
|
||||
@ -123,10 +123,12 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
||||
.observeOn(MainScheduler.instance)
|
||||
.flatMap(self.makeStartSoundIfNeeded)
|
||||
.flatMap {
|
||||
alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert)
|
||||
alert!.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recordDisposable?.dispose() }))
|
||||
alert!.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() }))
|
||||
self.present(alert!, animated: true)
|
||||
DispatchQueue.main.async {
|
||||
alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert)
|
||||
alert!.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recordDisposable?.dispose() }))
|
||||
alert!.addAction(UIAlertAction(title: "Done", style: .default, handler: { _ in self.recorder?.stopRecording() }))
|
||||
self.present(alert!, animated: true)
|
||||
}
|
||||
|
||||
let date = Date()
|
||||
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)
|
||||
}
|
||||
.subscribe(onSuccess: { record in
|
||||
print(record)
|
||||
let realm = try? Realm()
|
||||
try? realm?.write {
|
||||
realm?.add(record)
|
||||
}
|
||||
alert?.dismiss(animated: true)
|
||||
}) { 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
|
||||
if let number = record.number {
|
||||
self.check(number: number)
|
||||
self.check(number: number, event: record.event)
|
||||
}
|
||||
completion(true)
|
||||
}
|
||||
@ -276,9 +285,9 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
||||
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 }
|
||||
ad.quickAction = .checkNumber(number)
|
||||
ad.quickAction = .checkNumber(number, event)
|
||||
self.tabBarController?.selectedIndex = 0
|
||||
}
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ class RegionsController: UIViewController, UISearchResultsUpdating, UITableViewD
|
||||
return cell
|
||||
}
|
||||
|
||||
Api.getRegions().observeOn(MainScheduler.instance).subscribe(onNext: { regions in
|
||||
Api.getRegions().observeOn(MainScheduler.instance).subscribe(onSuccess: { regions in
|
||||
self.regions = regions
|
||||
self.regionsFiltered = regions
|
||||
self.updateTableView()
|
||||
|
||||
@ -1,22 +1,44 @@
|
||||
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 {
|
||||
static func error(_ description: String) -> NSError {
|
||||
return error(Code(rawValue: 0), userInfo: [NSLocalizedDescriptionKey: description], url: nil) as NSError
|
||||
}
|
||||
|
||||
static func error(_ description: String, suggestion: String) -> NSError {
|
||||
let info = [
|
||||
NSLocalizedDescriptionKey: description,
|
||||
NSLocalizedRecoverySuggestionErrorKey: suggestion
|
||||
static func error(_ error: String, reason: String, suggestion: String? = nil) -> NSError {
|
||||
var info = [
|
||||
NSLocalizedFailureErrorKey: error,
|
||||
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 {
|
||||
func show(error: NSError) {
|
||||
let alert = UIAlertController(title: error.localizedDescription, message: error.localizedRecoverySuggestion, preferredStyle: .alert)
|
||||
func show(error: Error) {
|
||||
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))
|
||||
self.present(alert, animated: true)
|
||||
}
|
||||
@ -27,3 +49,10 @@ extension UIViewController {
|
||||
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">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Access is needed for storing locations of vehicles</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
@ -44,6 +42,8 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Access is needed for storing locations of vehicles</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Access is needed for voice recordings</string>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
|
||||
@ -70,6 +70,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
||||
@objc dynamic var addedBy: String = ""
|
||||
let photos = List<VehiclePhoto>()
|
||||
let ownershipPeriods = List<VehicleOwnershipPeriod>()
|
||||
let events = List<VehicleEvent>()
|
||||
|
||||
var identity: String { number }
|
||||
|
||||
@ -91,6 +92,7 @@ class Vehicle: Object, Decodable, IdentifiableType {
|
||||
case addedBy
|
||||
case photos
|
||||
case ownershipPeriods
|
||||
case events
|
||||
}
|
||||
|
||||
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.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
|
||||
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.engine = try container.decodeIfPresent(VehicleEngine.self, forKey: .engine)
|
||||
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) {
|
||||
self.ownershipPeriods.append(objectsIn: ownersipsArray)
|
||||
}
|
||||
|
||||
if let eventsArray = try container.decodeIfPresent([VehicleEvent].self, forKey: .events) {
|
||||
self.events.append(objectsIn: eventsArray)
|
||||
}
|
||||
}
|
||||
|
||||
required init() {
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import Foundation
|
||||
import RealmSwift
|
||||
|
||||
class VehicleEvent: Object {
|
||||
@objc dynamic var date: Date = Date()
|
||||
class VehicleEvent: Object, Codable {
|
||||
@objc dynamic var date: TimeInterval = Date().timeIntervalSince1970
|
||||
@objc dynamic var latitude: Double = 0
|
||||
@objc dynamic var longitude: Double = 0
|
||||
@objc dynamic var speed: Double = 0
|
||||
@objc dynamic var direction: Double = 0
|
||||
@objc dynamic var address: String? = nil
|
||||
|
||||
init(lat: Double, lon: Double, speed: Double, dir: Double) {
|
||||
self.latitude = lat
|
||||
@ -18,4 +19,8 @@ class VehicleEvent: Object {
|
||||
required 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])
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
if let body = body, method.uppercased() == "GET" {
|
||||
urlComponents.queryItems = body.map { URLQueryItem(name: $0, value: String($1)) }
|
||||
if let params = params, method.uppercased() == "GET" {
|
||||
urlComponents.queryItems = params.map { URLQueryItem(name: $0, value: String($1)) }
|
||||
}
|
||||
|
||||
var request = URLRequest(url: urlComponents.url!)
|
||||
@ -20,21 +20,29 @@ class Api {
|
||||
request.addValue("Bearer " + Settings.shared.user.token, forHTTPHeaderField: "Authorization")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private static func makeRequest<T>(api: String, method: String = "GET", body: [String: String]? = nil) -> Observable<T> where T: Decodable {
|
||||
guard let request = self.createRequest(api: api, method: method, body: body) else {
|
||||
return Observable.error(self.genError("Error creating request", suggestion: ""))
|
||||
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, params: params) else {
|
||||
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)
|
||||
// print("================================")
|
||||
// print(str?.replacingOccurrences(of: "\\\"", with: "\""))
|
||||
// if let string = str?.replacingOccurrences(of: "\\\"", with: "\"")
|
||||
// .replacingOccurrences(of: "\\'", with: "'")
|
||||
// .replacingOccurrences(of: "\\n", with: "") {
|
||||
// print(string)
|
||||
// }
|
||||
// print("================================")
|
||||
let resp = try JSONDecoder().decode(Response<T>.self, from: data)
|
||||
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 {
|
||||
return .just(())
|
||||
}
|
||||
@ -65,7 +93,7 @@ class Api {
|
||||
request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version")
|
||||
request.addValue(Constants.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
|
||||
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 }
|
||||
if let newToken = json["id_token"] as? String {
|
||||
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 = [
|
||||
"login": username,
|
||||
"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 = [
|
||||
"login": username,
|
||||
"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]> {
|
||||
return self.makeRequest(api: "vehicles", method: "GET", body: filter.queryDictionary())
|
||||
public static func getVehicles(with filter: Filter) -> Single<[Vehicle]> {
|
||||
return self.makeGetRequest(api: "vehicles", params: filter.queryDictionary())
|
||||
}
|
||||
|
||||
public static func checkVehicle(by number: String, force: Bool = false) -> Observable<Vehicle> {
|
||||
return self.refreshFbToken().flatMap { () -> Observable<Vehicle> in
|
||||
public static func checkVehicle(by number: String, force: Bool = false) -> Single<Vehicle> {
|
||||
return self.refreshFbToken().flatMap { () -> Single<Vehicle> in
|
||||
var body = [
|
||||
"number": number,
|
||||
"forceUpdate": String(force)
|
||||
@ -112,26 +140,34 @@ class Api {
|
||||
if let token = Settings.shared.user.googleIdToken {
|
||||
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
|
||||
return vehicle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func getBrands() -> Observable<[String]> {
|
||||
return self.makeRequest(api: "vehicles/brands")
|
||||
public static func getBrands() -> Single<[String]> {
|
||||
return self.makeEmptyGetRequest(api: "vehicles/brands")
|
||||
}
|
||||
|
||||
public static func getModels(of brand: String) -> Observable<[String]> {
|
||||
return self.makeRequest(api: "vehicles/models", body: ["brand": brand])
|
||||
public static func getModels(of brand: String) -> Single<[String]> {
|
||||
return self.makeGetRequest(api: "vehicles/models", params: ["brand": brand])
|
||||
}
|
||||
|
||||
public static func getColors() -> Observable<[String]> {
|
||||
return self.makeRequest(api: "vehicles/colors")
|
||||
public static func getColors() -> Single<[String]> {
|
||||
return self.makeEmptyGetRequest(api: "vehicles/colors")
|
||||
}
|
||||
|
||||
public static func getRegions() -> Observable<[Region]> {
|
||||
return self.makeRequest(api: "vehicles/regions")
|
||||
public static func getRegions() -> Single<[Region]> {
|
||||
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 {
|
||||
static var baseUrl: String {
|
||||
#if DEBUG
|
||||
//return "http://127.0.0.1:3000/"
|
||||
//return "http://192.168.1.67:3000/"
|
||||
return "https://vps.aliencat.pro:8443/"
|
||||
#else
|
||||
return "https://vps.aliencat.pro:8443/"
|
||||
|
||||
@ -107,7 +107,7 @@ class JWT {
|
||||
let bodyData = try JSONSerialization.data(withJSONObject: bodyDict, options: [])
|
||||
|
||||
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 signature = twoParts.hmac(algorithm: .SHA256, key: Constants.reportLinkTokenSecret)
|
||||
|
||||
@ -5,8 +5,11 @@ import CoreLocation
|
||||
|
||||
class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate {
|
||||
|
||||
let authSubject = PublishSubject<CLAuthorizationStatus>()
|
||||
let locationSubject = PublishSubject<CLLocation>()
|
||||
private let generalErrors: [CLError.Code] = [.locationUnknown, .denied, .network, .headingFailure, .rangingUnavailable, .rangingFailure]
|
||||
private let geocodingErrors: [CLError.Code] = [.geocodeCanceled, .geocodeFoundNoResult, .geocodeFoundPartialResult]
|
||||
|
||||
private(set) var authSubject = PublishSubject<CLAuthorizationStatus>()
|
||||
private(set) var locationSubject = PublishSubject<CLLocation>()
|
||||
|
||||
init(locationManager: ParentObject) {
|
||||
super.init(parentObject: locationManager, delegateProxy: RxLocationManagerDelegateProxy.self)
|
||||
@ -16,6 +19,17 @@ class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocatio
|
||||
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
|
||||
|
||||
static func registerKnownImplementations() {
|
||||
@ -43,39 +57,19 @@ class RxLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocatio
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
guard let err = error as? CLError else { return }
|
||||
|
||||
/*
|
||||
extension Reactive where Base: CLLocationManager {
|
||||
var delegate: DelegateProxy<CLLocationManager, CLLocationManagerDelegate> {
|
||||
return RxLocationManagerDelegateProxy.proxy(for: base)
|
||||
}
|
||||
|
||||
var didChangeAuthorization: ControlEvent<CLAuthorizationStatus> {
|
||||
let sel = #selector((CLLocationManagerDelegate.locationManager(_:didChangeAuthorization:)! as (CLLocationManagerDelegate) -> (CLLocationManager, CLAuthorizationStatus) -> Void))
|
||||
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"])
|
||||
}
|
||||
if self.generalErrors.contains(err.code) {
|
||||
// Pass general errors to all existing subjects
|
||||
self.authSubject.onError(error)
|
||||
self.locationSubject.onError(error)
|
||||
} else if self.geocodingErrors.contains(err.code) {
|
||||
// TODO: pass error to geocoding subject
|
||||
} else {
|
||||
print("Unexpected CoreLocation error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
class LocationManager {
|
||||
private static let manager: CLLocationManager = {
|
||||
@ -84,6 +78,7 @@ class LocationManager {
|
||||
return mgr
|
||||
}()
|
||||
private static let bag = DisposeBag()
|
||||
private(set) static var lastEvent: VehicleEvent?
|
||||
|
||||
private static func checkPermissions() -> Single<Void> {
|
||||
return Single<Void>.create { observer in
|
||||
@ -93,7 +88,8 @@ class LocationManager {
|
||||
break
|
||||
case .notDetermined:
|
||||
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 {
|
||||
observer(.success(()))
|
||||
} else {
|
||||
@ -110,14 +106,24 @@ class LocationManager {
|
||||
}
|
||||
|
||||
private static func requestLocation() -> Single<VehicleEvent> {
|
||||
let single = RxLocationManagerDelegateProxy.proxy(for: self.manager).locationSubject.take(1).asSingle().map { location in
|
||||
return VehicleEvent(lat: location.coordinate.latitude, lon: location.coordinate.longitude, speed: location.speed, dir: location.course)
|
||||
let proxy = RxLocationManagerDelegateProxy.proxy(for: self.manager)
|
||||
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()
|
||||
return single
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
||||
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> {
|
||||
return Single<Void>.create { observer in
|
||||
AVAudioSession.sharedInstance().requestRecordPermission { allowed in
|
||||
@ -36,25 +49,25 @@ class Recorder {
|
||||
observer(.success(()))
|
||||
break
|
||||
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))
|
||||
break
|
||||
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))
|
||||
break
|
||||
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))
|
||||
break
|
||||
@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))
|
||||
break
|
||||
}
|
||||
}
|
||||
} 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))
|
||||
}
|
||||
}
|
||||
@ -64,9 +77,13 @@ class Recorder {
|
||||
}
|
||||
|
||||
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
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,32 @@
|
||||
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) {
|
||||
super.init(coder: coder)
|
||||
|
||||
//self.borderStyle = .none
|
||||
self.layer.borderWidth = 1
|
||||
self.layer.cornerRadius = 6
|
||||
self.delegate = self
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
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