Adding number with puller instead of old textfield

This commit is contained in:
Selim Mustafaev 2023-01-08 16:22:07 +03:00
parent 512a50b217
commit 1d377b8faa
24 changed files with 624 additions and 252 deletions

View File

@ -84,6 +84,14 @@
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; };
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8B2435C38700258F61 /* CustomTextField.swift */; };
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB67E8D2435D1A000258F61 /* CustomButton.swift */; };
7AC3554A2969652F00889457 /* SwiftEntryKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7AC355492969652F00889457 /* SwiftEntryKit */; };
7AC3554C29696A1C00889457 /* MainTabController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3554B29696A1C00889457 /* MainTabController.swift */; };
7AC3554E29696C4500889457 /* DummyNewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3554D29696C4500889457 /* DummyNewController.swift */; };
7AC3555029696D5A00889457 /* NewNumberController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3554F29696D5A00889457 /* NewNumberController.swift */; };
7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3555129696E3F00889457 /* UIView+layout.swift */; };
7AC35554296973E100889457 /* ACButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC35553296973E100889457 /* ACButton.swift */; };
7AC355592969746600889457 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC355582969746600889457 /* UIControl.swift */; };
7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC3555A296995B200889457 /* UIEdgeInsets.swift */; };
7AC76D7B270083AE0084DB27 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AC76D7A270083AE0084DB27 /* TextView.swift */; };
7ADF6C93250B954900F237B2 /* Navigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C92250B954900F237B2 /* Navigation.swift */; };
7ADF6C95250D037700F237B2 /* ShowEventController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C94250D037700F237B2 /* ShowEventController.swift */; };
@ -251,6 +259,13 @@
7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleRegion.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>"; };
7AC3554B29696A1C00889457 /* MainTabController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabController.swift; sourceTree = "<group>"; };
7AC3554D29696C4500889457 /* DummyNewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyNewController.swift; sourceTree = "<group>"; };
7AC3554F29696D5A00889457 /* NewNumberController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNumberController.swift; sourceTree = "<group>"; };
7AC3555129696E3F00889457 /* UIView+layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+layout.swift"; sourceTree = "<group>"; };
7AC35553296973E100889457 /* ACButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACButton.swift; sourceTree = "<group>"; };
7AC355582969746600889457 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
7AC3555A296995B200889457 /* UIEdgeInsets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIEdgeInsets.swift; sourceTree = "<group>"; };
7AC76D7A270083AE0084DB27 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = "<group>"; };
7ADF6C92250B954900F237B2 /* Navigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigation.swift; sourceTree = "<group>"; };
7ADF6C94250D037700F237B2 /* ShowEventController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowEventController.swift; sourceTree = "<group>"; };
@ -285,6 +300,7 @@
7AA7BC3325A5DFB80053A5D5 /* Kingfisher in Frameworks */,
7A813DBE2506A57100CC93B9 /* AuthenticationServices.framework in Frameworks */,
7AA7BC3625A5DFB80053A5D5 /* PKHUD in Frameworks */,
7AC3554A2969652F00889457 /* SwiftEntryKit in Frameworks */,
7AF6D2042677C03B0086EA64 /* AutoCatCore.framework in Frameworks */,
7A35177B27E23F8800DC538C /* Eureka in Frameworks */,
7A96AE2F246B2BCD00297C33 /* WebKit.framework in Frameworks */,
@ -368,6 +384,7 @@
7A1146FF23FDE7E500B424AF /* AutoCat */ = {
isa = PBXGroup;
children = (
7AC355552969742800889457 /* ACUIKit */,
7A530B7C24017FBE00CBFE6E /* Cells */,
7A11471423FDEAF800B424AF /* Controllers */,
7A3F07A924360D9100E59687 /* Extensions */,
@ -408,6 +425,9 @@
7A3F07AC2436350B00E59687 /* SearchController.swift */,
7AEFE727240455E200910EB7 /* SettingsController.swift */,
7A6F095D26DB9F85003A965D /* NotesController.swift */,
7AC3554B29696A1C00889457 /* MainTabController.swift */,
7AC3554D29696C4500889457 /* DummyNewController.swift */,
7AC3554F29696D5A00889457 /* NewNumberController.swift */,
);
path = Controllers;
sourceTree = "<group>";
@ -558,6 +578,33 @@
path = Location;
sourceTree = "<group>";
};
7AC355552969742800889457 /* ACUIKit */ = {
isa = PBXGroup;
children = (
7AC355562969743800889457 /* Views */,
7AC355572969744100889457 /* Extensions */,
);
path = ACUIKit;
sourceTree = "<group>";
};
7AC355562969743800889457 /* Views */ = {
isa = PBXGroup;
children = (
7AC35553296973E100889457 /* ACButton.swift */,
);
path = Views;
sourceTree = "<group>";
};
7AC355572969744100889457 /* Extensions */ = {
isa = PBXGroup;
children = (
7AC3555129696E3F00889457 /* UIView+layout.swift */,
7AC355582969746600889457 /* UIControl.swift */,
7AC3555A296995B200889457 /* UIEdgeInsets.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
7AF6D1DE2677A7E00086EA64 /* AutoCatTests */ = {
isa = PBXGroup;
children = (
@ -642,6 +689,7 @@
7A813DC02508C4D900CC93B9 /* ExceptionCatcher */,
7AABDE1C2532F3EB0041AFC6 /* PKHUD */,
7A35177A27E23F8800DC538C /* Eureka */,
7AC355492969652F00889457 /* SwiftEntryKit */,
);
productName = AutoCat;
productReference = 7A1146FD23FDE7E500B424AF /* AutoCat.app */;
@ -734,6 +782,7 @@
7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */,
7AABDE21253327F10041AFC6 /* XCRemoteSwiftPackageReference "DifferenceKit" */,
7A35177927E23F8800DC538C /* XCRemoteSwiftPackageReference "Eureka" */,
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */,
);
productRefGroup = 7A1146FE23FDE7E500B424AF /* Products */;
projectDirPath = "";
@ -792,12 +841,14 @@
7A6DD90824329144009DE740 /* CenterTextLayer.swift in Sources */,
7A99406426E4BFAE002E9CB6 /* VehicleNoteCell.swift in Sources */,
7A8AB76B25A1D95500ECF2C1 /* SourceStatusRow.swift in Sources */,
7AC3554C29696A1C00889457 /* MainTabController.swift in Sources */,
7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */,
7A3F07AD2436350B00E59687 /* SearchController.swift in Sources */,
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */,
7A0B96A0257D6D4B000B39AD /* MultilineLabelRow.swift in Sources */,
7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */,
7A761C0B267E8FF90005F28F /* Error.swift in Sources */,
7AC3555029696D5A00889457 /* NewNumberController.swift in Sources */,
7AE26A3524F31B0700625033 /* EventsController.swift in Sources */,
7A2DE69E2589606A00A113FC /* ImageGridRow.swift in Sources */,
7AB67E8C2435C38700258F61 /* CustomTextField.swift in Sources */,
@ -808,6 +859,7 @@
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */,
7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */,
7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */,
7AC3554E29696C4500889457 /* DummyNewController.swift in Sources */,
7A659B5B24A3768A0043A0F2 /* Substrings.swift in Sources */,
7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */,
7A27ADF7249FEF690035F39E /* Recorder.swift in Sources */,
@ -836,10 +888,14 @@
7A813DCB250B5DC900CC93B9 /* LocationPickerController.swift in Sources */,
7A9FEEC82529AB23001CA50E /* RxRealmDataSource.swift in Sources */,
7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */,
7AC3555229696E3F00889457 /* UIView+layout.swift in Sources */,
7A0420AD2561A0B100034941 /* OsagoController.swift in Sources */,
7AC355592969746600889457 /* UIControl.swift in Sources */,
7AB67E8E2435D1A000258F61 /* CustomButton.swift in Sources */,
7A21112A24FC3D7E003BBF6F /* AudioEngine.swift in Sources */,
7AC35554296973E100889457 /* ACButton.swift in Sources */,
7A8A220B248D67B60073DFD9 /* VehicleReportImage.swift in Sources */,
7AC3555B296995B200889457 /* UIEdgeInsets.swift in Sources */,
7ADF6C95250D037700F237B2 /* ShowEventController.swift in Sources */,
7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */,
7AE492A1259232F000322D2E /* MultilineLinkRow.swift in Sources */,
@ -1080,7 +1136,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 100;
CURRENT_PROJECT_VERSION = 101;
DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -1105,7 +1161,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 100;
CURRENT_PROJECT_VERSION = 101;
DEVELOPMENT_TEAM = 46DTTB8X4S;
INFOPLIST_FILE = AutoCat/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
@ -1329,6 +1385,14 @@
minimumVersion = 1.1.5;
};
};
7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/huri000/SwiftEntryKit";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
};
};
7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";
@ -1380,6 +1444,11 @@
package = 7AABDE1B2532F3EB0041AFC6 /* XCRemoteSwiftPackageReference "PKHUD" */;
productName = PKHUD;
};
7AC355492969652F00889457 /* SwiftEntryKit */ = {
isa = XCSwiftPackageProductDependency;
package = 7AC355482969652F00889457 /* XCRemoteSwiftPackageReference "SwiftEntryKit" */;
productName = SwiftEntryKit;
};
7AF58D332402A91C00CE01A0 /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = 7AF58D322402A91C00CE01A0 /* XCRemoteSwiftPackageReference "Kingfisher" */;

