Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AttributionRule> = 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 <T : Any> roundTrip(
saver: Saver<T, Bundle>,
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()
}
}
}
45 changes: 39 additions & 6 deletions src/main/java/com/mapconductor/longdo/LongdoViewStateImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -62,27 +65,44 @@ class LongdoViewState(

/**
* [LongdoViewState] の保存・復元を行う Saver。
* デザインは [LongdoMapDesignTypeInterface.id] を保存し、[LongdoDesign.fromId] で復元する。
* デザインは id・レイヤ名・attributionRules を保存する。id だけで [LongdoDesign.fromId] に
* 頼ると、定義に無いデザインは [LongdoDesign.Normal] に化け、attributionRules も落ちる。
*/
class LongdoMapViewSaver : BaseMapViewSaver<LongdoViewState>() {
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
Expand All @@ -92,6 +112,19 @@ class LongdoMapViewSaver : BaseMapViewSaver<LongdoViewState>() {
}
}

// 公開 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<AttributionRule>,
) : LongdoMapDesignTypeInterface {
override fun getValue(): String = id
}

/**
* [LongdoViewState] を生成・記憶する Composable ファクトリ。
*
Expand Down
Loading