craft-native 0.0.236 → 0.0.238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/dist/android/src/emulator.d.ts +23 -0
- package/dist/android/src/index.d.ts +7 -1
- package/dist/android/src/index.js +280 -98
- package/dist/android/templates/CraftBridge.kt.template +65 -54
- package/dist/android/templates/CraftNative.kt.template +27 -0
- package/dist/android/templates/CraftNativeCapabilities.kt.template +55 -6
- package/dist/android/templates/CraftNativeServices.kt.template +45 -35
- package/dist/android/templates/CraftNativeShell.kt.template +103 -20
- package/dist/android/templates/LocationRecordingService.kt.template +252 -46
- package/dist/android/templates/MainActivityNative.kt.template +163 -5
- package/dist/android-adb.d.ts +2 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/mobile.d.ts +49 -14
- package/dist/browser/storage.d.ts +3 -0
- package/dist/browser.js +163 -51
- package/dist/cli.js +1 -1
- package/dist/components/index.d.ts +33 -0
- package/dist/index.cjs +126 -12
- package/dist/index.js +126 -12
- package/dist/ios/src/app-shortcuts.d.ts +11 -0
- package/dist/ios/src/index.d.ts +7 -0
- package/dist/ios/src/index.js +65 -1
- package/dist/ios/src/testing.d.ts +2 -0
- package/dist/ios/templates/CraftApp.swift +392 -88
- package/dist/ios/templates/CraftHeartRate.swift +141 -44
- package/dist/ios/templates/CraftNativeActions.swift +46 -48
- package/dist/ios/templates/CraftNativeScreen.swift +177 -10
- package/dist/ios/templates/CraftPersistentStorage.swift +99 -0
- package/dist/ios/templates/CraftRecordingArchive.swift +85 -0
- package/dist/ios/templates/testing/DriverTests.swift.template +8 -1
- package/dist/mobile-testing.js +30 -4
- package/dist/mobile.js +169 -17
- package/package.json +1 -1
|
@@ -600,19 +600,43 @@ private class CraftNativeSvgView(context: Context) : View(context) {
|
|
|
600
600
|
/** The Android host for the small, host-neutral native trail map contract. */
|
|
601
601
|
private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMapReadyCallback {
|
|
602
602
|
private val mapView = MapView(context)
|
|
603
|
+
private val scaleView = LinearLayout(context).apply {
|
|
604
|
+
orientation = LinearLayout.VERTICAL
|
|
605
|
+
setPadding(4, 2, 4, 2)
|
|
606
|
+
background = GradientDrawable().apply { setColor(Color.argb(230, 255, 255, 255)); cornerRadius = 4f }
|
|
607
|
+
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
608
|
+
}
|
|
609
|
+
private val scaleLabel = TextView(context).apply { textSize = 11f; setTextColor(Color.rgb(37, 51, 33)) }
|
|
610
|
+
private val scaleLine = View(context).apply { setBackgroundColor(Color.rgb(37, 51, 33)) }
|
|
603
611
|
private var googleMap: GoogleMap? = null
|
|
612
|
+
private var tileOverlay: com.google.android.gms.maps.model.TileOverlay? = null
|
|
613
|
+
private var tileTemplate: String? = null
|
|
614
|
+
private val tileAttribution = TextView(context).apply { textSize = 9f; setTextColor(Color.DKGRAY); setBackgroundColor(Color.argb(220, 255, 255, 255)); maxLines = 2; visibility = View.GONE }
|
|
604
615
|
private var pendingProps = JSONObject()
|
|
605
616
|
private var pendingChildren = JSONArray()
|
|
606
617
|
private val markers = mutableMapOf<String, Marker>()
|
|
607
618
|
private val polylines = mutableMapOf<String, Polyline>()
|
|
608
619
|
private val markerHandlers = mutableMapOf<String, String>()
|
|
609
620
|
private var onMarkerTap: ((String) -> Unit)? = null
|
|
621
|
+
var onMapPress: ((LatLng) -> Unit)? = null
|
|
622
|
+
var onRegionChangeComplete: ((JSONObject) -> Unit)? = null
|
|
623
|
+
private var userChangingCamera = false
|
|
624
|
+
private var markerBearings = mutableMapOf<String, Float>()
|
|
625
|
+
private var directionIcon: com.google.android.gms.maps.model.BitmapDescriptor? = null
|
|
610
626
|
private var lifecycleStarted = false
|
|
611
627
|
private var destroyed = false
|
|
612
628
|
private var initialCameraKey: String? = null
|
|
613
629
|
|
|
614
630
|
init {
|
|
615
631
|
addView(mapView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
|
|
632
|
+
scaleView.addView(scaleLabel)
|
|
633
|
+
scaleView.addView(scaleLine, LinearLayout.LayoutParams(1, 2))
|
|
634
|
+
addView(scaleView, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.BOTTOM or Gravity.START).apply {
|
|
635
|
+
leftMargin = (8 * resources.displayMetrics.density).roundToInt()
|
|
636
|
+
bottomMargin = (24 * resources.displayMetrics.density).roundToInt()
|
|
637
|
+
})
|
|
638
|
+
scaleView.visibility = View.GONE
|
|
639
|
+
addView(tileAttribution, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, Gravity.BOTTOM or Gravity.END).apply { rightMargin = (8 * resources.displayMetrics.density).roundToInt(); bottomMargin = (8 * resources.displayMetrics.density).roundToInt() })
|
|
616
640
|
mapView.onCreate(null)
|
|
617
641
|
mapView.getMapAsync(this)
|
|
618
642
|
}
|
|
@@ -620,6 +644,24 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
620
644
|
override fun onMapReady(map: GoogleMap) {
|
|
621
645
|
if (destroyed) return
|
|
622
646
|
googleMap = map
|
|
647
|
+
map.setOnMapClickListener { point -> onMapPress?.invoke(point) }
|
|
648
|
+
map.setOnCameraMoveStartedListener { reason ->
|
|
649
|
+
if (reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE) userChangingCamera = true
|
|
650
|
+
}
|
|
651
|
+
map.setOnCameraMoveListener { updateScale() }
|
|
652
|
+
map.setOnCameraIdleListener {
|
|
653
|
+
updateScale()
|
|
654
|
+
if (userChangingCamera) {
|
|
655
|
+
userChangingCamera = false
|
|
656
|
+
val bounds = map.projection.visibleRegion.latLngBounds
|
|
657
|
+
val position = map.cameraPosition
|
|
658
|
+
val longitudeDelta = (bounds.northeast.longitude - bounds.southwest.longitude + 360) % 360
|
|
659
|
+
onRegionChangeComplete?.invoke(JSONObject()
|
|
660
|
+
.put("center", JSONObject().put("latitude", position.target.latitude).put("longitude", position.target.longitude))
|
|
661
|
+
.put("span", JSONObject().put("latitudeDelta", bounds.northeast.latitude - bounds.southwest.latitude).put("longitudeDelta", longitudeDelta))
|
|
662
|
+
.put("heading", position.bearing).put("pitch", position.tilt))
|
|
663
|
+
}
|
|
664
|
+
}
|
|
623
665
|
map.setOnMarkerClickListener { marker ->
|
|
624
666
|
val id = markers.entries.firstOrNull { it.value == marker }?.key ?: return@setOnMarkerClickListener false
|
|
625
667
|
onMarkerTap?.invoke(id)
|
|
@@ -642,6 +684,10 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
642
684
|
|
|
643
685
|
private fun applyPending() {
|
|
644
686
|
val map = googleMap ?: return
|
|
687
|
+
applyMapPadding()
|
|
688
|
+
map.uiSettings.isCompassEnabled = craftNativeBoolean(pendingProps.opt("showsCompass"))
|
|
689
|
+
// The toolbar is replaced by authored actions; the scale uses the native projection.
|
|
690
|
+
map.uiSettings.isMapToolbarEnabled = false
|
|
645
691
|
map.uiSettings.isScrollGesturesEnabled = craftNativeBoolean(pendingProps.opt("scrollEnabled"), true)
|
|
646
692
|
map.uiSettings.isZoomGesturesEnabled = craftNativeBoolean(pendingProps.opt("zoomEnabled"), true)
|
|
647
693
|
map.uiSettings.isRotateGesturesEnabled = craftNativeBoolean(pendingProps.opt("rotateEnabled"))
|
|
@@ -651,9 +697,11 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
651
697
|
"terrain" -> GoogleMap.MAP_TYPE_TERRAIN
|
|
652
698
|
else -> GoogleMap.MAP_TYPE_NORMAL
|
|
653
699
|
}
|
|
700
|
+
applyTileOverlay()
|
|
654
701
|
val showUserLocation = craftNativeBoolean(pendingProps.opt("showsUserLocation")) && hasLocationPermission()
|
|
655
702
|
runCatching { map.isMyLocationEnabled = showUserLocation }
|
|
656
703
|
val nextMarkers = mutableMapOf<String, Pair<LatLng, String?>>()
|
|
704
|
+
val nextBearings = mutableMapOf<String, Float>()
|
|
657
705
|
val nextHandlers = mutableMapOf<String, String>()
|
|
658
706
|
val nextPolylines = mutableMapOf<String, Triple<List<LatLng>, Int, Float>>()
|
|
659
707
|
for (index in 0 until pendingChildren.length()) {
|
|
@@ -668,6 +716,7 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
668
716
|
if (type == "mapmarker" || type == "marker") {
|
|
669
717
|
points.firstOrNull()?.let { point ->
|
|
670
718
|
nextMarkers[id] = point to (props.opt("title") as? String)
|
|
719
|
+
(props.opt("bearing") as? Number)?.toDouble()?.takeIf { it.isFinite() }?.let { nextBearings[id] = (it % 360).toFloat() }
|
|
671
720
|
listOf(events.opt("onPress"), events.opt("onMarkerPress"))
|
|
672
721
|
.firstNotNullOfOrNull(::craftNativeOptionalString)
|
|
673
722
|
?.let { nextHandlers[id] = it }
|
|
@@ -694,14 +743,19 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
694
743
|
}
|
|
695
744
|
}
|
|
696
745
|
}
|
|
746
|
+
markerBearings = nextBearings
|
|
697
747
|
markers.keys.filter { nextMarkers[it] == null }.forEach { markers.remove(it)?.remove() }
|
|
698
748
|
for ((id, value) in nextMarkers) {
|
|
699
749
|
val marker = markers[id]
|
|
700
750
|
if (marker != null) {
|
|
701
751
|
marker.position = value.first
|
|
702
752
|
marker.title = value.second
|
|
753
|
+
marker.setIcon(if (markerBearings.containsKey(id)) directionMarkerIcon() else BitmapDescriptorFactory.defaultMarker())
|
|
754
|
+
marker.isFlat = markerBearings.containsKey(id)
|
|
755
|
+
marker.rotation = markerBearings[id] ?: 0f
|
|
756
|
+
marker.setAnchor(0.5f, if (markerBearings.containsKey(id)) 0.5f else 1f)
|
|
703
757
|
} else {
|
|
704
|
-
map.addMarker(MarkerOptions().position(value.first).title(value.second).icon(BitmapDescriptorFactory.defaultMarker()))?.let { markers[id] = it }
|
|
758
|
+
map.addMarker(MarkerOptions().position(value.first).title(value.second).icon(if (markerBearings.containsKey(id)) directionMarkerIcon() else BitmapDescriptorFactory.defaultMarker()).flat(markerBearings.containsKey(id)).rotation(markerBearings[id] ?: 0f).anchor(0.5f, if (markerBearings.containsKey(id)) 0.5f else 1f))?.let { markers[id] = it }
|
|
705
759
|
}
|
|
706
760
|
}
|
|
707
761
|
markerHandlers.clear()
|
|
@@ -734,17 +788,99 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
734
788
|
initialCameraKey = key
|
|
735
789
|
}
|
|
736
790
|
} else if (craftNativeBoolean(pendingProps.opt("fitToBounds"))) {
|
|
737
|
-
val key = "fit:${overlayKey(nextMarkers, nextPolylines)}"
|
|
791
|
+
val key = "fit:${overlayKey(nextMarkers, nextPolylines)}:${pendingProps.optJSONObject("edgePadding")}"
|
|
738
792
|
if (initialCameraKey != key) {
|
|
739
793
|
if (fitToBounds()) initialCameraKey = key
|
|
740
794
|
}
|
|
741
795
|
} else {
|
|
742
796
|
initialCameraKey = null
|
|
743
797
|
}
|
|
798
|
+
applyCameraOrientation()
|
|
799
|
+
updateScale()
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
private fun applyTileOverlay() {
|
|
803
|
+
val map = googleMap ?: return
|
|
804
|
+
val options = pendingProps.optJSONObject("tileOverlay")
|
|
805
|
+
val template = options?.optString("urlTemplate").orEmpty()
|
|
806
|
+
val credit = options?.optString("attribution").orEmpty()
|
|
807
|
+
val next = template.takeIf { it.startsWith("https://") && listOf("{z}", "{x}", "{y}").all { key -> it.contains(key) } && credit.isNotBlank() }
|
|
808
|
+
if (next != tileTemplate) {
|
|
809
|
+
tileOverlay?.remove(); tileOverlay = null; tileTemplate = next
|
|
810
|
+
if (next != null) {
|
|
811
|
+
val provider = object : com.google.android.gms.maps.model.UrlTileProvider(256, 256) {
|
|
812
|
+
override fun getTileUrl(x: Int, y: Int, zoom: Int): java.net.URL? = runCatching { java.net.URL(next.replace("{x}", x.toString()).replace("{y}", y.toString()).replace("{z}", zoom.toString())) }.getOrNull()
|
|
813
|
+
}
|
|
814
|
+
tileOverlay = map.addTileOverlay(com.google.android.gms.maps.model.TileOverlayOptions().tileProvider(provider).zIndex(-1f))
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (next != null) map.mapType = GoogleMap.MAP_TYPE_NONE
|
|
818
|
+
tileAttribution.text = credit
|
|
819
|
+
tileAttribution.visibility = if (next == null) View.GONE else View.VISIBLE
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
private fun applyMapPadding() {
|
|
823
|
+
val authored = pendingProps.optJSONObject("edgePadding")
|
|
824
|
+
fun inset(name: String): Double = (authored?.opt(name) as? Number)?.toDouble()?.takeIf { it.isFinite() }?.coerceIn(0.0, 10_000.0)?.times(resources.displayMetrics.density) ?: 0.0
|
|
825
|
+
var left = inset("left"); var top = inset("top"); var right = inset("right"); var bottom = inset("bottom")
|
|
826
|
+
val horizontal = left + right
|
|
827
|
+
val vertical = top + bottom
|
|
828
|
+
val availableWidth = (width - 40 * resources.displayMetrics.density).coerceAtLeast(0f).toDouble()
|
|
829
|
+
val availableHeight = (height - 40 * resources.displayMetrics.density).coerceAtLeast(0f).toDouble()
|
|
830
|
+
if (horizontal > availableWidth && horizontal > 0) { left *= availableWidth / horizontal; right *= availableWidth / horizontal }
|
|
831
|
+
if (vertical > availableHeight && vertical > 0) { top *= availableHeight / vertical; bottom *= availableHeight / vertical }
|
|
832
|
+
googleMap?.setPadding(left.roundToInt(), top.roundToInt(), right.roundToInt(), bottom.roundToInt())
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
private fun updateScale() {
|
|
836
|
+
val map = googleMap ?: return
|
|
837
|
+
if (!craftNativeBoolean(pendingProps.opt("showsScale")) || width <= 0 || height <= 0) { scaleView.visibility = View.GONE; return }
|
|
838
|
+
val maximumWidth = (80 * resources.displayMetrics.density).roundToInt()
|
|
839
|
+
val origin = map.projection.fromScreenLocation(android.graphics.Point(width / 2, height / 2))
|
|
840
|
+
val end = map.projection.fromScreenLocation(android.graphics.Point(width / 2 + maximumWidth, height / 2))
|
|
841
|
+
val distance = FloatArray(1)
|
|
842
|
+
android.location.Location.distanceBetween(origin.latitude, origin.longitude, end.latitude, end.longitude, distance)
|
|
843
|
+
val metres = distance[0].toDouble()
|
|
844
|
+
if (!metres.isFinite() || metres <= 0) { scaleView.visibility = View.GONE; return }
|
|
845
|
+
val magnitude = Math.pow(10.0, kotlin.math.floor(kotlin.math.log10(metres)))
|
|
846
|
+
val length = listOf(5.0, 2.0, 1.0).firstOrNull { it * magnitude <= metres }?.times(magnitude) ?: magnitude
|
|
847
|
+
scaleLabel.text = if (length >= 1000) "${length / 1000} km" else "${length.roundToInt()} m"
|
|
848
|
+
scaleLine.layoutParams = LinearLayout.LayoutParams((maximumWidth * length / metres).roundToInt().coerceAtLeast(1), 2)
|
|
849
|
+
scaleView.visibility = View.VISIBLE
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
private fun applyCameraOrientation() {
|
|
853
|
+
val map = googleMap ?: return
|
|
854
|
+
val camera = map.cameraPosition
|
|
855
|
+
val pitch = (pendingProps.opt("cameraPitch") as? Number)?.toDouble()?.takeIf { it.isFinite() }?.coerceIn(0.0, 60.0)?.toFloat() ?: camera.tilt
|
|
856
|
+
val heading = (pendingProps.opt("cameraHeading") as? Number)?.toDouble()?.takeIf { it.isFinite() }?.toFloat() ?: camera.bearing
|
|
857
|
+
if (kotlin.math.abs(camera.tilt - pitch) > 0.01f || kotlin.math.abs(camera.bearing - heading) > 0.01f) {
|
|
858
|
+
map.moveCamera(CameraUpdateFactory.newCameraPosition(com.google.android.gms.maps.model.CameraPosition.Builder(camera).tilt(pitch).bearing(heading).build()))
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private fun directionMarkerIcon(): com.google.android.gms.maps.model.BitmapDescriptor {
|
|
863
|
+
directionIcon?.let { return it }
|
|
864
|
+
val size = (26 * resources.displayMetrics.density).roundToInt().coerceAtLeast(24)
|
|
865
|
+
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
|
866
|
+
val canvas = Canvas(bitmap)
|
|
867
|
+
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
868
|
+
paint.color = Color.WHITE
|
|
869
|
+
canvas.drawCircle(size / 2f, size / 2f, size / 2f, paint)
|
|
870
|
+
paint.color = Color.rgb(36, 114, 75)
|
|
871
|
+
canvas.drawCircle(size / 2f, size / 2f, size * 0.43f, paint)
|
|
872
|
+
paint.color = Color.WHITE
|
|
873
|
+
paint.textSize = size * 0.76f
|
|
874
|
+
paint.textAlign = Paint.Align.CENTER
|
|
875
|
+
paint.typeface = Typeface.DEFAULT_BOLD
|
|
876
|
+
canvas.drawText("↑", size / 2f, size / 2f - (paint.ascent() + paint.descent()) / 2, paint)
|
|
877
|
+
return BitmapDescriptorFactory.fromBitmap(bitmap).also { directionIcon = it }
|
|
744
878
|
}
|
|
745
879
|
|
|
746
880
|
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
|
|
747
881
|
super.onSizeChanged(width, height, oldWidth, oldHeight)
|
|
882
|
+
applyMapPadding()
|
|
883
|
+
updateScale()
|
|
748
884
|
// MapView can become ready before its host has a usable viewport. Do
|
|
749
885
|
// not mark a fit as complete until the camera update can be issued.
|
|
750
886
|
if (width > 0 && height > 0 && craftNativeBoolean(pendingProps.opt("fitToBounds")) && initialCameraKey == null) {
|
|
@@ -754,7 +890,10 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
754
890
|
|
|
755
891
|
private fun moveCamera(bounds: LatLngBounds, padding: Int = 24) {
|
|
756
892
|
post {
|
|
757
|
-
if (width > 0 && height > 0)
|
|
893
|
+
if (width > 0 && height > 0) {
|
|
894
|
+
googleMap?.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, padding))
|
|
895
|
+
applyCameraOrientation()
|
|
896
|
+
}
|
|
758
897
|
}
|
|
759
898
|
}
|
|
760
899
|
|
|
@@ -780,7 +919,12 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
780
919
|
polylines.values.flatMap { it.points }.forEach { builder.include(it); count++ }
|
|
781
920
|
if (count == 0) return false
|
|
782
921
|
post {
|
|
783
|
-
if (width > 0 && height > 0)
|
|
922
|
+
if (width > 0 && height > 0) {
|
|
923
|
+
applyMapPadding()
|
|
924
|
+
val padding = if (pendingProps.has("edgePadding")) 0 else (24 * resources.displayMetrics.density).roundToInt()
|
|
925
|
+
map.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), padding))
|
|
926
|
+
applyCameraOrientation()
|
|
927
|
+
}
|
|
784
928
|
}
|
|
785
929
|
return true
|
|
786
930
|
}
|
|
@@ -811,6 +955,10 @@ private class CraftNativeMapView(context: Context) : FrameLayout(context), OnMap
|
|
|
811
955
|
lifecycleStarted = false
|
|
812
956
|
}
|
|
813
957
|
onMarkerTap = null
|
|
958
|
+
onMapPress = null
|
|
959
|
+
onRegionChangeComplete = null
|
|
960
|
+
directionIcon = null
|
|
961
|
+
tileOverlay?.remove(); tileOverlay = null
|
|
814
962
|
markerHandlers.clear()
|
|
815
963
|
markers.values.forEach { it.remove() }
|
|
816
964
|
markers.clear()
|
|
@@ -5746,6 +5894,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
5746
5894
|
}
|
|
5747
5895
|
"Map" -> (previous as? CraftNativeMapView ?: CraftNativeMapView(this)).apply {
|
|
5748
5896
|
val mapMarkerHandler = craftNativeOptionalString(events.opt("onMarkerPress"))
|
|
5897
|
+
onMapPress = { point ->
|
|
5898
|
+
craftNativeOptionalString(events.opt("onPress"))?.let { handler ->
|
|
5899
|
+
send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject().put("latitude", point.latitude).put("longitude", point.longitude)))
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5902
|
+
onRegionChangeComplete = { region ->
|
|
5903
|
+
craftNativeOptionalString(events.opt("onRegionChangeComplete"))?.let { handler ->
|
|
5904
|
+
send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", region))
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5749
5907
|
configure(props, children) { markerId ->
|
|
5750
5908
|
(markerHandler(markerId) ?: mapMarkerHandler)?.let { handler ->
|
|
5751
5909
|
send(screen, "EVENT", JSONObject().put("handlerName", handler).put("nativeEvent", JSONObject().put("id", markerId)))
|
|
@@ -7153,7 +7311,7 @@ class MainActivity : AppCompatActivity() {
|
|
|
7153
7311
|
|
|
7154
7312
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
|
7155
7313
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
7156
|
-
if (::capabilities.isInitialized) capabilities.onBluetoothPermissions(requestCode)
|
|
7314
|
+
if (::capabilities.isInitialized) { capabilities.onBluetoothPermissions(requestCode); capabilities.onNotificationPermissions(requestCode) }
|
|
7157
7315
|
if (::services.isInitialized) services.onRequestPermissionsResult(requestCode)
|
|
7158
7316
|
}
|
|
7159
7317
|
|
package/dist/api/index.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export type { HttpClientOptions, RequestOptions, HttpResponse, WebSocketOptions
|
|
|
17
17
|
export { crypto, uuid, randomString, hmac, timingSafeEqual, hashPassword, verifyPassword } from './crypto.js';
|
|
18
18
|
export { env, getPlatform, isDesktop, isMobile, isCraft, getSystemInfo, exec, spawn, ChildProcess, cwd, homeDir, tempDir, exit, argv, open } from './process.js';
|
|
19
19
|
export type { Platform, SystemInfo, ExecOptions, ExecResult, SpawnOptions } from './process.js';
|
|
20
|
-
export { device, haptics, permissions, camera, biometrics, secureStorage, location, share, lifecycle, notifications, notifications as notification } from './mobile.js';
|
|
20
|
+
export { device, haptics, permissions, camera, biometrics, secureStorage, storage, location, share, lifecycle, notifications, notifications as notification } from './mobile.js';
|
|
21
21
|
export type { DeviceInfo, DeviceCapabilities, HapticStyle, HapticNotificationType, PermissionType, PermissionStatus, CameraOptions as MobileCameraOptions, PhotoResult, BiometricType, Location, LocationOptions, ShareOptions, AppState, NotificationOptions as MobileNotificationOptions } from './mobile.js';
|
|
22
22
|
export { carplay, appClips, liveActivities, sharePlay, storeKit, appIntents, tipKit, focusFilters } from './ios-advanced.js';
|
|
23
23
|
export type { CarPlayTemplateType, CarPlayListItem, CarPlayGridItem, CarPlayTemplate, AppClipInvocation, LiveActivityContentState, LiveActivityAttributes, LiveActivityConfig, LiveActivityHandle, SharePlaySessionState, SharePlayParticipant, SharePlayActivity, ProductType, Product, Transaction, IntentParameterType, IntentParameter, AppIntent, TipDisplayFrequency, Tip, FocusStatus, FocusFilter } from './ios-advanced.js';
|
package/dist/api/mobile.d.ts
CHANGED
|
@@ -459,6 +459,8 @@ export declare const secureStorage: {
|
|
|
459
459
|
* Location coordinates.
|
|
460
460
|
*/
|
|
461
461
|
export interface Location {
|
|
462
|
+
/** Owner captured when a native recording emits this sample. */
|
|
463
|
+
recordingId?: string;
|
|
462
464
|
latitude: number;
|
|
463
465
|
longitude: number;
|
|
464
466
|
altitude?: number;
|
|
@@ -478,6 +480,30 @@ export interface LocationOptions {
|
|
|
478
480
|
/** Maximum age of cached position in milliseconds */
|
|
479
481
|
maximumAge?: number;
|
|
480
482
|
}
|
|
483
|
+
/** Persist this ID with the owning intent before starting a native recording. */
|
|
484
|
+
export interface RecordingStartOptions {
|
|
485
|
+
recordingId?: string;
|
|
486
|
+
}
|
|
487
|
+
/** Fence a recording operation to the ID returned by start or explicit recovery. */
|
|
488
|
+
export interface RecordingOwnershipOptions {
|
|
489
|
+
expectedId: string;
|
|
490
|
+
}
|
|
491
|
+
/** Secure IDs supplied by the platform, including WebView-free mobile hosts. */
|
|
492
|
+
export declare const crypto: {
|
|
493
|
+
randomUUID(): Promise<string>;
|
|
494
|
+
};
|
|
495
|
+
/** Native storage, including an existing-key atomic checkpoint. */
|
|
496
|
+
export declare const storage: {
|
|
497
|
+
supportsSetIfAbsent(): boolean;
|
|
498
|
+
supportsCompareAndSet(): boolean;
|
|
499
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
500
|
+
set(key: string, value: unknown): Promise<boolean>;
|
|
501
|
+
remove(key: string): Promise<boolean>;
|
|
502
|
+
clear(): Promise<boolean>;
|
|
503
|
+
keys(): Promise<string[]>;
|
|
504
|
+
setIfAbsent(key: string, value: unknown): Promise<boolean>;
|
|
505
|
+
compareAndSet(key: string, expectedValue: unknown, nextValue: unknown): Promise<boolean>;
|
|
506
|
+
};
|
|
481
507
|
export interface LocationRecordingState {
|
|
482
508
|
id: string | null;
|
|
483
509
|
active: boolean;
|
|
@@ -507,6 +533,8 @@ export interface LocationRecordingResult extends LocationRecordingState {
|
|
|
507
533
|
* ```
|
|
508
534
|
*/
|
|
509
535
|
export declare const location: {
|
|
536
|
+
supportsRecordingOwnership: () => boolean;
|
|
537
|
+
supportsRecordingIntentIdentity: () => boolean;
|
|
510
538
|
/**
|
|
511
539
|
* Get current position.
|
|
512
540
|
*
|
|
@@ -528,12 +556,13 @@ export declare const location: {
|
|
|
528
556
|
* @param watchId - Watch ID returned from watchPosition
|
|
529
557
|
*/
|
|
530
558
|
clearWatch(watchId: number): void;
|
|
531
|
-
startRecording(options?: LocationOptions): Promise<LocationRecordingState>;
|
|
532
|
-
pauseRecording(): Promise<LocationRecordingState>;
|
|
533
|
-
resumeRecording(): Promise<LocationRecordingState>;
|
|
534
|
-
stopRecording(): Promise<LocationRecordingResult>;
|
|
535
|
-
getRecordingState(): Promise<LocationRecordingState>;
|
|
536
|
-
|
|
559
|
+
startRecording(options?: LocationOptions & RecordingStartOptions): Promise<LocationRecordingState>;
|
|
560
|
+
pauseRecording(options?: RecordingOwnershipOptions): Promise<LocationRecordingState>;
|
|
561
|
+
resumeRecording(options?: RecordingOwnershipOptions): Promise<LocationRecordingState>;
|
|
562
|
+
stopRecording(options?: RecordingOwnershipOptions): Promise<LocationRecordingResult>;
|
|
563
|
+
getRecordingState(options?: RecordingOwnershipOptions): Promise<LocationRecordingState>;
|
|
564
|
+
acknowledgeRecording(options: RecordingOwnershipOptions): Promise<boolean>;
|
|
565
|
+
readRecording(options?: RecordingOwnershipOptions): Promise<Location[]>;
|
|
537
566
|
};
|
|
538
567
|
/**
|
|
539
568
|
* Share options.
|
|
@@ -914,7 +943,7 @@ export interface HeartRateSample {
|
|
|
914
943
|
/** Milliseconds between beats, when the strap sent them. */
|
|
915
944
|
rr?: number[];
|
|
916
945
|
}
|
|
917
|
-
export type HeartRateMethod = 'getState' | 'requestAuthorization' | 'scan' | 'stopScan' | 'getDevices' | 'connect' | 'reconnect' | 'disconnect' | 'startRecording' | 'pauseRecording' | 'resumeRecording' | 'stopRecording' | 'getRecordingState' | 'readRecording';
|
|
946
|
+
export type HeartRateMethod = 'getState' | 'requestAuthorization' | 'scan' | 'stopScan' | 'getDevices' | 'connect' | 'reconnect' | 'disconnect' | 'startRecording' | 'pauseRecording' | 'resumeRecording' | 'stopRecording' | 'getRecordingState' | 'readRecording' | 'acknowledgeRecording';
|
|
918
947
|
export declare function normalizeHeartRateDevice(value: unknown): HeartRateDevice | null;
|
|
919
948
|
/** A state from native, made safe to render whatever an older or newer build sent. */
|
|
920
949
|
export declare function normalizeHeartRateState(value: unknown): HeartRateState;
|
|
@@ -923,6 +952,8 @@ export declare function normalizeHeartRateReading(value: unknown): HeartRateRead
|
|
|
923
952
|
/** A recording's samples, oldest first, one per timestamp, implausible ones left out. */
|
|
924
953
|
export declare function normalizeHeartRateSamples(value: unknown): HeartRateSample[];
|
|
925
954
|
export declare const heartRate: {
|
|
955
|
+
supportsRecordingOwnership: () => boolean;
|
|
956
|
+
supportsRecordingIntentIdentity: () => boolean;
|
|
926
957
|
/** Whether this app can read a heart-rate strap. */
|
|
927
958
|
isAvailable(): boolean;
|
|
928
959
|
/** Whether this app build answers `method`; an older build may lack one. */
|
|
@@ -955,18 +986,20 @@ export declare const heartRate: {
|
|
|
955
986
|
forget?: boolean;
|
|
956
987
|
}) => Promise<HeartRateState>;
|
|
957
988
|
/** Keep every reading natively from now on: screen locked, page suspended or app closed. */
|
|
958
|
-
startRecording
|
|
959
|
-
pauseRecording
|
|
960
|
-
resumeRecording
|
|
989
|
+
startRecording(options?: RecordingStartOptions): Promise<HeartRateRecordingState>;
|
|
990
|
+
pauseRecording(options?: RecordingOwnershipOptions): Promise<HeartRateRecordingState>;
|
|
991
|
+
resumeRecording(options?: RecordingOwnershipOptions): Promise<HeartRateRecordingState>;
|
|
961
992
|
/** Ends the recording and hands back every reading it kept. */
|
|
962
|
-
stopRecording
|
|
993
|
+
stopRecording(options?: RecordingOwnershipOptions): Promise<HeartRateRecordingState & {
|
|
963
994
|
samples: HeartRateSample[];
|
|
964
995
|
}>;
|
|
965
|
-
getRecordingState
|
|
996
|
+
getRecordingState(options?: RecordingOwnershipOptions): Promise<HeartRateRecordingState>;
|
|
997
|
+
acknowledgeRecording(options: RecordingOwnershipOptions): Promise<boolean>;
|
|
966
998
|
/** The recording so far; only what came after `since` (ms) when given. */
|
|
967
|
-
readRecording
|
|
999
|
+
readRecording(options?: {
|
|
968
1000
|
since?: number;
|
|
969
|
-
|
|
1001
|
+
expectedId?: string;
|
|
1002
|
+
}): Promise<HeartRateSample[]>;
|
|
970
1003
|
/** Every reading from the connected strap, about once a second. */
|
|
971
1004
|
onReading: (callback: (reading: HeartRateReading) => void) => (() => void);
|
|
972
1005
|
/** Each strap a scan finds. */
|
|
@@ -1685,6 +1718,8 @@ export declare const watchConnectivity: {
|
|
|
1685
1718
|
onReachabilityChange(callback: (reachable: boolean) => void): () => void;
|
|
1686
1719
|
};
|
|
1687
1720
|
declare const mobile: {
|
|
1721
|
+
crypto: typeof crypto;
|
|
1722
|
+
storage: typeof storage;
|
|
1688
1723
|
device: typeof device;
|
|
1689
1724
|
haptics: typeof haptics;
|
|
1690
1725
|
permissions: typeof permissions;
|
|
@@ -6,8 +6,11 @@ export interface BrowserStorageOptions {
|
|
|
6
6
|
accountKeys?: string[];
|
|
7
7
|
hash: (value: string) => Promise<string>;
|
|
8
8
|
changed: (name: string) => void;
|
|
9
|
+
/** A host lock primitive; browsers use their native Web Locks implementation. */
|
|
10
|
+
lock?: (<T>(name: string, operation: () => T | Promise<T>) => Promise<T>) | null;
|
|
9
11
|
}
|
|
10
12
|
export interface BrowserStorage {
|
|
13
|
+
atomicStorage: boolean;
|
|
11
14
|
craft: {
|
|
12
15
|
storage: {
|
|
13
16
|
getSync: (key: string) => unknown;
|