View File

@ -89,6 +89,15 @@
"revision" : "6190d0cefff3013e77ed567e6b074f324e5c5bf5",
"version" : "6.3.1"
}
},
{
"identity" : "swiftentrykit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/huri000/SwiftEntryKit",
"state" : {
"revision" : "5ad36cccf0c4b9fea32f4e9b17a8e38f07563ef0",
"version" : "2.0.0"
}
}
],
"version" : 2

View File

@ -4,38 +4,18 @@
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint">
<BreakpointContent
uuid = "C14D0996-5708-44D2-A6BA-4A4B50B522EE"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
<BreakpointContent
uuid = "CF01B44D-372B-4C78-A197-7FDEC607CE0E"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
scope = "1"
stopOnStyle = "0">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SymbolicBreakpoint">
<BreakpointContent
uuid = "B15A9E9C-A0CD-4FC9-8E24-DD93FB1B677F"
shouldBeEnabled = "No"
uuid = "676638C8-1CC5-4C04-98B0-1C0D6CB28B76"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "UITableViewAlertForLayoutOutsideViewHierarchy"
moduleName = "">
<Locations>
<Location
uuid = "B15A9E9C-A0CD-4FC9-8E24-DD93FB1B677F - 620169ab4c7c265a"
uuid = "676638C8-1CC5-4C04-98B0-1C0D6CB28B76 - 620169ab4c7c265a"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
@ -47,74 +27,5 @@
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "CFC2DD73-A257-45F3-A7A8-7D9462944F86"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AutoCat/Extensions/AudioEngine.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "12"
endingLineNumber = "12"
landmarkName = "setCategoryAsync(_:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.SwiftErrorBreakpoint">
<BreakpointContent
uuid = "4F72AC4F-FBCE-4C10-8BC2-2F434652DB31"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
<BreakpointContent
uuid = "42580582-DA14-40D4-869A-FF91FCA9957C"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
breakpointStackSelectionBehavior = "1"
scope = "1"
stopOnStyle = "0">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "CA002588-A70B-4F96-BD13-7CC6C39BC2D5"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../../../Library/Developer/Xcode/DerivedData/AutoCat-fhilwnlnsrpirleiajogdcyhyyey/SourcePackages/checkouts/RxSwift/RxCocoa/Foundation/URLSession+Rx.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "193"
endingLineNumber = "193"
landmarkName = "data(request:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "2B5BCCAE-FCA2-43D1-9835-08DB7E24B91C"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "AutoCat/Utils/RxRealmDataSource.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
landmarkName = "init(table:data:cellIdentifier:onSizeChanged:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -0,0 +1,35 @@
import UIKit
extension UIControl {
func addAction(for controlEvent: UIControl.Event = .touchUpInside, _ closure: @escaping () -> Void) {
addActionImpl(for: controlEvent, closure: closure)
}
func addActionAsync(for controlEvent: UIControl.Event = .touchUpInside, _ closure: @escaping () async -> Void) {
addActionImpl(for: controlEvent) {
Task {
await closure()
}
}
}
func addActionImpl(for controlEvents: UIControl.Event, closure: @escaping () -> Void) {
if #available(iOS 14.0, *) {
addAction(UIAction { _ in closure() }, for: controlEvents)
} else {
@objc class ClosureSleeve: NSObject {
let closure:()->()
init(_ closure: @escaping()->()) { self.closure = closure }
@objc func invoke() { closure() }
}
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, "\(UUID())", sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}

View File

@ -0,0 +1,15 @@
import UIKit
extension UIEdgeInsets {
init(all: CGFloat) {
self.init(top: all, left: all, bottom: all, right: all)
}
}
extension UIEdgeInsets {
static func all(_ value: CGFloat) -> UIEdgeInsets {
.init(all: value)
}
}

View File

@ -0,0 +1,13 @@
import UIKit
extension UIView {
func pin(to view: UIView, insets: UIEdgeInsets = .zero) {
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: insets.left),
trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -insets.right),
topAnchor.constraint(equalTo: view.topAnchor, constant: insets.top),
bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -insets.bottom)
])
}
}

