Voice recording fixes.
Adding experimental siri support
2
.gitignore
vendored
@ -1 +1 @@
|
|||||||
Carthage/
|
design/generated/
|
||||||
|
|||||||
@ -11,6 +11,7 @@ extension OSLog {
|
|||||||
enum QuickAction {
|
enum QuickAction {
|
||||||
case none
|
case none
|
||||||
case check
|
case check
|
||||||
|
case checkNumber(String)
|
||||||
case addVoiceRecord
|
case addVoiceRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
let config = Realm.Configuration(
|
let config = Realm.Configuration(
|
||||||
schemaVersion: 8,
|
schemaVersion: 9,
|
||||||
migrationBlock: { migration, oldSchemaVersion in
|
migrationBlock: { migration, oldSchemaVersion in
|
||||||
if oldSchemaVersion <= 3 {
|
if oldSchemaVersion <= 3 {
|
||||||
var numbers: [String] = []
|
var numbers: [String] = []
|
||||||
@ -39,7 +40,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Realm.Configuration.defaultConfiguration = config
|
Realm.Configuration.defaultConfiguration = config
|
||||||
print(Realm.Configuration.defaultConfiguration.fileURL!)
|
|
||||||
|
|
||||||
IHProgressHUD.set(defaultStyle: .dark)
|
IHProgressHUD.set(defaultStyle: .dark)
|
||||||
IHProgressHUD.set(defaultMaskType: .black)
|
IHProgressHUD.set(defaultMaskType: .black)
|
||||||
|
|||||||
6
AutoCat/Assets.xcassets/TabBar/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
25
AutoCat/Assets.xcassets/TabBar/check-compact.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "check-37.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "check-54.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/check-compact.imageset/check-37.png
vendored
Normal file
|
After Width: | Height: | Size: 967 B |
BIN
AutoCat/Assets.xcassets/TabBar/check-compact.imageset/check-54.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
25
AutoCat/Assets.xcassets/TabBar/check.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "check-51.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "check-75.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/check.imageset/check-51.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
AutoCat/Assets.xcassets/TabBar/check.imageset/check-75.png
vendored
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
25
AutoCat/Assets.xcassets/TabBar/record-compact.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "record-47.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "record-69.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/record-compact.imageset/record-47.png
vendored
Normal file
|
After Width: | Height: | Size: 924 B |
BIN
AutoCat/Assets.xcassets/TabBar/record-compact.imageset/record-69.png
vendored
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
25
AutoCat/Assets.xcassets/TabBar/record.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "record-63.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "record-93.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/record.imageset/record-63.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
AutoCat/Assets.xcassets/TabBar/record.imageset/record-93.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
25
AutoCat/Assets.xcassets/TabBar/search-compact.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "search-35.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "search-51.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/search-compact.imageset/search-35.png
vendored
Normal file
|
After Width: | Height: | Size: 870 B |
BIN
AutoCat/Assets.xcassets/TabBar/search-compact.imageset/search-51.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
25
AutoCat/Assets.xcassets/TabBar/search.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "search-47.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "search-69.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/search.imageset/search-47.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
AutoCat/Assets.xcassets/TabBar/search.imageset/search-69.png
vendored
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
25
AutoCat/Assets.xcassets/TabBar/settings-compact.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "settings-37.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "settings-54.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/settings-compact.imageset/settings-37.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
AutoCat/Assets.xcassets/TabBar/settings-compact.imageset/settings-54.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
25
AutoCat/Assets.xcassets/TabBar/settings.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "settings-51.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "settings-75.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AutoCat/Assets.xcassets/TabBar/settings.imageset/settings-51.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
AutoCat/Assets.xcassets/TabBar/settings.imageset/settings-75.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
@ -223,14 +223,14 @@
|
|||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<prototypes>
|
<prototypes>
|
||||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="VehicleCell" id="VEP-QD-i6y" customClass="VehicleCell" customModule="AutoCat" customModuleProvider="target">
|
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="VehicleCell" id="VEP-QD-i6y" customClass="VehicleCell" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="28" width="375" height="85.5"/>
|
<rect key="frame" x="0.0" y="28" width="375" height="85"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VEP-QD-i6y" id="8hH-8I-XLB">
|
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VEP-QD-i6y" id="8hH-8I-XLB">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="85.5"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="85"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Kia (JF) Optima" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AQY-7N-q8D">
|
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Kia (JF) Optima" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AQY-7N-q8D">
|
||||||
<rect key="frame" x="8" y="8" width="124" height="21.5"/>
|
<rect key="frame" x="8" y="8" width="124" height="21"/>
|
||||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@ -242,7 +242,7 @@
|
|||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</label>
|
||||||
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cvf-vM-QnT" customClass="PlateView" customModule="AutoCat" customModuleProvider="target">
|
<view contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cvf-vM-QnT" customClass="PlateView" customModule="AutoCat" customModuleProvider="target">
|
||||||
<rect key="frame" x="8" y="37.5" width="317" height="40"/>
|
<rect key="frame" x="8" y="37" width="317" height="40"/>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="40" id="Xoz-Iw-PCU"/>
|
<constraint firstAttribute="height" constant="40" id="Xoz-Iw-PCU"/>
|
||||||
@ -376,7 +376,7 @@
|
|||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
<viewLayoutGuide key="safeArea" id="Uix-8K-fxh"/>
|
<viewLayoutGuide key="safeArea" id="Uix-8K-fxh"/>
|
||||||
</view>
|
</view>
|
||||||
<tabBarItem key="tabBarItem" title="Settings" image="gear" catalog="system" id="zEL-ph-E2f"/>
|
<tabBarItem key="tabBarItem" title="Settings" image="settings" landscapeImage="settings-compact" id="zEL-ph-E2f"/>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="trD-gZ-yAv" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="trD-gZ-yAv" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
@ -419,14 +419,14 @@
|
|||||||
<action selector="onPlay:" destination="mzE-bt-IiX" eventType="touchUpInside" id="hwo-ns-0RK"/>
|
<action selector="onPlay:" destination="mzE-bt-IiX" eventType="touchUpInside" id="hwo-ns-0RK"/>
|
||||||
</connections>
|
</connections>
|
||||||
</button>
|
</button>
|
||||||
<label 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">
|
<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="56.5" y="0.0" width="46" height="44.5"/>
|
<rect key="frame" x="50.5" y="0.0" width="0.0" height="44.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
<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"/>
|
<color key="textColor" systemColor="systemTealColor" red="0.35294117650000001" green="0.7843137255" blue="0.98039215690000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
</label>
|
</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">
|
<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="114.5" y="0.0" width="195" height="44.5"/>
|
<rect key="frame" x="56.5" y="0.0" width="253" height="44.5"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||||
<nil key="textColor"/>
|
<nil key="textColor"/>
|
||||||
<nil key="highlightedColor"/>
|
<nil key="highlightedColor"/>
|
||||||
@ -752,7 +752,7 @@
|
|||||||
<scene sceneID="pUX-kf-oY1">
|
<scene sceneID="pUX-kf-oY1">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="TSb-ZG-qfD" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="TSb-ZG-qfD" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="Check" image="eye" catalog="system" selectedImage="eye.fill" id="QJd-35-4OB"/>
|
<tabBarItem key="tabBarItem" title="Check" image="check" landscapeImage="check-compact" id="QJd-35-4OB"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="AAc-4d-GNh">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="AAc-4d-GNh">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
@ -771,7 +771,7 @@
|
|||||||
<scene sceneID="kiS-EQ-VFl">
|
<scene sceneID="kiS-EQ-VFl">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="GCa-Re-j14" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="GCa-Re-j14" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="Search" image="magnifyingglass" catalog="system" id="gDG-z8-R0t"/>
|
<tabBarItem key="tabBarItem" title="Search" image="search" landscapeImage="search-compact" id="gDG-z8-R0t"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="vdY-9n-hjX">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="vdY-9n-hjX">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
@ -808,7 +808,7 @@
|
|||||||
<scene sceneID="oyu-oz-pC4">
|
<scene sceneID="oyu-oz-pC4">
|
||||||
<objects>
|
<objects>
|
||||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="RK6-pn-2Bg" sceneMemberID="viewController">
|
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="RK6-pn-2Bg" sceneMemberID="viewController">
|
||||||
<tabBarItem key="tabBarItem" title="Records" image="recordingtape" catalog="system" id="lxF-EY-z8V"/>
|
<tabBarItem key="tabBarItem" title="Records" image="record" landscapeImage="record-compact" id="lxF-EY-z8V"/>
|
||||||
<toolbarItems/>
|
<toolbarItems/>
|
||||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="8YG-pw-LE7">
|
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="8YG-pw-LE7">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
|
||||||
@ -825,13 +825,16 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
|
<image name="check" width="25" height="25"/>
|
||||||
|
<image name="check-compact" width="18" height="18"/>
|
||||||
<image name="doc.on.doc" catalog="system" width="117" height="128"/>
|
<image name="doc.on.doc" catalog="system" width="117" height="128"/>
|
||||||
<image name="eye" catalog="system" width="128" height="81"/>
|
|
||||||
<image name="eye.fill" catalog="system" width="128" height="78"/>
|
|
||||||
<image name="gear" catalog="system" width="128" height="119"/>
|
|
||||||
<image name="line.horizontal.3.decrease" catalog="system" width="128" height="73"/>
|
<image name="line.horizontal.3.decrease" catalog="system" width="128" height="73"/>
|
||||||
<image name="magnifyingglass" catalog="system" width="128" height="115"/>
|
|
||||||
<image name="play.fill" catalog="system" width="116" height="128"/>
|
<image name="play.fill" catalog="system" width="116" height="128"/>
|
||||||
<image name="recordingtape" catalog="system" width="128" height="60"/>
|
<image name="record" width="31" height="31"/>
|
||||||
|
<image name="record-compact" width="23" height="23"/>
|
||||||
|
<image name="search" width="23" height="23"/>
|
||||||
|
<image name="search-compact" width="17" height="17"/>
|
||||||
|
<image name="settings" width="25" height="25"/>
|
||||||
|
<image name="settings-compact" width="18" height="18"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@ -73,11 +73,20 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
func handleQuickActions() {
|
func handleQuickActions() {
|
||||||
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
|
||||||
if ad.quickAction == .check {
|
switch ad.quickAction {
|
||||||
|
case .check:
|
||||||
ad.quickAction = .none
|
ad.quickAction = .none
|
||||||
self.number.becomeFirstResponder()
|
self.number.becomeFirstResponder()
|
||||||
} else if ad.quickAction == .addVoiceRecord {
|
break
|
||||||
|
case .checkNumber(let number):
|
||||||
|
ad.quickAction = .none
|
||||||
|
self.check(number: number)
|
||||||
|
break
|
||||||
|
case .addVoiceRecord:
|
||||||
self.tabBarController?.selectedIndex = 1
|
self.tabBarController?.selectedIndex = 1
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,17 +94,20 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITabl
|
|||||||
guard let number = self.number.text else { return }
|
guard let number = self.number.text else { return }
|
||||||
|
|
||||||
let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
|
let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
|
||||||
|
self.check(number: numberNormalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(number: String) {
|
||||||
self.number.resignFirstResponder()
|
self.number.resignFirstResponder()
|
||||||
self.number.text = nil
|
self.number.text = nil
|
||||||
self.check.isEnabled = false
|
self.check.isEnabled = false
|
||||||
IHProgressHUD.show()
|
IHProgressHUD.show()
|
||||||
Api.checkVehicle(by: numberNormalized)
|
Api.checkVehicle(by: number)
|
||||||
.observeOn(MainScheduler.instance)
|
.observeOn(MainScheduler.instance)
|
||||||
.subscribe(onNext: onReceivedVehicle(_:), onError: { err in
|
.subscribe(onNext: onReceivedVehicle(_:), onError: { err in
|
||||||
if let realm = try? Realm() {
|
if let realm = try? Realm() {
|
||||||
try? realm.write {
|
try? realm.write {
|
||||||
realm.add(Vehicle(numberNormalized), update: .all)
|
realm.add(Vehicle(number), update: .all)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IHProgressHUD.showError(withStatus: err.localizedDescription)
|
IHProgressHUD.showError(withStatus: err.localizedDescription)
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import RealmSwift
|
|||||||
import RxSwift
|
import RxSwift
|
||||||
import RxRealm
|
import RxRealm
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
|
import Intents
|
||||||
|
import CoreSpotlight
|
||||||
|
import MobileCoreServices
|
||||||
|
|
||||||
class RecordsController: UIViewController, UITableViewDelegate {
|
class RecordsController: UIViewController, UITableViewDelegate {
|
||||||
|
|
||||||
@ -11,16 +14,16 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
|
|
||||||
var recorder: Recorder?
|
var recorder: Recorder?
|
||||||
var addButton: UIBarButtonItem!
|
var addButton: UIBarButtonItem!
|
||||||
var cancelButton: UIBarButtonItem!
|
|
||||||
let bag = DisposeBag()
|
let bag = DisposeBag()
|
||||||
|
|
||||||
|
let validLetters = ["А", "В", "Е", "К", "М", "Н", "О", "Р", "С", "Т", "У", "Х"]
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
guard let realm = try? Realm() else { return }
|
guard let realm = try? Realm() else { return }
|
||||||
|
|
||||||
self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(onAddVoiceRecord(_:)))
|
self.addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(onAddVoiceRecord(_:)))
|
||||||
self.cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(onCancelRecording(_:)))
|
|
||||||
self.navigationItem.rightBarButtonItem = self.addButton
|
self.navigationItem.rightBarButtonItem = self.addButton
|
||||||
|
|
||||||
self.recorder = try? Recorder()
|
self.recorder = try? Recorder()
|
||||||
@ -57,6 +60,7 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
|
|
||||||
self.tableView.rx.setDelegate(self).disposed(by: self.bag)
|
self.tableView.rx.setDelegate(self).disposed(by: self.bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
self.handleQuickActions()
|
self.handleQuickActions()
|
||||||
@ -65,14 +69,36 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
func handleQuickActions() {
|
func handleQuickActions() {
|
||||||
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
|
||||||
if ad.quickAction == .addVoiceRecord {
|
switch ad.quickAction {
|
||||||
|
case .addVoiceRecord:
|
||||||
ad.quickAction = .none
|
ad.quickAction = .none
|
||||||
if let addButton = self.navigationItem.rightBarButtonItem {
|
if let addButton = self.navigationItem.rightBarButtonItem {
|
||||||
self.onAddVoiceRecord(addButton)
|
self.onAddVoiceRecord(addButton)
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func donateUserActivity() {
|
||||||
|
let activityId = "pro.aliencat.autocat.addVoiceRecord"
|
||||||
|
let activity = NSUserActivity(activityType: activityId)
|
||||||
|
activity.persistentIdentifier = activityId
|
||||||
|
activity.isEligibleForSearch = true
|
||||||
|
activity.isEligibleForPrediction = true
|
||||||
|
activity.title = "Add new audio record"
|
||||||
|
activity.suggestedInvocationPhrase = "Запиши номер"
|
||||||
|
|
||||||
|
let attributes = CSSearchableItemAttributeSet()
|
||||||
|
attributes.contentType = kUTTypeItem as String
|
||||||
|
attributes.contentDescription = "Add new plate number via audio recording"
|
||||||
|
activity.contentAttributeSet = attributes
|
||||||
|
|
||||||
|
self.userActivity = activity
|
||||||
|
activity.becomeCurrent()
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Bar button handlers
|
// MARK: - Bar button handlers
|
||||||
|
|
||||||
@objc func onAddVoiceRecord(_ sender: UIBarButtonItem) {
|
@objc func onAddVoiceRecord(_ sender: UIBarButtonItem) {
|
||||||
@ -87,25 +113,24 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
self.show(error: error)
|
self.show(error: error)
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
|
let alert = UIAlertController(title: "Recording...", message: nil, preferredStyle: .alert)
|
||||||
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in self.recorder?.cancelRecording() }))
|
||||||
|
self.present(alert, animated: true)
|
||||||
|
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
|
let fileName = "recording-\(date.timeIntervalSince1970).m4a"
|
||||||
let url = try FileManager.default.url(for: fileName, in: "recordings")
|
let url = try FileManager.default.url(for: fileName, in: "recordings")
|
||||||
try recorder.startRecording(to: url) { result in
|
try recorder.startRecording(to: url) { result in
|
||||||
self.navigationItem.rightBarButtonItem?.isEnabled = true
|
|
||||||
self.navigationItem.leftBarButtonItem = nil
|
|
||||||
self.title = "Voice recordings"
|
|
||||||
|
|
||||||
let asset = AVURLAsset(url: url)
|
let asset = AVURLAsset(url: url)
|
||||||
let duration = TimeInterval(CMTimeGetSeconds(asset.duration))
|
let duration = TimeInterval(CMTimeGetSeconds(asset.duration))
|
||||||
let record = AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: result), duration: duration)
|
let record = AudioRecord(path: url.lastPathComponent, number: self.getPlateNumber(from: result), raw: result, duration: duration)
|
||||||
let realm = try? Realm()
|
let realm = try? Realm()
|
||||||
try? realm?.write {
|
try? realm?.write {
|
||||||
realm?.add(record)
|
realm?.add(record)
|
||||||
}
|
}
|
||||||
|
alert.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
self.title = "Recording..."
|
self.donateUserActivity()
|
||||||
self.navigationItem.rightBarButtonItem?.isEnabled = false
|
|
||||||
self.navigationItem.leftBarButtonItem = self.cancelButton
|
|
||||||
} catch {
|
} catch {
|
||||||
IHProgressHUD.showError(withStatus: error.localizedDescription)
|
IHProgressHUD.showError(withStatus: error.localizedDescription)
|
||||||
}
|
}
|
||||||
@ -114,69 +139,103 @@ class RecordsController: UIViewController, UITableViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func onCancelRecording(_ sender: UIBarButtonItem) {
|
|
||||||
self.recorder?.cancelRecording()
|
|
||||||
self.navigationItem.rightBarButtonItem?.isEnabled = true
|
|
||||||
self.navigationItem.leftBarButtonItem = nil
|
|
||||||
self.title = "Voice recordings"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Processing
|
// MARK: - Processing
|
||||||
|
|
||||||
func getPlateNumber(from recognizedText: String) -> String? {
|
func getPlateNumber(from recognizedText: String) -> String? {
|
||||||
let trimmed = recognizedText.replacingOccurrences(of: " ", with: "").uppercased()
|
let trimmed = recognizedText
|
||||||
|
.replacingOccurrences(of: " ", with: "")
|
||||||
|
.uppercased()
|
||||||
|
.replacingOccurrences(of: "Ф", with: "В")
|
||||||
|
|
||||||
|
var result = ""
|
||||||
if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
|
if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
|
||||||
return String(trimmed[range])
|
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) {
|
||||||
let n = String(trimmed[range])
|
let n = String(trimmed[range])
|
||||||
return n.prefix(1) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + n.substring(from: 6)
|
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) {
|
} else if let range = trimmed.range(of: #"\S\d\d\d\S\S\d\d\d?"#, options: .regularExpression) {
|
||||||
return String(trimmed[range]) + "161"
|
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\S\S\d\d\d"#, options: .regularExpression) {
|
||||||
let n = String(trimmed[range])
|
let n = String(trimmed[range])
|
||||||
return n.prefix(1) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + "161"
|
result = n.prefix(1) + n.substring(with: 3..<6) + n.substring(with: 1..<3) + "161"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !result.isEmpty && valid(number: result) {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func valid(number: String) -> Bool {
|
||||||
|
let first = String(number.prefix(1))
|
||||||
|
let second = number.substring(with: 4..<5)
|
||||||
|
let third = number.substring(with: 5..<6)
|
||||||
|
|
||||||
|
return self.validLetters.contains(first)
|
||||||
|
&& self.validLetters.contains(second)
|
||||||
|
&& self.validLetters.contains(third)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - UITableViewDelegate
|
// MARK: - UITableViewDelegate
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||||
guard let record: AudioRecord = try? self.tableView.rx.model(at: indexPath) else { return nil }
|
guard let record: AudioRecord = try? self.tableView.rx.model(at: indexPath) else { return nil }
|
||||||
|
guard let cell = tableView.cellForRow(at: indexPath) else { return nil }
|
||||||
/*
|
|
||||||
let deleteAction = UIContextualAction(style: .normal, title: "Delete") { action, view, completion in
|
|
||||||
do {
|
|
||||||
let realm = try Realm()
|
|
||||||
try realm.write {
|
|
||||||
realm.delete(record)
|
|
||||||
}
|
|
||||||
completion(true)
|
|
||||||
} catch {
|
|
||||||
print("Error deleting audio record: \(error.localizedDescription)")
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deleteAction.image = UIImage(systemName: "trash")
|
|
||||||
deleteAction.backgroundColor = .systemRed
|
|
||||||
*/
|
|
||||||
|
|
||||||
let check = UIContextualAction(style: .normal, title: "Check") { action, view, completion in
|
let check = UIContextualAction(style: .normal, title: "Check") { action, view, completion in
|
||||||
|
if let number = record.number {
|
||||||
|
self.check(number: number)
|
||||||
|
}
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
check.backgroundColor = .systemGray2
|
check.backgroundColor = .systemGray2
|
||||||
|
check.image = UIImage(systemName: "eye")
|
||||||
|
|
||||||
let share = UIContextualAction(style: .normal, title: "Share") { action, view, completion in
|
let action = UIContextualAction(style: .normal, title: "Action") { action, view, completion in
|
||||||
|
self.moreActions(for: record, cell: cell)
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
share.backgroundColor = .systemGray2
|
action.backgroundColor = .systemGray2
|
||||||
|
action.image = UIImage(systemName: "ellipsis" /*"square.and.arrow.up"*/)
|
||||||
|
|
||||||
let delete = UIContextualAction(style: .destructive, title: "Delete") { action, view, completion in
|
let delete = UIContextualAction(style: .destructive, title: "Delete") { action, view, completion in
|
||||||
self.tableView.dataSource?.tableView!(self.tableView, commit: .delete, forRowAt: indexPath)
|
self.tableView.dataSource?.tableView!(self.tableView, commit: .delete, forRowAt: indexPath)
|
||||||
completion(true)
|
completion(true)
|
||||||
}
|
}
|
||||||
|
delete.image = UIImage(systemName: "trash")
|
||||||
|
|
||||||
return UISwipeActionsConfiguration(actions: [delete, check, share])
|
let actions = record.number == nil ? [delete, action] : [delete, check, action]
|
||||||
|
return UISwipeActionsConfiguration(actions: actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moreActions(for record: AudioRecord, cell: UITableViewCell) {
|
||||||
|
let sheet = UIAlertController(title: "More actions", message: nil, preferredStyle: .actionSheet)
|
||||||
|
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { _ in sheet.dismiss(animated: true, completion: nil) }
|
||||||
|
let share = UIAlertAction(title: "Share", style: .default) { _ in
|
||||||
|
do {
|
||||||
|
let url = try FileManager.default.url(for: record.path, in: "recordings")
|
||||||
|
let controller = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||||
|
self.present(controller, animated: true)
|
||||||
|
} catch {
|
||||||
|
print("Error sharing audio record: \(error.localizedDescription)")
|
||||||
|
IHProgressHUD.showError(withStatus: error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let showText = UIAlertAction(title: "Show recognized text", style: .default) { action in
|
||||||
|
self.showAlert(title: "Recognized text", message: record.rawText)
|
||||||
|
}
|
||||||
|
sheet.addAction(showText)
|
||||||
|
sheet.addAction(share)
|
||||||
|
sheet.addAction(cancel)
|
||||||
|
sheet.popoverPresentationController?.sourceView = cell
|
||||||
|
sheet.popoverPresentationController?.sourceRect = cell.frame
|
||||||
|
self.present(sheet, animated: true, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(number: String) {
|
||||||
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
ad.quickAction = .checkNumber(number)
|
||||||
|
self.tabBarController?.selectedIndex = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,8 +120,11 @@ class ReportController: UIViewController, UICollectionViewDataSource, UICollecti
|
|||||||
|
|
||||||
self.navigationController?.setNavigationBarHidden(self.vehicle == nil, animated: animated)
|
self.navigationController?.setNavigationBarHidden(self.vehicle == nil, animated: animated)
|
||||||
|
|
||||||
if ad.quickAction == .check {
|
switch ad.quickAction {
|
||||||
|
case .check:
|
||||||
self.dismiss(animated: false, completion: nil)
|
self.dismiss(animated: false, completion: nil)
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class SearchController: UIViewController, UISearchResultsUpdating {
|
|||||||
.subscribe(onNext: self.updateDetailController(with:))
|
.subscribe(onNext: self.updateDetailController(with:))
|
||||||
.disposed(by: self.bag)
|
.disposed(by: self.bag)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
self.filterRelay
|
self.filterRelay
|
||||||
//.throttle(.seconds(2), scheduler: MainScheduler.instance)
|
//.throttle(.seconds(2), scheduler: MainScheduler.instance)
|
||||||
.debounce(.milliseconds(500), scheduler: MainScheduler.instance)
|
.debounce(.milliseconds(500), scheduler: MainScheduler.instance)
|
||||||
@ -49,6 +50,7 @@ class SearchController: UIViewController, UISearchResultsUpdating {
|
|||||||
.bind(to: self.tableView.rx.items(dataSource: ds))
|
.bind(to: self.tableView.rx.items(dataSource: ds))
|
||||||
.disposed(by: self.bag)
|
.disposed(by: self.bag)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Code duplication
|
// FIXME: Code duplication
|
||||||
func updateDetailController(with vehicle: Vehicle) {
|
func updateDetailController(with vehicle: Vehicle) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ class AudioRecord: Object, IdentifiableType {
|
|||||||
|
|
||||||
@objc dynamic var path: String = ""
|
@objc dynamic var path: String = ""
|
||||||
@objc dynamic var number: String?
|
@objc dynamic var number: String?
|
||||||
|
@objc dynamic var rawText: String = ""
|
||||||
@objc dynamic var addedDate: TimeInterval = Date().timeIntervalSince1970
|
@objc dynamic var addedDate: TimeInterval = Date().timeIntervalSince1970
|
||||||
@objc dynamic var duration: TimeInterval = 0
|
@objc dynamic var duration: TimeInterval = 0
|
||||||
|
|
||||||
@ -17,13 +18,16 @@ class AudioRecord: Object, IdentifiableType {
|
|||||||
return self.identifier
|
return self.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
init(path: String, number: String?, duration: TimeInterval) {
|
init(path: String, number: String?, raw: String, duration: TimeInterval) {
|
||||||
self.path = path
|
self.path = path
|
||||||
self.number = number
|
self.number = number
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
self.rawText = raw
|
||||||
}
|
}
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
|
super.init()
|
||||||
|
self.identifier = self.addedDate
|
||||||
}
|
}
|
||||||
|
|
||||||
override class func ignoredProperties() -> [String] {
|
override class func ignoredProperties() -> [String] {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
import os.log
|
||||||
|
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
@ -9,6 +10,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
||||||
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
|
||||||
guard let windowScene = (scene as? UIWindowScene) else { return }
|
guard let windowScene = (scene as? UIWindowScene) else { return }
|
||||||
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
|
|
||||||
|
if let activity = connectionOptions.userActivities.first {
|
||||||
|
if activity.activityType == "pro.aliencat.autocat.addVoiceRecord" {
|
||||||
|
ad.quickAction = .addVoiceRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.window = UIWindow(windowScene: windowScene)
|
self.window = UIWindow(windowScene: windowScene)
|
||||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||||
@ -70,6 +78,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if shortcutItem.type == "AddVoiceRecordAction" {
|
} else if shortcutItem.type == "AddVoiceRecordAction" {
|
||||||
|
self.handleAddVoiceRecordAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
|
||||||
|
if userActivity.activityType == "pro.aliencat.autocat.addVoiceRecord" {
|
||||||
|
self.handleAddVoiceRecordAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAddVoiceRecordAction() {
|
||||||
|
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
|
||||||
ad.quickAction = .addVoiceRecord
|
ad.quickAction = .addVoiceRecord
|
||||||
|
|
||||||
if let split = self.window?.rootViewController as? MainSplitController, let tabvc = split.viewControllers.first as? UITabBarController {
|
if let split = self.window?.rootViewController as? MainSplitController, let tabvc = split.viewControllers.first as? UITabBarController {
|
||||||
@ -86,6 +106,5 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ class Recorder {
|
|||||||
|
|
||||||
init() throws {
|
init() throws {
|
||||||
|
|
||||||
try self.session.setCategory(.playAndRecord, mode: .spokenAudio, options: .mixWithOthers)
|
try self.session.setCategory(.record, mode: .spokenAudio, options: .mixWithOthers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestPermissions(completion: @escaping (NSError?) -> Void) {
|
func requestPermissions(completion: @escaping (NSError?) -> Void) {
|
||||||
@ -72,6 +72,9 @@ class Recorder {
|
|||||||
throw CocoaError.error(CocoaError.Code.fileWriteUnknown)
|
throw CocoaError.error(CocoaError.Code.fileWriteUnknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Play sound (sms_alert_note.caf) to indicate start of recording
|
||||||
|
AudioServicesPlayAlertSound(SystemSoundID(4097))
|
||||||
|
|
||||||
ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout<AudioStreamBasicDescription>.size), inFormat.streamDescription)
|
ExtAudioFileSetProperty(fileRef, kExtAudioFileProperty_ClientDataFormat, UInt32(MemoryLayout<AudioStreamBasicDescription>.size), inFormat.streamDescription)
|
||||||
|
|
||||||
self.engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, time in
|
self.engine.inputNode.installTap(onBus: 0, bufferSize: 1024, format: inFormat) { buffer, time in
|
||||||
@ -85,14 +88,14 @@ class Recorder {
|
|||||||
self.result = transcription.formattedString
|
self.result = transcription.formattedString
|
||||||
self.endRecognitionTimer?.invalidate()
|
self.endRecognitionTimer?.invalidate()
|
||||||
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in
|
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in
|
||||||
self.cancelRecording()
|
self.stopRecording()
|
||||||
completion(self.result)
|
completion(self.result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
|
self.endRecognitionTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { timer in
|
||||||
self.cancelRecording()
|
self.stopRecording()
|
||||||
completion(self.result)
|
completion(self.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +104,12 @@ class Recorder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cancelRecording() {
|
func cancelRecording() {
|
||||||
|
self.stopRecording()
|
||||||
|
self.endRecognitionTimer?.invalidate()
|
||||||
|
self.endRecognitionTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopRecording() {
|
||||||
self.engine.stop()
|
self.engine.stop()
|
||||||
self.engine.inputNode.removeTap(onBus: 0)
|
self.engine.inputNode.removeTap(onBus: 0)
|
||||||
self.request.endAudio()
|
self.request.endAudio()
|
||||||
@ -110,9 +119,4 @@ class Recorder {
|
|||||||
ExtAudioFileDispose(fileRef)
|
ExtAudioFileDispose(fileRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopRecording() -> String {
|
|
||||||
self.cancelRecording()
|
|
||||||
return self.result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||