Files
iOS/Sources/Extensions/NotificationContent/MapViewController.swift
Zac West 5c104f76e9 Multi-server (#1906)
## Summary
Most, but not all, of the changes necessary to support multi-server throughout the app and all its features.

## Screenshots
| Light | Dark |
| ----- | ---- |
| ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 52 24](https://user-images.githubusercontent.com/74188/143670011-9b9905ac-1b5b-4a82-b9f3-1490465c4ec5.png) | ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 52 26](https://user-images.githubusercontent.com/74188/143670012-0080230a-8f68-4f34-9691-db9f5e825a83.png) |
| ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 52 30](https://user-images.githubusercontent.com/74188/143670015-ceeac558-e039-4639-a186-b5001ab418b8.png) | ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 52 29](https://user-images.githubusercontent.com/74188/143670016-d72bb69d-83f5-4197-a742-59d208467258.png) |
| ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 52 47](https://user-images.githubusercontent.com/74188/143670021-6c90c40f-c2f1-4a33-aad9-da6626e99d9d.png) | ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 52 45](https://user-images.githubusercontent.com/74188/143670024-e99de69d-61d8-4e12-be73-a172242806a0.png) |
| ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 53 05](https://user-images.githubusercontent.com/74188/143670033-1a41ac7e-d4d1-458b-974e-2efdaf8e2288.png) | ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 53 03](https://user-images.githubusercontent.com/74188/143670049-baf4db64-64db-4bfb-88cf-4930f9e5661b.png) |
| ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 53 21](https://user-images.githubusercontent.com/74188/143670053-7ec794f1-857c-4ef6-a92a-5318e90ac6b6.png) | ![Simulator Screen Shot - iPhone 13 Pro - 2021-11-26 at 21 53 19](https://user-images.githubusercontent.com/74188/143670056-a6a5207c-3bba-49fc-b5c6-fc6fa8141f9c.png) |

## Any other notes
- Encapsulates all connectivity, token & server-specific knowledge in a Server model object which gets passed around.
- Updates various places throughout the app to know about and use Server rather than accessing said information through non-server-specific methods.
- Visually requests/notes server in places where it's ambiguous. For example, the Open Page widget will gain a subtitle if multiple servers are set up.
- Allows switching which server is shown in the WebViews. Note that this doesn't take into account multi-window support on iPad/macOS yet.

Most things will migrate successfully however adding an additional server causes things like Shortcuts to start erroring requiring you specify which to use in the particular Shortcut.

Future work necessary:
- Model objects currently clobber each other if their identifiers match. For example, both servers having a zone named `home` means one of them wins the fight for which is known to the app.
- Being remotely logged out on any account causes the app to require onboarding again, when instead it should only do that if the last known server is logged out.
2021-11-27 12:33:46 -08:00

169 lines
6.1 KiB
Swift

import MapKit
import PromiseKit
import Shared
import UIKit
import UserNotifications
import UserNotificationsUI
class MapViewController: UIViewController, NotificationCategory, MKMapViewDelegate {
let api: HomeAssistantAPI
let location: CLLocationCoordinate2D
let haDict: [String: Any]
required init(api: HomeAssistantAPI, notification: UNNotification, attachmentURL: URL?) throws {
guard let haDict = notification.request.content.userInfo["homeassistant"] as? [String: Any] else {
throw MapError.missingPayload
}
guard let latitude = CLLocationDegrees(templateValue: haDict["latitude"]) else {
throw MapError.missingLatitude
}
guard let longitude = CLLocationDegrees(templateValue: haDict["longitude"]) else {
throw MapError.missingLongitude
}
self.api = api
self.haDict = haDict
self.location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
enum MapError: LocalizedError {
case missingPayload
case missingLatitude
case missingLongitude
var errorDescription: String? {
switch self {
case .missingPayload:
return L10n.Extensions.Map.PayloadMissingHomeassistant.message
case .missingLatitude:
return L10n.Extensions.Map.ValueMissingOrUncastable.Latitude.message
case .missingLongitude:
return L10n.Extensions.Map.ValueMissingOrUncastable.Longitude.message
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
NSLayoutConstraint.activate([
view.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5625),
])
}
func start() -> Promise<Void> {
let mapView = MKMapView()
view.addSubview(mapView)
mapView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
mapView.topAnchor.constraint(equalTo: view.topAnchor),
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
mapView.delegate = self
mapView.mapType = .standard
mapView.showsUserLocation = (haDict["shows_user_location"] != nil)
if #available(iOS 13, *) {
if haDict["shows_points_of_interest"] != nil {
mapView.pointOfInterestFilter = .includingAll
} else {
mapView.pointOfInterestFilter = .excludingAll
}
} else {
mapView.showsPointsOfInterest = (haDict["shows_points_of_interest"] != nil)
}
mapView.showsCompass = (haDict["shows_compass"] != nil)
mapView.showsScale = (haDict["shows_scale"] != nil)
mapView.showsTraffic = (haDict["shows_traffic"] != nil)
mapView.accessibilityIdentifier = "notification_map"
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
let region = MKCoordinateRegion(center: location, span: span)
mapView.setRegion(region, animated: true)
let dropPin = MKPointAnnotation()
dropPin.coordinate = location
if let secondLatitude = CLLocationDegrees(templateValue: haDict["second_latitude"]),
let secondLongitude = CLLocationDegrees(templateValue: haDict["second_longitude"]) {
let secondDropPin = MKPointAnnotation()
secondDropPin.coordinate = CLLocationCoordinate2D(latitude: secondLatitude, longitude: secondLongitude)
secondDropPin.title = L10n.Extensions.Map.Location.new
mapView.addAnnotation(secondDropPin)
mapView.selectAnnotation(secondDropPin, animated: true)
dropPin.title = L10n.Extensions.Map.Location.original
}
mapView.addAnnotation(dropPin)
if mapView.annotations.count > 1 {
if haDict["shows_line_between_points"] != nil {
var polylinePoints = [CLLocationCoordinate2D]()
for annotation in mapView.annotations {
polylinePoints.append(annotation.coordinate)
}
mapView.addOverlay(MKPolyline(coordinates: &polylinePoints, count: polylinePoints.count))
}
mapView.showAnnotations(mapView.annotations, animated: true)
mapView.camera.altitude *= 1.4
}
return .value(())
}
var mediaPlayPauseButtonType: UNNotificationContentExtensionMediaPlayPauseButtonType { .none }
var mediaPlayPauseButtonFrame: CGRect?
var mediaPlayPauseButtonTintColor: UIColor?
func mediaPlay() {}
func mediaPause() {}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
// if annotation is not an MKPointAnnotation (eg. MKUserLocation),
// return nil so map draws default view for it (eg. blue dot)...
return nil
}
let pinView = MKPinAnnotationView()
pinView.annotation = annotation
if let title = annotation.title {
if title == L10n.Extensions.Map.Location.original {
pinView.pinTintColor = .red
} else if title == L10n.Extensions.Map.Location.new {
pinView.pinTintColor = .green
}
} else {
pinView.pinTintColor = .red
}
pinView.animatesDrop = true
pinView.canShowCallout = true
return pinView
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let polylineRenderer = MKPolylineRenderer(overlay: overlay)
polylineRenderer.strokeColor = UIColor.red
polylineRenderer.fillColor = UIColor.red.withAlphaComponent(0.1)
polylineRenderer.lineWidth = 1
polylineRenderer.lineDashPattern = [2, 5]
return polylineRenderer
}
}