View File

@ -0,0 +1,80 @@
import UIKit
enum ACButtonStyle {
case generic
case roundedBlue
}
class ACButton: UIButton {
private var style: ACButtonStyle = .generic
convenience init(style: ACButtonStyle = .roundedBlue, title: String, onTap: @escaping () -> Void) {
self.init()
self.style(style)
self.onTap(onTap)
self.title(title)
translatesAutoresizingMaskIntoConstraints = false
}
convenience init(style: ACButtonStyle = .roundedBlue, title: String, onTapAsync: @escaping () async -> Void) {
self.init()
self.style(style)
self.onTapAsync(onTapAsync)
self.title(title)
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
if style == .roundedBlue {
self.layer.opacity = self.isEnabled ? 1 : 0.5
} else {
self.layer.opacity = 1
}
}
@discardableResult
func style(_ style: ACButtonStyle) -> ACButton {
self.style = style
switch style {
case .generic:
break
case .roundedBlue:
backgroundColor = .systemBlue
layer.cornerRadius = 6
}
return self
}
@discardableResult
func title(_ title: String) -> ACButton {
setTitle(title, for: .normal)
return self
}
@discardableResult
func enable(_ enabled: Bool) -> ACButton {
isEnabled = enabled
return self
}
@discardableResult
func onTap(_ handler: @escaping () -> Void) -> ACButton {
addAction(for: .touchUpInside, handler)
return self
}
@discardableResult
func onTapAsync(_ handler: @escaping () async -> Void) -> ACButton {
addActionAsync(for: .touchUpInside, handler)
return self
}
}

View File

