diff --git a/LocoKit Demo App/MapView.swift b/LocoKit Demo App/MapView.swift index 5671ba13..ff118f5c 100644 --- a/LocoKit Demo App/MapView.swift +++ b/LocoKit Demo App/MapView.swift @@ -11,13 +11,8 @@ import MapKit class MapView: MKMapView { - let timeline: TimelineManager - - init(timeline: TimelineManager) { - self.timeline = timeline - + init() { super.init(frame: CGRect.zero) - self.delegate = self self.isRotateEnabled = false self.isPitchEnabled = false @@ -162,7 +157,7 @@ class MapView: MKMapView { var coords = path.samples.compactMap { $0.location?.coordinate } let line = PathPolyline(coordinates: &coords, count: coords.count) - line.color = timeline.activeItems.contains(path) ? .brown : .darkGray + line.color = .brown add(line) } @@ -173,7 +168,7 @@ class MapView: MKMapView { addAnnotation(VisitAnnotation(coordinate: center.coordinate, visit: visit)) let circle = VisitCircle(center: center.coordinate, radius: visit.radius2sd) - circle.color = timeline.activeItems.contains(visit) ? .orange : .darkGray + circle.color = .orange add(circle, level: .aboveLabels) } @@ -199,26 +194,13 @@ class MapView: MKMapView { extension MapView: MKMapViewDelegate { func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { - if let path = overlay as? PathPolyline { - return path.renderer - - } else if let circle = overlay as? VisitCircle { - return circle.renderer - - } else { - fatalError("you wot?") - } + if let path = overlay as? PathPolyline { return path.renderer } + if let circle = overlay as? VisitCircle { return circle.renderer } + fatalError("you wot?") } func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { - if let annotation = annotation as? VisitAnnotation { - let view = annotation.view - if !timeline.activeItems.contains(annotation.visit) { - view.image = UIImage(named: "inactiveDot") - } - return view - } - return nil + return (annotation as? VisitAnnotation)?.view } } diff --git a/LocoKit Demo App/TimelineView.swift b/LocoKit Demo App/TimelineView.swift index 1c14b059..a4551055 100644 --- a/LocoKit Demo App/TimelineView.swift +++ b/LocoKit Demo App/TimelineView.swift @@ -11,16 +11,13 @@ import Anchorage class TimelineView: UIScrollView { - let timeline: TimelineManager - lazy var rows: UIStackView = { let box = UIStackView() box.axis = .vertical return box }() - init(timeline: TimelineManager) { - self.timeline = timeline + init() { super.init(frame: CGRect.zero) backgroundColor = .white alwaysBounceVertical = true @@ -54,11 +51,12 @@ class TimelineView: UIScrollView { return } - var nextItem: TimelineItem? for timelineItem in items { - if let next = nextItem, next.previousItem != timelineItem || timelineItem.nextItem != next { addDataGap() } - nextItem = timelineItem - add(timelineItem) + if timelineItem.isDataGap { + addDataGap(timelineItem) + } else { + add(timelineItem) + } } } @@ -70,10 +68,6 @@ class TimelineView: UIScrollView { } if timelineItem.isCurrentItem { title += "Current " - } else if timeline.activeItems.contains(timelineItem) { - title += "Active " - } else { - title += "Finalised " } title += timelineItem.isNolo ? "Nolo" : timelineItem is Visit ? "Visit" : "Path" if let path = timelineItem as? Path, let activityType = path.movingActivityType { @@ -130,16 +124,14 @@ class TimelineView: UIScrollView { rows.addRow(leftText: "ItemId", rightText: timelineItem.itemId.uuidString, background: debugColor) } - func addDataGap(duration: TimeInterval? = nil) { + func addDataGap(_ timelineItem: TimelineItem) { + guard timelineItem.isDataGap else { return } + rows.addGap(height: 14) rows.addUnderline() rows.addGap(height: 14) - if let duration = duration { - rows.addSubheading(title: "Timeline Gap (\(String(duration: duration)))", color: .red) - } else { - rows.addSubheading(title: "Timeline Gap", color: .red) - } + rows.addSubheading(title: "Timeline Gap (\(String(duration: timelineItem.duration)))", color: .red) rows.addGap(height: 14) rows.addUnderline() diff --git a/LocoKit Demo App/ViewController.swift b/LocoKit Demo App/ViewController.swift index 7da193e2..e1bc10b3 100644 --- a/LocoKit Demo App/ViewController.swift +++ b/LocoKit Demo App/ViewController.swift @@ -13,57 +13,58 @@ import CoreLocation class ViewController: UIViewController { - /** - The recording manager for Timeline Items (Visits and Paths) + // using an Activity Types Classifier requires an API key (see below) + let useActivityTypesClassifier = false - - Note: Use a plain TimelineManager() instead if you don't require persistent SQL storage - **/ - let timeline: TimelineManager = PersistentTimelineManager() + // use a plain TimelineStore instead of PersistentTimelineStore if you don't require persistent SQL storage + let store: TimelineStore = PersistentTimelineStore() - lazy var mapView = { return MapView(timeline: self.timeline) }() - lazy var timelineView = { return TimelineView(timeline: self.timeline) }() + var recorder: TimelineRecorder + + var dataSet: TimelineSegment? + + lazy var mapView = { return MapView() }() + lazy var timelineView = { return TimelineView() }() let classifierView = ClassifierView() let settingsView = SettingsView() let locoView = LocoView() let logView = LogView() // MARK: controller lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - // the CoreLocation / CoreMotion recording singleton - let loco = LocomotionManager.highlander - /** EXAMPLE SETTINGS **/ + init() { + if useActivityTypesClassifier { - // enable this if you have an API key and want to determine activity types - timeline.activityTypeClassifySamples = false - - if timeline.activityTypeClassifySamples { + // using an Activity Types Classifier requires an API key // API keys can be created at: https://www.bigpaua.com/arckit/account LocoKitService.apiKey = "" + + recorder = TimelineRecorder(store: store, classifier: TimelineClassifier.highlander) + + } else { + recorder = TimelineRecorder(store: store) } - // this accuracy level is excessive, and is for demo purposes only. - // the default value (30 metres) best balances accuracy with energy use. - loco.maximumDesiredLocationAccuracy = kCLLocationAccuracyNearestTenMeters + super.init(nibName: nil, bundle: nil) + } - // this is independent of the user's setting, and will show a blue bar if user has denied "always" - loco.locationManager.allowsBackgroundLocationUpdates = true + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - /** TIMELINE STARTUP **/ + override func viewDidLoad() { + super.viewDidLoad() - // restore the active timeline items from local db - if let timeline = timeline as? PersistentTimelineManager { - timeline.bootstrapActiveItems() + if let store = store as? PersistentTimelineStore { + let query = "deleted = 0 AND endDate > datetime('now','-24 hours') AND startDate < datetime('now') ORDER BY startDate DESC" + dataSet = TimelineSegment(for: query, in: store) { + onMain { self.update() } + } } - /** EXAMPLE OBSERVERS **/ - // observe new timeline items - when(timeline, does: .newTimelineItem) { _ in - if let currentItem = self.timeline.currentItem { + when(.newTimelineItem) { _ in + if let currentItem = self.recorder.currentItem { log(".newTimelineItem (\(String(describing: type(of: currentItem))))") } onMain { @@ -74,7 +75,7 @@ class ViewController: UIViewController { } // observe timeline item updates - when(timeline, does: .updatedTimelineItem) { _ in + when(.updatedTimelineItem) { _ in onMain { let items = self.itemsToShow self.mapView.update(with: items) @@ -82,21 +83,15 @@ class ViewController: UIViewController { } } - // observe timeline items finalised after post processing - when(timeline, does: .finalisedTimelineItem) { note in - if let item = note.userInfo?["timelineItem"] as? TimelineItem { - log(".finalisedTimelineItem (\(String(describing: type(of: item))))") - } - onMain { self.timelineView.update(with: self.itemsToShow) } - } - - when(timeline, does: .mergedTimelineItems) { note in + when(.mergedTimelineItems) { note in if let description = note.userInfo?["merge"] as? String { log(".mergedItems (\(description))") } onMain { self.timelineView.update(with: self.itemsToShow) } } + let loco = LocomotionManager.highlander + // observe incoming location / locomotion updates when(loco, does: .locomotionSampleUpdated) { _ in self.locomotionSampleUpdated() @@ -125,14 +120,6 @@ class ViewController: UIViewController { log(".stoppedSleepMode") } - when(.debugInfo) { note in - if let info = note.userInfo?["info"] as? String { - log(".debug (\(info))") - } else { - log(".debug (nil)") - } - } - when(settingsView, does: .settingsChanged) { _ in self.mapView.update(with: self.itemsToShow) self.setNeedsStatusBarAppearanceUpdate() @@ -175,7 +162,7 @@ class ViewController: UIViewController { @objc func tappedStart() { log("tappedStart()") - timeline.startRecording() + recorder.startRecording() startButton.isHidden = true stopButton.isHidden = false @@ -184,7 +171,7 @@ class ViewController: UIViewController { @objc func tappedStop() { log("tappedStop()") - timeline.stopRecording() + recorder.stopRecording() stopButton.isHidden = true startButton.isHidden = false @@ -287,9 +274,9 @@ class ViewController: UIViewController { } var itemsToShow: [TimelineItem] { - if timeline is PersistentTimelineManager { return persistentItemsToShow } + if store is PersistentTimelineStore { return persistentItemsToShow } - guard let currentItem = timeline.currentItem else { return [] } + guard let currentItem = recorder.currentItem else { return [] } // collect the linked list of timeline items var items: [TimelineItem] = [currentItem] @@ -303,14 +290,7 @@ class ViewController: UIViewController { } var persistentItemsToShow: [TimelineItem] { - guard let timeline = timeline as? PersistentTimelineManager else { return [] } - - // make sure the db is fresh - timeline.store.save() - - // feth all items in the past 24 hours - let boundary = Date(timeIntervalSinceNow: -60 * 60 * 24) - return timeline.store.items(where: "deleted = 0 AND endDate > ? ORDER BY endDate DESC", arguments: [boundary]) + return dataSet?.timelineItems ?? [] } // MARK: view property getters