mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Refactor the MapTiler configuration into a single place. (#3944)
* Refactor the MapTiler configuration into a single place. * Merge the MapAssets catalog into the normal one.
This commit is contained in:
@@ -97,7 +97,8 @@ final class AppSettings {
|
||||
copyrightURL: URL,
|
||||
acceptableUseURL: URL,
|
||||
privacyURL: URL,
|
||||
supportEmailAddress: String) {
|
||||
supportEmailAddress: String,
|
||||
mapTilerConfiguration: MapTilerConfiguration) {
|
||||
self.defaultHomeserverAddress = defaultHomeserverAddress
|
||||
self.oidcRedirectURL = oidcRedirectURL
|
||||
self.websiteURL = websiteURL
|
||||
@@ -106,6 +107,7 @@ final class AppSettings {
|
||||
self.acceptableUseURL = acceptableUseURL
|
||||
self.privacyURL = privacyURL
|
||||
self.supportEmailAddress = supportEmailAddress
|
||||
self.mapTilerConfiguration = mapTilerConfiguration
|
||||
}
|
||||
|
||||
// MARK: - Application
|
||||
@@ -290,10 +292,10 @@ final class AppSettings {
|
||||
// MARK: - Maps
|
||||
|
||||
// maptiler base url
|
||||
let mapTilerBaseURL: URL = "https://api.maptiler.com/maps"
|
||||
|
||||
// maptiler api key
|
||||
let mapTilerApiKey = Secrets.mapLibreAPIKey
|
||||
private(set) var mapTilerConfiguration = MapTilerConfiguration(baseURL: "https://api.maptiler.com/maps",
|
||||
apiKey: Secrets.mapLibreAPIKey,
|
||||
lightStyleID: "9bc819c8-e627-474a-a348-ec144fe3d810",
|
||||
darkStyleID: "dea61faf-292b-4774-9660-58fcef89a7f3")
|
||||
|
||||
// MARK: - Presence
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let timelineControllerFactory: TimelineControllerFactoryProtocol
|
||||
private let roomProxy: JoinedRoomProxyProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
private let appSettings: AppSettings
|
||||
private let appMediator: AppMediatorProtocol
|
||||
private let emojiProvider: EmojiProviderProtocol
|
||||
|
||||
@@ -36,6 +37,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
timelineControllerFactory: TimelineControllerFactoryProtocol,
|
||||
roomProxy: JoinedRoomProxyProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol,
|
||||
appSettings: AppSettings,
|
||||
appMediator: AppMediatorProtocol,
|
||||
emojiProvider: EmojiProviderProtocol) {
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
@@ -43,6 +45,7 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self.timelineControllerFactory = timelineControllerFactory
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
self.appSettings = appSettings
|
||||
self.appMediator = appMediator
|
||||
self.emojiProvider = emojiProvider
|
||||
}
|
||||
@@ -106,7 +109,9 @@ class PinnedEventsTimelineFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private func presentMapNavigator(geoURI: GeoURI, description: String?) {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let params = StaticLocationScreenCoordinatorParameters(interactionMode: .viewOnly(geoURI: geoURI, description: description), appMediator: appMediator)
|
||||
let params = StaticLocationScreenCoordinatorParameters(interactionMode: .viewOnly(geoURI: geoURI, description: description),
|
||||
mapURLBuilder: appSettings.mapTilerConfiguration,
|
||||
appMediator: appMediator)
|
||||
let coordinator = StaticLocationScreenCoordinator(parameters: params)
|
||||
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
|
||||
@@ -1076,7 +1076,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private func presentMapNavigator(interactionMode: StaticLocationInteractionMode) {
|
||||
let stackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode, appMediator: appMediator)
|
||||
let params = StaticLocationScreenCoordinatorParameters(interactionMode: interactionMode,
|
||||
mapURLBuilder: appSettings.mapTilerConfiguration,
|
||||
appMediator: appMediator)
|
||||
let coordinator = StaticLocationScreenCoordinator(parameters: params)
|
||||
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
@@ -1563,6 +1565,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
timelineControllerFactory: timelineControllerFactory,
|
||||
roomProxy: roomProxy,
|
||||
userIndicatorController: userIndicatorController,
|
||||
appSettings: appSettings,
|
||||
appMediator: appMediator,
|
||||
emojiProvider: emojiProvider)
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ internal enum Asset {
|
||||
internal static let stopRecording = ImageAsset(name: "images/stop-recording")
|
||||
internal static let launchBackground = ImageAsset(name: "images/launch-background")
|
||||
internal static let locationMarkerShape = ImageAsset(name: "images/location-marker-shape")
|
||||
internal static let mapBlurred = ImageAsset(name: "images/mapBlurred")
|
||||
internal static let mediaPause = ImageAsset(name: "images/media-pause")
|
||||
internal static let mediaPlay = ImageAsset(name: "images/media-play")
|
||||
internal static let notificationsPromptGraphic = ImageAsset(name: "images/notifications-prompt-graphic")
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension MapTilerStyleBuilder {
|
||||
init(appSettings: AppSettings) {
|
||||
self.init(baseURL: appSettings.mapTilerBaseURL, key: appSettings.mapTilerApiKey)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ struct MapLibreMapView: UIViewRepresentable {
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
let builder: MapTilerStyleBuilderProtocol
|
||||
let mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
|
||||
let options: Options
|
||||
|
||||
@@ -64,7 +64,7 @@ struct MapLibreMapView: UIViewRepresentable {
|
||||
// Don't set the same value twice. Otherwise, if there is an error loading the map, a loop
|
||||
// is caused as the `error` binding being set, which triggers this update, which sets a
|
||||
// new URL, which causes another error, and so it goes on round and round in a circle.
|
||||
let dynamicMapURL = builder.dynamicMapURL(for: .init(colorScheme))
|
||||
let dynamicMapURL = mapURLBuilder.dynamicMapURL(for: .init(colorScheme))
|
||||
if mapView.styleURL != dynamicMapURL {
|
||||
mapView.styleURL = dynamicMapURL
|
||||
}
|
||||
@@ -85,7 +85,7 @@ struct MapLibreMapView: UIViewRepresentable {
|
||||
}
|
||||
|
||||
private func makeMapView() -> MGLMapView {
|
||||
let mapView = MGLMapView(frame: .zero, styleURL: colorScheme == .dark ? builder.dynamicMapURL(for: .dark) : builder.dynamicMapURL(for: .light))
|
||||
let mapView = MGLMapView(frame: .zero, styleURL: mapURLBuilder.dynamicMapURL(for: colorScheme == .dark ? .dark : .light))
|
||||
mapView.logoViewPosition = .topLeft
|
||||
mapView.attributionButtonPosition = .topLeft
|
||||
mapView.attributionButtonMargins = .init(x: mapView.logoView.frame.maxX + 8, y: mapView.logoView.center.y / 2)
|
||||
|
||||
@@ -24,6 +24,14 @@ enum MapLibreError: Error {
|
||||
case failedLocatingUser
|
||||
}
|
||||
|
||||
/// The style to show a map in.
|
||||
///
|
||||
/// There can be any number of styles, we have defined one for light and another for dark.
|
||||
enum MapTilerStyle {
|
||||
case light
|
||||
case dark
|
||||
}
|
||||
|
||||
enum MapTilerAttributionPlacement: String {
|
||||
case bottomRight = "bottomright"
|
||||
case bottomLeft = "bottomleft"
|
||||
|
||||
@@ -11,7 +11,7 @@ import SwiftUI
|
||||
struct MapLibreStaticMapView<PinAnnotation: View>: View {
|
||||
private let coordinates: CLLocationCoordinate2D
|
||||
private let zoomLevel: Double
|
||||
private let mapTilerStatic: MapTilerStaticMapProtocol
|
||||
private let mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
private let mapTilerAttributionPlacement: MapTilerAttributionPlacement
|
||||
private let mapSize: CGSize
|
||||
private let pinAnnotationView: PinAnnotation
|
||||
@@ -22,12 +22,12 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
|
||||
init(coordinates: CLLocationCoordinate2D,
|
||||
zoomLevel: Double,
|
||||
attributionPlacement: MapTilerAttributionPlacement,
|
||||
mapTilerStatic: MapTilerStaticMapProtocol,
|
||||
mapURLBuilder: MapTilerURLBuilderProtocol,
|
||||
mapSize: CGSize,
|
||||
@ViewBuilder pinAnnotationView: () -> PinAnnotation) {
|
||||
self.coordinates = coordinates
|
||||
self.zoomLevel = zoomLevel
|
||||
self.mapTilerStatic = mapTilerStatic
|
||||
self.mapURLBuilder = mapURLBuilder
|
||||
mapTilerAttributionPlacement = attributionPlacement
|
||||
self.mapSize = mapSize
|
||||
self.pinAnnotationView = pinAnnotationView()
|
||||
@@ -35,11 +35,11 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
if let url = mapTilerStatic.staticMapURL(for: colorScheme.mapStyle,
|
||||
coordinates: coordinates,
|
||||
zoomLevel: zoomLevel,
|
||||
size: mapSize, // temporary using a fixed size since the refresh doesn't work properly on the UITableView based timeline
|
||||
attribution: mapTilerAttributionPlacement) {
|
||||
if let url = mapURLBuilder.staticMapURL(for: colorScheme.mapStyle,
|
||||
coordinates: coordinates,
|
||||
zoomLevel: zoomLevel,
|
||||
size: mapSize, // temporary using a fixed size since the refresh doesn't work properly on the UITableView based timeline
|
||||
attribution: mapTilerAttributionPlacement) {
|
||||
AsyncImage(url: url) { phase in
|
||||
switch phase {
|
||||
case .empty:
|
||||
@@ -66,7 +66,7 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
|
||||
}
|
||||
|
||||
private var placeholderImage: some View {
|
||||
Image("mapBlurred")
|
||||
Image(asset: Asset.Images.mapBlurred)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
}
|
||||
@@ -104,15 +104,22 @@ struct MapLibreStaticMapView_Previews: PreviewProvider, TestablePreview {
|
||||
MapLibreStaticMapView(coordinates: CLLocationCoordinate2D(),
|
||||
zoomLevel: 15,
|
||||
attributionPlacement: .bottomLeft,
|
||||
mapTilerStatic: MapTilerStaticMapMock(), mapSize: .init(width: 300, height: 200)) {
|
||||
mapURLBuilder: MapTilerURLBuilderMock(),
|
||||
mapSize: .init(width: 300, height: 200)) {
|
||||
Image(systemName: "mappin.circle.fill")
|
||||
.padding(.bottom, 35)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct MapTilerStaticMapMock: MapTilerStaticMapProtocol {
|
||||
func staticMapURL(for style: MapTilerStyle, coordinates: CLLocationCoordinate2D, zoomLevel: Double, size: CGSize, attribution: MapTilerAttributionPlacement) -> URL? {
|
||||
private struct MapTilerURLBuilderMock: MapTilerURLBuilderProtocol {
|
||||
func dynamicMapURL(for style: MapTilerStyle) -> URL? { nil }
|
||||
|
||||
func staticMapURL(for style: MapTilerStyle,
|
||||
coordinates: CLLocationCoordinate2D,
|
||||
zoomLevel: Double,
|
||||
size: CGSize,
|
||||
attribution: MapTilerAttributionPlacement) -> URL? {
|
||||
switch style {
|
||||
case .light:
|
||||
return URL(string: "https://www.maptiler.com/img/cloud/home/map5.webp")
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MapTilerAuthorization {
|
||||
private let key: String
|
||||
|
||||
init(key: String) {
|
||||
self.key = key
|
||||
}
|
||||
|
||||
func authorizeURL(_ url: URL) -> URL {
|
||||
url.appending(queryItems: [URLQueryItem(name: "key", value: key)])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
/// All of the configuration necessary to use MapTiler maps.
|
||||
///
|
||||
/// The style IDs need to be generated with the account that the API key belongs to. For more information read
|
||||
/// [FORKING.md](https://github.com/element-hq/element-x-ios/blob/develop/docs/FORKING.md#setup-the-location-sharing)
|
||||
struct MapTilerConfiguration {
|
||||
let baseURL: URL
|
||||
let apiKey: String
|
||||
/// A MapLibre style ID for a light-mode map.
|
||||
let lightStyleID: String
|
||||
/// A MapLibre style ID for a dark-mode map.
|
||||
let darkStyleID: String
|
||||
}
|
||||
|
||||
extension MapTilerConfiguration: MapTilerURLBuilderProtocol {
|
||||
func dynamicMapURL(for style: MapTilerStyle) -> URL? {
|
||||
var url = makeNewURL(for: style)
|
||||
url.appendPathComponent("style.json", conformingTo: .json)
|
||||
return url
|
||||
}
|
||||
|
||||
func staticMapURL(for style: MapTilerStyle,
|
||||
coordinates: CLLocationCoordinate2D,
|
||||
zoomLevel: Double,
|
||||
size: CGSize,
|
||||
attribution: MapTilerAttributionPlacement) -> URL? {
|
||||
var url = makeNewURL(for: style)
|
||||
url.appendPathComponent(String(format: "static/%f,%f,%f/%dx%d@2x.png",
|
||||
coordinates.longitude,
|
||||
coordinates.latitude,
|
||||
zoomLevel,
|
||||
Int(size.width),
|
||||
Int(size.height)),
|
||||
conformingTo: .png)
|
||||
url.append(queryItems: [.init(name: "attribution", value: attribution.rawValue)])
|
||||
return url
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func makeNewURL(for style: MapTilerStyle) -> URL {
|
||||
var url: URL = baseURL
|
||||
url.appendPathComponent(styleID(for: style), conformingTo: .item)
|
||||
url.append(queryItems: [URLQueryItem(name: "key", value: apiKey)])
|
||||
return url
|
||||
}
|
||||
|
||||
private func styleID(for style: MapTilerStyle) -> String {
|
||||
switch style {
|
||||
case .light: lightStyleID
|
||||
case .dark: darkStyleID
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
|
||||
struct MapTilerStaticMap: MapTilerStaticMapProtocol {
|
||||
private let baseURL: URL
|
||||
private let key: String
|
||||
|
||||
init(baseURL: URL, key: String) {
|
||||
self.baseURL = baseURL
|
||||
self.key = key
|
||||
}
|
||||
|
||||
func staticMapURL(for style: MapTilerStyle, coordinates: CLLocationCoordinate2D, zoomLevel: Double, size: CGSize, attribution: MapTilerAttributionPlacement) -> URL? {
|
||||
var url: URL = baseURL
|
||||
url.appendPathComponent(style.rawValue, conformingTo: .item)
|
||||
url.appendPathComponent(String(format: "static/%f,%f,%f/%dx%d@2x.png", coordinates.longitude, coordinates.latitude, zoomLevel, Int(size.width), Int(size.height)), conformingTo: .png)
|
||||
url.append(queryItems: [.init(name: "attribution", value: attribution.rawValue)])
|
||||
let authorization = MapTilerAuthorization(key: key)
|
||||
return authorization.authorizeURL(url)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
/// The style for a map.
|
||||
/// Values should be Map Libre style IDs generated with an account where the API key belongs to.
|
||||
/// For more information read [FORKING.md](https://github.com/element-hq/element-x-ios/blob/develop/docs/FORKING.md#setup-the-location-sharing).
|
||||
enum MapTilerStyle: String {
|
||||
case light = "9bc819c8-e627-474a-a348-ec144fe3d810"
|
||||
case dark = "dea61faf-292b-4774-9660-58fcef89a7f3"
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct MapTilerStyleBuilder: MapTilerStyleBuilderProtocol {
|
||||
private let baseURL: URL
|
||||
private let key: String
|
||||
|
||||
init(baseURL: URL, key: String) {
|
||||
self.baseURL = baseURL
|
||||
self.key = key
|
||||
}
|
||||
|
||||
func dynamicMapURL(for style: MapTilerStyle) -> URL? {
|
||||
var url: URL = baseURL
|
||||
url.appendPathComponent(style.rawValue, conformingTo: .item)
|
||||
url.appendPathComponent("style.json", conformingTo: .json)
|
||||
let authorization = MapTilerAuthorization(key: key)
|
||||
return authorization.authorizeURL(url)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//
|
||||
// Copyright 2023, 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol MapTilerStyleBuilderProtocol {
|
||||
func dynamicMapURL(for style: MapTilerStyle) -> URL?
|
||||
}
|
||||
+3
-1
@@ -7,7 +7,9 @@
|
||||
|
||||
import CoreLocation
|
||||
|
||||
protocol MapTilerStaticMapProtocol {
|
||||
protocol MapTilerURLBuilderProtocol {
|
||||
func dynamicMapURL(for style: MapTilerStyle) -> URL?
|
||||
|
||||
func staticMapURL(for style: MapTilerStyle,
|
||||
coordinates: CLLocationCoordinate2D,
|
||||
zoomLevel: Double,
|
||||
@@ -25,24 +25,26 @@ enum StaticLocationInteractionMode: Hashable {
|
||||
}
|
||||
|
||||
struct StaticLocationScreenViewState: BindableState {
|
||||
init(interactionMode: StaticLocationInteractionMode) {
|
||||
init(interactionMode: StaticLocationInteractionMode, mapURLBuilder: MapTilerURLBuilderProtocol) {
|
||||
self.interactionMode = interactionMode
|
||||
switch interactionMode {
|
||||
case .picker:
|
||||
bindings.showsUserLocationMode = .showAndFollow
|
||||
case .viewOnly:
|
||||
bindings.showsUserLocationMode = .show
|
||||
self.mapURLBuilder = mapURLBuilder
|
||||
|
||||
bindings.showsUserLocationMode = switch interactionMode {
|
||||
case .picker: .showAndFollow
|
||||
case .viewOnly: .show
|
||||
}
|
||||
}
|
||||
|
||||
let interactionMode: StaticLocationInteractionMode
|
||||
let mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
|
||||
var bindings = StaticLocationScreenBindings(showsUserLocationMode: .hide)
|
||||
|
||||
/// Indicates whether the user is sharing his current location
|
||||
var isSharingUserLocation: Bool {
|
||||
bindings.isLocationAuthorized == true && bindings.showsUserLocationMode == .showAndFollow
|
||||
}
|
||||
|
||||
var bindings = StaticLocationScreenBindings(showsUserLocationMode: .hide)
|
||||
|
||||
var initialMapCenter: CLLocationCoordinate2D {
|
||||
switch interactionMode {
|
||||
case .picker:
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
|
||||
struct StaticLocationScreenCoordinatorParameters {
|
||||
let interactionMode: StaticLocationInteractionMode
|
||||
let mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
let appMediator: AppMediatorProtocol
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ final class StaticLocationScreenCoordinator: CoordinatorProtocol {
|
||||
init(parameters: StaticLocationScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = StaticLocationScreenViewModel(interactionMode: parameters.interactionMode)
|
||||
viewModel = StaticLocationScreenViewModel(interactionMode: parameters.interactionMode, mapURLBuilder: parameters.mapURLBuilder)
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@@ -17,8 +17,8 @@ class StaticLocationScreenViewModel: StaticLocationScreenViewModelType, StaticLo
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(interactionMode: StaticLocationInteractionMode) {
|
||||
super.init(initialViewState: .init(interactionMode: interactionMode))
|
||||
init(interactionMode: StaticLocationInteractionMode, mapURLBuilder: MapTilerURLBuilderProtocol) {
|
||||
super.init(initialViewState: .init(interactionMode: interactionMode, mapURLBuilder: mapURLBuilder))
|
||||
}
|
||||
|
||||
override func process(viewAction: StaticLocationScreenViewAction) {
|
||||
|
||||
@@ -11,8 +11,6 @@ import SwiftUI
|
||||
struct StaticLocationScreen: View {
|
||||
@ObservedObject var context: StaticLocationScreenViewModel.Context
|
||||
|
||||
private let builder = MapTilerStyleBuilder(appSettings: ServiceLocator.shared.settings)
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
if let locationDescription = context.viewState.locationDescription {
|
||||
@@ -34,7 +32,7 @@ struct StaticLocationScreen: View {
|
||||
|
||||
private var mapView: some View {
|
||||
ZStack(alignment: .center) {
|
||||
MapLibreMapView(builder: builder,
|
||||
MapLibreMapView(mapURLBuilder: context.viewState.mapURLBuilder,
|
||||
options: mapOptions,
|
||||
showsUserLocationMode: $context.showsUserLocationMode,
|
||||
error: $context.mapError,
|
||||
@@ -148,19 +146,29 @@ struct StaticLocationScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct StaticLocationScreenViewer_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = StaticLocationScreenViewModel(interactionMode: .viewOnly(geoURI: .init(latitude: 41.9027835,
|
||||
longitude: 12.4963655)),
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration)
|
||||
static let pickerViewModel = StaticLocationScreenViewModel(interactionMode: .picker,
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration)
|
||||
static let descriptionViewModel = StaticLocationScreenViewModel(interactionMode: .viewOnly(geoURI: .init(latitude: 41.9027835,
|
||||
longitude: 12.4963655),
|
||||
description: "Cool position"),
|
||||
mapURLBuilder: ServiceLocator.shared.settings.mapTilerConfiguration)
|
||||
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
StaticLocationScreen(context: StaticLocationScreenViewModel(interactionMode: .picker).context)
|
||||
StaticLocationScreen(context: pickerViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Picker")
|
||||
|
||||
NavigationStack {
|
||||
StaticLocationScreen(context: StaticLocationScreenViewModel(interactionMode: .viewOnly(geoURI: .init(latitude: 41.9027835, longitude: 12.4963655))).context)
|
||||
StaticLocationScreen(context: viewModel.context)
|
||||
}
|
||||
.previewDisplayName("View Only")
|
||||
|
||||
NavigationStack {
|
||||
StaticLocationScreen(context: StaticLocationScreenViewModel(interactionMode: .viewOnly(geoURI: .init(latitude: 41.9027835, longitude: 12.4963655), description: "Cool position")).context)
|
||||
StaticLocationScreen(context: descriptionViewModel.context)
|
||||
}
|
||||
.previewDisplayName("View Only (with description)")
|
||||
}
|
||||
|
||||
@@ -109,8 +109,6 @@ struct TimelineViewState: BindableState {
|
||||
/// an openURL closure which opens URLs first using the App's environment rather than skipping out to external apps
|
||||
var openURL: OpenURLAction?
|
||||
|
||||
var bindings: TimelineViewStateBindings
|
||||
|
||||
/// A closure providing the associated audio player state for an item in the timeline.
|
||||
var audioPlayerStateProvider: (@MainActor (_ itemId: TimelineItemIdentifier) -> AudioPlayerState?)?
|
||||
|
||||
@@ -124,6 +122,10 @@ struct TimelineViewState: BindableState {
|
||||
var roomNameForAliasResolver: (@MainActor (String) -> String?)?
|
||||
|
||||
var emojiProvider: EmojiProviderProtocol
|
||||
|
||||
var mapURLBuilder: MapTilerURLBuilderProtocol
|
||||
|
||||
var bindings: TimelineViewStateBindings
|
||||
}
|
||||
|
||||
struct TimelineViewStateBindings {
|
||||
|
||||
@@ -94,8 +94,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
isViewSourceEnabled: appSettings.viewSourceEnabled,
|
||||
hideTimelineMedia: appSettings.hideTimelineMedia,
|
||||
pinnedEventIDs: roomProxy.infoPublisher.value.pinnedEventIDs,
|
||||
bindings: .init(reactionsCollapsed: [:]),
|
||||
emojiProvider: emojiProvider),
|
||||
emojiProvider: emojiProvider,
|
||||
mapURLBuilder: appSettings.mapTilerConfiguration,
|
||||
bindings: .init(reactionsCollapsed: [:])),
|
||||
mediaProvider: mediaProvider)
|
||||
|
||||
if focussedEventID != nil {
|
||||
|
||||
+8
-6
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LocationRoomTimelineView: View {
|
||||
@Environment(\.timelineContext) private var context
|
||||
@Environment(\.timelineContext) private var context: TimelineViewModel.Context!
|
||||
let timelineItem: LocationRoomTimelineItem
|
||||
|
||||
var body: some View {
|
||||
@@ -17,7 +17,7 @@ struct LocationRoomTimelineView: View {
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(accessibilityLabel)
|
||||
.onTapGesture {
|
||||
context?.send(viewAction: .mediaTapped(itemID: timelineItem.id))
|
||||
context.send(viewAction: .mediaTapped(itemID: timelineItem.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,9 @@ struct LocationRoomTimelineView: View {
|
||||
descriptionView
|
||||
.frame(maxWidth: mapAspectRatio * mapMaxHeight, alignment: .leading)
|
||||
|
||||
MapLibreStaticMapView(geoURI: geoURI, mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
|
||||
MapLibreStaticMapView(geoURI: geoURI,
|
||||
mapURLBuilder: context.viewState.mapURLBuilder,
|
||||
mapSize: .init(width: mapAspectRatio * mapMaxHeight, height: mapMaxHeight)) {
|
||||
LocationMarkerView()
|
||||
}
|
||||
.frame(maxHeight: mapMaxHeight)
|
||||
@@ -64,12 +66,11 @@ struct LocationRoomTimelineView: View {
|
||||
}
|
||||
|
||||
private extension MapLibreStaticMapView {
|
||||
init(geoURI: GeoURI, mapSize: CGSize, @ViewBuilder pinAnnotationView: () -> PinAnnotation) {
|
||||
init(geoURI: GeoURI, mapURLBuilder: MapTilerURLBuilderProtocol, mapSize: CGSize, @ViewBuilder pinAnnotationView: () -> PinAnnotation) {
|
||||
self.init(coordinates: .init(latitude: geoURI.latitude, longitude: geoURI.longitude),
|
||||
zoomLevel: 15,
|
||||
attributionPlacement: .bottomLeft,
|
||||
mapTilerStatic: MapTilerStaticMap(baseURL: ServiceLocator.shared.settings.mapTilerBaseURL,
|
||||
key: ServiceLocator.shared.settings.mapTilerApiKey),
|
||||
mapURLBuilder: mapURLBuilder,
|
||||
mapSize: mapSize,
|
||||
pinAnnotationView: pinAnnotationView)
|
||||
}
|
||||
@@ -85,6 +86,7 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview {
|
||||
}
|
||||
}
|
||||
.environmentObject(viewModel.context)
|
||||
.environment(\.timelineContext, viewModel.context)
|
||||
.previewDisplayName("Bubbles")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user