diff --git a/AutoCat.xcodeproj/project.pbxproj b/AutoCat.xcodeproj/project.pbxproj index 3955773..79b69ce 100644 --- a/AutoCat.xcodeproj/project.pbxproj +++ b/AutoCat.xcodeproj/project.pbxproj @@ -96,6 +96,8 @@ 7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */; }; 7ADF6C99250F872C00F237B2 /* RoadNumbers.otf in Resources */ = {isa = PBXBuildFile; fileRef = 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */; }; 7ADF6C9D250FA96000F237B2 /* SwiftMaskTextfield.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C9C250FA96000F237B2 /* SwiftMaskTextfield.swift */; }; + 7ADF6C9F251201D200F237B2 /* GlobalEventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6C9E251201D200F237B2 /* GlobalEventsController.swift */; }; + 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADF6CA02512244400F237B2 /* MapExt.swift */; }; 7AE26A3324EEF9EC00625033 /* UIViewControllerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */; }; 7AE26A3524F31B0700625033 /* EventsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE26A3424F31B0700625033 /* EventsController.swift */; }; 7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEFE727240455E200910EB7 /* SettingsController.swift */; }; @@ -188,6 +190,8 @@ 7ADF6C96250F41B000F237B2 /* PNKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNKeyboard.swift; sourceTree = ""; }; 7ADF6C98250F872C00F237B2 /* RoadNumbers.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = RoadNumbers.otf; sourceTree = ""; }; 7ADF6C9C250FA96000F237B2 /* SwiftMaskTextfield.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMaskTextfield.swift; sourceTree = ""; }; + 7ADF6C9E251201D200F237B2 /* GlobalEventsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalEventsController.swift; sourceTree = ""; }; + 7ADF6CA02512244400F237B2 /* MapExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapExt.swift; sourceTree = ""; }; 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewControllerExt.swift; sourceTree = ""; }; 7AE26A3424F31B0700625033 /* EventsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsController.swift; sourceTree = ""; }; 7AEFE727240455E200910EB7 /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = ""; }; @@ -347,6 +351,7 @@ 7AE26A3224EEF9EC00625033 /* UIViewControllerExt.swift */, 7A21112924FC3D7E003BBF6F /* AudioEngine.swift */, 7ADF6C92250B954900F237B2 /* Navigation.swift */, + 7ADF6CA02512244400F237B2 /* MapExt.swift */, ); path = Extensions; sourceTree = ""; @@ -431,6 +436,7 @@ 7A813DC8250B5C9700CC93B9 /* LocationRow.swift */, 7A813DCA250B5DC900CC93B9 /* LocationPickerController.swift */, 7ADF6C94250D037700F237B2 /* ShowEventController.swift */, + 7ADF6C9E251201D200F237B2 /* GlobalEventsController.swift */, ); path = Location; sourceTree = ""; @@ -560,6 +566,7 @@ 7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */, 7A8A2209248D10EC0073DFD9 /* ResizeImage.swift in Sources */, 7A6DD90E24337930009DE740 /* PlateNumber.swift in Sources */, + 7ADF6CA12512244400F237B2 /* MapExt.swift in Sources */, 7A659B5B24A3768A0043A0F2 /* Substrings.swift in Sources */, 7AEFE728240455E200910EB7 /* SettingsController.swift in Sources */, 7A27ADF7249FEF690035F39E /* Recorder.swift in Sources */, @@ -587,6 +594,7 @@ 7A813DC5250AAF3C00CC93B9 /* LocationEditController.swift in Sources */, 7A43F9F8246C8A6200BA5B49 /* JWT.swift in Sources */, 7A6DD903242BF4A5009DE740 /* PlateView.swift in Sources */, + 7ADF6C9F251201D200F237B2 /* GlobalEventsController.swift in Sources */, 7A488C3F24A74B990054D0B2 /* RealmBindObserver.swift in Sources */, 7AAE6AD324CDDF950023860B /* VehicleEvent.swift in Sources */, 7A11470323FDE7E500B424AF /* SceneDelegate.swift in Sources */, @@ -755,7 +763,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 34; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; @@ -777,7 +785,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 32; + CURRENT_PROJECT_VERSION = 34; DEVELOPMENT_TEAM = 46DTTB8X4S; INFOPLIST_FILE = AutoCat/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 13.0; diff --git a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 52768a0..737454d 100644 --- a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -47,22 +47,6 @@ - - - - + + + + diff --git a/AutoCat/Base.lproj/Main.storyboard b/AutoCat/Base.lproj/Main.storyboard index 405da82..8c6d1a5 100644 --- a/AutoCat/Base.lproj/Main.storyboard +++ b/AutoCat/Base.lproj/Main.storyboard @@ -328,14 +328,14 @@ - + - + - + @@ -387,13 +387,21 @@ - - - - - + + + + + + + + + + + + + @@ -762,7 +770,6 @@ - @@ -888,10 +895,10 @@ - + - + @@ -909,11 +916,22 @@ - + + + + + + + + + + + + - + @@ -990,24 +1008,23 @@ - - + + - - + - + - + - + - + diff --git a/AutoCat/Controllers/CheckController.swift b/AutoCat/Controllers/CheckController.swift index 3704186..fff8317 100644 --- a/AutoCat/Controllers/CheckController.swift +++ b/AutoCat/Controllers/CheckController.swift @@ -178,7 +178,9 @@ class CheckController: UIViewController, UITableViewDelegate, UITextFieldDelegat } eventSingle .flatMap { event in event.findAddress().map{ event }.catchErrorJustReturn(event) } - .flatMap { Api.add(event: $0, to: vehicle.number) } + .flatMap { + Api.add(event: $0, to: vehicle.number) + } .subscribe(onSuccess: self.save(vehicle:), onError: { print("Error adding event: \($0)") }) .disposed(by: self.bag) } diff --git a/AutoCat/Controllers/Location/EventsController.swift b/AutoCat/Controllers/Location/EventsController.swift index dabb006..efccd04 100644 --- a/AutoCat/Controllers/Location/EventsController.swift +++ b/AutoCat/Controllers/Location/EventsController.swift @@ -17,13 +17,18 @@ class EventPin: NSObject, MKAnnotation { convenience init(event: VehicleEvent) { let coordinate = CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude) - let subtitle = event.address ?? "\(event.latitude), \(event.longitude)" + let address = event.address ?? "\(event.latitude), \(event.longitude)" let date = Date(timeIntervalSince1970: event.date) let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short - let title = formatter.string(from: date) - self.init(coordinate: coordinate, title: title, subtitle: subtitle) + let dateStr = formatter.string(from: date) + + if let number = event.number { + self.init(coordinate: coordinate, title: number, subtitle: dateStr) + } else { + self.init(coordinate: coordinate, title: dateStr, subtitle: address) + } } } @@ -75,33 +80,10 @@ class EventsController: UIViewController, UITableViewDataSource, UITableViewDele func updateInterface() { self.map.removeAnnotations(self.map.annotations) self.map.addAnnotations(self.pins) - self.centerMap() + self.map.centerOnPins() self.tableView.reloadData() } - func centerMap() { - guard let vehicle = self.vehicle else { return } - - var minLat = 0.0, maxLat = 0.0, minLon = 0.0, maxLon = 0.0 - for event in vehicle.events { - if event.latitude < minLat || minLat == 0 { minLat = event.latitude } - if event.latitude > maxLat || maxLat == 0 { maxLat = event.latitude } - if event.longitude < minLon || minLon == 0 { minLon = event.longitude } - if event.longitude > maxLon || maxLon == 0 { maxLon = event.longitude } - } - - let center = CLLocationCoordinate2D(latitude: (minLat + maxLat)/2, longitude: (minLon + maxLon)/2) - let leftTop = CLLocation(latitude: minLat, longitude: minLon) - let rightBottom = CLLocation(latitude: maxLat, longitude: maxLon) - var diagonal = leftTop.distance(from: rightBottom)*1.2 - if diagonal < 1000 { - diagonal = 1000 - } - - let region = MKCoordinateRegion(center: center, latitudinalMeters: diagonal, longitudinalMeters: diagonal) - self.map.setRegion(region, animated: true) - } - @objc func switchMode(_ sender: UIBarButtonItem) { switch self.mode { case .map: diff --git a/AutoCat/Controllers/Location/GlobalEventsController.swift b/AutoCat/Controllers/Location/GlobalEventsController.swift new file mode 100644 index 0000000..ea7c721 --- /dev/null +++ b/AutoCat/Controllers/Location/GlobalEventsController.swift @@ -0,0 +1,38 @@ +import UIKit +import MapKit +import RxSwift + +class GlobalEventsController: UIViewController { + + @IBOutlet weak var map: MKMapView! + + let bag = DisposeBag() + var filter: Filter! + + override func viewDidLoad() { + super.viewDidLoad() + + #if targetEnvironment(macCatalyst) + self.map.showsZoomControls = true + #endif + + IHProgressHUD.show() + Api.events(with: self.filter) + .observeOn(MainScheduler.init()) + .subscribe(onSuccess: { events in + self.title = "Events found: \(events.count)" + let pins = events.map(EventPin.init(event:)) + self.map.removeAnnotations(self.map.annotations) + self.map.addAnnotations(pins) + self.map.centerOnPins() + IHProgressHUD.dismiss() + }) { error in + IHProgressHUD.show(error: error) + } + .disposed(by: self.bag) + } + + @IBAction func close(_ sender: UIBarButtonItem) { + self.dismiss(animated: true, completion: nil) + } +} diff --git a/AutoCat/Controllers/SearchController.swift b/AutoCat/Controllers/SearchController.swift index d2a7f4b..8b7f49c 100644 --- a/AutoCat/Controllers/SearchController.swift +++ b/AutoCat/Controllers/SearchController.swift @@ -6,6 +6,7 @@ import RxCocoa class SearchController: UIViewController, UISearchResultsUpdating { @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var showMapButton: UIBarButtonItem! let bag = DisposeBag() let searchController = UISearchController(searchResultsController: nil) @@ -16,6 +17,8 @@ class SearchController: UIViewController, UISearchResultsUpdating { override func viewDidLoad() { super.viewDidLoad() + self.showMapButton.isEnabled = false + searchController.searchResultsUpdater = self searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.placeholder = "Search plate numbers" @@ -45,7 +48,10 @@ class SearchController: UIViewController, UISearchResultsUpdating { .debounce(.milliseconds(500), scheduler: MainScheduler.instance) .flatMap(Api.getVehicles) .observeOn(MainScheduler.instance) - .do(onNext: { self.navigationItem.title = "\($0.count) vehicles found" }) + .do(onNext: { + self.navigationItem.title = "\($0.count) vehicles found" + self.showMapButton.isEnabled = $0.count > 0 + }) .map { $0.groupedByDate() } .bind(to: self.tableView.rx.items(dataSource: ds)) .disposed(by: self.bag) @@ -96,4 +102,16 @@ class SearchController: UIViewController, UISearchResultsUpdating { } self.navigationController?.pushViewController(controller, animated: true) } + + @IBAction func showOnMap(_ sender: UIBarButtonItem) { + 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.navigationController?.pushViewController(controller, animated: true) + self.present(controller, animated: true) + } } diff --git a/AutoCat/Extensions/MapExt.swift b/AutoCat/Extensions/MapExt.swift new file mode 100644 index 0000000..bf57821 --- /dev/null +++ b/AutoCat/Extensions/MapExt.swift @@ -0,0 +1,25 @@ +import Foundation +import MapKit + +extension MKMapView { + func centerOnPins() { + var minLat = 0.0, maxLat = 0.0, minLon = 0.0, maxLon = 0.0 + for annotation in self.annotations { + if annotation.coordinate.latitude < minLat || minLat == 0 { minLat = annotation.coordinate.latitude } + if annotation.coordinate.latitude > maxLat || maxLat == 0 { maxLat = annotation.coordinate.latitude } + if annotation.coordinate.longitude < minLon || minLon == 0 { minLon = annotation.coordinate.longitude } + if annotation.coordinate.longitude > maxLon || maxLon == 0 { maxLon = annotation.coordinate.longitude } + } + + let center = CLLocationCoordinate2D(latitude: (minLat + maxLat)/2, longitude: (minLon + maxLon)/2) + let leftTop = CLLocation(latitude: minLat, longitude: minLon) + let rightBottom = CLLocation(latitude: maxLat, longitude: maxLon) + var diagonal = leftTop.distance(from: rightBottom)*1.2 + if diagonal < 1000 { + diagonal = 1000 + } + + let region = MKCoordinateRegion(center: center, latitudinalMeters: diagonal, longitudinalMeters: diagonal) + self.setRegion(region, animated: true) + } +} diff --git a/AutoCat/Models/VehicleEvent.swift b/AutoCat/Models/VehicleEvent.swift index 2ac6518..b50b1d0 100644 --- a/AutoCat/Models/VehicleEvent.swift +++ b/AutoCat/Models/VehicleEvent.swift @@ -3,7 +3,7 @@ import RealmSwift import RxSwift import CoreLocation -public class VehicleEvent: Object, Codable { +class VehicleEvent: Object, Codable { @objc dynamic var id: String? @objc dynamic var date: TimeInterval = Date().timeIntervalSince1970 @objc dynamic var latitude: Double = 0 @@ -12,6 +12,7 @@ public class VehicleEvent: Object, Codable { @objc dynamic var direction: Double = 0 @objc dynamic var address: String? = nil + var number: String? var coordinate: CLLocationCoordinate2D { return CLLocationCoordinate2D(latitude: self.latitude, longitude: self.longitude) } @@ -42,4 +43,8 @@ public class VehicleEvent: Object, Codable { } } } + + override static func ignoredProperties() -> [String] { + return ["plateNumber"] + } } diff --git a/AutoCat/Utils/Api.swift b/AutoCat/Utils/Api.swift index c925416..94befd9 100644 --- a/AutoCat/Utils/Api.swift +++ b/AutoCat/Utils/Api.swift @@ -241,4 +241,8 @@ class Api { let body = ["event": event] return self.makeBodyRequest(api: "events", body: body, method: "PUT") } + + public static func events(with filter: Filter) -> Single<[VehicleEvent]> { + return self.makeGetRequest(api: "events", params: filter.queryDictionary()) + } }