@ -173,17 +173,17 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="EventCell" id="QIb-Hv-tvk" customClass="EventCell" customModule="AutoCat" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="375" height="430.5"/>
<rect key="frame" x="0.0" y="50" width="375" height="431.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QIb-Hv-tvk" id="Ypt-ch-fGT">
<rect key="frame" x="0.0" y="0.0" width="375" height="430.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="431.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="HP8-oO-yhP">
<rect key="frame" x="16" y="8" width="343" height="414.5"/>
<rect key="frame" x="16" y="8" width="343" height="415.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="k4Z-KM-byE">
<rect key="frame" x="0.0" y="0.0" width="335" height="414.5"/>
<rect key="frame" x="0.0" y="0.0" width="335" height="415.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xcQ-Wz-gJ0">
<rect key="frame" x="0.0" y="0.0" width="335" height="201"/>
@ -192,7 +192,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1tQ-zM-6T9">
<rect key="frame" x="0.0" y="209" width="335" height="205.5"/>
<rect key="frame" x="0.0" y="209" width="335" height="206.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<color key="textColor" systemColor="secondaryLabelColor"/>
<nil key="highlightedColor"/>
@ -200,7 +200,7 @@
</subviews>
</stackView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="750" verticalHuggingPriority="251" image="person" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="CFI-xa-eLs">
<rect key="frame" x="343" y="1.5" width="0.0" height="412"/>
<rect key="frame" x="343" y="1.5" width="0.0" height="413"/>
</imageView>
</subviews>
</stackView>
@ -597,50 +597,19 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="Peq-Zq-kNT">
<rect key="frame" x="62.5" y="68" width="250" height="114.5"/>
<subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="A001AA 777" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="8FU-Gy-4MU" customClass="SwiftMaskTextfield" customModule="AutoCat" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="250" height="50.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle0"/>
<textInputTraits key="textInputTraits" returnKeyType="done"/>
<connections>
<action selector="textFieldDidBeginEditing:" destination="twc-qR-t1G" eventType="editingDidBegin" id="Kvu-xK-QuZ"/>
<action selector="textFieldDidChange:" destination="twc-qR-t1G" eventType="editingChanged" id="x69-Ym-Zdm"/>
</connections>
</textField>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" 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"/>
<color key="backgroundColor" systemColor="systemBlueColor"/>
<constraints>
<constraint firstAttribute="height" constant="48" id="YE0-lL-OwL"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<state key="normal" title="Check">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="checkTapped:" destination="twc-qR-t1G" eventType="touchUpInside" id="WiK-lL-7q7"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="250" id="lqO-Yy-NyQ"/>
</constraints>
</stackView>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" keyboardDismissMode="onDrag" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="JKr-UE-x8f">
<rect key="frame" x="0.0" y="206.5" width="375" height="411.5"/>
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" selectionStyle="blue" hidesAccessoryWhenEditing="NO" indentationLevel="1" indentationWidth="0.0" reuseIdentifier="VehicleCell" id="3ON-lr-UlV" customClass="VehicleCell" customModule="AutoCat" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="375" height="84.5"/>
<rect key="frame" x="0.0" y="50" width="375" height="94.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="3ON-lr-UlV" id="IGw-UK-ebp">
<rect key="frame" x="0.0" y="0.0" width="375" height="84.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="94.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="6IZ-gM-D28">
<rect key="frame" x="8" y="8" width="359" height="68.5"/>
<rect key="frame" x="8" y="8" width="359" height="78.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="Rz6-wH-O1q">
<rect key="frame" x="0.0" y="0.0" width="359" height="20"/>
@ -675,18 +644,15 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" alignment="bottom" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="nX1-KA-jqp">
<rect key="frame" x="0.0" y="28" width="359" height="40.5"/>
<rect key="frame" x="0.0" y="28" width="359" height="50.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VHX-o0-3BP" customClass="PlateView" customModule="AutoCat" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.5" width="295" height="40"/>
<rect key="frame" x="0.0" y="0.5" width="295" height="50"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="tintColor" systemColor="systemOrangeColor"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="vzo-oF-mWb"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="ApN-fV-Qw4">
<rect key="frame" x="303" y="0.5" width="56" height="40"/>
<rect key="frame" x="303" y="10.5" width="56" height="40"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="1.01.2021" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rKr-3e-WYb">
<rect key="frame" x="0.0" y="0.0" width="56" height="16"/>
@ -731,11 +697,9 @@
<viewLayoutGuide key="safeArea" id="3Jk-FW-b3r"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="JKr-UE-x8f" firstAttribute="top" secondItem="3Jk-FW-b3r" secondAttribute="top" id="PUg-nV-TN6"/>
<constraint firstItem="JKr-UE-x8f" firstAttribute="trailing" secondItem="3Jk-FW-b3r" secondAttribute="trailing" id="Tc5-ri-5RD"/>
<constraint firstItem="Peq-Zq-kNT" firstAttribute="top" secondItem="3Jk-FW-b3r" secondAttribute="top" constant="24" id="Tft-8o-Cb1"/>
<constraint firstItem="JKr-UE-x8f" firstAttribute="bottom" secondItem="3Jk-FW-b3r" secondAttribute="bottom" id="Zwj-XD-UhD"/>
<constraint firstItem="Peq-Zq-kNT" firstAttribute="centerX" secondItem="g5m-iL-O7A" secondAttribute="centerX" id="fXl-Na-1gb"/>
<constraint firstItem="JKr-UE-x8f" firstAttribute="top" secondItem="Peq-Zq-kNT" secondAttribute="bottom" constant="24" id="mm9-rL-KQ1"/>
<constraint firstItem="JKr-UE-x8f" firstAttribute="bottom" secondItem="g5m-iL-O7A" secondAttribute="bottom" id="Zwj-XD-UhD"/>
<constraint firstItem="JKr-UE-x8f" firstAttribute="leading" secondItem="3Jk-FW-b3r" secondAttribute="leading" id="vfN-fu-5pA"/>
</constraints>
</view>
@ -754,9 +718,7 @@
</rightBarButtonItems>
</navigationItem>
<connections>
<outlet property="check" destination="Ync-fd-xQI" id="oxC-p6-Mou"/>
<outlet property="history" destination="JKr-UE-x8f" id="GP9-Gz-WBm"/>
<outlet property="number" destination="8FU-Gy-4MU" id="PwP-Pn-eeO"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="h7Y-mR-2Ti" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
@ -812,10 +774,10 @@
</objects>
<point key="canvasLocation" x="1704.8" y="-701.19940029985014"/>
</scene>
<!--Tab Bar Controller-->
<!--Main Tab Controller-->
<scene sceneID="YhQ-kn-py3">
<objects>
<tabBarController id="s9R-9a-TOT" sceneMemberID="viewController">
<tabBarController id="s9R-9a-TOT" customClass="MainTabController" customModule="AutoCat" customModuleProvider="target" sceneMemberID="viewController">
<tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="7gI-Jq-j4q">
<rect key="frame" x="0.0" y="0.0" width="414" height="49"/>
<autoresizingMask key="autoresizingMask"/>
@ -824,6 +786,7 @@
<connections>
<segue destination="TSb-ZG-qfD" kind="relationship" relationship="viewControllers" id="Bwf-98-gjF"/>
<segue destination="RK6-pn-2Bg" kind="relationship" relationship="viewControllers" id="KNz-WF-Kyy"/>
<segue destination="dxo-XS-x8Q" kind="relationship" relationship="viewControllers" id="MgV-gx-h7p"/>
<segue destination="GCa-Re-j14" kind="relationship" relationship="viewControllers" id="FGp-f6-fUh"/>
<segue destination="4jU-Z3-PF2" kind="relationship" relationship="viewControllers" id="aH2-IT-86l"/>
</connections>
@ -951,6 +914,22 @@
</objects>
<point key="canvasLocation" x="199" y="143"/>
</scene>
<!--Add-->
<scene sceneID="bCG-fh-cdJ">
<objects>
<viewController id="dxo-XS-x8Q" customClass="DummyNewController" customModule="AutoCat" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="7Rw-QC-Av7">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="vIj-pz-YHR"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
<tabBarItem key="tabBarItem" title="Add" image="plus" catalog="system" id="X0w-PF-Dn8"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="C2T-ZN-q8G" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="929" y="-260"/>
</scene>
<!--Global Events Controller-->
<scene sceneID="akP-Pw-M4Q">
<objects>
@ -989,11 +968,11 @@
</objects>
<point key="canvasLocation" x="7518" y="144"/>
</scene>
<!--Check-->
<!--History-->
<scene sceneID="pUX-kf-oY1">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="TSb-ZG-qfD" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Check" image="check" landscapeImage="check-compact" id="QJd-35-4OB"/>
<tabBarItem key="tabBarItem" title="History" image="clock.arrow.circlepath" catalog="system" id="QJd-35-4OB"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="AAc-4d-GNh">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
@ -1084,14 +1063,14 @@
</scene>
</scenes>
<resources>
<image name="check" width="25" height="25"/>
<image name="check-compact" width="18" height="18"/>
<image name="clock.arrow.circlepath" catalog="system" width="128" height="112"/>
<image name="doc.on.doc" catalog="system" width="116" height="128"/>
<image name="exclamationmark.arrow.triangle.2.circlepath" catalog="system" width="128" height="104"/>
<image name="line.horizontal.3.decrease" catalog="system" width="128" height="73"/>
<image name="map" catalog="system" width="128" height="112"/>
<image name="person" catalog="system" width="128" height="121"/>
<image name="play.fill" catalog="system" width="117" height="128"/>
<image name="plus" catalog="system" width="128" height="113"/>
<image name="record" width="31" height="31"/>
<image name="record-compact" width="23" height="23"/>
<image name="search" width="23" height="23"/>

