Adding export CSV from search results
This commit is contained in:
parent
3ea006c208
commit
a60f57b4c6
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pme-aR-UNJ">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="pme-aR-UNJ">
|
||||
<device id="retina4_7" orientation="portrait" appearance="dark"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@ -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="429.5"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="430.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="429.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="430.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="413.5"/>
|
||||
<rect key="frame" x="16" y="8" width="343" height="414.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="413.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="414.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="204.5"/>
|
||||
<rect key="frame" x="0.0" y="209" width="335" height="205.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="411"/>
|
||||
<rect key="frame" x="343" y="1.5" width="0.0" height="412"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
@ -386,12 +386,12 @@
|
||||
<rightBarButtonItems>
|
||||
<barButtonItem image="line.horizontal.3.decrease" catalog="system" id="mvq-Q5-tVc">
|
||||
<connections>
|
||||
<action selector="onFilter:" destination="UPf-uT-oOr" id="z2g-n9-tJ0"/>
|
||||
<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="showOnMap:" destination="UPf-uT-oOr" id="PYv-B4-dqB"/>
|
||||
<action selector="onMapTapped:" destination="UPf-uT-oOr" id="ekk-vL-cGI"/>
|
||||
</connections>
|
||||
</barButtonItem>
|
||||
</rightBarButtonItems>
|
||||
@ -535,13 +535,13 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MjS-Hy-iGH">
|
||||
<rect key="frame" x="56" y="0.5" width="253.5" height="50"/>
|
||||
<rect key="frame" x="56" y="0.5" width="291" height="50"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="20"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Jgb-TO-YHq">
|
||||
<rect key="frame" x="321.5" y="0.5" width="37.5" height="50"/>
|
||||
<rect key="frame" x="359" y="0.5" width="0.0" height="50"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
||||
@ -199,10 +199,7 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat
|
||||
|
||||
func shareFile(_ url: URL) {
|
||||
let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
|
||||
self.present(activityController, animated: true) {
|
||||
print("")
|
||||
}
|
||||
print("")
|
||||
self.present(activityController, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Checking new number
|
||||
|
||||
@ -13,6 +13,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
|
||||
private var refreshButton: UIBarButtonItem!
|
||||
private var refreshIndicator: UIBarButtonItem!
|
||||
private var moreActionsButton: UIBarButtonItem?
|
||||
|
||||
private let bag = DisposeBag()
|
||||
private let searchController = UISearchController(searchResultsController: nil)
|
||||
@ -35,13 +36,19 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
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)
|
||||
self.navigationItem.leftBarButtonItem = self.refreshButton
|
||||
#if targetEnvironment(macCatalyst)
|
||||
self.navigationItem.leftBarButtonItem = self.refreshButton
|
||||
#endif
|
||||
|
||||
//self.refreshControl.attributedTitle = NSAttributedString(string: "")
|
||||
self.refreshControl.addTarget(self, action: #selector(self.refresh(_:)), for: .valueChanged)
|
||||
@ -55,8 +62,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
//.throttle(.seconds(2), scheduler: MainScheduler.instance)
|
||||
.debounce(.milliseconds(500), scheduler: MainScheduler.instance)
|
||||
.do(onNext: { _ in
|
||||
self.navigationItem.leftBarButtonItem = self.refreshIndicator
|
||||
self.pageLoadingIndicator.startAnimating()
|
||||
self.showProgress()
|
||||
})
|
||||
.flatMap { Api.getVehicles(with: $0, pageToken: self.datasource.pageToken).do(onError: { print($0) }).catchErrorJustReturn(PagedResponse<Vehicle>()) }
|
||||
.observeOn(MainScheduler.instance)
|
||||
@ -68,13 +74,29 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
self.refreshControl.endRefreshing()
|
||||
self.isLoadingPage = false
|
||||
self.pageLoadingIndicator.stopAnimating()
|
||||
self.navigationItem.leftBarButtonItem = self.refreshButton
|
||||
self.hideProgress()
|
||||
})
|
||||
.bind(to: self.datasource.data)
|
||||
.disposed(by: self.bag)
|
||||
}
|
||||
}
|
||||
|
||||
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: Vehicle) {
|
||||
if let splitViewController = self.view.window?.rootViewController as? UISplitViewController
|
||||
@ -108,13 +130,43 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
self.filterRelay.accept(self.filter)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: NavigationBar actions
|
||||
|
||||
// @IBAction func onUpdate(_ sender: UIBarButtonItem) {
|
||||
// self.refresh(sender)
|
||||
// }
|
||||
@available(iOS 14.0, *)
|
||||
func setupActionsMenu() {
|
||||
|
||||
@IBAction func onFilter(_ sender: UIBarButtonItem) {
|
||||
let menu = UIMenu(children: [
|
||||
UIAction(title: NSLocalizedString("Filter results", comment: ""),
|
||||
image: UIImage(systemName: "line.horizontal.3.decrease"),
|
||||
handler: { _ in 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) {
|
||||
showFilter()
|
||||
}
|
||||
|
||||
@IBAction func onMapTapped(_ sender: UIBarButtonItem) {
|
||||
showOnMap()
|
||||
}
|
||||
|
||||
@objc func refresh(_ sender: AnyObject) {
|
||||
self.showMapButton.isEnabled = false
|
||||
self.datasource.reset()
|
||||
self.filterRelay.accept(self.filter)
|
||||
}
|
||||
|
||||
func showFilter() {
|
||||
let sb = UIStoryboard(name: "Main", bundle: nil)
|
||||
let controller = sb.instantiateViewController(identifier: "FiltersController") as FiltersController
|
||||
controller.filter = self.filter
|
||||
@ -127,7 +179,7 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
self.navigationController?.pushViewController(controller, animated: true)
|
||||
}
|
||||
|
||||
@IBAction func showOnMap(_ sender: UIBarButtonItem) {
|
||||
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 {
|
||||
@ -135,14 +187,38 @@ class SearchController: UIViewController, UISearchResultsUpdating, UITableViewDe
|
||||
}
|
||||
|
||||
controller.modalPresentationStyle = .fullScreen
|
||||
//self.navigationController?.pushViewController(controller, animated: true)
|
||||
self.present(controller, animated: true)
|
||||
}
|
||||
|
||||
@objc func refresh(_ sender: AnyObject) {
|
||||
self.showMapButton.isEnabled = false
|
||||
self.datasource.reset()
|
||||
self.filterRelay.accept(self.filter)
|
||||
func exportSearchResults() {
|
||||
showProgress()
|
||||
|
||||
Api.getVehicles(with: filter, pageSize: 0)
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribe(onSuccess: { resp in
|
||||
self.hideProgress()
|
||||
|
||||
let newLine = "\r\n"
|
||||
var csvString = Vehicle.csvHeader + newLine
|
||||
|
||||
for vehicle in resp.items {
|
||||
csvString.append(vehicle.csvLine)
|
||||
csvString.append(newLine)
|
||||
}
|
||||
|
||||
do {
|
||||
let tmpUrl = FileManager.default.tmpUrl(name: "search", ext: "csv")
|
||||
try csvString.write(to: tmpUrl, atomically: true, encoding: .utf8)
|
||||
let activityController = UIActivityViewController(activityItems: [tmpUrl], applicationActivities: nil)
|
||||
self.present(activityController, animated: true)
|
||||
} catch {
|
||||
HUD.show(error: error)
|
||||
}
|
||||
}, onError: { error in
|
||||
self.hideProgress()
|
||||
HUD.show(error: error)
|
||||
})
|
||||
.disposed(by: bag)
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDelegate
|
||||
|
||||
@ -139,12 +139,18 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Events" = "События";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Export" = "Экспорт";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Export history as" = "Экспортировать историю как";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter check history" = "Фильтр истории поиска";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter results" = "Фильтры";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"From" = "С";
|
||||
|
||||
|
||||
@ -364,13 +364,31 @@ 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"
|
||||
return "Model, Color, Year, Plate Number, VIN, STS, PTS, Added Date, Updated date, Locations"
|
||||
}
|
||||
|
||||
public var csvLine: String {
|
||||
let model = self.brand?.name?.original ?? "<unknown>"
|
||||
let added = self.formatter.string(from: Date(timeIntervalSince1970: self.addedDate))
|
||||
let updated = self.formatter.string(from: Date(timeIntervalSince1970: self.updatedDate))
|
||||
return String(format: "\"%@\", %@, %d, %@, %@, %@, %@, \"%@\", \"%@\"", model, self.color ?? "", self.year, self.number, self.vin1 ?? "", self.sts ?? "", self.pts ?? "", added, updated)
|
||||
|
||||
var eventsString = ""
|
||||
for event in events {
|
||||
let location = event.address ?? "lat: \(event.latitude), lon: \(event.longitude)"
|
||||
let date = formatter.string(from: Date(timeIntervalSince1970: event.date))
|
||||
eventsString.append(location + "; " + date + "\r\n")
|
||||
}
|
||||
|
||||
return String(format: "\"%@\", %@, %d, %@, %@, %@, %@, \"%@\", \"%@\", \"%@\"",
|
||||
model,
|
||||
self.color ?? "",
|
||||
self.year,
|
||||
self.number,
|
||||
self.vin1 ?? "",
|
||||
self.sts ?? "",
|
||||
self.pts ?? "",
|
||||
added,
|
||||
updated,
|
||||
eventsString)
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,8 +198,9 @@ public class Api {
|
||||
return self.makeBodyRequest(api: "user/signup", body: body)
|
||||
}
|
||||
|
||||
public static func getVehicles(with filter: Filter, pageToken: String? = nil) -> Single<PagedResponse<Vehicle>> {
|
||||
public static func getVehicles(with filter: Filter, pageToken: String? = nil, pageSize: Int = 50) -> Single<PagedResponse<Vehicle>> {
|
||||
var params = filter.queryDictionary()
|
||||
params["pageSize"] = String(pageSize)
|
||||
if let token = pageToken {
|
||||
params["pageToken"] = token;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user