Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d5f1767
feat(map): migrate from MapKit to MapLibre Native
Avi0n Mar 14, 2026
eb2bb37
fix(l10n): use Measurement formatting for trace path distances instea…
Avi0n Mar 15, 2026
2215544
fix(ui): use alert(presenting:) to safely unwrap optional in delete c…
Avi0n Mar 15, 2026
a5c570f
fix(style): use Color() shorthand instead of Color(uiColor:) for syst…
Avi0n Mar 15, 2026
724aafd
refactor(ui): apply SwiftUI Pro quick fixes across branch
Avi0n Mar 15, 2026
3678e1c
refactor(ui): replace _VariadicView private SPI with Group(subviews:)
Avi0n Mar 15, 2026
781eed7
fix(a11y): use Dynamic Type fonts and accessible Button form in map c…
Avi0n Mar 15, 2026
b431af2
fix(map): reuse LayersMenu in line-of-sight view, fixing missing netw…
Avi0n Mar 15, 2026
06f9380
refactor(ui): replace UIActivityViewController wrapper with ShareLink
Avi0n Mar 15, 2026
4d2761c
refactor(structure): extract types to own files per project convention
Avi0n Mar 15, 2026
c933796
refactor(los): extract computed view properties into View structs
Avi0n Mar 15, 2026
3d5a58c
refactor(trace): extract computed view properties into View structs
Avi0n Mar 15, 2026
2e4da08
refactor(views): extract remaining computed view properties into View…
Avi0n Mar 15, 2026
472bdaa
fix(map): correct Metal drawable scale in landscape
Avi0n Mar 16, 2026
b2be865
fix(map): render pin sprites at retina scale
Avi0n Mar 16, 2026
a30ed90
refactor: simplify maplibre-migration branch code
Avi0n Mar 16, 2026
695c441
fix(map): fix landscape scale flicker via dual UIView swizzle
Avi0n Mar 20, 2026
20c0929
refactor(map): move mapPoints construction to MapViewModel
Avi0n Mar 20, 2026
f577f88
feat(offline-maps): add satellite and topo offline layer support
Avi0n Mar 25, 2026
61ca8b7
refactor(map): simplify view hierarchy and add north-lock button
Avi0n Mar 25, 2026
9c96097
refactor(los): move display state to view model and extract subviews
Avi0n Mar 25, 2026
68461a9
refactor(trace-path): cache computed state and rebuild selectively
Avi0n Mar 25, 2026
bb46490
ui(offline-maps): remove section title, rename complete to downloaded
Avi0n Mar 25, 2026
d9f220f
fix(map): always show map regardless of contact location data
Avi0n Mar 25, 2026
aef671a
fix(trace-path): respect sidebar safe area when auto-zooming map
Avi0n Mar 25, 2026
2f90fc2
fix(map): use black text with no halo on map labels
Avi0n Mar 25, 2026
2ecc2c6
feat(map): add north lock button to trace path and line of sight maps
Avi0n Mar 25, 2026
2e643de
fix(map): use fixed white pill background for label readability in da…
Avi0n Mar 25, 2026
eac737c
fix(offline-maps): remove satellite offline download
Avi0n Mar 25, 2026
1604b43
refactor(map): persist map style and label settings with @AppStorage
Avi0n Mar 27, 2026
3eab6cb
fix(map): improve node label layer rendering
Avi0n Mar 27, 2026
f5744f8
fix(map): update renamed adminAccess l10n key to management
Avi0n Mar 27, 2026
6d29cb3
fix(map): pre-render label sprites to fix z-ordering
Avi0n Mar 27, 2026
29721e2
chore(l10n): regenerate with updated remote nodes strings
Avi0n Mar 27, 2026
af6ff4d
fix(map): prevent repeater pin blink on trace path tap
Avi0n Mar 27, 2026
49b908a
fix(map): disable quick-zoom gesture
Avi0n Mar 27, 2026
3476b73
ui(map): add white border to trace path lines
Avi0n Mar 27, 2026
96c0ba8
fix(trace-path): use outbound path index for SNR hop lookup
Avi0n Mar 27, 2026
06f0ef7
chore(merge): merge dev into feature/maplibre-migration
Avi0n Mar 27, 2026
ac38a90
fix(trace): use SNRQuality for signal level and color in TraceResultH…
Avi0n Mar 27, 2026
c0a5946
fix(i18n): translate missing offline map strings, add MainActor to Pi…
Avi0n Mar 27, 2026
87dbe65
fix(offline-maps): remove delete confirmation to fix row flicker
Avi0n Mar 27, 2026
69ee50a
fix(map): flush deferred data updates after gestures, gate offline st…
Avi0n Mar 27, 2026
96cb358
fix(map): address PR review findings
Avi0n Mar 27, 2026
28a411c
chore(l10n): add accessibility and fallback strings
Avi0n Mar 27, 2026
2fa4beb
fix(map): update estimated size on pinch-to-zoom in region picker
Avi0n Mar 27, 2026
2cbe574
fix(offline-maps): correct vector tile size estimates
Avi0n Mar 28, 2026
6541d53
fix(map): restore bestAvailableLocation for recenter
Avi0n Mar 28, 2026
409e1c8
fix(offline): force liberty style offline, check viewport coverage
Avi0n Mar 28, 2026
a722d1a
fix(map): accessibility and code quality cleanup
Avi0n Mar 28, 2026
2f780d4
fix(map): guard repeater and room actions on connection state
Avi0n Mar 28, 2026
94e12c5
refactor(map): store path index structurally on MapLine
Avi0n Mar 28, 2026
e379d70
perf(map): download offline map layers concurrently
Avi0n Mar 28, 2026
2de70ce
fix(a11y): add VoiceOver labels to overlay dismiss buttons
Avi0n Mar 28, 2026
11ffb90
fix(map): replace deprecated Text concat and fix CI concurrency error
Avi0n Mar 28, 2026
912f62f
fix(map): replace task group with sequential downloads for CI compat
Avi0n Mar 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ playground.xcworkspace

