also looking at this
feat(maps): add key-free native Android maps and shared map controls
#363
28 files
+1857
-481
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -271,6 +271,16 @@ internal class CraftNativeMapView(context: Context) : FrameLayout(context), OnMa | ||
| 271 | 271 | if (horizontal > availableWidth && horizontal > 0) { left *= availableWidth / horizontal; right *= availableWidth / horizontal } |
| 272 | 272 | if (vertical > availableHeight && vertical > 0) { top *= availableHeight / vertical; bottom *= availableHeight / vertical } |
| 273 | 273 | map?.setPadding(left.roundToInt(), top.roundToInt(), right.roundToInt(), bottom.roundToInt()) |
| 274 | // Floating STX controls reserve the same space for credits and scale | |
| 275 | // as for route fitting, so a profile never covers either utility. | |
| 276 | (attribution.layoutParams as LayoutParams).let { params -> | |
| 277 | val margin = bottom.roundToInt() + (8 * resources.displayMetrics.density).roundToInt() | |
| 278 | if (params.bottomMargin != margin) { params.bottomMargin = margin; attribution.layoutParams = params } | |
| 279 | } | |
| 280 | (scale.layoutParams as LayoutParams).let { params -> | |
| 281 | val margin = bottom.roundToInt() + (32 * resources.displayMetrics.density).roundToInt() | |
| 282 | if (params.bottomMargin != margin) { params.bottomMargin = margin; scale.layoutParams = params } | |
| 283 | } | |
| 274 | 284 | } |
| 275 | 285 | |
| 276 | 286 | private fun moveCamera(bounds: LatLngBounds, padding: Int) { |
| @@ -843,7 +843,7 @@ final class NativeRenderUnitTests: XCTestCase { | ||
| 843 | 843 | let controller = CraftNativeScreenController(config: CraftConfig()) |
| 844 | 844 | controller.loadViewIfNeeded() |
| 845 | 845 | controller.render([ |
| 846 | "type": "Map", "props": ["key": "topo-map", "tileOverlay": ["urlTemplate": "https://example.org/tiles/{z}/{y}/{x}.png", "attribution": "Test map data"]], | |
| 846 | "type": "Map", "props": ["key": "topo-map", "edgePadding": ["bottom": 160], "tileOverlay": ["urlTemplate": "https://example.org/tiles/{z}/{y}/{x}.png", "attribution": "Test map data"]], | |
| 847 | 847 | "style": ["width": 320, "height": 480], |
| 848 | 848 | "children": [["type": "MapPolyline", "props": ["geometry": [37.7, -119.5, 37.72, -119.49]]]], |
| 849 | 849 | ]) |
| @@ -852,7 +852,10 @@ final class NativeRenderUnitTests: XCTestCase { | ||
| 852 | 852 | XCTAssertTrue(tiles.canReplaceMapContent) |
| 853 | 853 | XCTAssertTrue(map.delegate?.mapView?(map, rendererFor: tiles) is MKTileOverlayRenderer) |
| 854 | 854 | XCTAssertTrue(map.overlays.last is MKPolyline, "The route must remain above the basemap") |
| 855 | XCTAssertTrue(map.subviews.compactMap { $0 as? UILabel }.contains { $0.text == "Test map data" && !$0.isHidden }) | |
| 855 | controller.view.layoutIfNeeded() | |
| 856 | map.layoutIfNeeded() | |
| 857 | let credit = try XCTUnwrap(map.subviews.compactMap { $0 as? UILabel }.first { $0.text == "Test map data" && !$0.isHidden }) | |
| 858 | XCTAssertLessThanOrEqual(credit.frame.maxY, map.bounds.height - 160, "The floating profile must not cover map attribution") | |
| 856 | 859 | } |
| 857 | 860 | |
| 858 | 861 | func testMapReservesRoomForFloatingControls() throws { |
| @@ -1174,6 +1174,7 @@ private final class CraftNativeMapView: MKMapView, MKMapViewDelegate { | ||
| 1174 | 1174 | private var markerPinColors: [String: UIColor] = [:] |
| 1175 | 1175 | private var tileOverlay: MKTileOverlay? |
| 1176 | 1176 | private var tileTemplate: String? |
| 1177 | private var tileAttributionBottom: NSLayoutConstraint? | |
| 1177 | 1178 | private lazy var tileAttribution: UILabel = { |
| 1178 | 1179 | let label = UILabel() |
| 1179 | 1180 | label.font = .systemFont(ofSize: 9) |
| @@ -1182,7 +1183,9 @@ private final class CraftNativeMapView: MKMapView, MKMapViewDelegate { | ||
| 1182 | 1183 | label.translatesAutoresizingMaskIntoConstraints = false |
| 1183 | 1184 | label.numberOfLines = 2 |
| 1184 | 1185 | addSubview(label) |
| 1185 | NSLayoutConstraint.activate([label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8), label.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 48)]) | |
| 1186 | let bottom = label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8) | |
| 1187 | tileAttributionBottom = bottom | |
| 1188 | NSLayoutConstraint.activate([label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8), bottom, label.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 48)]) | |
| 1186 | 1189 | return label |
| 1187 | 1190 | }() |
| 1188 | 1191 | |
| @@ -1433,7 +1436,10 @@ private final class CraftNativeMapView: MKMapView, MKMapViewDelegate { | ||
| 1433 | 1436 | |
| 1434 | 1437 | override func layoutSubviews() { |
| 1435 | 1438 | super.layoutSubviews() |
| 1436 | if tileTemplate != nil { bringSubviewToFront(tileAttribution) } | |
| 1439 | if tileTemplate != nil { | |
| 1440 | tileAttributionBottom?.constant = -min(edgePadding.bottom, max(0, bounds.height - 40)) - 8 | |
| 1441 | bringSubviewToFront(tileAttribution) | |
| 1442 | } | |
| 1437 | 1443 | if let region = pendingRegion, bounds.width > 0, bounds.height > 0 { |
| 1438 | 1444 | pendingRegion = nil |
| 1439 | 1445 | setRegion(region, animated: false) |