chore: prepare V3-08 android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -2,14 +2,18 @@
|
||||
|
||||
这是 MapLibreParticleLab 内的独立 React Native 开发宿主,不是 NavSea 业务应用。它只有一个 `MLRNMapView` 与 WeatherRuntime 控制页,用于证明低频控制面:React 的 `reactTag` 经薄 Android 模块定位该 View,再定位该 View 的 `MapLibreMap` 与 map-owned `WeatherRuntime`。
|
||||
|
||||
## 版本与首次 prebuild 边界
|
||||
## 当前 V3-08 版本与首次 prebuild 边界
|
||||
|
||||
- 当前验收目标是 RN/TS `13.4.1-particle.3` 配合 Native `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`。`.2` 是 STEP-069 v2 multi-plane 新 artifact;不得复用或覆盖 `.1`。
|
||||
- 本地 legacy `@maplibre-particle/weather-runtime-bridge` 已移除;`@navsea/maplibre-particle-rn@13.4.1-particle.3` 是唯一的 `WeatherRuntimeBridge` 提供者。
|
||||
- 必须先由用户手工构建并 Maven Local publish Native `.2`,再对 Test Host 执行 clean prebuild、build、install 与启动。当前仅完成坐标准备,AAR 尚未构建、发布或验收。
|
||||
- Metro 当前 binary asset extension 为 `.f32` 与 `.u8`。
|
||||
|
||||
- package manager 为 npm(`packageManager: npm@10.9.4`),锁文件为 `package-lock.json`;当前环境的 `npm ci --dry-run --ignore-scripts` 无须 `--legacy-peer-deps` 即可解析。
|
||||
- package 明确固定 React Native `0.85.2`、React `19.2.3`、Expo `56.0.4`、`expo-file-system` `56.0.11`、TypeScript `5.9.3`、MLRN `11.3.6`;`app.json` 明确请求 New Architecture。Hermes 的最终启用状态必须在首次 Android prebuild 后检查生成工程。
|
||||
- AGP `8.5.0`、Gradle `8.14.3`、Kotlin `2.1.20`、NDK `27.1.12297006`、JDK 17、minSdk 24、compile/targetSdk 36 是本 Test Host 的**人工验收配置目标**,不是尚未生成 Android 工程的已验证结果。首次 prebuild 后必须以实际 Gradle wrapper、AGP/Kotlin plugin、SDK/NDK 与 Hermes/New Architecture 配置为准核对。
|
||||
- MLRN 的 root Gradle extras 固定 `org.maplibre.reactnative.nativeVersion=13.4.1-step061` 与 `nativeVariant=opengl`。STEP-061 必须构建并 Maven Local publish 这个唯一的新坐标,禁止覆盖已经冻结的 `13.4.1-step060f`;本轮 RDC 修复仅推进坐标和文档,未构建或发布。
|
||||
|
||||
## Custom SDK 来源(手工执行)
|
||||
## 历史:STEP-061/062 Custom SDK 来源(手工执行)
|
||||
|
||||
Gate C 封存环境负责生成 custom AAR;Test Host 只负责 Maven Local handoff。Expo plugin 对唯一 custom 坐标 `org.maplibre.gl:android-sdk-opengl` 排除 Maven Central,而将 Maven Local 排在首位。因此未先发布本仓库 custom SDK 时 dependency resolution 必须失败,不能悄悄使用官方 SDK;MLRN 的独立 turf/annotation/scalebar 依赖不被这个 SDK-only 排除误伤。
|
||||
|
||||
@@ -51,7 +55,7 @@ Style JSON 明确创建 background、host-field `RasterArraySource`、`WeatherCo
|
||||
|
||||
Test Host 固定消费已验收的 `org.maplibre.gl:android-sdk-opengl:13.4.1-step061`,本阶段不构建或发布 AAR。页面 style 顺序是 `background → GSI raster → WeatherColorLayer → RasterParticleLayer`;GSI 使用国土地理院标准地图 `https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png`(256、maxzoom 18、含 attribution),相机初始聚焦日本及周边 WeatherV2 bounds。
|
||||
|
||||
受控 assets `assets/weather/ecmwf-hres-wind-20260807/` 原样复制 Android Validation 的 `dataset.json` 与四个 `metadata.json`/`field.f32`。`expo-asset` 是明确直接依赖,`metro.config.js` 仅增加 `.f32` asset extension。点击 `LOAD REAL WEATHER T0-T3` 会先 clear,然后通过 `Asset.fromModule(...).downloadAsync()` 取得四个 Expo cache `file://` URI,按 producer validTime 提交;JS 只传小型 metadata JSON 和文件引用,不 materialize 3.01 MiB payload 为数组、JSON、base64 或 hex。
|
||||
受控 assets `assets/weather/ecmwf-hres-wind-20260807/` 原样复制 Android Validation 的 `dataset.json` 与四个 `metadata.json`/`field.f32`。`expo-asset` 是明确直接依赖,`metro.config.js` 当前增加 `.f32` 与 `.u8` binary asset extension。点击 `LOAD REAL WEATHER T0-T3` 会先 clear,然后通过 `Asset.fromModule(...).downloadAsync()` 取得四个 Expo cache `file://` URI,按 producer validTime 提交;JS 只传小型 metadata JSON 和文件引用,不 materialize 3.01 MiB payload 为数组、JSON、base64 或 hex。
|
||||
|
||||
硬断言依次为 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、T2/T3=`ACCEPTED_NEXT_SNAPSHOT`,并在最后要求 Native state=`READY`;任何偏差均显示 `ERROR`。界面提供 PLAY/PAUSE、HALF/NORMAL/DOUBLE、四个 epoch-ms forecast seek、AUTO/30/60 FPS、两层可见性、冻结 WeatherColor stops、独立 particle/trail 色彩,以及粒子/轨迹视觉参数。Expo/系统管理 asset cache;本页不自建 `.f32` 临时文件。
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
plugins {
|
||||
id "com.android.library"
|
||||
id "org.jetbrains.kotlin.android"
|
||||
}
|
||||
|
||||
apply plugin: "com.facebook.react"
|
||||
|
||||
android {
|
||||
namespace "org.maplibre.particle.rntesthost.weather"
|
||||
compileSdk 36
|
||||
defaultConfig { minSdk 24 }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "com.facebook.react:react-android"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20"
|
||||
implementation "cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1"
|
||||
compileOnly project(":maplibre_maplibre-react-native")
|
||||
}
|
||||
|
||||
react {
|
||||
jsRootDir = file("../src/")
|
||||
libraryName = "WeatherRuntimeBridge"
|
||||
codegenJavaPackageName = "org.maplibre.particle.rntesthost.weather"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
@@ -1,225 +0,0 @@
|
||||
package org.maplibre.particle.rntesthost.weather
|
||||
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.turbomodule.core.interfaces.TurboModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.uimanager.IllegalViewOperationException
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
import com.facebook.react.uimanager.common.UIManagerType
|
||||
import org.maplibre.android.maps.MapLibreMap
|
||||
import org.maplibre.android.maps.Style
|
||||
import org.maplibre.android.maps.WeatherRuntime
|
||||
import org.maplibre.reactnative.components.mapview.MLRNMapView
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* A low-frequency, per-view bridge. It resolves the Fabric view for each call and never stores a
|
||||
* reactTag, current map, timeline, playback state, or a second Map registry.
|
||||
*/
|
||||
class WeatherRuntimeBridgeModule(private val context: ReactApplicationContext) : NativeWeatherRuntimeBridgeSpec(context), TurboModule {
|
||||
|
||||
@ReactMethod
|
||||
override fun attach(reactTag: Double, promise: Promise) = withMapView(reactTag, promise) { view, map ->
|
||||
val style = map.style ?: return@withMapView reject(promise, "STYLE_NOT_READY", "The MapLibre style is not loaded")
|
||||
val sourceExists = map.hasStyleSource(WeatherIds.SOURCE)
|
||||
val weatherColorLayerExists = map.hasStyleLayer(WeatherIds.COLOR_LAYER)
|
||||
val particleLayerExists = map.hasStyleLayer(WeatherIds.PARTICLE_LAYER)
|
||||
if (!sourceExists || !weatherColorLayerExists || !particleLayerExists) {
|
||||
return@withMapView reject(
|
||||
promise,
|
||||
"WEATHER_OBJECTS_NOT_READY",
|
||||
weatherObjectsNotReadyDiagnostic(
|
||||
reactTag.toInt(),
|
||||
style,
|
||||
sourceExists,
|
||||
weatherColorLayerExists,
|
||||
particleLayerExists,
|
||||
),
|
||||
)
|
||||
}
|
||||
val runtime = map.replaceWeatherRuntime(WeatherRuntime.Config(WeatherIds.SOURCE, WeatherIds.COLOR_LAYER, WeatherIds.PARTICLE_LAYER))
|
||||
// MLRN drops its MapView by detaching it. This listener captures only that concrete map,
|
||||
// disposes its map-owned facade, and cannot target a replacement view.
|
||||
view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: View) = Unit
|
||||
override fun onViewDetachedFromWindow(v: View) { map.disposeWeatherRuntime() }
|
||||
})
|
||||
promise.resolve(snapshot(runtime))
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
override fun detach(reactTag: Double, promise: Promise) = withMap(reactTag, promise) { map ->
|
||||
if (map.weatherRuntime == null) reject(promise, "RUNTIME_DISPOSED", "No attached WeatherRuntime exists for this map")
|
||||
else { map.disposeWeatherRuntime(); promise.resolve(null) }
|
||||
}
|
||||
|
||||
@ReactMethod override fun getState(reactTag: Double, promise: Promise) = withRuntime(reactTag, promise) { runtime -> promise.resolve(snapshot(runtime)) }
|
||||
@ReactMethod override fun clearWindFields(reactTag: Double, promise: Promise) = command(reactTag, promise) { it.clearWindFields() }
|
||||
|
||||
/** Binary ingress is a local file reference, never a JS number array or encoded string. */
|
||||
@ReactMethod
|
||||
override fun submitWindField(reactTag: Double, metadataJson: String, payloadFileUri: String, promise: Promise) {
|
||||
if (metadataJson.isEmpty() || metadataJson.toByteArray(Charsets.UTF_8).size > MAX_METADATA_UTF8_BYTES) return reject(promise, "INVALID_ARGUMENT", "metadataJson UTF-8 payload must be non-empty and at most $MAX_METADATA_UTF8_BYTES bytes")
|
||||
val payload = try { readLocalPayload(payloadFileUri) }
|
||||
catch (error: IllegalArgumentException) { return reject(promise, "PAYLOAD_FILE_UNREADABLE", error.message ?: "payload file is unreadable") }
|
||||
catch (error: IOException) { return reject(promise, "PAYLOAD_FILE_UNREADABLE", "payload file could not be read: ${error.message ?: "I/O error"}") }
|
||||
catch (error: RuntimeException) { return reject(promise, "PAYLOAD_FILE_UNREADABLE", "payload file could not be read") }
|
||||
if (payload.isEmpty()) return reject(promise, "PAYLOAD_FILE_UNREADABLE", "payload file is empty")
|
||||
withRuntime(reactTag, promise) { runtime ->
|
||||
try { promise.resolve(runtime.submitWindField(metadataJson, payload).name) }
|
||||
catch (error: IllegalStateException) { reject(promise, "NATIVE_REJECTED", error.message ?: "WeatherRuntime rejected the field") }
|
||||
}
|
||||
}
|
||||
@ReactMethod override fun setWeatherEnabled(reactTag: Double, enabled: Boolean, promise: Promise) = command(reactTag, promise) { it.setWeatherEnabled(enabled) }
|
||||
@ReactMethod override fun setWeatherColorVisible(reactTag: Double, visible: Boolean, promise: Promise) = command(reactTag, promise) { it.setWeatherColorVisible(visible) }
|
||||
@ReactMethod override fun setParticleVisible(reactTag: Double, visible: Boolean, promise: Promise) = command(reactTag, promise) { it.setParticleVisible(visible) }
|
||||
@ReactMethod override fun setTargetFps(reactTag: Double, target: String, promise: Promise) = enumCommand(reactTag, promise, target, WeatherRuntime.TargetFps.values()) { runtime, value -> runtime.setTargetFps(value) }
|
||||
@ReactMethod override fun setPlaybackState(reactTag: Double, playback: String, promise: Promise) = enumCommand(reactTag, promise, playback, WeatherRuntime.PlaybackState.values()) { runtime, value -> runtime.setPlaybackState(value) }
|
||||
@ReactMethod override fun setPlaybackSpeed(reactTag: Double, speed: String, promise: Promise) = enumCommand(reactTag, promise, speed, WeatherRuntime.PlaybackSpeed.values()) { runtime, value -> runtime.setPlaybackSpeed(value) }
|
||||
|
||||
@ReactMethod
|
||||
override fun setForecastTime(reactTag: Double, timestampMs: Double, promise: Promise) {
|
||||
if (!timestampMs.isFinite() || timestampMs < 0 || timestampMs > Long.MAX_VALUE.toDouble() || timestampMs != kotlin.math.floor(timestampMs)) {
|
||||
return reject(promise, "INVALID_ARGUMENT", "forecast timestamp must be a non-negative epoch millisecond integer")
|
||||
}
|
||||
command(reactTag, promise) { it.setForecastTime(timestampMs.toLong()) }
|
||||
}
|
||||
|
||||
@ReactMethod override fun setParticleCount(reactTag: Double, count: Double, promise: Promise) {
|
||||
if (!count.isFinite() || count < 0 || count > Int.MAX_VALUE || count != kotlin.math.floor(count)) return reject(promise, "INVALID_ARGUMENT", "particle count must be a non-negative integer")
|
||||
command(reactTag, promise) { it.setParticleCount(count.toInt()) }
|
||||
}
|
||||
@ReactMethod override fun setParticleLineWidth(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setParticleLineWidth(valid) }
|
||||
@ReactMethod override fun setParticleLength(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setParticleLength(valid) }
|
||||
@ReactMethod override fun setParticleOpacity(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setParticleOpacity(valid) }
|
||||
@ReactMethod override fun setTrailOpacity(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setTrailOpacity(valid) }
|
||||
@ReactMethod override fun setTrailFade(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setTrailFade(valid) }
|
||||
@ReactMethod override fun setWeatherColorOpacity(reactTag: Double, value: Double, promise: Promise) = floatCommand(reactTag, value, promise) { runtime, valid -> runtime.setWeatherColorOpacity(valid) }
|
||||
@ReactMethod override fun setParticleColor(reactTag: Double, red: Double, green: Double, blue: Double, alpha: Double, promise: Promise) = colorCommand(reactTag, red, green, blue, alpha, promise) { runtime, r, g, b, a -> runtime.setParticleColor(r, g, b, a) }
|
||||
@ReactMethod override fun setTrailColor(reactTag: Double, red: Double, green: Double, blue: Double, alpha: Double, promise: Promise) = colorCommand(reactTag, red, green, blue, alpha, promise) { runtime, r, g, b, a -> runtime.setTrailColor(r, g, b, a) }
|
||||
|
||||
@ReactMethod
|
||||
override fun setWeatherColorStops(reactTag: Double, stops: ReadableArray, promise: Promise) {
|
||||
val parsed = ArrayList<WeatherRuntime.ColorStop>()
|
||||
for (index in 0 until stops.size()) {
|
||||
val stop: ReadableMap = try { stops.getMap(index) ?: throw IllegalArgumentException() } catch (_: RuntimeException) { return reject(promise, "INVALID_ARGUMENT", "color stop $index must be an object") }
|
||||
try {
|
||||
val speed = stop.getDouble("speedMps"); val red = stop.getDouble("red"); val green = stop.getDouble("green"); val blue = stop.getDouble("blue"); val alpha = stop.getDouble("alpha")
|
||||
if (!speed.isFinite() || !red.isFinite() || !green.isFinite() || !blue.isFinite() || !alpha.isFinite() || red !in 0.0..1.0 || green !in 0.0..1.0 || blue !in 0.0..1.0 || alpha !in 0.0..1.0 || (parsed.isNotEmpty() && speed <= parsed.last().speedMps)) {
|
||||
return reject(promise, "INVALID_ARGUMENT", "stops require finite, strictly increasing speedMps and finite RGBA in [0,1]")
|
||||
}
|
||||
parsed.add(WeatherRuntime.ColorStop(speed.toFloat(), red.toFloat(), green.toFloat(), blue.toFloat(), alpha.toFloat()))
|
||||
} catch (_: RuntimeException) { return reject(promise, "INVALID_ARGUMENT", "color stop $index is incomplete") }
|
||||
}
|
||||
command(reactTag, promise) { it.setWeatherColorStops(parsed) }
|
||||
}
|
||||
|
||||
private fun command(reactTag: Double, promise: Promise, fn: (WeatherRuntime) -> WeatherRuntime.CommandResult) = withRuntime(reactTag, promise) { runtime ->
|
||||
try {
|
||||
promise.resolve(fn(runtime).name)
|
||||
} catch (error: IllegalStateException) {
|
||||
// WeatherRuntime rejects disabled/disposed commands by contract. Do not let an
|
||||
// exception escape the UI queue or turn a stale command into an implicit success.
|
||||
reject(promise, "NATIVE_REJECTED", error.message ?: "WeatherRuntime rejected the command")
|
||||
}
|
||||
}
|
||||
private fun floatCommand(reactTag: Double, value: Double, promise: Promise, fn: (WeatherRuntime, Float) -> WeatherRuntime.CommandResult) {
|
||||
if (!value.isFinite()) return reject(promise, "INVALID_ARGUMENT", "value must be finite")
|
||||
command(reactTag, promise) { fn(it, value.toFloat()) }
|
||||
}
|
||||
private fun colorCommand(reactTag: Double, red: Double, green: Double, blue: Double, alpha: Double, promise: Promise, fn: (WeatherRuntime, Float, Float, Float, Float) -> WeatherRuntime.CommandResult) {
|
||||
if (listOf(red, green, blue, alpha).any { !it.isFinite() || it !in 0.0..1.0 }) return reject(promise, "INVALID_ARGUMENT", "RGBA must be finite values in [0,1]")
|
||||
command(reactTag, promise) { fn(it, red.toFloat(), green.toFloat(), blue.toFloat(), alpha.toFloat()) }
|
||||
}
|
||||
private fun <T : Enum<T>> enumCommand(reactTag: Double, promise: Promise, raw: String, values: Array<T>, fn: (WeatherRuntime, T) -> WeatherRuntime.CommandResult) {
|
||||
val value = values.firstOrNull { it.name == raw } ?: return reject(promise, "INVALID_ARGUMENT", "unsupported enum value: $raw")
|
||||
command(reactTag, promise) { fn(it, value) }
|
||||
}
|
||||
private fun readLocalPayload(reference: String): ByteArray {
|
||||
if (reference.isBlank()) throw IllegalArgumentException("payload file reference is empty")
|
||||
val uri = Uri.parse(reference)
|
||||
if (uri.scheme != null && uri.scheme != "file") throw IllegalArgumentException("payload file reference must use file:// or an absolute local path")
|
||||
val file = if (uri.scheme == "file") File(uri.path ?: throw IllegalArgumentException("payload file URI has no path")) else File(reference).also {
|
||||
if (!it.isAbsolute) throw IllegalArgumentException("payload path without a URI scheme must be absolute")
|
||||
}
|
||||
if (!file.isFile || !file.canRead()) throw IllegalArgumentException("payload file is not a readable local file")
|
||||
if (file.length() <= 0L || file.length() > MAX_PAYLOAD_BYTES) throw IllegalArgumentException("payload file size must be in 1..$MAX_PAYLOAD_BYTES bytes")
|
||||
return file.inputStream().use(::readBounded)
|
||||
}
|
||||
private fun readBounded(input: InputStream): ByteArray {
|
||||
val output = ByteArrayOutputStream(); val buffer = ByteArray(DEFAULT_READ_BUFFER_BYTES)
|
||||
while (true) {
|
||||
val count = input.read(buffer)
|
||||
if (count < 0) break
|
||||
if (output.size().toLong() + count > MAX_PAYLOAD_BYTES) throw IllegalArgumentException("payload file exceeds $MAX_PAYLOAD_BYTES bytes")
|
||||
output.write(buffer, 0, count)
|
||||
}
|
||||
return output.toByteArray()
|
||||
}
|
||||
private fun withRuntime(reactTag: Double, promise: Promise, fn: (WeatherRuntime) -> Unit) = withMap(reactTag, promise) { map ->
|
||||
val runtime = map.weatherRuntime
|
||||
if (runtime == null || runtime.isDisposed) reject(promise, "RUNTIME_DISPOSED", "WeatherRuntime is not attached to this map") else fn(runtime)
|
||||
}
|
||||
private fun withMap(reactTag: Double, promise: Promise, fn: (MapLibreMap) -> Unit) = withMapView(reactTag, promise) { _, map -> fn(map) }
|
||||
private fun withMapView(reactTag: Double, promise: Promise, fn: (MLRNMapView, MapLibreMap) -> Unit) {
|
||||
if (!reactTag.isFinite() || reactTag <= 0 || reactTag != kotlin.math.floor(reactTag)) return reject(promise, "INVALID_ARGUMENT", "reactTag must be a positive integer")
|
||||
context.runOnUiQueueThread {
|
||||
val view = try { UIManagerHelper.getUIManager(context, UIManagerType.FABRIC)?.resolveView(reactTag.toInt()) } catch (_: IllegalViewOperationException) { null }
|
||||
if (view !is MLRNMapView) return@runOnUiQueueThread reject(promise, "MAP_VIEW_NOT_FOUND", "reactTag does not resolve to a current MLRNMapView")
|
||||
val map = view.mapLibreMap ?: return@runOnUiQueueThread reject(promise, "MAP_NOT_READY", "MLRNMapView has no MapLibreMap yet")
|
||||
fn(view, map)
|
||||
}
|
||||
}
|
||||
private fun snapshot(runtime: WeatherRuntime) = Arguments.createMap().apply {
|
||||
putString("state", runtime.state.name); putString("playbackState", runtime.playbackState.name); putString("targetFps", runtime.targetFps.name); putBoolean("disposed", runtime.isDisposed)
|
||||
}
|
||||
|
||||
/**
|
||||
* Core-only layer types do not have a Java Layer peer. Presence is checked directly in Core
|
||||
* before this diagnostic is built; the style URI distinguishes a JSON style (empty URI) from
|
||||
* a URI-backed style without materialising all sources or layers.
|
||||
*/
|
||||
private fun weatherObjectsNotReadyDiagnostic(
|
||||
reactTag: Int,
|
||||
style: Style,
|
||||
sourceExists: Boolean,
|
||||
weatherColorLayerExists: Boolean,
|
||||
particleLayerExists: Boolean,
|
||||
): String {
|
||||
return buildString {
|
||||
append("WEATHER_OBJECTS_NOT_READY reactTag=").append(reactTag)
|
||||
append(" mapView=present mapLibreMap=present style=present")
|
||||
append(" styleUri=").append(style.getUri().ifEmpty { "<empty>" })
|
||||
append(" source[").append(WeatherIds.SOURCE).append("]=")
|
||||
.append(presence(sourceExists))
|
||||
append(" layer[").append(WeatherIds.COLOR_LAYER).append("]=")
|
||||
.append(presence(weatherColorLayerExists))
|
||||
append(" layer[").append(WeatherIds.PARTICLE_LAYER).append("]=")
|
||||
.append(presence(particleLayerExists))
|
||||
}
|
||||
}
|
||||
|
||||
private fun presence(exists: Boolean) = if (exists) "present" else "missing"
|
||||
|
||||
private fun reject(promise: Promise, code: String, message: String) = promise.reject(code, message)
|
||||
private object WeatherIds {
|
||||
const val SOURCE = "rn-weather-fields"
|
||||
const val COLOR_LAYER = "rn-weather-color"
|
||||
const val PARTICLE_LAYER = "rn-weather-particles"
|
||||
}
|
||||
companion object {
|
||||
const val NAME = "WeatherRuntimeBridge"
|
||||
private const val MAX_METADATA_UTF8_BYTES = 1_048_576
|
||||
private const val MAX_PAYLOAD_BYTES = 64L * 1024L * 1024L
|
||||
private const val DEFAULT_READ_BUFFER_BYTES = 16 * 1024
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package org.maplibre.particle.rntesthost.weather
|
||||
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.module.model.ReactModuleInfo
|
||||
import com.facebook.react.module.model.ReactModuleInfoProvider
|
||||
import com.facebook.react.TurboReactPackage
|
||||
|
||||
class WeatherRuntimeBridgePackage : TurboReactPackage() {
|
||||
override fun getModule(name: String, context: ReactApplicationContext): NativeModule? =
|
||||
if (name == WeatherRuntimeBridgeModule.NAME) WeatherRuntimeBridgeModule(context) else null
|
||||
|
||||
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
|
||||
mapOf(WeatherRuntimeBridgeModule.NAME to ReactModuleInfo(
|
||||
WeatherRuntimeBridgeModule.NAME,
|
||||
WeatherRuntimeBridgeModule::class.java.name,
|
||||
false, false, false, false, true
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "@maplibre-particle/weather-runtime-bridge",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "src/index.ts",
|
||||
"react-native": "src/index.ts",
|
||||
"peerDependencies": { "react-native": "0.85.2" },
|
||||
"codegenConfig": {
|
||||
"name": "WeatherRuntimeBridgeSpec",
|
||||
"type": "modules",
|
||||
"jsSrcsDir": "src",
|
||||
"android": { "javaPackageName": "org.maplibre.particle.rntesthost.weather" }
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = { dependency: { platforms: { android: { sourceDir: "./android" } } } };
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { TurboModule } from "react-native";
|
||||
import { TurboModuleRegistry } from "react-native";
|
||||
|
||||
type RuntimeState = "DISABLED" | "WAITING_FOR_FIELDS" | "READY" | "ERROR";
|
||||
type PlaybackState = "PLAY" | "PAUSE";
|
||||
type TargetFps = "AUTO" | "FPS_30" | "FPS_60";
|
||||
type CommandResult = "ACCEPTED" | "REJECTED";
|
||||
type FieldSubmitResult = "REJECTED_SOURCE_MODE" | "REJECTED_ADAPTER" | "REJECTED_INCOMPATIBLE" | "REJECTED_NON_MONOTONIC" | "REJECTED_CAPACITY" | "ACCEPTED_PENDING_INITIAL_WINDOW" | "INITIAL_WINDOW_READY" | "ACCEPTED_NEXT_SNAPSHOT" | "DUPLICATE" | "UNKNOWN";
|
||||
type ColorStop = { speedMps: number; red: number; green: number; blue: number; alpha: number };
|
||||
type RuntimeSnapshot = { state: RuntimeState; playbackState: PlaybackState; targetFps: TargetFps; disposed: boolean };
|
||||
|
||||
export interface Spec extends TurboModule {
|
||||
attach(reactTag: number): Promise<RuntimeSnapshot>;
|
||||
detach(reactTag: number): Promise<void>;
|
||||
getState(reactTag: number): Promise<RuntimeSnapshot>;
|
||||
clearWindFields(reactTag: number): Promise<CommandResult>;
|
||||
submitWindField(reactTag: number, metadataJson: string, payloadFileUri: string): Promise<FieldSubmitResult>;
|
||||
setWeatherEnabled(reactTag: number, enabled: boolean): Promise<CommandResult>;
|
||||
setWeatherColorVisible(reactTag: number, visible: boolean): Promise<CommandResult>;
|
||||
setParticleVisible(reactTag: number, visible: boolean): Promise<CommandResult>;
|
||||
setTargetFps(reactTag: number, target: TargetFps): Promise<CommandResult>;
|
||||
setForecastTime(reactTag: number, timestampMs: number): Promise<CommandResult>;
|
||||
setPlaybackState(reactTag: number, playback: PlaybackState): Promise<CommandResult>;
|
||||
setPlaybackSpeed(reactTag: number, speed: "HALF" | "NORMAL" | "DOUBLE"): Promise<CommandResult>;
|
||||
setWeatherColorStops(reactTag: number, stops: ColorStop[]): Promise<CommandResult>;
|
||||
setParticleColor(reactTag: number, red: number, green: number, blue: number, alpha: number): Promise<CommandResult>;
|
||||
setTrailColor(reactTag: number, red: number, green: number, blue: number, alpha: number): Promise<CommandResult>;
|
||||
setParticleCount(reactTag: number, count: number): Promise<CommandResult>;
|
||||
setParticleLineWidth(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setParticleLength(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setParticleOpacity(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setTrailOpacity(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setTrailFade(reactTag: number, value: number): Promise<CommandResult>;
|
||||
setWeatherColorOpacity(reactTag: number, value: number): Promise<CommandResult>;
|
||||
}
|
||||
|
||||
export default TurboModuleRegistry.getEnforcing<Spec>("WeatherRuntimeBridge");
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
export type RuntimeState = "DISABLED" | "WAITING_FOR_FIELDS" | "READY" | "ERROR";
|
||||
export type PlaybackState = "PLAY" | "PAUSE";
|
||||
export type TargetFps = "AUTO" | "FPS_30" | "FPS_60";
|
||||
export type CommandResult = "ACCEPTED" | "REJECTED";
|
||||
export type FieldSubmitResult = "REJECTED_SOURCE_MODE" | "REJECTED_ADAPTER" | "REJECTED_INCOMPATIBLE" | "REJECTED_NON_MONOTONIC" | "REJECTED_CAPACITY" | "ACCEPTED_PENDING_INITIAL_WINDOW" | "INITIAL_WINDOW_READY" | "ACCEPTED_NEXT_SNAPSHOT" | "DUPLICATE" | "UNKNOWN";
|
||||
export type ColorStop = Readonly<{ speedMps: number; red: number; green: number; blue: number; alpha: number }>;
|
||||
export type RuntimeSnapshot = Readonly<{ state: RuntimeState; playbackState: PlaybackState; targetFps: TargetFps; disposed: boolean }>;
|
||||
|
||||
export { default as WeatherRuntimeBridge } from "./NativeWeatherRuntimeBridge";
|
||||
@@ -6,7 +6,7 @@ const marker = "// MapLibreParticleRNTestHost NavSea Particle release SDK";
|
||||
module.exports = function withParticleNativeAndroid(config) {
|
||||
return withProjectBuildGradle(config, (project) => {
|
||||
if (project.modResults.contents.includes(marker)) return project;
|
||||
project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
|
||||
project.modResults.contents += `\n${marker}\ngradle.projectsEvaluated {\n allprojects { p ->\n p.repositories.mavenLocal()\n p.configurations.configureEach { configuration ->\n configuration.resolutionStrategy.dependencySubstitution {\n substitute module("org.maplibre.gl:android-sdk-opengl") using module("cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2")\n }\n }\n }\n}\nrootProject.ext.set("org.maplibre.reactnative.nativeVersion", "13.4.1")\nrootProject.ext.set("org.maplibre.reactnative.nativeVariant", "opengl")\n`;
|
||||
return project;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
dependencies: {
|
||||
"@maplibre-particle/weather-runtime-bridge": {
|
||||
root: "./modules/weather-runtime-bridge"
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": { "strict": true, "noEmit": true, "preserveSymlinks": true },
|
||||
"include": ["App.tsx", "src", "modules/weather-runtime-bridge/src"]
|
||||
"include": ["App.tsx", "src"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user