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,87 @@
package com.mapconductor.tomtom

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 com.tomtom.sdk.map.display.style.StyleDescriptor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import android.net.Uri
import android.os.Bundle
import android.os.Parcel

/**
* 回転などの構成変更で state が [TomTomMapViewSaver] を通って作り直されても、デザイン(id・
* attributionRules ほか)と uiSettings・カメラが落ちないこと。Bundle は実機の Parcel を一度通す。
*/
@RunWith(AndroidJUnit4::class)
class TomTomMapViewSaverTest {
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 keepsCustomDesignWithItsStyleDescriptor() {
val descriptor = StyleDescriptor(Uri.parse("https://example.com/style.json"))
val design = TomTomMapDesign.Custom("my-style", descriptor, rules)
val state = TomTomMapViewState(id = "tomtom-1", mapDesignType = design, cameraPosition = camera)
state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false)

val restored = roundTrip(TomTomMapViewSaver().createSaver(), state)

// 以前は id だけを保存して create(id) で戻しており、Standard になっていた。
val actual = restored.mapDesignType
assertTrue(actual is TomTomMapDesign.Custom)
actual as TomTomMapDesign.Custom
assertEquals("my-style", actual.id)
assertEquals(descriptor, actual.styleDescriptor)
assertEquals(rules, actual.attributionRules)
assertCommon(restored, "tomtom-1")
}

@Test
fun keepsPresetDesignAsTheSameObject() {
val state = TomTomMapViewState(id = "tomtom-2", mapDesignType = TomTomMapDesign.Satellite)
assertSame(TomTomMapDesign.Satellite, roundTrip(TomTomMapViewSaver().createSaver(), state).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()
}
}
}
12 changes: 11 additions & 1 deletion src/main/java/com/mapconductor/tomtom/TomTomMapView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,24 @@ fun TomTomMapView(
}
mapController.setMapClickListener(onMapClick)
mapController.setMapLongClickListener(onMapLongClick)
mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange)
cameraRestriction?.let { mapController.setCameraRestriction(it) }
// 大量マーカーをラスタタイル化するため、マーカータイル用ラスタレイヤーの
// 実体化(スタイル合成 + loadStyle)を配線する。
mapController.setupMarkerTileRaster(
apiKey = tomtomApiKey(context),
cacheDir = context.cacheDir,
)
// 地図は MapOptions の既定スタイル(= Standard)で作られる。state のデザインが
// それ以外なら載せる。そのあとでリスナーを繋ぐ: 先に繋ぐとコントローラの既定値
// (Standard)が state へ返り、アプリが渡した(あるいは回転後に復元した)
// デザインが Standard に化ける。
val initialDesign = state.mapDesignType
if (initialDesign === TomTomMapDesign.Standard) {
mapController.mapDesignType = initialDesign
} else {
mapController.setMapDesignType(initialDesign)
}
mapController.setMapDesignTypeChangeListener(state::onMapDesignTypeChange)
holder.mapView.post {
// Style loading can reset the camera, so reapply it once the view is attached.
mapController.moveCamera(initialCameraPosition)
Expand Down
33 changes: 28 additions & 5 deletions src/main/java/com/mapconductor/tomtom/TomTomMapViewStateImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapCameraPositionInterface
import com.mapconductor.core.map.MapPaddings
import com.mapconductor.core.map.MapViewState
import com.mapconductor.core.map.MapViewStateBundle
import com.mapconductor.core.map.MapViewStateInterface
import com.tomtom.sdk.map.display.style.StyleDescriptor
import java.util.UUID
import android.os.Bundle
import kotlinx.coroutines.flow.MutableStateFlow
Expand Down Expand Up @@ -61,7 +63,14 @@ class TomTomMapViewSaver : BaseMapViewSaver<TomTomMapViewState>() {
state: TomTomMapViewState,
bundle: Bundle,
) {
bundle.putString("id", state.mapDesignType.id)
val design = state.mapDesignType
bundle.putString("id", design.id)
// 定義済み(Standard など)以外は、地図に読み込ませる StyleDescriptor(Parcelable)ごと
// 保存して Custom で戻す。id だけだと Custom は Standard に化け、attributionRules も落ちる。
if (design is TomTomMapDesign && design !== TomTomMapDesign.create(design.id)) {
bundle.putParcelable(KEY_STYLE_DESCRIPTOR, design.styleDescriptor)
}
MapViewStateBundle.putAttributionRules(bundle, design.attributionRules)
}

override fun createState(
Expand All @@ -71,16 +80,30 @@ class TomTomMapViewSaver : BaseMapViewSaver<TomTomMapViewState>() {
): TomTomMapViewState =
TomTomMapViewState(
id = stateId,
mapDesignType =
TomTomMapDesign.create(
id = mapDesignBundle?.getString("id") ?: TomTomMapDesign.Standard.id,
),
mapDesignType = restoreDesign(mapDesignBundle),
cameraPosition = cameraPosition,
)

private fun restoreDesign(bundle: Bundle?): TomTomMapDesign {
val id = bundle?.getString("id") ?: return TomTomMapDesign.Standard
val rules = MapViewStateBundle.getAttributionRules(bundle)

@Suppress("DEPRECATION")
val descriptor = bundle.getParcelable<StyleDescriptor>(KEY_STYLE_DESCRIPTOR)
val preset = TomTomMapDesign.create(id)
return when {
descriptor != null -> TomTomMapDesign.Custom(id, descriptor, rules)
rules.isNotEmpty() -> TomTomMapDesign.Custom(id, preset.styleDescriptor, rules)
else -> preset
}
}

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

// 公開 API の門番(javap)にコンパニオンの定数が載らないよう、ファイル private にする。
private const val KEY_STYLE_DESCRIPTOR = "styleDescriptor"

@Composable
fun rememberTomTomMapViewState(
mapDesign: TomTomMapDesign = TomTomMapDesign.Standard,
Expand Down
Loading