build/
.build/
.spm-cache/

# CocoaPods
#
Expand Down
46 changes: 46 additions & 0 deletions MC1/Extensions/CLLocationCoordinate2D+BoundingRegion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import CoreLocation
import MapKit
import MapLibre

extension Array where Element == CLLocationCoordinate2D {
/// Computes a bounding `MKCoordinateRegion` that fits all coordinates with padding.
func boundingRegion(paddingMultiplier: Double = 1.5) -> MKCoordinateRegion? {
guard let first else { return nil }

var minLat = first.latitude, maxLat = first.latitude
var minLon = first.longitude, maxLon = first.longitude

for coord in dropFirst() {
minLat = Swift.min(minLat, coord.latitude)
maxLat = Swift.max(maxLat, coord.latitude)
minLon = Swift.min(minLon, coord.longitude)
maxLon = Swift.max(maxLon, coord.longitude)
}

return MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: (minLat + maxLat) / 2,
longitude: (minLon + maxLon) / 2
),
span: MKCoordinateSpan(
latitudeDelta: Swift.min(180, Swift.max(0.01, (maxLat - minLat) * paddingMultiplier)),
longitudeDelta: Swift.min(360, Swift.max(0.01, (maxLon - minLon) * paddingMultiplier))
)
)
}
}

extension MKCoordinateRegion {
func toMLNCoordinateBounds() -> MLNCoordinateBounds {
MLNCoordinateBounds(
sw: CLLocationCoordinate2D(
latitude: center.latitude - span.latitudeDelta / 2,
longitude: center.longitude - span.longitudeDelta / 2
),
ne: CLLocationCoordinate2D(
latitude: center.latitude + span.latitudeDelta / 2,
longitude: center.longitude + span.longitudeDelta / 2
)
)
}
}
7 changes: 7 additions & 0 deletions MC1/Extensions/CLLocationCoordinate2D+Formatting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import CoreLocation

extension CLLocationCoordinate2D {
var formattedString: String {
"\(latitude.formatted(.number.precision(.fractionLength(6)))), \(longitude.formatted(.number.precision(.fractionLength(6))))"
}
}
8 changes: 8 additions & 0 deletions MC1/Extensions/ContactDTO+Coordinate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import CoreLocation
import MC1Services

extension ContactDTO {
var coordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
}
28 changes: 28 additions & 0 deletions MC1/Extensions/ContactType+Display.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import MeshCore
import SwiftUI

