Removing SwiftJWT with all its dependencies (along with some audio playing/recording code adjustments)

This commit is contained in:
Selim Mustafaev 2020-07-03 18:00:05 +03:00
parent f513222d72
commit 55e3a3008e
14 changed files with 241 additions and 181 deletions

View File

@ -28,7 +28,6 @@
7A11472323FEA18700B424AF /* RxBlocking in Frameworks */ = {isa = PBXBuildFile; productRef = 7A11472223FEA18700B424AF /* RxBlocking */; };
7A11472623FEA1F400B424AF /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 7A11472523FEA1F400B424AF /* Realm */; };
7A11472823FEA1F400B424AF /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A11472723FEA1F400B424AF /* RealmSwift */; };
7A11472B23FEA24D00B424AF /* Action in Frameworks */ = {isa = PBXBuildFile; productRef = 7A11472A23FEA24D00B424AF /* Action */; };
7A11474423FF06CA00B424AF /* Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474323FF06CA00B424AF /* Api.swift */; };
7A11474723FF2AA500B424AF /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474623FF2AA500B424AF /* User.swift */; };
7A11474923FF2B2D00B424AF /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A11474823FF2B2D00B424AF /* Response.swift */; };
@ -75,7 +74,6 @@
7A7547E024032CB6004E8406 /* VehiclePhotoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7547DF24032CB6004E8406 /* VehiclePhotoCell.swift */; };
7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; };
7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A220A248D67B60073DFD9 /* VehicleReportImage.swift */; };
7A8A220E248EF5830073DFD9 /* SwiftJWT in Frameworks */ = {isa = PBXBuildFile; productRef = 7A8A220D248EF5830073DFD9 /* SwiftJWT */; };
7A96AE2A246AFD6200297C33 /* Eureka in Frameworks */ = {isa = PBXBuildFile; productRef = 7A96AE29246AFD6200297C33 /* Eureka */; };
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */; };
7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A96AE2E246B2BCD00297C33 /* WebKit.framework */; };
@ -177,11 +175,9 @@
7A11472123FEA18700B424AF /* RxCocoa in Frameworks */,
7A0516182414EC1200FC55AC /* RxDataSources in Frameworks */,
7A96AE2A246AFD6200297C33 /* Eureka in Frameworks */,
7A8A220E248EF5830073DFD9 /* SwiftJWT in Frameworks */,
7A051611241412CA00FC55AC /* SwiftDate in Frameworks */,
7A11472323FEA18700B424AF /* RxBlocking in Frameworks */,
7A530B8B240181F500CBFE6E /* RxRealm in Frameworks */,
7A11472B23FEA24D00B424AF /* Action in Frameworks */,
7A11471F23FEA18700B424AF /* RxRelay in Frameworks */,
7AF58D2F24029C5200CE01A0 /* MagazineLayout in Frameworks */,
7A530B78240010D900CBFE6E /* InputMask in Frameworks */,
@ -405,7 +401,6 @@
7A11472223FEA18700B424AF /* RxBlocking */,
7A11472523FEA1F400B424AF /* Realm */,
7A11472723FEA1F400B424AF /* RealmSwift */,
7A11472A23FEA24D00B424AF /* Action */,
7A530B77240010D900CBFE6E /* InputMask */,
7A530B8A240181F500CBFE6E /* RxRealm */,
7AF58D2E24029C5200CE01A0 /* MagazineLayout */,
@ -414,7 +409,6 @@
7A0516152414EC1200FC55AC /* Differentiator */,
7A0516172414EC1200FC55AC /* RxDataSources */,
7A96AE29246AFD6200297C33 /* Eureka */,
7A8A220D248EF5830073DFD9 /* SwiftJWT */,
);
productName = AutoCat;
productReference = 7A1146FD23FDE7E500B424AF /* AutoCat.app */;
@ -447,7 +441,6 @@
packageReferences = (
7A11471B23FEA18700B424AF /* XCRemoteSwiftPackageReference "RxSwift" */,
7A11472423FEA1F400B424AF /* XCRemoteSwiftPackageReference "realm-cocoa" */,
7A11472923FEA24D00B424AF /* XCRemoteSwiftPackageReference "Action" */,
7A530B76240010D900CBFE6E /* XCRemoteSwiftPackageReference "input-mask-ios" */,
7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */,
7AF58D2D24029C5200CE01A0 /* XCRemoteSwiftPackageReference "MagazineLayout" */,
@ -455,7 +448,6 @@
7A05160F241412CA00FC55AC /* XCRemoteSwiftPackageReference "SwiftDate" */,
7A0516142414EC1200FC55AC /* XCRemoteSwiftPackageReference "RxDataSources" */,
7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */,
7A8A220C248EF5830073DFD9 /* XCRemoteSwiftPackageReference "Swift-JWT" */,
);
productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */;
projectDirPath = "";
@ -691,7 +683,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
CURRENT_PROJECT_VERSION = 19;
DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -713,7 +705,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
CURRENT_PROJECT_VERSION = 19;
DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -785,14 +777,6 @@
minimumVersion = 5.0.0;
};
};
7A11472923FEA24D00B424AF /* XCRemoteSwiftPackageReference "Action" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/RxSwiftCommunity/Action";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 4.0.1;
};
};
7A530B76240010D900CBFE6E /* XCRemoteSwiftPackageReference "input-mask-ios" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/RedMadRobot/input-mask-ios";
@ -809,14 +793,6 @@
minimumVersion = 3.0.0;
};
};
7A8A220C248EF5830073DFD9 /* XCRemoteSwiftPackageReference "Swift-JWT" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/IBM-Swift/Swift-JWT.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 3.6.1;
};
};
7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/xmartlabs/Eureka";
@ -889,11 +865,6 @@
package = 7A11472423FEA1F400B424AF /* XCRemoteSwiftPackageReference "realm-cocoa" */;
productName = RealmSwift;
};
7A11472A23FEA24D00B424AF /* Action */ = {
isa = XCSwiftPackageProductDependency;
package = 7A11472923FEA24D00B424AF /* XCRemoteSwiftPackageReference "Action" */;
productName = Action;
};
7A530B77240010D900CBFE6E /* InputMask */ = {
isa = XCSwiftPackageProductDependency;
package = 7A530B76240010D900CBFE6E /* XCRemoteSwiftPackageReference "input-mask-ios" */;
@ -904,11 +875,6 @@
package = 7A530B89240181F500CBFE6E /* XCRemoteSwiftPackageReference "RxRealm" */;
productName = RxRealm;
};
7A8A220D248EF5830073DFD9 /* SwiftJWT */ = {
isa = XCSwiftPackageProductDependency;
package = 7A8A220C248EF5830073DFD9 /* XCRemoteSwiftPackageReference "Swift-JWT" */;
productName = SwiftJWT;
};
7A96AE29246AFD6200297C33 /* Eureka */ = {
isa = XCSwiftPackageProductDependency;
package = 7A96AE28246AFD6200297C33 /* XCRemoteSwiftPackageReference "Eureka" */;

View File

@ -1,42 +1,6 @@
{
"object": {
"pins": [
{
"package": "Action",
"repositoryURL": "https://github.com/RxSwiftCommunity/Action",
"state": {
"branch": null,
"revision": "1079e557787a3ffe88f833eb5f637dcf1396421b",
"version": "4.1.0"
}
},
{
"package": "Cryptor",
"repositoryURL": "https://github.com/IBM-Swift/BlueCryptor.git",
"state": {
"branch": null,
"revision": "12d2bf3ec7207ec3cd004b9582f69ef5fae1da3b",
"version": "1.0.32"
}
},
{
"package": "CryptorECC",
"repositoryURL": "https://github.com/IBM-Swift/BlueECC.git",
"state": {
"branch": null,
"revision": "73f362cb0d9c5f1fd0089240d7b293cd2bff18db",
"version": "1.2.5"
}
},
{
"package": "CryptorRSA",
"repositoryURL": "https://github.com/IBM-Swift/BlueRSA.git",
"state": {
"branch": null,
"revision": "8ea901f2582296837d88f882b0fa5a0601759598",
"version": "1.0.35"
}
},
{
"package": "Eureka",
"repositoryURL": "https://github.com/xmartlabs/Eureka",
@ -64,24 +28,6 @@
"version": "5.14.0"
}
},
{
"package": "KituraContracts",
"repositoryURL": "https://github.com/IBM-Swift/KituraContracts.git",
"state": {
"branch": null,
"revision": "a30e2fb79e926672776a05ec6b919c239870a221",
"version": "1.2.1"
}
},
{
"package": "LoggerAPI",
"repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git",
"state": {
"branch": null,
"revision": "3357dd9526cdf9436fa63bb792b669e6efdc43da",
"version": "1.9.0"
}
},
{
"package": "MagazineLayout",
"repositoryURL": "https://github.com/airbnb/MagazineLayout",
@ -136,24 +82,6 @@
"version": "5.1.1"
}
},
{
"package": "SwiftJWT",
"repositoryURL": "https://github.com/IBM-Swift/Swift-JWT.git",
"state": {
"branch": null,
"revision": "0d435423d12e61c0d14adb6d04396c08a6a650f1",
"version": "3.6.1"
}
},
{
"package": "swift-log",
"repositoryURL": "https://github.com/apple/swift-log.git",
"state": {
"branch": null,
"revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
"version": "1.2.0"
}
},
{
"package": "SwiftDate",
"repositoryURL": "https://github.com/malcommac/SwiftDate.git",

View File

@ -8,7 +8,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint">
<BreakpointContent
uuid = "C14D0996-5708-44D2-A6BA-4A4B50B522EE"
shouldBeEnabled = "No"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No">
</BreakpointContent>
@ -17,29 +17,13 @@
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
<BreakpointContent
uuid = "CF01B44D-372B-4C78-A197-7FDEC607CE0E"
shouldBeEnabled = "No"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
scope = "1"
stopOnStyle = "0">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "143FAB5A-C171-426A-A0BA-67C44E72B9D5"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AutoCat/Controllers/GoogleSignInController.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "61"
endingLineNumber = "61"
landmarkName = "webView(_:decidePolicyFor:decisionHandler:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent

View File

@ -405,14 +405,14 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="44.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
<stackView opaque="NO" contentMode="scaleToFill" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="QSJ-FJ-C4c">
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="QSJ-FJ-C4c">
<rect key="frame" x="8" y="0.0" width="359" height="44.5"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="BKm-HE-Aor">
<rect key="frame" x="0.0" y="0.0" width="44.5" height="44.5"/>
<rect key="frame" x="0.0" y="0.5" width="44" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="44" id="I8M-qD-lKL"/>
<constraint firstAttribute="width" secondItem="BKm-HE-Aor" secondAttribute="height" multiplier="1:1" id="Y4G-Ki-oYP"/>
<constraint firstAttribute="height" constant="44" id="lQk-Ob-dxJ"/>
</constraints>
<state key="normal" image="play.fill" catalog="system"/>
<connections>
@ -420,19 +420,19 @@
</connections>
</button>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" text="00:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bFQ-eU-5YJ">
<rect key="frame" x="50.5" y="0.0" width="0.0" height="44.5"/>
<rect key="frame" x="50" y="12" width="0.0" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="systemTealColor" red="0.35294117650000001" green="0.7843137255" blue="0.98039215690000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MjS-Hy-iGH">
<rect key="frame" x="56.5" y="0.0" width="253" height="44.5"/>
<rect key="frame" x="56" y="10.5" width="253.5" height="24"/>
<fontDescription key="fontDescription" type="system" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jgb-TO-YHq">
<rect key="frame" x="321.5" y="0.0" width="37.5" height="44.5"/>
<rect key="frame" x="321.5" y="13.5" width="37.5" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@ -830,11 +830,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="31" height="31"/>
<image name="record" width="128" height="128"/>
<image name="record-compact" width="23" height="23"/>
<image name="search" width="23" height="23"/>
<image name="search" width="128" height="128"/>
<image name="search-compact" width="17" height="17"/>
<image name="settings" width="25" height="25"/>
<image name="settings" width="128" height="128"/>
<image name="settings-compact" width="18" height="18"/>
</resources>
</document>

View File

@ -74,6 +74,7 @@ class AudioRecordCell: UITableViewCell {
try AudioPlayer.shared.play(url: url)
} catch {
print("Error playing audio record: \(error.localizedDescription)")
IHProgressHUD.showError(withStatus: error.localizedDescription)
}
}
}

View File

@ -120,15 +120,18 @@ class RecordsController: UIViewController, UITableViewDelegate {
let date = Date()
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
let url = try FileManager.default.url(for: fileName, in: "recordings")
try recorder.startRecording(to: url) { result in
let asset = AVURLAsset(url: url)
let duration = TimeInterval(CMTimeGetSeconds(asset.duration))
let record = AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: result), raw: result, duration: duration)
let realm = try? Realm()
try? realm?.write {
realm?.add(record)
try self.makeStartSoundIfNeeded {
try recorder.startRecording(to: url) { result in
let asset = AVURLAsset(url: url)
let duration = TimeInterval(CMTimeGetSeconds(asset.duration))
let record = AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: result), raw: result, duration: duration)
let realm = try? Realm()
try? realm?.write {
realm?.add(record)
}
alert.dismiss(animated: true)
print("New record saved to: \(url.path)")
}
alert.dismiss(animated: true)
}
self.donateUserActivity()
} catch {
@ -150,14 +153,14 @@ class RecordsController: UIViewController, UITableViewDelegate {
var result = ""
if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
result = String(trimmed[range])
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression) {
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d\d\d\d?"#, options: .regularExpression), Settings.shared.recognizeAlternativeOrder {
let n = String(trimmed[range])
result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6)
} else if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
result = String(trimmed[range]) + "161"
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression) {
} else if let range = trimmed.range(of: #"\S\d\d\d\S\S"#, options: .regularExpression), Settings.shared.recognizeShortenedNumbers {
result = String(trimmed[range]) + Settings.shared.defaultRegion
} else if let range = trimmed.range(of: #"\S\S\S\d\d\d"#, options: .regularExpression), Settings.shared.recognizeAlternativeOrder && Settings.shared.recognizeShortenedNumbers {
let n = String(trimmed[range])
result = n.prefix(1) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + "161"
result = String(n.prefix(1)) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + Settings.shared.defaultRegion
}
if !result.isEmpty && valid(number: result) {
@ -176,6 +179,32 @@ class RecordsController: UIViewController, UITableViewDelegate {
&& self.validLetters.contains(second)
&& self.validLetters.contains(third)
}
func makeStartSoundIfNeeded(completion: @escaping () throws -> Void) throws {
if !Settings.shared.recordBeep {
try completion()
} else {
//let session = AVAudioSession.sharedInstance()
//try session.setCategory(.playback, mode: .default, options: [.defaultToSpeaker])
//try session.setActive(true)
var err: Error?
var soundId = SystemSoundID()
let url = URL(fileURLWithPath: "/System/Library/Audio/UISounds/short_double_high.caf")
AudioServicesCreateSystemSoundID(url as CFURL, &soundId)
AudioServicesPlaySystemSoundWithCompletion(soundId) {
do {
//try session.setActive(false)
try completion()
} catch {
err = error
}
}
if let error = err {
throw error
}
}
}
// MARK: - UITableViewDelegate

View File

@ -2,7 +2,6 @@ import UIKit
import MagazineLayout
import Kingfisher
import LinkPresentation
import SwiftJWT
enum ReportSection: Int, CaseIterable, CustomStringConvertible {
case header = 0
@ -76,12 +75,6 @@ enum ReportEngineSection: Int, CaseIterable, CustomStringConvertible {
}
}
struct ReportLinkClaims: Claims {
let exp: Date
let iat: Date
let plateNumber: String
}
class ReportController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateMagazineLayout, MediaBrowserViewControllerDataSource, MediaBrowserViewControllerDelegate, UIActivityItemSource {
@IBOutlet weak var collection: UICollectionView!
@ -397,12 +390,7 @@ class ReportController: UIViewController, UICollectionViewDataSource, UICollecti
let shareLink = UIAlertAction(title: "As link", style: .default) { _ in
guard let vehicle = self.vehicle else { return }
let iat = Date()
let exp = Date(timeIntervalSinceNow: 24*60*60)
let claims = ReportLinkClaims(exp: exp, iat: iat, plateNumber: vehicle.number)
let signer = JWTSigner.hs256(key: Constants.reportLinkTokenSecret.data(using: .utf8)!)
var jwt = SwiftJWT.JWT(claims: claims)
if let jwtSigned = try? jwt.sign(using: signer), let url = URL(string: Constants.reportLinkBaseURL + "?token=" + jwtSigned) {
if let jwt = try? JWT.generate(for: vehicle.number), let url = URL(string: Constants.reportLinkBaseURL + "?token=" + jwt) {
let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
controller.popoverPresentationController?.barButtonItem = sender
self.present(controller, animated: true)

View File

@ -28,7 +28,50 @@ class SettingsController: FormViewController {
self.loginToGoogle()
}
}
+++ Section()
+++ Section(header: "Plate number recognition", footer: "Recognize plate numbers in alternative form. For example 'ЕВА 123 777' instead of 'Е123ВА 777'")
<<< SwitchRow("AlternateRecognition") { row in
row.title = "Alternative order"
row.value = Settings.shared.recognizeAlternativeOrder
}.onChange { row in
if let val = row.value {
Settings.shared.recognizeAlternativeOrder = val
}
}
+++ Section(footer: "If enabled, app will try to recognize shortened plate numbers (without region) and add default region")
<<< SwitchRow("ShortenedNumbers") { row in
row.title = "Shortened numbers"
row.value = Settings.shared.recognizeShortenedNumbers
}.onChange { row in
if let val = row.value {
Settings.shared.recognizeShortenedNumbers = val
}
}
<<< TextRow("") { row in
row.title = "Default region"
row.placeholder = "161"
row.value = Settings.shared.defaultRegion
row.hidden = Condition.function(["ShortenedNumbers"], { form in
return !((form.rowBy(tag: "ShortenedNumbers") as? SwitchRow)?.value ?? false)
})
}.onChange { row in
if let val = row.value {
Settings.shared.defaultRegion = val
}
}
+++ Section(footer: "When enabled, you will hear short sound before starting audio recording. This will only work when audio record is started via Siri")
<<< SwitchRow("BeepRecord") { row in
row.title = "Beep before record"
row.value = Settings.shared.recordBeep
}.onChange{ row in
if let val = row.value {
Settings.shared.recordBeep = val
}
}
+++ Section("")
<<< ButtonRow("SignOut") { $0.title = "Sign Out" }.onCellSelection { cell, row in
self.logout()
}

View File

@ -2,10 +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>NSSpeechRecognitionUsageDescription</key>
<string>Access is needed for recognizing plate numbers from voice recordings</string>
<key>NSMicrophoneUsageDescription</key>
<string>Access is needed for voice recordings</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
@ -46,8 +42,12 @@
</dict>
</dict>
</dict>
<key>NSMicrophoneUsageDescription</key>
<string>Access is needed for voice recordings</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>AutoCat needs access to photo library to save reports</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Access is needed for recognizing plate numbers from voice recordings</string>
<key>UIAppFonts</key>
<array>
<string>RoadNumbers2.0.otf</string>
@ -74,12 +74,12 @@
<key>UIApplicationShortcutItems</key>
<array>
<dict>
<key>UIApplicationShortcutItemType</key>
<string>AddVoiceRecordAction</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Add voice record</string>
<key>UIApplicationShortcutItemIconType</key>
<string>UIApplicationShortcutIconTypeAudio</string>
<key>UIApplicationShortcutItemTitle</key>
<string>Add voice record</string>
<key>UIApplicationShortcutItemType</key>
<string>AddVoiceRecordAction</string>
</dict>
<dict>
<key>UIApplicationShortcutItemIconType</key>

View File

@ -13,8 +13,47 @@ class Settings {
}
}
var recognizeAlternativeOrder: Bool = false {
didSet {
Settings.defaults.set(self.recognizeAlternativeOrder, forKey: "recognizeAlternativeOrder")
Settings.defaults.synchronize()
}
}
var recognizeShortenedNumbers: Bool = false {
didSet {
Settings.defaults.set(self.recognizeShortenedNumbers, forKey: "recognizeShortenedNumbers")
Settings.defaults.synchronize()
}
}
var defaultRegion: String = "" {
didSet {
Settings.defaults.set(self.defaultRegion, forKey: "defaultRegion")
Settings.defaults.synchronize()
}
}
var recordBeep: Bool = false {
didSet {
Settings.defaults.set(self.recordBeep, forKey: "recordBeep")
Settings.defaults.synchronize()
}
}
init() {
UserDefaults.standard.register(defaults: [
"recognizeAlternativeOrder": false,
"recognizeShortenedNumbers": false,
"defaultRegion": "161",
"recordBeep": false
])
self.user = Settings.getUser()
self.recognizeAlternativeOrder = Settings.defaults.bool(forKey: "recognizeAlternativeOrder")
self.recognizeShortenedNumbers = Settings.defaults.bool(forKey: "recognizeShortenedNumbers")
self.defaultRegion = Settings.defaults.string(forKey: "defaultRegion") ?? "161"
self.recordBeep = Settings.defaults.bool(forKey: "recordBeep")
}
private static func getUser() -> User {

View File

@ -149,11 +149,11 @@ public class Base64FS {
public static func encodeString(str: String) -> String {
// Get the ascii representation and return
let data = str.data(using: .ascii)!
let data = str.data(using: .utf8)!
let encData = encode(data: [UInt8](data))
let retStr = String(data: Data(encData), encoding: .ascii)!
let retStr = String(data: Data(encData), encoding: .utf8)!
return retStr
}

View File

@ -30,12 +30,15 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
self.player?.delegate = self
}
func play() {
func play() throws {
if let player = self.player {
if player.isPlaying {
player.pause()
try AVAudioSession.sharedInstance().setActive(false)
self.state.accept(.paused)
} else {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
player.play()
self.state.accept(.playing)
if self.progressTimer == nil {
@ -47,12 +50,13 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
func play(url: URL) throws {
try self.set(url: url)
self.play()
try self.play()
}
func pause() {
if let player = self.player {
player.pause()
try? AVAudioSession.sharedInstance().setActive(false)
self.state.accept(.paused)
}
}
@ -60,6 +64,7 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
func stop() {
if let player = self.player {
player.stop()
try? AVAudioSession.sharedInstance().setActive(false)
self.state.accept(.stopped)
self.progressTimer?.invalidate()
self.progressTimer = nil
@ -93,6 +98,7 @@ class AudioPlayer: NSObject, AVAudioPlayerDelegate {
// MARK: - AVAudioPlayerDelegate
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
try? AVAudioSession.sharedInstance().setActive(false)
self.progress.accept(1)
self.stop()
self.state.accept(.stopped)

View File

@ -1,4 +1,65 @@
import Foundation
import CommonCrypto
enum CryptoAlgorithm {
case MD5, SHA1, SHA224, SHA256, SHA384, SHA512
var HMACAlgorithm: CCHmacAlgorithm {
var result: Int = 0
switch self {
case .MD5: result = kCCHmacAlgMD5
case .SHA1: result = kCCHmacAlgSHA1
case .SHA224: result = kCCHmacAlgSHA224
case .SHA256: result = kCCHmacAlgSHA256
case .SHA384: result = kCCHmacAlgSHA384
case .SHA512: result = kCCHmacAlgSHA512
}
return CCHmacAlgorithm(result)
}
var digestLength: Int {
var result: Int32 = 0
switch self {
case .MD5: result = CC_MD5_DIGEST_LENGTH
case .SHA1: result = CC_SHA1_DIGEST_LENGTH
case .SHA224: result = CC_SHA224_DIGEST_LENGTH
case .SHA256: result = CC_SHA256_DIGEST_LENGTH
case .SHA384: result = CC_SHA384_DIGEST_LENGTH
case .SHA512: result = CC_SHA512_DIGEST_LENGTH
}
return Int(result)
}
}
extension String {
func hmac(algorithm: CryptoAlgorithm, key: String) -> String {
let str = self.cString(using: String.Encoding.utf8)
let strLen = Int(self.lengthOfBytes(using: String.Encoding.utf8))
let digestLen = algorithm.digestLength
let result = UnsafeMutablePointer<UInt8>.allocate(capacity: digestLen)
let keyStr = key.cString(using: String.Encoding.utf8)
let keyLen = Int(key.lengthOfBytes(using: String.Encoding.utf8))
CCHmac(algorithm.HMACAlgorithm, keyStr!, keyLen, str!, strLen, result)
let array = Array(UnsafeBufferPointer(start: result, count: digestLen))
let encodedArray = Base64FS.encode(data: array)
let string = String(data: Data(encodedArray), encoding: .utf8)!.trimmingCharacters(in: CharacterSet(charactersIn: "="))
result.deallocate()
return string
}
private func stringFromResult(result: UnsafeMutablePointer<CUnsignedChar>, length: Int) -> String {
let hash = NSMutableString()
for i in 0..<length {
hash.appendFormat("%02x", result[i])
}
return String(hash).lowercased()
}
}
struct JwtPayload: Codable {
var email: String
@ -35,4 +96,21 @@ class JWT {
return nil
}
}
static func generate(for plateNumber: String) throws -> String {
let header = #"{ "typ": "JWT", "alg": "HS256" }"#
let bodyDict: [String: Any] = [
"iat": Date().timeIntervalSince1970,
"exp": Date(timeIntervalSinceNow: 24*60*60).timeIntervalSince1970,
"plateNumber": plateNumber
]
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")
}
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)
return twoParts + "." + signature
}
}

View File

@ -5,7 +5,6 @@ import AudioToolbox
class Recorder {
let session = AVAudioSession.sharedInstance()
let engine = AVAudioEngine()
var fileRef: ExtAudioFileRef? = nil
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ru_RU"))
@ -23,13 +22,11 @@ class Recorder {
AVEncoderAudioQualityKey:AVAudioQuality.max.rawValue
]
init() throws {
try self.session.setCategory(.record, mode: .spokenAudio, options: .mixWithOthers)
init() {
}
func requestPermissions(completion: @escaping (NSError?) -> Void) {
self.session.requestRecordPermission { allowed in
AVAudioSession.sharedInstance().requestRecordPermission { allowed in
if allowed {
SFSpeechRecognizer.requestAuthorization { status in
switch status {
@ -72,8 +69,8 @@ class Recorder {
throw CocoaError.error(CocoaError.Code.fileWriteUnknown)
}
// Play sound (sms_alert_note.caf) to indicate start of recording
AudioServicesPlayAlertSound(SystemSoundID(4097))
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default, options: [])
try AVAudioSession.sharedInstance().setActive(true)
ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout<AudioStreamBasicDescription>.size), inFormat.streamDescription)
@ -114,6 +111,7 @@ class Recorder {
self.engine.inputNode.removeTap(onBus: 0)
self.request.endAudio()
self.recognitionTask?.cancel()
try? AVAudioSession.sharedInstance().setActive(false)
if let fileRef = self.fileRef {
ExtAudioFileDispose(fileRef)