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
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.mapconductor.compose.map

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.MapDesignTypeInterface
import com.mapconductor.core.map.MapPaddings
import com.mapconductor.core.map.MapUISettings
import com.mapconductor.core.map.MapViewState
import com.mapconductor.core.map.MapViewStateBundle
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import android.os.Bundle
import android.os.Parcel

/**
* プロバイダの `*MapViewSaver` が継承する [BaseMapViewSaver] が、uiSettings とカメラ
* (paddings を含む)を落とさないこと。Bundle は実機の Parcel を一度通す。
*/
@RunWith(AndroidJUnit4::class)
class BaseMapViewSaverRoundTripTest {
@Test
fun keepsUiSettingsCameraAndDesignRules() {
val state =
FakeState(
id = "compose-state",
design = FakeDesign("d", listOf(AttributionRule("© test", minZoom = 2))),
camera =
MapCameraPosition(
position = GeoPoint(-33.86, 151.21),
zoom = 9.0,
bearing = 90.0,
tilt = 30.0,
paddings = MapPaddings(top = 5.0, left = 6.0, bottom = 7.0, right = 8.0),
),
)
state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false)

val saver = FakeSaver().createSaver()
val saved = with(saver) { SaverScope { true }.save(state) }!!
val restored = saver.restore(throughParcel(saved))!!

assertEquals("compose-state", restored.id)
assertEquals("d", restored.mapDesignType.id)
assertEquals(
"© test",
restored.mapDesignType.attributionRules
.single()
.attribution,
)
assertEquals(
2,
restored.mapDesignType.attributionRules
.single()
.minZoom,
)
assertEquals(true, restored.uiSettings.scrollGesture)
assertEquals(true, restored.uiSettings.zoomGesture)
assertEquals(false, restored.uiSettings.rotateGesture)
assertEquals(false, restored.uiSettings.tiltGesture)
assertEquals(-33.86, restored.cameraPosition.position.latitude, 1e-9)
assertEquals(151.21, restored.cameraPosition.position.longitude, 1e-9)
assertEquals(9.0, restored.cameraPosition.zoom, 1e-9)
assertEquals(90.0, restored.cameraPosition.bearing, 1e-9)
assertEquals(30.0, restored.cameraPosition.tilt, 1e-9)
assertEquals(5.0, restored.cameraPosition.paddings!!.top, 1e-9)
assertEquals(8.0, restored.cameraPosition.paddings!!.right, 1e-9)
}

private class FakeDesign(
override val id: String,
override val attributionRules: List<AttributionRule>,
) : MapDesignTypeInterface<String> {
override fun getValue(): String = id
}

private class FakeState(
override val id: String,
design: FakeDesign,
camera: MapCameraPosition = MapCameraPosition.Default,
) : MapViewState<FakeDesign>(camera) {
override var mapDesignType: FakeDesign = design
}

private class FakeSaver : BaseMapViewSaver<FakeState>() {
override fun saveMapDesign(
state: FakeState,
bundle: Bundle,
) {
bundle.putString("id", state.mapDesignType.id)
MapViewStateBundle.putAttributionRules(bundle, state.mapDesignType.attributionRules)
}

override fun createState(
stateId: String,
mapDesignBundle: Bundle?,
cameraPosition: MapCameraPosition,
): FakeState =
FakeState(
id = stateId,
design =
FakeDesign(
id = mapDesignBundle?.getString("id") ?: "",
attributionRules = MapViewStateBundle.getAttributionRules(mapDesignBundle),
),
camera = cameraPosition,
)

override fun getStateId(state: FakeState): String = state.id
}

private fun throughParcel(bundle: Bundle): Bundle {
val parcel = Parcel.obtain()
try {
parcel.writeBundle(bundle)
parcel.setDataPosition(0)
return parcel.readBundle(javaClass.classLoader)!!
} finally {
parcel.recycle()
}
}
}
44 changes: 21 additions & 23 deletions src/main/java/com/mapconductor/compose/map/BaseMapViewSaver.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package com.mapconductor.compose.map

import androidx.compose.runtime.saveable.Saver
import com.mapconductor.core.features.GeoPoint
import com.mapconductor.core.InternalMapConductorApi
import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapPaddingsInterface
import com.mapconductor.core.map.MapViewStateBundle
import com.mapconductor.core.map.MapViewStateInterface
import android.os.Bundle

/**
* Base class for MapView state savers
*
* カメラ(paddings を含む)と [MapViewStateInterface.uiSettings] はここで保存・復元する。
* デザインは型がプロバイダごとに違うので [saveMapDesign] / [createState] が受け持つ。
* デザインの `attributionRules` を含め、アプリが設定したものは全部書くこと
* (`MapViewStateBundle.putAttributionRules` を使う)。書かなかった値は回転などの
* 構成変更のあと黙って既定値に戻る。
*
* @param T MapViewStateInterface type
*/
@OptIn(InternalMapConductorApi::class)
abstract class BaseMapViewSaver<T : MapViewStateInterface<*>> {
/**
* Save map design type to bundle
Expand Down Expand Up @@ -50,6 +59,7 @@ abstract class BaseMapViewSaver<T : MapViewStateInterface<*>> {
putString("stateId", getStateId(state))
putBundle("mapDesign", mapDesignBundle)
putBundle("camera", cameraStateBundle)
putBundle(MapViewStateBundle.KEY_UI_SETTINGS, MapViewStateBundle.uiSettingsToBundle(state.uiSettings))
}
},
restore = { storedData ->
Expand All @@ -59,7 +69,14 @@ abstract class BaseMapViewSaver<T : MapViewStateInterface<*>> {

val cameraPosition = createCameraPositionFromBundle(cameraBundle)

createState(stateId, mapDesignBundle, cameraPosition)
createState(stateId, mapDesignBundle, cameraPosition).also { state ->
// uiSettings はどのプロバイダでも MapViewStateInterface の var なので、
// createState に渡さずここで戻す(以前は保存しておらず、復元後は
// 全部 true の既定値に戻っていた)。
MapViewStateBundle.uiSettingsFromBundle(storedData.getBundle(MapViewStateBundle.KEY_UI_SETTINGS))?.let {
state.uiSettings = it
}
}
},
)

Expand All @@ -68,27 +85,8 @@ abstract class BaseMapViewSaver<T : MapViewStateInterface<*>> {
*/
protected abstract fun getStateId(state: T): String

private fun createCameraBundle(state: T): Bundle =
state.cameraPosition.let {
Bundle().apply {
putDouble("zoom", it.zoom)
putDouble("tilt", it.tilt)
putDouble("bearing", it.bearing)
putDouble("latitude", it.position.latitude)
putDouble("longitude", it.position.longitude)
}
}
private fun createCameraBundle(state: T): Bundle = MapViewStateBundle.cameraToBundle(state.cameraPosition)

private fun createCameraPositionFromBundle(cameraBundle: Bundle?): MapCameraPosition =
MapCameraPosition(
position =
GeoPoint.fromLatLong(
latitude = cameraBundle?.getDouble("latitude") ?: 0.0,
longitude = cameraBundle?.getDouble("longitude") ?: 0.0,
),
zoom = cameraBundle?.getDouble("zoom") ?: 0.0,
bearing = cameraBundle?.getDouble("bearing") ?: 0.0,
tilt = cameraBundle?.getDouble("tilt") ?: 0.0,
paddings = getCameraPaddings(),
)
MapViewStateBundle.cameraFromBundle(cameraBundle, fallbackPaddings = getCameraPaddings())
}
Loading