extension ContactType {
var iconSystemName: String {
switch self {
case .chat: "person.fill"
case .repeater: "antenna.radiowaves.left.and.right"
case .room: "person.3.fill"
}
}

var displayColor: Color {
switch self {
case .chat: .blue
case .repeater: .green
case .room: .purple
}
}

var pinStyle: MapPoint.PinStyle {
switch self {
case .chat: .contactChat
case .repeater: .contactRepeater
case .room: .contactRoom
}
}
}
10 changes: 10 additions & 0 deletions MC1/Extensions/View+LiquidGlass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ extension View {
}
}

/// Applies glass button style on iOS 26+, falls back to bordered (secondary weight) on earlier versions
@ViewBuilder
func liquidGlassSecondaryButtonStyle() -> some View {
if #available(iOS 26.0, *) {
self.buttonStyle(.glass)
} else {
self.buttonStyle(.bordered)
}
}

/// Applies prominent glass button style with tint on iOS 26+, falls back to borderedProminent on earlier versions
@ViewBuilder
func liquidGlassProminentButtonStyle() -> some View {
Expand Down
110 changes: 100 additions & 10 deletions MC1/Resources/Generated/L10n.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,12 @@ public enum L10n {
public static func viewRuns(_ p1: Int) -> String {
return L10n.tr("Contacts", "contacts.results.viewRuns", p1, fallback: "View %d runs")
}
public enum Comparison {
/// Decreased
public static let decreased = L10n.tr("Contacts", "contacts.results.comparison.decreased", fallback: "Decreased")
/// Location: ComparisonRowView.swift - Purpose: Accessibility labels for change direction
public static let increased = L10n.tr("Contacts", "contacts.results.comparison.increased", fallback: "Increased")
}
public enum Hop {
/// Location: TraceResultsSheet.swift - Purpose: Average SNR display
public static func avgSNR(_ p1: Any, _ p2: Any, _ p3: Any) -> String {
Expand Down Expand Up @@ -1772,8 +1778,12 @@ public enum L10n {
}
}
public enum Map {
/// Location: TracePathMapView.swift - Purpose: Center on path accessibility
public static let centerOnPath = L10n.tr("Contacts", "contacts.trace.map.centerOnPath", fallback: "Center on path")
/// Location: TracePathMapView.swift - Purpose: Clear button
public static let clear = L10n.tr("Contacts", "contacts.trace.map.clear", fallback: "Clear")
/// Location: TracePathMapViewModel.swift - Purpose: Default path name fallback
public static let defaultPathName = L10n.tr("Contacts", "contacts.trace.map.defaultPathName", fallback: "Path")
/// Location: TracePathMapView.swift - Purpose: Hide labels accessibility
public static let hideLabels = L10n.tr("Contacts", "contacts.trace.map.hideLabels", fallback: "Hide labels")
/// Location: TracePathMapView.swift - Purpose: Hops count in results banner
Expand Down Expand Up @@ -2021,10 +2031,10 @@ public enum L10n {
}
}
public enum Common {
/// Dismiss
public static let dismissOverlay = L10n.tr("Map", "map.common.dismissOverlay", fallback: "Dismiss")
/// Location: MapView.swift - Purpose: Done button for sheets
public static let done = L10n.tr("Map", "map.common.done", fallback: "Done")
/// Location: MapView.swift - Purpose: Refresh button label
public static let refresh = L10n.tr("Map", "map.common.refresh", fallback: "Refresh")
}
public enum Controls {
/// Location: MapView.swift - Purpose: Accessibility label for center on all contacts button
Expand All @@ -2035,8 +2045,14 @@ public enum L10n {
public static let hideLabels = L10n.tr("Map", "map.controls.hideLabels", fallback: "Hide labels")
/// Location: MapControlsToolbar.swift - Purpose: Accessibility label for layers button
public static let layers = L10n.tr("Map", "map.controls.layers", fallback: "Map layers")
/// Location: MapCanvasView.swift - Purpose: Accessibility label for north lock button (lock)
public static let lockNorth = L10n.tr("Map", "map.controls.lockNorth", fallback: "Lock to north")
/// Location: MapView.swift - Purpose: Accessibility label for refresh button
public static let refresh = L10n.tr("Map", "map.controls.refresh", fallback: "Refresh contacts")
/// Location: MapView.swift - Purpose: Accessibility label when labels are hidden
public static let showLabels = L10n.tr("Map", "map.controls.showLabels", fallback: "Show labels")
/// Location: MapCanvasView.swift - Purpose: Accessibility label for north lock button (unlock)
public static let unlockNorth = L10n.tr("Map", "map.controls.unlockNorth", fallback: "Unlock rotation")
}
public enum Detail {
/// Location: MapView.swift ContactDetailSheet - Purpose: Value showing contact is favorited
Expand Down Expand Up @@ -2088,12 +2104,6 @@ public enum L10n {
public static let networkPath = L10n.tr("Map", "map.detail.section.networkPath", fallback: "Network Path")
}
}
public enum EmptyState {
/// Location: MapView.swift - Purpose: Empty state description
public static let description = L10n.tr("Map", "map.emptyState.description", fallback: "Contacts with location data will appear here once discovered on the mesh network.")
/// Location: MapView.swift - Purpose: Empty state title when no contacts have location
public static let title = L10n.tr("Map", "map.emptyState.title", fallback: "No Contacts on Map")
}
public enum NodeKind {
/// Location: MapView.swift ContactDetailSheet - Purpose: Display name for chat contact type
public static let chatContact = L10n.tr("Map", "map.nodeKind.chatContact", fallback: "Chat Contact")
Expand All @@ -2102,13 +2112,23 @@ public enum L10n {
/// Location: MapView.swift ContactDetailSheet - Purpose: Display name for room type
public static let room = L10n.tr("Map", "map.nodeKind.room", fallback: "Room")
}
public enum OfflineBadge {
/// Label shown on map when device has no internet connection
public static let label = L10n.tr("Map", "map.offlineBadge.label", fallback: "Offline")
}
public enum Style {
/// Location: MapStyleSelection.swift - Purpose: Hybrid map style option
public static let hybrid = L10n.tr("Map", "map.style.hybrid", fallback: "Hybrid")
/// Location: LayersMenu.swift - Purpose: Accessibility label for map style menu
public static let accessibilityLabel = L10n.tr("Map", "map.style.accessibilityLabel", fallback: "Map style")
/// Location: LayersMenu.swift - Purpose: Hint when no offline pack covers viewport
public static let noOfflineCoverage = L10n.tr("Map", "map.style.noOfflineCoverage", fallback: "No offline map covers this area")
/// Location: LayersMenu.swift - Purpose: Hint when style requires network
public static let requiresNetwork = L10n.tr("Map", "map.style.requiresNetwork", fallback: "Requires network connection")
/// Location: MapStyleSelection.swift - Purpose: Satellite map style option
public static let satellite = L10n.tr("Map", "map.style.satellite", fallback: "Satellite")
/// Location: MapStyleSelection.swift - Purpose: Standard map style option
public static let standard = L10n.tr("Map", "map.style.standard", fallback: "Standard")
/// Location: MapStyleSelection.swift - Purpose: Topo map style option
public static let topo = L10n.tr("Map", "map.style.topo", fallback: "Topography")
}
}
}
Expand Down Expand Up @@ -3701,6 +3721,76 @@ public enum L10n {
/// Toggle label for room messages notifications
public static let roomMessages = L10n.tr("Settings", "notifications.roomMessages", fallback: "Room Messages")
}
public enum OfflineMaps {
/// Cancel button
public static let cancel = L10n.tr("Settings", "offlineMaps.cancel", fallback: "Cancel")
/// Status when pack download is complete
public static let complete = L10n.tr("Settings", "offlineMaps.complete", fallback: "Downloaded")
/// Delete button
public static let delete = L10n.tr("Settings", "offlineMaps.delete", fallback: "Delete")
/// Delete confirmation message
public static let deleteMessage = L10n.tr("Settings", "offlineMaps.deleteMessage", fallback: "The downloaded map data will be removed.")
/// Delete confirmation title
public static let deleteTitle = L10n.tr("Settings", "offlineMaps.deleteTitle", fallback: "Delete Offline Map?")
/// Button to start download
public static let download = L10n.tr("Settings", "offlineMaps.download", fallback: "Download")
/// Hint shown before estimate is available
public static let downloadHint = L10n.tr("Settings", "offlineMaps.downloadHint", fallback: "Enter a name and select an area to download.")
/// Status when pack is downloading
public static let downloading = L10n.tr("Settings", "offlineMaps.downloading", fallback: "Downloading…")
/// Button to download a new offline region
public static let downloadRegion = L10n.tr("Settings", "offlineMaps.downloadRegion", fallback: "Download Region")
/// Description for empty state
public static let emptyDescription = L10n.tr("Settings", "offlineMaps.emptyDescription", fallback: "Download map regions for use without internet.")
/// Title for empty state when no offline packs exist
public static let emptyTitle = L10n.tr("Settings", "offlineMaps.emptyTitle", fallback: "No Offline Maps")
/// Estimated download size
public static func estimatedSize(_ p1: Any) -> String {
return L10n.tr("Settings", "offlineMaps.estimatedSize", String(describing: p1), fallback: "Estimated size: ~%@")
}
/// Download exceeds available storage
public static let exceedsStorage = L10n.tr("Settings", "offlineMaps.exceedsStorage", fallback: "Not enough storage on this device. Zoom in to select a smaller area.")
/// Include layers prompt
public static let includeLayers = L10n.tr("Settings", "offlineMaps.includeLayers", fallback: "Include additional layers for offline use.")
/// Large tile download warning
public static let largeTileWarning = L10n.tr("Settings", "offlineMaps.largeTileWarning", fallback: "Large download area. This may take a while and use significant storage.")
/// Layers section header
public static let layers = L10n.tr("Settings", "offlineMaps.layers", fallback: "Layers")
/// No network available
public static let noNetwork = L10n.tr("Settings", "offlineMaps.noNetwork", fallback: "An internet connection is required to download maps.")
/// Pause download button
public static let pause = L10n.tr("Settings", "offlineMaps.pause", fallback: "Pause")
/// Paused status label
public static let paused = L10n.tr("Settings", "offlineMaps.paused", fallback: "Paused")
/// Navigation title for region picker sheet
public static let pickRegion = L10n.tr("Settings", "offlineMaps.pickRegion", fallback: "Select Region")
/// Placeholder for region name text field
public static let regionName = L10n.tr("Settings", "offlineMaps.regionName", fallback: "Region Name")
/// Resume download button
public static let resume = L10n.tr("Settings", "offlineMaps.resume", fallback: "Resume")
/// Section header for storage info
public static let storage = L10n.tr("Settings", "offlineMaps.storage", fallback: "Storage")
/// Storage section footer
public static let storageFooter = L10n.tr("Settings", "offlineMaps.storageFooter", fallback: "Includes map data and internal indexes. Total may be larger than the sum of individual downloads.")
/// Label for total storage used
public static let storageUsed = L10n.tr("Settings", "offlineMaps.storageUsed", fallback: "Storage Used")
/// Navigation title for offline maps settings
public static let title = L10n.tr("Settings", "offlineMaps.title", fallback: "Offline Maps")
/// Fallback name for unknown region
public static let unknownRegion = L10n.tr("Settings", "offlineMaps.unknownRegion", fallback: "Unknown Region")
public enum Error {
/// Error: insufficient disk space
public static let insufficientDiskSpace = L10n.tr("Settings", "offlineMaps.error.insufficientDiskSpace", fallback: "Not enough storage space. At least 100 MB is required.")
/// Error: tile limit reached
public static let tileLimitReached = L10n.tr("Settings", "offlineMaps.error.tileLimitReached", fallback: "The download tile limit has been reached.")
}
public enum Layer {
/// Layer type labels
public static let base = L10n.tr("Settings", "offlineMaps.layer.base", fallback: "Base Map")
/// Topography
public static let topo = L10n.tr("Settings", "offlineMaps.layer.topo", fallback: "Topography")
}
}
public enum PathHashMode {
/// Footer explaining path hash mode tradeoff
public static let footer = L10n.tr("Settings", "pathHashMode.footer", fallback: "Larger hashes reduce routing collisions but limit the maximum number of hops per path.")
Expand Down
10 changes: 10 additions & 0 deletions MC1/Resources/Localization/de.lproj/Contacts.strings
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,9 @@
/* Location: TracePathMapView.swift - Purpose: Show labels accessibility */
"contacts.trace.map.showLabels" = "Beschriftungen einblenden";

/* Location: TracePathMapView.swift - Purpose: Center on path accessibility */
"contacts.trace.map.centerOnPath" = "Auf Pfad zentrieren";

/* Location: TracePathMapView.swift - Purpose: Save path alert title */
"contacts.trace.map.saveTitle" = "Pfad speichern";

Expand All @@ -920,6 +923,9 @@
/* Location: TracePathMapView.swift - Purpose: View results button */
"contacts.trace.map.viewResults" = "Ergebnisse";

/* Location: TracePathMapViewModel.swift - Purpose: Default path name fallback */
"contacts.trace.map.defaultPathName" = "Pfad";

// MARK: - Trace Results Sheet

/* Location: TraceResultsSheet.swift - Purpose: Navigation title */
Expand Down Expand Up @@ -949,6 +955,10 @@
/* Location: TraceResultsSheet.swift - Purpose: Comparison text */
"contacts.results.comparison" = "vs. %d ms am %@";

/* Location: ComparisonRowView.swift - Purpose: Accessibility labels for change direction */
"contacts.results.comparison.increased" = "Gestiegen";
"contacts.results.comparison.decreased" = "Gesunken";

/* Location: TraceResultsSheet.swift - Purpose: View runs link */
"contacts.results.viewRuns" = "%d Durchläufe anzeigen";

Expand Down
39 changes: 26 additions & 13 deletions MC1/Resources/Localization/de.lproj/Map.strings
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,7 @@

/* Location: MapView.swift - Purpose: Done button for sheets */
"map.common.done" = "Fertig";

/* Location: MapView.swift - Purpose: Refresh button label */
"map.common.refresh" = "Aktualisieren";

// MARK: - Map View Empty State

/* Location: MapView.swift - Purpose: Empty state title when no contacts have location */
"map.emptyState.title" = "Keine Kontakte auf der Karte";

/* Location: MapView.swift - Purpose: Empty state description */
"map.emptyState.description" = "Kontakte mit Standortdaten erscheinen hier, sobald sie im Mesh-Netzwerk entdeckt werden.";
"map.common.dismissOverlay" = "Schließen";

// MARK: - Map Controls

Expand All @@ -39,6 +29,15 @@
/* Location: MapControlsToolbar.swift - Purpose: Accessibility label for layers button */
"map.controls.layers" = "Kartenebenen";

/* Location: MapCanvasView.swift - Purpose: Accessibility label for north lock button (lock) */
"map.controls.lockNorth" = "Nach Norden ausrichten";

/* Location: MapCanvasView.swift - Purpose: Accessibility label for north lock button (unlock) */
"map.controls.unlockNorth" = "Drehung freigeben";

/* Location: MapView.swift - Purpose: Accessibility label for refresh button */
"map.controls.refresh" = "Kontakte aktualisieren";

// MARK: - Map Style Selection

/* Location: MapStyleSelection.swift - Purpose: Standard map style option */
Expand All @@ -47,8 +46,17 @@
/* Location: MapStyleSelection.swift - Purpose: Satellite map style option */
"map.style.satellite" = "Satellit";

/* Location: MapStyleSelection.swift - Purpose: Hybrid map style option */
"map.style.hybrid" = "Hybrid";
/* Location: MapStyleSelection.swift - Purpose: Topo map style option */
"map.style.topo" = "Topografie";

/* Location: LayersMenu.swift - Purpose: Accessibility label for map style menu */
"map.style.accessibilityLabel" = "Map style";

/* Location: LayersMenu.swift - Purpose: Hint when style requires network */
"map.style.requiresNetwork" = "Requires network connection";

/* Location: LayersMenu.swift - Purpose: Hint when no offline pack covers viewport */
"map.style.noOfflineCoverage" = "No offline map covers this area";

// MARK: - Contact Detail Sheet

Expand Down Expand Up @@ -155,3 +163,8 @@

/* Location: ContactAnnotation.swift - Purpose: Subtitle for room nodes */
"map.annotation.room" = "Raum";

// MARK: - Offline Badge

/* Label shown on map when device has no internet connection */
"map.offlineBadge.label" = "Offline";
Loading
Loading