View File

@ -23,6 +23,8 @@ class VehicleCell: UITableViewCell, ConfigurableCell {
func configure(with vehicle: Vehicle) {
self.name.text = vehicle.brand?.name?.original ?? "<unknown>"
self.plate.number = PlateNumber(vehicle.getNumber())
self.plate.fontSize = 40
if vehicle.unrecognized {
self.plate.foreground = .systemRed
} else if vehicle.outdated {

View File

@ -27,29 +27,30 @@ extension String.StringInterpolation {
}
}
class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegate, PNKeyboardDelegate {
class CheckController: UIViewController, UITableViewDelegate, UISearchResultsUpdating {
@IBOutlet weak var number: SwiftMaskTextfield!
@IBOutlet weak var check: UIButton!
@IBOutlet weak var history: UITableView!
private let bag = DisposeBag()
private var historyDataSource: RealmSectionedDataSource<Vehicle, VehicleCell>!
private var historyFilter: HistoryFilter = .all
private let searchController = UISearchController(searchResultsController: nil)
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = NSLocalizedString("Search plate numbers", comment: "")
searchController.hidesNavigationBarDuringPresentation = false
navigationItem.searchController = searchController
guard let realm = try? Realm() else { return }
self.hideKeyboardWhenTappedAround()
let keyboard = PNKeyboard(target: self.number)
keyboard.delegate = self
//self.number.formatPattern = "@###@@###"
self.number.inputView = keyboard
self.check.isEnabled = false
DispatchQueue.main.async {
self.historyDataSource = RealmSectionedDataSource(table: self.history, data: realm.objects(Vehicle.self).sorted(byKeyPath: "updatedDate", ascending: false)) { count in
@ -61,11 +62,6 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
NotificationCenter.default.addObserver(self, selector:#selector(self.calendarDayDidChange(_:)), name:NSNotification.Name.NSCalendarDayChanged, object:nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//self.navigationController?.setNavigationBarHidden(true, animated: false)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if let index = self.history.indexPathForSelectedRow {
@ -86,7 +82,9 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
switch ad.quickAction {
case .check:
ad.quickAction = .none
self.number.becomeFirstResponder()
if let tabBar = tabBarController as? MainTabController {
tabBar.showCheckPuller()
}
break
case .checkNumber(let number, let event):
ad.quickAction = .none
@ -204,13 +202,8 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
// MARK: - Checking new number
@IBAction func checkTapped(_ sender: UIButton) {
guard let number = self.number.text else { return }
func checkTapped(number: String) {
let numberNormalized = number.filter { !$0.isWhitespace }.uppercased()
self.number.resignFirstResponder()
self.number.text = nil
self.check.isEnabled = false
var events: [VehicleEvent] = []
do {
@ -258,29 +251,6 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
}
}
// MARK: - UITextFieldDelegate
@IBAction func textFieldDidBeginEditing(_ textField: UITextField) {
RxLocationManager.requestCurrentLocation().subscribe().disposed(by: self.bag)
}
@IBAction func textFieldDidChange(_ textField: UITextField) {
guard let text = textField.text else {
self.check.isEnabled = false
return
}
self.check.isEnabled = text.count >= 8
}
// MARK: - PNKeyboardDelegate
func returnClicked() {
if self.check.isEnabled {
self.checkTapped(self.check)
}
}
// MARK: - UITableViewDelegate
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
@ -322,6 +292,23 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
}
}
// MARK: - UISearchResultsUpdating
func updateSearchResults(for searchController: UISearchController) {
guard let realm = try? Realm() else {
return
}
var query = realm.objects(Vehicle.self)
.sorted(byKeyPath: "updatedDate", ascending: false)
if let text = searchController.searchBar.text?.uppercased(), !text.isEmpty {
query = query.filter("number CONTAINS '\(text)'")
}
historyDataSource.setQuery(query)
}
// MARK: - Contextual actions
func update(vehicle: Vehicle) {

View File

@ -0,0 +1,6 @@
import UIKit
/// Dummy controller for detecting tap on "plus" tab
class DummyNewController: UIViewController {
}

View File

@ -0,0 +1,55 @@
import UIKit
import SwiftEntryKit
import AutoCatCore
import RxSwift
class MainTabController: UITabBarController, UITabBarControllerDelegate {
private let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
if viewController is DummyNewController {
showCheckPuller()
return false
} else {
return true
}
}
func showCheckPuller() {
guard let checkNav = viewControllers?.first as? UINavigationController,
let checkController = checkNav.viewControllers.first as? CheckController
else {
return
}
var attributes = EKAttributes.bottomToast
attributes.displayDuration = .infinity
attributes.entryBackground = .color(color: .init(.secondarySystemBackground))
attributes.screenBackground = .color(color: EKColor(UIColor(white: 0, alpha: 0.7)))
attributes.roundCorners = .top(radius: 24)
attributes.screenInteraction = .dismiss
attributes.entryInteraction = .forward
attributes.entranceAnimation = .init(translate: .init(duration: 0.2))
attributes.exitAnimation = .init(translate: .init(duration: 0.2))
let newNumberController = NewNumberController()
newNumberController.onCheck = { number in
SwiftEntryKit.dismiss {
self.selectedIndex = 0
checkController.checkTapped(number: number)
}
}
SwiftEntryKit.display(entry: newNumberController, using: attributes)
// User probably just saw a vehicle and is about to start entering plate number
// Requesting current location ASAP while we still close to initial location
RxLocationManager.requestCurrentLocation().subscribe().disposed(by: self.bag)
}
}

View File

@ -0,0 +1,83 @@
import UIKit
import AutoCatCore
class NewNumberController: UIViewController {
public var onCheck: ((String) -> Void)?
private lazy var keyboardView: PNKeyboard = {
let keyboard = PNKeyboard(target: self.plateView)
keyboard.delegate = self
return keyboard
}()
private lazy var plateView: PlateView = {
let view = PlateView(frame: .zero)
view.fontSize = 48
view.number = PlateNumber("")
view.onChange = onNumberChanged
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
view.accessibilityIdentifier = "plateView"
return view
}()
private lazy var checkButton: ACButton = {
let button = ACButton(title: NSLocalizedString("Check", comment: ""), onTap: check)
button.isEnabled = false
button.contentEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 8)
button.accessibilityIdentifier = "checkButton"
return button
}()
private lazy var stackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [plateView, checkButton])
stack.axis = .horizontal
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
private let titleLabel: UILabel = {
let label = UILabel()
label.text = NSLocalizedString("Check number", comment: "")
label.font = UIFont.preferredFont(forTextStyle: .headline)
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private lazy var mainStackView: UIStackView = {
let stack = UIStackView(arrangedSubviews: [titleLabel, stackView, keyboardView])
stack.axis = .vertical
stack.spacing = 16
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mainStackView)
mainStackView.pin(to: view, insets: .init(top: 16, left: 16, bottom: 16, right: 16))
}
func check() {
guard let number = plateView.number, number.isValid else {
return
}
let numberNormalized = number.asString().filter { !$0.isWhitespace }.uppercased()
onCheck?(numberNormalized)
}
func onNumberChanged() {
checkButton.isEnabled = plateView.number?.isValid ?? false
}
}
extension NewNumberController: PNKeyboardDelegate {
func returnClicked() {
check()
}
}

View File

@ -15,10 +15,12 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
var vehicle: Vehicle? {
didSet {
loadViewIfNeeded()
self.updateReport()
self.form.allSections.forEach { $0.reload() }
self.navigationController?.setNavigationBarHidden(self.vehicle == nil, animated: false)
//loadViewIfNeeded()
if isViewLoaded {
self.updateReport()
self.form.allSections.forEach { $0.reload() }
self.navigationController?.setNavigationBarHidden(self.vehicle == nil, animated: false)
}
}
}
@ -205,8 +207,8 @@ class ReportController: FormViewController, MediaBrowserViewControllerDataSource
return value ? yes : no
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let ad = UIApplication.shared.delegate as? AppDelegate else { return }
self.navigationController?.setNavigationBarHidden(self.vehicle == nil, animated: animated)

View File

@ -33,6 +33,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.placeholder = NSLocalizedString("Search plate numbers", comment: "")
searchController.hidesNavigationBarDuringPresentation = false
navigationItem.searchController = searchController
definesPresentationContext = true

View File

@ -18,16 +18,23 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
private var cellIdentifier: String
private var filterPredicate: FilterPredicate<Item>?
private let onSizeChanged: ((Int) -> Void)?
init(table: UITableView, data: Results<Item>, cellIdentifier: String = String(describing: Cell.self), onSizeChanged: ((Int) -> Void)? = nil) {
self.tv = table
self.data = data
self.cellIdentifier = cellIdentifier
self.onSizeChanged = onSizeChanged
super.init()
self.tv.dataSource = self
self.tv.reloadData()
startObservingChanges()
}
func startObservingChanges() {
self.notificationToken = self.data.observe { changes in
onSizeChanged?(self.data.count)
self.onSizeChanged?(self.data.count)
switch changes {
case .initial:
self.reload(animated: false)
@ -170,4 +177,9 @@ class RealmSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource
return result
}
func setQuery(_ query: Results<Item>) {
self.data = query
startObservingChanges()
}
}

View File

@ -66,11 +66,9 @@ class RxSectionedDataSource<Item,Cell>: NSObject, UITableViewDataSource where I
DispatchQueue.global().async {
let newSections = self.items.groupedByDate(type: self.sortParam)
let changeset = StagedChangeset(source: self.sections, target: newSections)
DispatchQueue.main.async {
self.tv.reload(using: changeset, with: .automatic) { newSects in
self.sections = newSects
}
self.sections = newSections
self.tv.reloadData()
}
}
}

View File

@ -110,8 +110,11 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
private weak var target: UIKeyInput?
weak var delegate: PNKeyboardDelegate?
init(target: UIKeyInput) {
private let insets: UIEdgeInsets
init(target: UIKeyInput, insets: UIEdgeInsets = .zero) {
self.target = target
self.insets = insets
super.init(frame: .zero)
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
@ -176,10 +179,10 @@ class PNKeyboard: UIView, UIInputViewAudioFeedback, PNButtonDelegate {
self.addSubview(mainStack)
NSLayoutConstraint.activate([
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16),
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16),
mainStack.topAnchor.constraint(equalTo: self.topAnchor, constant: 16),
mainStack.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -16)
mainStack.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: insets.left),
mainStack.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -insets.right),
mainStack.topAnchor.constraint(equalTo: self.topAnchor, constant: insets.top),
mainStack.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -insets.bottom)
])
}

View File

@ -1,6 +1,15 @@
import UIKit
import AutoCatCore
private class CALayerAnimationsDisablingDelegate: NSObject, CALayerDelegate {
static let shared = CALayerAnimationsDisablingDelegate()
private let null = NSNull()
func action(for layer: CALayer, forKey event: String) -> CAAction? {
null
}
}
class PlateView: UIView {
// Some driver plate parameters from "ГОСТ Р 50577-93"
// http://docs.cntd.ru/document/gost-r-50577-93
@ -26,10 +35,26 @@ class PlateView: UIView {
self.layoutSubviews()
}
}
var fontSize: CGFloat = 10 {
didSet {
self.layoutSubviews()
}
}
var onChange: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
func setup() {
self.layer.backgroundColor = UIColor.clear.cgColor
self.bgLayer.cornerRadius = 6 //self.bounds.height/8
@ -56,7 +81,21 @@ class PlateView: UIView {
self.flagLayer.contentsScale = UIScreen.main.scale
self.layer.addSublayer(self.flagLayer)
disableAnimations(for: [
bgLayer,
mainBgLayer,
regionBgLayer,
numberLayer,
regionLayer,
countryLayer,
flagLayer
])
}
func disableAnimations(for layers: [CALayer]) {
layers.forEach { $0.delegate = CALayerAnimationsDisablingDelegate.shared }
}
private func pathForFlag(in rect: CGRect) -> CGPath {
let path = CGMutablePath()
@ -116,15 +155,31 @@ class PlateView: UIView {
}
override var intrinsicContentSize: CGSize {
guard self.bounds.width != 0 || self.bounds.height != 0 else {
return CGSize.zero
}
let curAspectRatio = self.bounds.height/self.bounds.width
if curAspectRatio >= PlateView.aspectRatio {
return CGSize(width: self.bounds.width, height: self.bounds.width*PlateView.aspectRatio)
} else {
return CGSize(width: self.bounds.height/PlateView.aspectRatio, height: self.bounds.height)
}
let height = fontSize/1.1 + 4
let width = height/PlateView.aspectRatio
print("")
return CGSize(width: width, height: height)
}
}
extension PlateView: UIKeyInput {
var hasText: Bool {
number?.hasText ?? false
}
func insertText(_ text: String) {
if number?.insertText(text) == true {
layoutSubviews()
onChange?()
}
}
func deleteBackward() {
if number?.deleteBackward() == true {
layoutSubviews()
onChange?()
}
}
}

View File

@ -70,6 +70,9 @@
/* No comment provided by engineer. */
"Check date" = "Дата проверки";
/* No comment provided by engineer. */
"Check number" = "Проверить номер";
/* No comment provided by engineer. */
"Check parameters" = "Параметры проверки";

View File

@ -1,9 +1,6 @@
/* Class = "UIBarButtonItem"; title = "Back"; ObjectID = "7QQ-Qi-qEw"; */
"7QQ-Qi-qEw.title" = "Назад";
/* Class = "UITextField"; placeholder = "A001AA 777"; ObjectID = "8FU-Gy-4MU"; */
"8FU-Gy-4MU.placeholder" = "A001AA 777";
/* Class = "UILabel"; text = "00:00"; ObjectID = "bFQ-eU-5YJ"; */
"bFQ-eU-5YJ.text" = "00:00";
@ -31,8 +28,8 @@
/* Class = "UITabBarItem"; title = "Records"; ObjectID = "lxF-EY-z8V"; */
"lxF-EY-z8V.title" = "Аудио";
/* Class = "UITabBarItem"; title = "Check"; ObjectID = "QJd-35-4OB"; */
"QJd-35-4OB.title" = "Проверка";
/* Class = "UITabBarItem"; title = "History"; ObjectID = "QJd-35-4OB"; */
"QJd-35-4OB.title" = "История";
/* Class = "UILabel"; text = "or"; ObjectID = "SXb-1Q-TxY"; */
"SXb-1Q-TxY.text" = "или";
@ -43,8 +40,8 @@
/* Class = "UITextField"; placeholder = "Email"; ObjectID = "Uey-88-eZL"; */
"Uey-88-eZL.placeholder" = "Email";
/* Class = "UIButton"; normalTitle = "Check"; ObjectID = "Ync-fd-xQI"; */
"Ync-fd-xQI.normalTitle" = "Проверить";
/* Class = "UITabBarItem"; title = "Add"; ObjectID = "X0w-PF-Dn8"; */
"X0w-PF-Dn8.title" = "Добавить";
/* Class = "UITabBarItem"; title = "Settings"; ObjectID = "zEL-ph-E2f"; */
"zEL-ph-E2f.title" = "Настройки";

View File

@ -5,12 +5,16 @@ import DifferenceKit
public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable where T: Differentiable & Hashable {
var timestamp: Double = 0
public var header: String
var dateString: String
public var elements: [T]
public var header: String {
"\(dateString) (\(elements.count))"
}
public init<C>(source: DateSection<T>, elements: C) where C : Swift.Collection, C.Element == Self.Collection.Element {
self.timestamp = source.timestamp
self.header = source.header
self.dateString = source.dateString
self.elements = Array(elements)
}
@ -31,20 +35,20 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
let date = Date(timeIntervalSince1970: timestamp)
let dateInRegion = DateInRegion(date, region: Region.current)
if dateInRegion.isToday {
self.header = NSLocalizedString("Today", comment: "")
self.dateString = NSLocalizedString("Today", comment: "")
}
else if dateInRegion.isYesterday {
self.header = NSLocalizedString("Yesterday", comment: "")
self.dateString = NSLocalizedString("Yesterday", comment: "")
} else if dateInRegion.isAfterDate(weekStart, granularity: .day) {
formatter.dateFormat = "EEEE"
self.header = formatter.string(from: date)
self.dateString = formatter.string(from: date)
} else if dateInRegion.isAfterDate(monthStart, orEqual: false, granularity: .day) {
formatter.dateStyle = .medium
formatter.timeStyle = .none
self.header = formatter.string(from: date)
self.dateString = formatter.string(from: date)
} else {
formatter.dateFormat = "LLLL yyyy"
self.header = formatter.string(from: date)
self.dateString = formatter.string(from: date)
}
self.timestamp = timestamp
@ -74,7 +78,7 @@ public struct DateSection<T>: Differentiable, DifferentiableSection, Hashable wh
// MARK: - Differentiable
public var differenceIdentifier: String {
return header
return dateString
}
public func isContentEqual(to source: DateSection<T>) -> Bool {

View File

@ -1,12 +1,15 @@
import Foundation
public class PlateNumber {
private var number: String
private var numberEnglish: String
private var number: String = ""
private var numberEnglish: String = ""
private let maxLength = 9
private let mainPartLength = 6
private let isDigitMask = [false, true, true, true, false, false, true, true, true]
public init(_ string: String) {
self.number = string
self.numberEnglish = String(self.number.map { Constants.pnLettersMap[$0] ?? $0 })
setNumber(string)
}
public func asString() -> String {
@ -14,12 +17,56 @@ public class PlateNumber {
}
public func mainPart() -> String {
let index = self.numberEnglish.index(self.numberEnglish.startIndex, offsetBy: 6)
return String(self.numberEnglish[..<index])
return String(self.numberEnglish.prefix(mainPartLength))
}
public func region() -> String {
let index = self.numberEnglish.index(self.numberEnglish.startIndex, offsetBy: 6)
return String(self.numberEnglish[index...])
guard numberEnglish.count > mainPartLength else {
return ""
}
return String(self.numberEnglish.dropFirst(mainPartLength))
}
public var isValid: Bool {
number.count >= 8
}
public func setNumber(_ number: String) {
self.number = number
self.numberEnglish = String(self.number.map { Constants.pnLettersMap[$0] ?? $0 })
}
// MARK: - UIKeyInput
public var hasText: Bool {
return !number.isEmpty
}
// Returns true if number was changed
public func insertText(_ text: String) -> Bool {
guard number.count < maxLength, text.count == 1 else {
return false
}
// TODO: Do proper validation
// if text.first?.isNumber == isDigitMask[number.count] {
// setNumber(number + text)
// return true
// } else {
// return false
// }
setNumber(number + text)
return true
}
public func deleteBackward() -> Bool {
guard !number.isEmpty else {
return false
}
setNumber(String(number.dropLast()))
return true
}
}

View File

@ -364,7 +364,7 @@ public class Vehicle: Object, Decodable, Identifiable, Differentiable, Cloneable
// MARK: - Exportable
public static var csvHeader: String {
return "Model, Color, Year, Plate Number, VIN, STS, PTS, Added Date, Updated date, Locations"
return "Plate Number, Model, Color, Year, Owners, VIN, STS, PTS, Added Date, Updated date, Locations, Notes"
}
public var csvLine: String {
@ -379,16 +379,22 @@ public class Vehicle: Object, Decodable, Identifiable, Differentiable, Cloneable
eventsString.append(location + "; " + date + "\r\n")
}
return String(format: "\"%@\", %@, %d, %@, %@, %@, %@, \"%@\", \"%@\", \"%@\"",
let notesString = self.notes.reduce("") { partialResult, note in
partialResult + note.text + "\r\n"
}
return String(format: "%@, \"%@\", %@, %d, %d, %@, %@, %@, \"%@\", \"%@\", \"%@\", \"%@\"",
self.number,
model,
self.color ?? "",
self.year,
self.number,
self.ownershipPeriods.count,
self.vin1 ?? "",
self.sts ?? "",
self.pts ?? "",
added,
updated,
eventsString)
eventsString,
notesString)
}
}