Compare commits
No commits in common. "107df9149d7e602eddcddcfa885fd30e89d39752" and "bfd97877d31c9890168ad25dbb98073ff1258155" have entirely different histories.
107df9149d
...
bfd97877d3
@ -56,6 +56,7 @@
|
||||
7A3E12D72C7B42B700EE710D /* UserDefaults+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */; };
|
||||
7A3E30F32C18840600567704 /* ActivityItemSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3E30F22C18840600567704 /* ActivityItemSource.swift */; };
|
||||
7A3F07AB24360DC800E59687 /* Dated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3F07AA24360DC800E59687 /* Dated.swift */; };
|
||||
7A3F07AD2436350B00E59687 /* SearchController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3F07AC2436350B00E59687 /* SearchController.swift */; };
|
||||
7A4322912CB2CC8A00085CF6 /* FiltersScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */; };
|
||||
7A4322932CB2CCAA00085CF6 /* FiltersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */; };
|
||||
7A4322952CB2CD0F00085CF6 /* FiltersCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4322942CB2CD0F00085CF6 /* FiltersCoordinator.swift */; };
|
||||
@ -64,10 +65,6 @@
|
||||
7A4955822D58CCF900912E66 /* HistoryFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A4955812D58CCF900912E66 /* HistoryFilter.swift */; };
|
||||
7A530B7E24017FEE00CBFE6E /* VehicleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */; };
|
||||
7A54BFD32D43B95E00176D6D /* DbUpdatePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */; };
|
||||
7A5911EE2D63226F00EC51BA /* SearchScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911ED2D63226F00EC51BA /* SearchScreen.swift */; };
|
||||
7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911EF2D63266B00EC51BA /* SearchViewModel.swift */; };
|
||||
7A5911F22D63268400EC51BA /* SearchCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5911F12D63268400EC51BA /* SearchCoordinator.swift */; };
|
||||
7A5912052D648A6000EC51BA /* AutoCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5912042D648A6000EC51BA /* AutoCancellable.swift */; };
|
||||
7A599C362C18AC7F00D47C18 /* ApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C352C18AC7F00D47C18 /* ApiError.swift */; };
|
||||
7A599C392C18B22900D47C18 /* FbRefreshTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */; };
|
||||
7A599C3B2C18B36A00D47C18 /* FbVerifyTokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */; };
|
||||
@ -127,7 +124,6 @@
|
||||
7A761C08267E8EA20005F28F /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A43F9F7246C8A6200BA5B49 /* JWT.swift */; };
|
||||
7A761C09267E8EE40005F28F /* Base64FS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A96AE32246C095700297C33 /* Base64FS.swift */; };
|
||||
7A761C0B267E8FF90005F28F /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A761C0A267E8FF90005F28F /* Error.swift */; };
|
||||
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A809F382D66755B00CF1B3C /* Error+Canceled.swift */; };
|
||||
7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A813DC22508EE4F00CC93B9 /* EventCell.swift */; };
|
||||
7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */; };
|
||||
7A8AB76525A0DB8F00ECF2C1 /* BundleVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8AB76425A0DB8F00ECF2C1 /* BundleVersion.swift */; };
|
||||
@ -329,6 +325,7 @@
|
||||
7A3E12D62C7B42B700EE710D /* UserDefaults+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Settings.swift"; sourceTree = "<group>"; };
|
||||
7A3E30F22C18840600567704 /* ActivityItemSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityItemSource.swift; sourceTree = "<group>"; };
|
||||
7A3F07AA24360DC800E59687 /* Dated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dated.swift; sourceTree = "<group>"; };
|
||||
7A3F07AC2436350B00E59687 /* SearchController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchController.swift; sourceTree = "<group>"; };
|
||||
7A4322902CB2CC8A00085CF6 /* FiltersScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersScreen.swift; sourceTree = "<group>"; };
|
||||
7A4322922CB2CCAA00085CF6 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = "<group>"; };
|
||||
7A4322942CB2CD0F00085CF6 /* FiltersCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersCoordinator.swift; sourceTree = "<group>"; };
|
||||
@ -340,10 +337,6 @@
|
||||
7A530B7D24017FEE00CBFE6E /* VehicleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleCell.swift; sourceTree = "<group>"; };
|
||||
7A530B7F2401803A00CBFE6E /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = "<group>"; };
|
||||
7A54BFD22D43B95E00176D6D /* DbUpdatePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DbUpdatePolicy.swift; sourceTree = "<group>"; };
|
||||
7A5911ED2D63226F00EC51BA /* SearchScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchScreen.swift; sourceTree = "<group>"; };
|
||||
7A5911EF2D63266B00EC51BA /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
|
||||
7A5911F12D63268400EC51BA /* SearchCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchCoordinator.swift; sourceTree = "<group>"; };
|
||||
7A5912042D648A6000EC51BA /* AutoCancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoCancellable.swift; sourceTree = "<group>"; };
|
||||
7A599C352C18AC7F00D47C18 /* ApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiError.swift; sourceTree = "<group>"; };
|
||||
7A599C382C18B22900D47C18 /* FbRefreshTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbRefreshTokenModel.swift; sourceTree = "<group>"; };
|
||||
7A599C3A2C18B36A00D47C18 /* FbVerifyTokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FbVerifyTokenModel.swift; sourceTree = "<group>"; };
|
||||
@ -405,7 +398,6 @@
|
||||
7A7158112C444A6400852088 /* AdsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdsViewModel.swift; sourceTree = "<group>"; };
|
||||
7A71EF562D0A26B200943129 /* EventModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventModel.swift; sourceTree = "<group>"; };
|
||||
7A761C0A267E8FF90005F28F /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
|
||||
7A809F382D66755B00CF1B3C /* Error+Canceled.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Error+Canceled.swift"; sourceTree = "<group>"; };
|
||||
7A813DBD2506A57100CC93B9 /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/AuthenticationServices.framework; sourceTree = DEVELOPER_DIR; };
|
||||
7A813DC22508EE4F00CC93B9 /* EventCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCell.swift; sourceTree = "<group>"; };
|
||||
7A8A2208248D10EC0073DFD9 /* ResizeImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResizeImage.swift; sourceTree = "<group>"; };
|
||||
@ -643,6 +635,7 @@
|
||||
7A96AE2C246B2B7400297C33 /* GoogleSignInController.swift */,
|
||||
7A11471523FDEB2A00B424AF /* MainSplitController.swift */,
|
||||
7A27ADF2249F8B650035F39E /* RecordsController.swift */,
|
||||
7A3F07AC2436350B00E59687 /* SearchController.swift */,
|
||||
7AC3554B29696A1C00889457 /* MainTabController.swift */,
|
||||
7AC3554D29696C4500889457 /* DummyNewController.swift */,
|
||||
7AC3554F29696D5A00889457 /* NewNumberController.swift */,
|
||||
@ -718,7 +711,6 @@
|
||||
7A1441632C297E9800E79018 /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A5911EC2D63225500EC51BA /* SearchScreen */,
|
||||
7A131FD12D37B74100DC7755 /* HistoryScreen */,
|
||||
7AB9FE202D08C28E005DE374 /* EventsScreen */,
|
||||
7ABD1B452D044A0900B43213 /* GalleryScreen */,
|
||||
@ -815,16 +807,6 @@
|
||||
path = Cells;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A5911EC2D63225500EC51BA /* SearchScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A5911ED2D63226F00EC51BA /* SearchScreen.swift */,
|
||||
7A5911EF2D63266B00EC51BA /* SearchViewModel.swift */,
|
||||
7A5911F12D63268400EC51BA /* SearchCoordinator.swift */,
|
||||
);
|
||||
path = SearchScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A599C372C18B21200D47C18 /* Firebase */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1118,7 +1100,6 @@
|
||||
7AE8424D26109F78002F6B31 /* Exportable.swift */,
|
||||
7A1CF81529A42117007962DA /* Realm.swift */,
|
||||
7A6B65B22CFB0DB500AABA6B /* NullifyDate.swift */,
|
||||
7A809F382D66755B00CF1B3C /* Error+Canceled.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@ -1139,7 +1120,6 @@
|
||||
7AABBE3A2CF9F85600346588 /* Binding+Map.swift */,
|
||||
7A912F362D381B7400002938 /* LicensePlateView.swift */,
|
||||
7AB4E42B2D397D8E0006D052 /* VehicleCellView.swift */,
|
||||
7A5912042D648A6000EC51BA /* AutoCancellable.swift */,
|
||||
);
|
||||
path = SwiftUI;
|
||||
sourceTree = "<group>";
|
||||
@ -1386,6 +1366,7 @@
|
||||
7A813DC32508EE4F00CC93B9 /* EventCell.swift in Sources */,
|
||||
7A1441682C297EFD00E79018 /* NotesViewModel.swift in Sources */,
|
||||
7AFBE8C02C3024E5003C491D /* ACHud.swift in Sources */,
|
||||
7A3F07AD2436350B00E59687 /* SearchController.swift in Sources */,
|
||||
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */,
|
||||
7AAAFADA2C4D1AFE0050410D /* Zoomable.swift in Sources */,
|
||||
7A6DD90C24335A6D009DE740 /* FlagLayer.swift in Sources */,
|
||||
@ -1397,7 +1378,6 @@
|
||||
7AB9FE262D08C2D7005DE374 /* EventsCoordinator.swift in Sources */,
|
||||
7AB9FE282D08C2F4005DE374 /* EventsViewModel.swift in Sources */,
|
||||
7A4927D52CCE438600851C01 /* OptionalBinding.swift in Sources */,
|
||||
7A5911EE2D63226F00EC51BA /* SearchScreen.swift in Sources */,
|
||||
7A17CE4A2A2E820300626A6E /* UIStackView.swift in Sources */,
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */,
|
||||
7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */,
|
||||
@ -1432,7 +1412,6 @@
|
||||
7AE24C5F251F1B4E00758E39 /* Buttons.swift in Sources */,
|
||||
7A11471A23FE839000B424AF /* AuthController.swift in Sources */,
|
||||
7A64AE742469DFB600ABE48E /* MediaContentView.swift in Sources */,
|
||||
7A5911F22D63268400EC51BA /* SearchCoordinator.swift in Sources */,
|
||||
7A1090EC24A4E3E100B4F0B2 /* CellProgressView.swift in Sources */,
|
||||
7AB9FE2A2D08CF35005DE374 /* EventsScreenMode.swift in Sources */,
|
||||
7A96AE2D246B2B7400297C33 /* GoogleSignInController.swift in Sources */,
|
||||
@ -1469,14 +1448,12 @@
|
||||
7ABD1B472D044A3200B43213 /* GalleryScreen.swift in Sources */,
|
||||
7ADF6C95250D037700F237B2 /* ShowEventController.swift in Sources */,
|
||||
7A71580C2C44453200852088 /* AdsScreen.swift in Sources */,
|
||||
7A5912052D648A6000EC51BA /* AutoCancellable.swift in Sources */,
|
||||
7A06E0B02C7065D8005731AC /* SettingsCoordinator.swift in Sources */,
|
||||
7A91894F29A2BD8700519C74 /* GestureRecognizers.swift in Sources */,
|
||||
7AFBE8CC2C3085C6003C491D /* ACProgressView.swift in Sources */,
|
||||
7A131FD32D37B75500DC7755 /* HistoryScreen.swift in Sources */,
|
||||
7AB9FE222D08C2A5005DE374 /* EventsScreen.swift in Sources */,
|
||||
7ADF6C93250B954900F237B2 /* Navigation.swift in Sources */,
|
||||
7A5911F02D63266B00EC51BA /* SearchViewModel.swift in Sources */,
|
||||
7A64AE752469DFB600ABE48E /* MediaBrowserViewController.swift in Sources */,
|
||||
7ABD1B4B2D044A7D00B43213 /* GalleryCoordinator.swift in Sources */,
|
||||
7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */,
|
||||
@ -1557,7 +1534,6 @@
|
||||
7A60D2512C5A9E4200D13F7B /* GeocoderProtocol.swift in Sources */,
|
||||
7AB4E4382D3D0C5C0006D052 /* VehiclesArchive.swift in Sources */,
|
||||
7A64A21C2C19E87B00284124 /* OsagoDto.swift in Sources */,
|
||||
7A809F392D66755B00CF1B3C /* Error+Canceled.swift in Sources */,
|
||||
7AF6D21D2677C1680086EA64 /* Osago.swift in Sources */,
|
||||
7A1CF81629A42117007962DA /* Realm.swift in Sources */,
|
||||
7A64A2142C19E3B700284124 /* VehicleEngineDto.swift in Sources */,
|
||||
@ -1769,7 +1745,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 148;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||
@ -1796,7 +1772,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 148;
|
||||
CURRENT_PROJECT_VERSION = 147;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||
|
||||
@ -8,6 +8,146 @@
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Search Controller-->
|
||||
<scene sceneID="3Md-yW-a0R">
|
||||
<objects>
|
||||
<viewController id="UPf-uT-oOr" customClass="SearchController" customModule="AutoCat" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="IJP-gV-Ojc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="dB3-iP-QRo">
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="554"/>
|
||||
<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="VEP-QD-i6y" customClass="VehicleCell" customModule="AutoCat" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="84.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VEP-QD-i6y" id="8hH-8I-XLB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="84.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="E0H-HY-s2L">
|
||||
<rect key="frame" x="8" y="8" width="359" height="68.5"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="KFu-ew-wX3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="359" height="20"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="Kia Optima" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wCR-6h-HgF">
|
||||
<rect key="frame" x="0.0" y="0.0" width="290.5" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="exclamationmark.arrow.triangle.2.circlepath" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="Qo7-51-ou9">
|
||||
<rect key="frame" x="294" y="0.5" width="21" height="19"/>
|
||||
<color key="tintColor" systemColor="systemOrangeColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="Pct-H5-e3e"/>
|
||||
<constraint firstAttribute="height" constant="20" id="UBl-5J-zAY"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" image="text.bubble" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="k5v-Ic-Stn">
|
||||
<rect key="frame" x="318.5" y="0.5" width="20" height="19.5"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="20" id="M1i-uQ-eYH"/>
|
||||
<constraint firstAttribute="width" constant="20" id="wyl-SJ-L78"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="12" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QnE-Cx-aUe">
|
||||
<rect key="frame" x="342.5" y="0.0" width="16.5" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" tag="8" contentMode="scaleToFill" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="vMC-Bk-8rZ">
|
||||
<rect key="frame" x="0.0" y="28" width="359" height="40.5"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cvf-vM-QnT" customClass="PlateView" customModule="AutoCat" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.5" width="303" height="40"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="40" id="Xoz-Iw-PCU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="quG-e3-h5v">
|
||||
<rect key="frame" x="303" y="0.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="RPA-NR-0C6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="56" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aOM-gr-Yem">
|
||||
<rect key="frame" x="23" y="24" width="33" height="16"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleFootnote"/>
|
||||
<color key="textColor" systemColor="tertiaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="E0H-HY-s2L" secondAttribute="bottom" constant="8" id="Ijr-HL-UZx"/>
|
||||
<constraint firstItem="E0H-HY-s2L" firstAttribute="top" secondItem="8hH-8I-XLB" secondAttribute="top" constant="8" id="SbP-00-gE3"/>
|
||||
<constraint firstItem="E0H-HY-s2L" firstAttribute="leading" secondItem="8hH-8I-XLB" secondAttribute="leading" constant="8" id="V57-T6-4bW"/>
|
||||
<constraint firstAttribute="trailing" secondItem="E0H-HY-s2L" secondAttribute="trailing" constant="8" id="hxm-tH-1Ja"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<inset key="separatorInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
|
||||
<connections>
|
||||
<outlet property="addedDate" destination="aOM-gr-Yem" id="aQL-6B-r1G"/>
|
||||
<outlet property="bubbleImage" destination="k5v-Ic-Stn" id="bZR-Qo-0FU"/>
|
||||
<outlet property="name" destination="wCR-6h-HgF" id="rXr-iq-H2Y"/>
|
||||
<outlet property="notesCount" destination="QnE-Cx-aUe" id="KlD-As-G9y"/>
|
||||
<outlet property="plate" destination="cvf-vM-QnT" id="8Bm-gA-bbm"/>
|
||||
<outlet property="syncImage" destination="Qo7-51-ou9" id="4fV-fJ-Q4y"/>
|
||||
<outlet property="updatedDate" destination="RPA-NR-0C6" id="c0e-Ew-Bc3"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="6Uh-9i-MKR"/>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="6Uh-9i-MKR" firstAttribute="bottom" secondItem="dB3-iP-QRo" secondAttribute="bottom" id="Qq7-Rm-AvA"/>
|
||||
<constraint firstItem="dB3-iP-QRo" firstAttribute="top" secondItem="6Uh-9i-MKR" secondAttribute="top" id="SUK-IH-xTR"/>
|
||||
<constraint firstItem="dB3-iP-QRo" firstAttribute="leading" secondItem="6Uh-9i-MKR" secondAttribute="leading" id="XTb-EI-bvm"/>
|
||||
<constraint firstItem="dB3-iP-QRo" firstAttribute="trailing" secondItem="6Uh-9i-MKR" secondAttribute="trailing" id="w3e-Ir-qlo"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" id="kEp-Rw-7hu">
|
||||
<barButtonItem key="backBarButtonItem" title="Back" id="7QQ-Qi-qEw"/>
|
||||
<rightBarButtonItems>
|
||||
<barButtonItem image="line.horizontal.3.decrease" catalog="system" id="mvq-Q5-tVc">
|
||||
<connections>
|
||||
<action selector="onFilterTapped:" destination="UPf-uT-oOr" id="eCM-JW-z1h"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
<barButtonItem image="map" catalog="system" id="iVh-uQ-fX5">
|
||||
<connections>
|
||||
<action selector="onMapTapped:" destination="UPf-uT-oOr" id="ekk-vL-cGI"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</rightBarButtonItems>
|
||||
</navigationItem>
|
||||
<connections>
|
||||
<outlet property="showMapButton" destination="iVh-uQ-fX5" id="19X-3N-bDb"/>
|
||||
<outlet property="tableView" destination="dB3-iP-QRo" id="b3n-R9-6lI"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="xsk-7S-rvc" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="4200.8000000000002" y="142.57871064467767"/>
|
||||
</scene>
|
||||
<!--Voice records-->
|
||||
<scene sceneID="9pI-G0-wG0">
|
||||
<objects>
|
||||
@ -166,6 +306,7 @@
|
||||
</tabBar>
|
||||
<connections>
|
||||
<segue destination="RK6-pn-2Bg" kind="relationship" relationship="viewControllers" id="KNz-WF-Kyy"/>
|
||||
<segue destination="GCa-Re-j14" kind="relationship" relationship="viewControllers" id="FGp-f6-fUh"/>
|
||||
</connections>
|
||||
</tabBarController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="AJs-8F-Qbu" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
@ -312,7 +453,26 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="XB6-0a-b8N" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3554" y="143"/>
|
||||
<point key="canvasLocation" x="7518" y="144"/>
|
||||
</scene>
|
||||
<!--Search-->
|
||||
<scene sceneID="kiS-EQ-VFl">
|
||||
<objects>
|
||||
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="GCa-Re-j14" sceneMemberID="viewController">
|
||||
<tabBarItem key="tabBarItem" title="Search" image="search" landscapeImage="search-compact" id="gDG-z8-R0t"/>
|
||||
<toolbarItems/>
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="vdY-9n-hjX">
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
<connections>
|
||||
<segue destination="UPf-uT-oOr" kind="relationship" relationship="rootViewController" id="aun-Tj-SJT"/>
|
||||
</connections>
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="XQB-kc-hUv" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3261.5999999999999" y="142.57871064467767"/>
|
||||
</scene>
|
||||
<!--Records-->
|
||||
<scene sceneID="oyu-oz-pC4">
|
||||
@ -349,13 +509,19 @@
|
||||
</navigationController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="Zbo-fQ-UCM" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2614" y="142"/>
|
||||
<point key="canvasLocation" x="6577" y="143"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="exclamationmark.arrow.triangle.2.circlepath" catalog="system" width="128" height="117"/>
|
||||
<image name="line.horizontal.3.decrease" catalog="system" width="128" height="73"/>
|
||||
<image name="map" catalog="system" width="128" height="112"/>
|
||||
<image name="play.fill" catalog="system" width="120" height="128"/>
|
||||
<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="text.bubble" catalog="system" width="128" height="110"/>
|
||||
<systemColor name="secondaryLabelColor">
|
||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
@ -365,8 +531,14 @@
|
||||
<systemColor name="systemBlueColor">
|
||||
<color red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemOrangeColor">
|
||||
<color red="1" green="0.58431372550000005" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="systemTealColor">
|
||||
<color red="0.18823529410000001" green="0.69019607839999997" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
<systemColor name="tertiaryLabelColor">
|
||||
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.29803921570000003" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</systemColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -35,24 +35,13 @@ class EventPin: NSObject, MKAnnotation {
|
||||
|
||||
class GlobalEventsController: UIViewController {
|
||||
|
||||
var map: MKMapView!
|
||||
@IBOutlet weak var map: MKMapView!
|
||||
|
||||
var filter: Filter!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
map = MKMapView()
|
||||
map.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(map)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
map.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
map.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
map.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
map.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||
])
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
|
||||
if #available(OSX 11.0, *) {
|
||||
|
||||
@ -22,7 +22,6 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
addDummyTab()
|
||||
#endif
|
||||
addSearchTab()
|
||||
Task { await addSettings() }
|
||||
}
|
||||
|
||||
@ -43,14 +42,6 @@ class MainTabController: UITabBarController, UITabBarControllerDelegate {
|
||||
viewControllers?.insert(controller, at: 2)
|
||||
}
|
||||
|
||||
func addSearchTab() {
|
||||
let coordinator = SearchCoordinator()
|
||||
let controller = coordinator.start()
|
||||
controller.tabBarItem = UITabBarItem(title: NSLocalizedString("Search", comment: ""),
|
||||
image: UIImage(systemName: "magnifyingglass"), tag: 0)
|
||||
viewControllers?.append(controller)
|
||||
}
|
||||
|
||||
func addSettings() async {
|
||||
|
||||
let coordinator = SettingsCoordinator(tabController: self)
|
||||
|
||||
320
AutoCat/Controllers/SearchController.swift
Normal file
320
AutoCat/Controllers/SearchController.swift
Normal file
@ -0,0 +1,320 @@
|
||||
import UIKit
|
||||
import RealmSwift
|
||||
import PKHUD
|
||||
import ExceptionCatcher
|
||||
import AutoCatCore
|
||||
|
||||
class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDelegate, UIScrollViewDelegate, UISearchBarDelegate {
|
||||
|
||||
@IBOutlet weak var tableView: UITableView!
|
||||
@IBOutlet weak var showMapButton: UIBarButtonItem?
|
||||
|
||||
private var refreshButton: UIBarButtonItem!
|
||||
private var refreshIndicator: UIBarButtonItem!
|
||||
private var moreActionsButton: UIBarButtonItem?
|
||||
|
||||
private lazy var searchController: UISearchController = .default
|
||||
.placeholder(NSLocalizedString("Search plate numbers", comment: ""))
|
||||
.resultsUpdater(self)
|
||||
.searchBarDelegate(self)
|
||||
.makeDumb()
|
||||
.scopeButtons(SearchScope.allCases.map(\.title))
|
||||
|
||||
private var refreshControl = UIRefreshControl()
|
||||
private var datasource: SectionedDataSource<VehicleDto,VehicleCell>!
|
||||
private var isLoadingPage = false
|
||||
private var pageLoadingIndicator = UIActivityIndicatorView(style: .medium)
|
||||
|
||||
var filter = Filter()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.showMapButton?.isEnabled = false
|
||||
|
||||
navigationItem.searchController = searchController
|
||||
definesPresentationContext = true
|
||||
|
||||
if #available(iOS 14.0, *) {
|
||||
setupActionsMenu()
|
||||
}
|
||||
|
||||
self.refreshButton = UIBarButtonItem(image: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(refresh))
|
||||
|
||||
self.refreshIndicator = UIBarButtonItem(customView: self.pageLoadingIndicator)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
self.navigationItem.leftBarButtonItem = self.refreshButton
|
||||
#endif
|
||||
|
||||
//self.refreshControl.attributedTitle = NSAttributedString(string: "")
|
||||
self.refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
|
||||
self.tableView.addSubview(self.refreshControl)
|
||||
|
||||
self.datasource = SectionedDataSource(table: self.tableView)
|
||||
self.tableView.delegate = self
|
||||
self.tableView.keyboardDismissMode = .onDrag
|
||||
|
||||
updateSearchResults(with: filter)
|
||||
}
|
||||
|
||||
func updateSearchResults(with filter: Filter) {
|
||||
Task {
|
||||
showProgress()
|
||||
|
||||
if filter.needReset {
|
||||
self.datasource.reset()
|
||||
}
|
||||
|
||||
let vehicles = (try? await ApiService.shared.getVehicles(with: filter, pageToken: self.datasource.pageToken, pageSize: 50)) ?? PagedResponse<VehicleDto>()
|
||||
|
||||
if let count = vehicles.count {
|
||||
self.navigationItem.title = String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""), count)
|
||||
self.showMapButton?.isEnabled = count > 0
|
||||
}
|
||||
|
||||
refreshControl.endRefreshing()
|
||||
isLoadingPage = false
|
||||
pageLoadingIndicator.stopAnimating()
|
||||
hideProgress()
|
||||
datasource.update(with: vehicles)
|
||||
}
|
||||
}
|
||||
|
||||
func showProgress() {
|
||||
navigationItem.leftBarButtonItem = self.refreshIndicator
|
||||
pageLoadingIndicator.startAnimating()
|
||||
moreActionsButton?.isEnabled = false
|
||||
}
|
||||
|
||||
func hideProgress() {
|
||||
#if targetEnvironment(macCatalyst)
|
||||
navigationItem.leftBarButtonItem = self.refreshButton
|
||||
#else
|
||||
navigationItem.leftBarButtonItem = nil
|
||||
#endif
|
||||
|
||||
moreActionsButton?.isEnabled = true
|
||||
}
|
||||
|
||||
// FIXME: Code duplication
|
||||
func updateDetailController(with vehicle: VehicleDto, indexPath: IndexPath) {
|
||||
if let splitViewController = self.view.window?.rootViewController as? UISplitViewController
|
||||
{
|
||||
Task {
|
||||
let coordinator = ReportCoordinator(controller: splitViewController, vehicle: vehicle, isPersistent: false)
|
||||
if let updatedVehicle = try? await coordinator.start() {
|
||||
datasource.set(item: updatedVehicle, at: indexPath)
|
||||
tableView.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UISearchResultsUpdating
|
||||
|
||||
func updateSearchResults(for searchController: UISearchController) {
|
||||
let newQuery = searchController.searchBar.text?.uppercased() ?? ""
|
||||
guard self.filter.searchString != newQuery else { return }
|
||||
|
||||
self.filter.searchString = newQuery
|
||||
self.filter.needReset = true
|
||||
self.filter.scope = SearchScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
|
||||
updateSearchResults(with: filter)
|
||||
}
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
||||
guard let scope = SearchScope(rawValue: selectedScope) else {
|
||||
return
|
||||
}
|
||||
|
||||
filter.scope = scope
|
||||
updateSearchResults(with: filter)
|
||||
}
|
||||
|
||||
// MARK: NavigationBar actions
|
||||
|
||||
@available(iOS 14.0, *)
|
||||
func setupActionsMenu() {
|
||||
|
||||
let menu = UIMenu(children: [
|
||||
UIAction(title: NSLocalizedString("Filter results", comment: ""),
|
||||
image: UIImage(systemName: "line.horizontal.3.decrease"),
|
||||
handler: { [weak self] _ in Task { try? await self?.showFilter() } }),
|
||||
UIAction(title: NSLocalizedString("Show on map", comment: ""),
|
||||
image: UIImage(systemName: "map"),
|
||||
handler: { _ in self.showOnMap() }),
|
||||
UIAction(title: NSLocalizedString("Export", comment: ""),
|
||||
image: UIImage(systemName: "square.and.arrow.up"),
|
||||
handler: { _ in self.exportSearchResults() })
|
||||
])
|
||||
|
||||
let menuBarButton = UIBarButtonItem(title: nil, image: UIImage(systemName: "ellipsis"), primaryAction: nil, menu: menu)
|
||||
self.navigationItem.rightBarButtonItems = [menuBarButton]
|
||||
self.moreActionsButton = menuBarButton
|
||||
}
|
||||
|
||||
@IBAction func onFilterTapped(_ sender: UIBarButtonItem) {
|
||||
Task { try? await showFilter() }
|
||||
}
|
||||
|
||||
@IBAction func onMapTapped(_ sender: UIBarButtonItem) {
|
||||
showOnMap()
|
||||
}
|
||||
|
||||
@objc func refresh(_ sender: AnyObject) {
|
||||
self.showMapButton?.isEnabled = false
|
||||
self.filter.needReset = true
|
||||
updateSearchResults(with: filter)
|
||||
}
|
||||
|
||||
func showFilter() async throws {
|
||||
// let sb = UIStoryboard(name: "Main", bundle: nil)
|
||||
// let controller = sb.instantiateViewController(identifier: "FiltersController") as FiltersController
|
||||
// controller.filter = self.filter
|
||||
// controller.onDone = {
|
||||
// self.filter = controller.filter
|
||||
// self.datasource.setSortParameter(self.filter.sortBy ?? .updatedDate)
|
||||
// self.filter.needReset = true
|
||||
// self.filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
// self.updateSearchResults(with: self.filter)
|
||||
// }
|
||||
// self.navigationController?.pushViewController(controller, animated: true)
|
||||
|
||||
if let navigationController = self.navigationController {
|
||||
let coordinator = FiltersCoordinator(navController: navigationController, filter: filter)
|
||||
if let newFilter = try await coordinator.start() {
|
||||
filter = newFilter
|
||||
datasource.setSortParameter(self.filter.sortBy)
|
||||
filter.needReset = true
|
||||
filter.scope = SearchScope(rawValue: self.searchController.searchBar.selectedScopeButtonIndex) ?? .plateNumber
|
||||
updateSearchResults(with: self.filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showOnMap() {
|
||||
let sb = UIStoryboard(name: "Main", bundle: nil)
|
||||
let controller = sb.instantiateViewController(identifier: "GlobalEventsNavigation") as UINavigationController
|
||||
if let eventsVC = controller.viewControllers.first as? GlobalEventsController {
|
||||
eventsVC.filter = self.filter
|
||||
}
|
||||
|
||||
controller.modalPresentationStyle = .fullScreen
|
||||
self.present(controller, animated: true)
|
||||
}
|
||||
|
||||
func exportSearchResults() {
|
||||
Task {
|
||||
do {
|
||||
showProgress()
|
||||
let resp = try await ApiService.shared.getVehicles(with: filter, pageSize: 0)
|
||||
|
||||
let newLine = "\r\n"
|
||||
var csvString = VehicleDto.csvHeader + newLine
|
||||
|
||||
for vehicle in resp.items {
|
||||
csvString.append(vehicle.csvLine)
|
||||
csvString.append(newLine)
|
||||
}
|
||||
|
||||
let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv")
|
||||
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
|
||||
#if targetEnvironment(macCatalyst)
|
||||
self.save(file: tmpUrl)
|
||||
#else
|
||||
self.share(file: tmpUrl)
|
||||
#endif
|
||||
|
||||
hideProgress()
|
||||
} catch {
|
||||
hideProgress()
|
||||
HUD.show(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func share(file url: URL) {
|
||||
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||
self.present(activityController, animated: true)
|
||||
}
|
||||
|
||||
func save(file url: URL) {
|
||||
if #available(iOS 14, *) {
|
||||
let controller = UIDocumentPickerViewController(forExporting: [url])
|
||||
self.present(controller, animated: true)
|
||||
} else {
|
||||
let controller = UIDocumentPickerViewController(url: url, in: .exportToService)
|
||||
present(controller, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
let vehicle = self.datasource.item(at: indexPath)
|
||||
let updateAction = UIContextualAction(style: .normal, title: NSLocalizedString("Update", comment: "")) { action, view, completion in
|
||||
self.update(vehicle: vehicle, at: indexPath)
|
||||
completion(true)
|
||||
}
|
||||
updateAction.image = UIImage(systemName: "arrow.2.circlepath")
|
||||
updateAction.backgroundColor = .systemBlue
|
||||
|
||||
let configuration = UISwipeActionsConfiguration(actions: [updateAction])
|
||||
configuration.performsFirstActionWithFullSwipe = false
|
||||
return configuration
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
let vehicle = self.datasource.item(at: indexPath)
|
||||
|
||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
|
||||
let update = UIAction(title: NSLocalizedString("Update", comment: ""), image: UIImage(systemName: "arrow.2.circlepath")) { action in
|
||||
self.update(vehicle: vehicle, at: indexPath)
|
||||
}
|
||||
|
||||
return UIMenu(title: NSLocalizedString("Actions", comment: ""), children: [update])
|
||||
}
|
||||
}
|
||||
|
||||
func update(vehicle: VehicleDto, at indexPath: IndexPath) {
|
||||
|
||||
Task {
|
||||
do {
|
||||
HUD.show(.progress)
|
||||
let newVehicle = try await ApiService.shared.checkVehicle(by: vehicle.getNumber(), notes: vehicle.notes, events: [], force: true)
|
||||
HUD.hide()
|
||||
let realm = try await Realm()
|
||||
if realm.object(ofType: Vehicle.self, forPrimaryKey: vehicle.getNumber()) != nil {
|
||||
try realm.write {
|
||||
realm.add(Vehicle(dto: newVehicle), update: .all)
|
||||
}
|
||||
}
|
||||
datasource.set(item: newVehicle, at: indexPath)
|
||||
updateDetailController(with: newVehicle, indexPath: indexPath)
|
||||
} catch {
|
||||
HUD.hide()
|
||||
show(error: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let vehicle = self.datasource.item(at: indexPath)
|
||||
self.updateDetailController(with: vehicle, indexPath: indexPath)
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard tableView.contentSize.height > 0 else { return }
|
||||
|
||||
let toBottom = tableView.contentSize.height - (tableView.contentOffset.y + tableView.frame.size.height)
|
||||
if toBottom < 100 && !self.isLoadingPage && self.datasource.needMoreData() {
|
||||
self.isLoadingPage = true
|
||||
self.filter.needReset = false
|
||||
updateSearchResults(with: filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -10,9 +10,9 @@ import AutoCatCore
|
||||
import UIKit
|
||||
|
||||
@MainActor
|
||||
class FiltersCoordinator {
|
||||
class FiltersCoordinator: Coordinator {
|
||||
|
||||
let viewController: UINavigationController
|
||||
let viewController: UINavigationController?
|
||||
let filter: Filter
|
||||
|
||||
init(navController: UINavigationController, filter: Filter) {
|
||||
@ -21,13 +21,13 @@ class FiltersCoordinator {
|
||||
self.filter = filter
|
||||
}
|
||||
|
||||
func start() async -> Filter? {
|
||||
func start() async throws -> Filter? {
|
||||
let viewModel = FiltersViewModel(
|
||||
apiService: ServiceContainer.shared.resolve(ApiServiceProtocol.self),
|
||||
filter: filter
|
||||
)
|
||||
let controller = CustomHostingController(rootView: FiltersScreen(viewModel: viewModel))
|
||||
viewController.pushViewController(controller, animated: true)
|
||||
viewController?.pushViewController(controller, animated: true)
|
||||
await controller.waitForDisappear()
|
||||
return viewModel.filterResult
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ struct HistoryScreen: View {
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehiclesCount))
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
.autocorrectionDisabled()
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.asciiCapable)
|
||||
|
||||
@ -11,7 +11,7 @@ import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class ReportCoordinator {
|
||||
class ReportCoordinator: Coordinator {
|
||||
|
||||
let viewController: UIViewController?
|
||||
let vehicle: VehicleDto
|
||||
@ -26,10 +26,10 @@ class ReportCoordinator {
|
||||
self.isPersistent = isPersistent
|
||||
}
|
||||
|
||||
func start() async -> VehicleDto {
|
||||
func start() async throws -> VehicleDto {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = ReportViewModel(
|
||||
let viewModel = await ReportViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
settingsService: resolver.resolve(SettingsServiceProtocol.self),
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
//
|
||||
// SearchCoordinator.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.02.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
final class SearchCoordinator {
|
||||
|
||||
var navController = UINavigationController()
|
||||
|
||||
func start() -> UIViewController {
|
||||
|
||||
let resolver = ServiceContainer.shared
|
||||
let viewModel = SearchViewModel(
|
||||
apiService: resolver.resolve(ApiServiceProtocol.self),
|
||||
storageService: resolver.resolve(StorageServiceProtocol.self),
|
||||
vehicleService: resolver.resolve(VehicleServiceProtocol.self)
|
||||
)
|
||||
viewModel.coordinator = self
|
||||
|
||||
let view = SearchScreen(viewModel: viewModel)
|
||||
let controller = UIHostingController(rootView: view)
|
||||
|
||||
let navController = UINavigationController(rootViewController: controller)
|
||||
self.navController = navController
|
||||
return navController
|
||||
}
|
||||
|
||||
func openReport(vehicle: VehicleDto) async -> VehicleDto? {
|
||||
|
||||
let coordinator = ReportCoordinator(controller: navController,
|
||||
vehicle: vehicle,
|
||||
isPersistent: false)
|
||||
return await coordinator.start()
|
||||
}
|
||||
|
||||
func openFilterDetail(filter: Filter) async -> Filter? {
|
||||
|
||||
let coordinator = FiltersCoordinator(navController: navController, filter: filter)
|
||||
return await coordinator.start()
|
||||
}
|
||||
|
||||
func showOnMap(filter: Filter) {
|
||||
|
||||
let controller = GlobalEventsController()
|
||||
controller.filter = filter
|
||||
controller.modalPresentationStyle = .fullScreen
|
||||
navController.pushViewController(controller, animated: true)
|
||||
}
|
||||
|
||||
func export(url: URL) {
|
||||
guard let currentController = navController.visibleViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
let controller = UIDocumentPickerViewController(forExporting: [url])
|
||||
currentController.present(controller, animated: true)
|
||||
#else
|
||||
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||
currentController.present(activityController, animated: true)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
//
|
||||
// SearchScreen.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.02.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AutoCatCore
|
||||
|
||||
struct SearchScreen: View {
|
||||
|
||||
@State var viewModel: SearchViewModel
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
vehicles
|
||||
if viewModel.hasMoreData && !viewModel.vehicleSections.isEmpty {
|
||||
progressCell
|
||||
}
|
||||
}
|
||||
.listStyle(.plain)
|
||||
.hud($viewModel.hud)
|
||||
.searchable(text: $viewModel.searchText, prompt: "Search plate numbers")
|
||||
.searchPresentationToolbarBehavior(.avoidHidingContent)
|
||||
.navigationTitle(String.localizedStringWithFormat(NSLocalizedString("vehicles found", comment: ""),
|
||||
viewModel.vehiclesCount))
|
||||
.onAppear {
|
||||
Task { await viewModel.onAppear() }
|
||||
}
|
||||
.refreshable {
|
||||
Task { await viewModel.reloadData() }
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
toolbarMenu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var vehicles: some View {
|
||||
ForEach(viewModel.vehicleSections) { section in
|
||||
Section(header: Text(section.header)) {
|
||||
ForEach(section.elements) { vehicle in
|
||||
VehicleCellView(vehicle: vehicle)
|
||||
.onTapGesture {
|
||||
Task { await viewModel.openReport(vehicle: vehicle) }
|
||||
}
|
||||
.swipeActions(allowsFullSwipe: false) {
|
||||
makeActions(for: vehicle)
|
||||
}
|
||||
.contextMenu {
|
||||
makeActions(for: vehicle, useLabels: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var progressCell: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
ProgressView()
|
||||
.id(UUID())
|
||||
Spacer()
|
||||
}
|
||||
.onAppear {
|
||||
Task { await viewModel.loadMoreData() }
|
||||
}
|
||||
}
|
||||
|
||||
var toolbarMenu: some View {
|
||||
Menu("", systemImage: "ellipsis") {
|
||||
Button("Filter results", systemImage: "line.horizontal.3.decrease") {
|
||||
Task { await viewModel.openFilterDetail() }
|
||||
}
|
||||
Button("Show on map", systemImage: "map") {
|
||||
viewModel.showOnMap()
|
||||
}
|
||||
ShareLink(item: viewModel.vehiclesArchive, preview: SharePreview(VehiclesArchive.fileName))
|
||||
//ShareLink(items: [viewModel.vehiclesArchive])
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func makeActions(for vehicle: VehicleDto, useLabels: Bool = false) -> some View {
|
||||
|
||||
Button {
|
||||
Task { await viewModel.updateVehicle(vehicle) }
|
||||
} label: {
|
||||
Label(useLabels ? "Update" : "", systemImage: "arrow.2.circlepath")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// SearchScreen(viewModel: .init())
|
||||
//}
|
||||
@ -1,189 +0,0 @@
|
||||
//
|
||||
// SearchViewModel.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 17.02.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import AutoCatCore
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
@Observable
|
||||
final class SearchViewModel: ACHudContainer {
|
||||
|
||||
let apiService: ApiServiceProtocol
|
||||
let storageService: StorageServiceProtocol
|
||||
let vehicleService: VehicleServiceProtocol
|
||||
var coordinator: SearchCoordinator?
|
||||
|
||||
var hud: ACHud?
|
||||
|
||||
@ObservationIgnored
|
||||
var vehicles: [VehicleDto] = []
|
||||
var vehicleSections: [DateSection<VehicleDto>] = []
|
||||
|
||||
var filter = Filter()
|
||||
var pageToken: String?
|
||||
var hasMoreData: Bool = true
|
||||
var vehiclesCount: Int = 0
|
||||
|
||||
var searchText: String = "" {
|
||||
didSet {
|
||||
if searchText != oldValue {
|
||||
filter.searchString = searchText
|
||||
searchTask = Task { [filter] in
|
||||
try? await Task.sleep(for: .milliseconds(500))
|
||||
await reloadData(with: filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ObservationIgnored
|
||||
@AutoCancellable
|
||||
var searchTask: Task<Void, Never>?
|
||||
|
||||
var vehiclesArchive: VehiclesArchive {
|
||||
VehiclesArchive(apiService: apiService, filter: filter)
|
||||
}
|
||||
|
||||
init(apiService: ApiServiceProtocol,
|
||||
storageService: StorageServiceProtocol,
|
||||
vehicleService: VehicleServiceProtocol) {
|
||||
|
||||
self.apiService = apiService
|
||||
self.storageService = storageService
|
||||
self.vehicleService = vehicleService
|
||||
}
|
||||
|
||||
func onAppear() async {
|
||||
guard vehicles.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
resetData()
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else { return }
|
||||
try await loadSearchResults(filter: filter)
|
||||
}
|
||||
}
|
||||
|
||||
func resetData() {
|
||||
|
||||
vehicles = []
|
||||
vehicleSections = []
|
||||
pageToken = nil
|
||||
}
|
||||
|
||||
func loadSearchResults(filter: Filter) async throws {
|
||||
|
||||
let query = filter.searchString
|
||||
let response = try await apiService.getVehicles(with: filter, pageToken: pageToken, pageSize: 20)
|
||||
|
||||
if response.items.isEmpty {
|
||||
hasMoreData = false
|
||||
} else {
|
||||
vehicles += response.items
|
||||
pageToken = response.pageToken
|
||||
vehiclesCount = response.count ?? 0
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
}
|
||||
|
||||
func loadMoreData() async {
|
||||
do {
|
||||
try await loadSearchResults(filter: filter)
|
||||
} catch {
|
||||
if !error.isCanceled {
|
||||
hasMoreData = false
|
||||
hud = .error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func reloadData() async {
|
||||
await reloadData(with: filter)
|
||||
}
|
||||
|
||||
func reloadData(with filter: Filter) async {
|
||||
resetData()
|
||||
do {
|
||||
try await loadSearchResults(filter: filter)
|
||||
} catch {
|
||||
if !error.isCanceled {
|
||||
hasMoreData = false
|
||||
hud = .error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func openReport(vehicle: VehicleDto) async {
|
||||
guard let updatedVehicle = await coordinator?.openReport(vehicle: vehicle) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let index = vehicles.firstIndex(where: { $0.number == updatedVehicle.number }) {
|
||||
vehicles[index] = updatedVehicle
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
}
|
||||
|
||||
func openFilterDetail() async {
|
||||
guard let updatedFilter = await coordinator?.openFilterDetail(filter: filter) else {
|
||||
return
|
||||
}
|
||||
|
||||
filter = updatedFilter
|
||||
resetData()
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else { return }
|
||||
try await loadSearchResults(filter: filter)
|
||||
}
|
||||
}
|
||||
|
||||
func showOnMap() {
|
||||
coordinator?.showOnMap(filter: filter)
|
||||
}
|
||||
|
||||
func exportSearchResults() async {
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let resp = try await apiService.getVehicles(with: filter, pageToken: nil, pageSize: 0)
|
||||
|
||||
let newLine = "\r\n"
|
||||
var csvString = VehicleDto.csvHeader + newLine
|
||||
|
||||
for vehicle in resp.items {
|
||||
csvString.append(vehicle.csvLine)
|
||||
csvString.append(newLine)
|
||||
}
|
||||
|
||||
let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv")
|
||||
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
|
||||
|
||||
coordinator?.export(url: tmpUrl)
|
||||
}
|
||||
}
|
||||
|
||||
func updateVehicle(_ vehicle: VehicleDto) async {
|
||||
await wrapWithToast { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let updatedVehicle = try await apiService.checkVehicle(by: vehicle.getNumber(), notes: [], events: [], force: true)
|
||||
try await storageService.updateVehicle(dto: updatedVehicle, policy: .ifExists)
|
||||
|
||||
if let index = vehicles.firstIndex(where: { $0.number == updatedVehicle.number }) {
|
||||
vehicles[index] = updatedVehicle
|
||||
vehicleSections = vehicles.groupedByDate(type: .updatedDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,15 +41,10 @@ struct ACMessageView: View {
|
||||
.padding(20)
|
||||
|
||||
Divider()
|
||||
Button(action: action ?? {}) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text("OK")
|
||||
Button("OK") {
|
||||
action?()
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
}
|
||||
.background(.thickMaterial,
|
||||
in: RoundedRectangle(cornerRadius: 16))
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
//
|
||||
// AutoCancellable.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 18.02.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@propertyWrapper
|
||||
struct AutoCancellable<C: Sendable, E: Error> {
|
||||
|
||||
var wrappedValue: Task<C,E>? {
|
||||
didSet {
|
||||
if let oldValue, oldValue.isCancelled {
|
||||
return
|
||||
}
|
||||
oldValue?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
init(wrappedValue: Task<C,E>?) {
|
||||
self.wrappedValue = wrappedValue
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
//
|
||||
// Error+Canceled.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 19.02.2025.
|
||||
// Copyright © 2025 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Error {
|
||||
|
||||
public var isCanceled: Bool {
|
||||
let nsError = self as NSError
|
||||
|
||||
return nsError.domain == NSURLErrorDomain
|
||||
&& nsError.code == NSURLErrorCancelled
|
||||
}
|
||||
}
|
||||
@ -16,11 +16,11 @@ public enum DebugInfoStatus: Int, Sendable, Decodable, Equatable {
|
||||
|
||||
public struct DebugInfoDto: Decodable, Sendable, Equatable {
|
||||
|
||||
public var autocod: DebugInfoEntryDto?
|
||||
public var vin01vin: DebugInfoEntryDto?
|
||||
public var vin01base: DebugInfoEntryDto?
|
||||
public var vin01history: DebugInfoEntryDto?
|
||||
public var nomerogram: DebugInfoEntryDto?
|
||||
public var autocod: DebugInfoEntryDto
|
||||
public var vin01vin: DebugInfoEntryDto
|
||||
public var vin01base: DebugInfoEntryDto
|
||||
public var vin01history: DebugInfoEntryDto
|
||||
public var nomerogram: DebugInfoEntryDto
|
||||
}
|
||||
|
||||
public struct DebugInfoEntryDto: Decodable, Sendable, Equatable {
|
||||
|
||||
@ -21,22 +21,11 @@ public enum VehiclesArchiveError: LocalizedError {
|
||||
|
||||
public final class VehiclesArchive {
|
||||
|
||||
var vehicles: [VehicleDto]
|
||||
let apiService: ApiServiceProtocol?
|
||||
let filter: Filter?
|
||||
let vehicles: [VehicleDto]
|
||||
|
||||
public init(vehiles: [VehicleDto]) {
|
||||
|
||||
self.vehicles = vehiles
|
||||
self.apiService = nil
|
||||
self.filter = nil
|
||||
}
|
||||
|
||||
public init(apiService: ApiServiceProtocol, filter: Filter) {
|
||||
|
||||
self.apiService = apiService
|
||||
self.filter = filter
|
||||
self.vehicles = []
|
||||
}
|
||||
|
||||
func makeCsvString() throws -> String {
|
||||
@ -53,13 +42,6 @@ public final class VehiclesArchive {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func loadVehiclesIfNeeded() async throws {
|
||||
if let apiService, let filter {
|
||||
let result = try await apiService.getVehicles(with: filter, pageToken: nil, pageSize: 0)
|
||||
vehicles = result.items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension VehiclesArchive: Transferable {
|
||||
@ -72,9 +54,7 @@ extension VehiclesArchive: Transferable {
|
||||
|
||||
DataRepresentation(exportedContentType: .commaSeparatedText) { archive in
|
||||
|
||||
try await archive.loadVehiclesIfNeeded()
|
||||
let csvString = try archive.makeCsvString()
|
||||
|
||||
if let data = csvString.data(using: .utf8){
|
||||
return data
|
||||
} else {
|
||||
|
||||
@ -27,6 +27,4 @@ public protocol ApiServiceProtocol: Sendable {
|
||||
|
||||
func checkVehicle(by number: String, notes: [VehicleNoteDto], events: [VehicleEventDto], force: Bool) async throws -> VehicleDto
|
||||
func checkVehicleGb(by number: String) async throws -> VehicleDto
|
||||
|
||||
func getVehicles(with filter: Filter, pageToken: String?, pageSize: Int) async throws -> PagedResponse<VehicleDto>
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user