diff --git a/build.gradle.kts b/build.gradle.kts index 2e689d6..be28c68 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,6 +89,11 @@ dependencies { } else { api("com.mapconductor:compose:$libraryVersion") } + + testImplementation(libs.junit) + // Saver の保存 → 復元を実機の Bundle / Parcel で確かめる(SaverRoundTripTest)。 + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) } // Set project version for NMCP plugin diff --git a/src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt b/src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt new file mode 100644 index 0000000..eb6b41f --- /dev/null +++ b/src/androidTest/java/com/mapconductor/longdo/LongdoMapViewSaverTest.kt @@ -0,0 +1,97 @@ +package com.mapconductor.longdo + +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.SaverScope +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.map.AttributionRule +import com.mapconductor.core.map.MapCameraPosition +import com.mapconductor.core.map.MapUISettings +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import android.os.Bundle +import android.os.Parcel + +/** + * 回転などの構成変更で state が [LongdoMapViewSaver] を通って作り直されても、デザイン(id・ + * attributionRules ほか)と uiSettings・カメラが落ちないこと。Bundle は実機の Parcel を一度通す。 + */ +@RunWith(AndroidJUnit4::class) +class LongdoMapViewSaverTest { + private val rules = + listOf(AttributionRule("© test"), AttributionRule("© ranged", minZoom = 3, maxZoom = 15)) + + private val camera = + MapCameraPosition( + position = GeoPoint(21.38, -157.93), + zoom = 12.0, + bearing = 20.0, + tilt = 10.0, + ) + + @Test + fun keepsDesignOutsideThePresets() { + // 以前は id だけを保存して fromId で戻しており、定義に無い id は Normal になっていた。 + val design = LongdoDesign(id = "my-gray", layerName = "GRAY") + val state = LongdoViewState(mapDesignType = design, id = "longdo-1", initialCameraPosition = camera) + state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false) + + val restored = roundTrip(LongdoMapViewSaver().createSaver(), state) + + assertEquals(design, restored.mapDesignType) + assertCommon(restored, "longdo-1") + } + + @Test + fun keepsAttributionRulesOfAnAppDesign() { + val design = + object : LongdoMapDesignTypeInterface { + override val id: String = "app-design" + override val layerName: String = "POLITICAL" + override val attributionRules: List = rules + + override fun getValue(): String = id + } + val restored = roundTrip(LongdoMapViewSaver().createSaver(), LongdoViewState(design, "longdo-2")) + + assertEquals("app-design", restored.mapDesignType.id) + assertEquals("POLITICAL", restored.mapDesignType.layerName) + assertEquals(rules, restored.mapDesignType.attributionRules) + } + + @Test + fun keepsPresetDesign() { + val restored = roundTrip(LongdoMapViewSaver().createSaver(), LongdoViewState(LongdoDesign.Pastel, "longdo-3")) + assertEquals(LongdoDesign.Pastel, restored.mapDesignType) + } + + private fun assertCommon( + restored: com.mapconductor.core.map.MapViewStateInterface<*>, + id: String, + ) { + assertEquals(id, restored.id) + assertEquals(false, restored.uiSettings.rotateGesture) + assertEquals(false, restored.uiSettings.tiltGesture) + assertEquals(true, restored.uiSettings.scrollGesture) + assertEquals(12.0, restored.cameraPosition.zoom, 1e-9) + assertEquals(20.0, restored.cameraPosition.bearing, 1e-9) + assertEquals(21.38, restored.cameraPosition.position.latitude, 1e-9) + } + + private fun roundTrip( + saver: Saver, + value: T, + ): T { + val saved = with(saver) { SaverScope { true }.save(value) }!! + // 一度 Parcel に書いて読み戻す(プロセスが落ちたあとの復元と同じ経路)。 + val parcel = Parcel.obtain() + try { + parcel.writeBundle(saved) + parcel.setDataPosition(0) + return saver.restore(parcel.readBundle(javaClass.classLoader)!!)!! + } finally { + parcel.recycle() + } + } +} diff --git a/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt b/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt index fcb8400..0e689e7 100644 --- a/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt +++ b/src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt @@ -6,9 +6,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import com.mapconductor.compose.map.BaseMapViewSaver +import com.mapconductor.core.InternalMapConductorApi +import com.mapconductor.core.map.AttributionRule import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapCameraPositionInterface import com.mapconductor.core.map.MapViewState +import com.mapconductor.core.map.MapViewStateBundle import com.mapconductor.core.map.MapViewStateInterface import android.os.Bundle @@ -62,27 +65,44 @@ class LongdoViewState( /** * [LongdoViewState] の保存・復元を行う Saver。 - * デザインは [LongdoMapDesignTypeInterface.id] を保存し、[LongdoDesign.fromId] で復元する。 + * デザインは id・レイヤ名・attributionRules を保存する。id だけで [LongdoDesign.fromId] に + * 頼ると、定義に無いデザインは [LongdoDesign.Normal] に化け、attributionRules も落ちる。 */ class LongdoMapViewSaver : BaseMapViewSaver() { override fun saveMapDesign( state: LongdoViewState, bundle: Bundle, ) { - bundle.putString(KEY_DESIGN_ID, state.mapDesignType.id) + val design = state.mapDesignType + bundle.putString(KEY_DESIGN_ID, design.id) + bundle.putString(KEY_LAYER_NAME, design.layerName) + MapViewStateBundle.putAttributionRules(bundle, design.attributionRules) } override fun createState( stateId: String, mapDesignBundle: Bundle?, cameraPosition: MapCameraPosition, - ): LongdoViewState { - val designId = mapDesignBundle?.getString(KEY_DESIGN_ID) - return LongdoViewState( - mapDesignType = LongdoDesign.fromId(designId), + ): LongdoViewState = + LongdoViewState( + mapDesignType = restoreDesign(mapDesignBundle), id = stateId, initialCameraPosition = cameraPosition, ) + + private fun restoreDesign(bundle: Bundle?): LongdoMapDesignTypeInterface { + val designId = bundle?.getString(KEY_DESIGN_ID) + // レイヤ名を持たない以前の形式は id から引く。 + val layerName = bundle?.getString(KEY_LAYER_NAME) ?: return LongdoDesign.fromId(designId) + val id = designId ?: LongdoDesign.Normal.id + val rules = MapViewStateBundle.getAttributionRules(bundle) + // LongdoDesign は attributionRules を持てない。規則が無ければ LongdoDesign で戻し、 + // 定義済み(LongdoDesign.Gray など)と等しくなるようにする。 + return if (rules.isEmpty()) { + LongdoDesign(id = id, layerName = layerName) + } else { + RestoredLongdoDesign(id = id, layerName = layerName, attributionRules = rules) + } } override fun getStateId(state: LongdoViewState): String = state.id @@ -92,6 +112,19 @@ class LongdoMapViewSaver : BaseMapViewSaver() { } } +// 公開 API の門番(javap)にコンパニオンの定数が載らないよう、ファイル private にする。 +private const val KEY_LAYER_NAME = "longdo_layer_name" + +/** attributionRules を持つデザインを復元するときの入れ物([LongdoDesign] は規則を持てない)。 */ +@InternalMapConductorApi +internal data class RestoredLongdoDesign( + override val id: String, + override val layerName: String, + override val attributionRules: List, +) : LongdoMapDesignTypeInterface { + override fun getValue(): String = id +} + /** * [LongdoViewState] を生成・記憶する Composable ファクトリ。 *