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
9 changes: 9 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ dependencies {
),
),
)
// 実機テスト(HereMapViewSaverTest)は MapScheme などを実行時に読むので、テスト APK には同梱する。
androidTestImplementation(
fileTree(
mapOf(
"dir" to rootDir.resolve("libs").toString(),
"include" to arrayOf("heresdk*.jar", "heresdk*.aar"),
),
),
)
if (findProject(":android-sdk-compose") != null) {
api(project(":android-sdk-compose"))
} else {
Expand Down
85 changes: 85 additions & 0 deletions src/androidTest/java/com/mapconductor/here/HereMapViewSaverTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.mapconductor.here

import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.here.sdk.mapview.MapScheme
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.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import android.os.Bundle
import android.os.Parcel

/**
* 回転などの構成変更で state が [HereMapViewSaver] を通って作り直されても、デザイン(id・
* attributionRules ほか)と uiSettings・カメラが落ちないこと。Bundle は実機の Parcel を一度通す。
*/
@RunWith(AndroidJUnit4::class)
class HereMapViewSaverTest {
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 keepsCustomDesignWithAttributionRules() {
val design = HereMapDesign.Custom(MapScheme.SATELLITE, rules)
val state = HereViewState(id = "here-1", mapDesignType = design, cameraPosition = camera)
state.uiSettings = MapUISettings(rotateGesture = false, tiltGesture = false)

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

val actual = restored.mapDesignType
assertTrue(actual is HereMapDesign.Custom)
assertEquals(MapScheme.SATELLITE, actual.id)
assertEquals(rules, actual.attributionRules)
assertCommon(restored, "here-1")
}

@Test
fun keepsPresetDesignAsTheSameObject() {
val state = HereViewState(id = "here-2", mapDesignType = HereMapDesign.LiteNight)
assertSame(HereMapDesign.LiteNight, roundTrip(HereMapViewSaver().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()
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/mapconductor/here/HereMapView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ fun HereMapView(
controller.setMapClickListener(onMapClick)
controller.setMapLongClickListener(onMapLongClick)
state.setController(controller)
// コントローラの既定値(NormalDay)のままリスナーを繋ぐと、それが state へ返り、
// アプリが渡した(あるいは回転後に復元した)デザインが NormalDay に化ける。
// 下の loadScene は state のデザインを読むので、その前に揃えておく。
controller.mapDesignType = state.mapDesignType
controller.setMapDesignTypeChangeListener(state::onMapDesignTypeChange)
cameraRestriction?.let { controller.setCameraRestriction(it) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class HereMapViewController(
polygonController.clickListener = listener
}

private var mapDesignType: HereMapDesignType = HereMapDesign.NormalDay
internal var mapDesignType: HereMapDesignType = HereMapDesign.NormalDay
private var mapDesignTypeChangeListener: HereMapDesignTypeChangeHandler? = null

override fun applyUISettings(settings: MapUISettings) {
Expand Down
37 changes: 30 additions & 7 deletions src/main/java/com/mapconductor/here/HereViewStateImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import com.here.sdk.mapview.MapScheme
import com.mapconductor.compose.map.BaseMapViewSaver
import com.mapconductor.core.map.MapCameraPosition
import com.mapconductor.core.map.MapCameraPositionInterface
import com.mapconductor.core.map.MapPaddings
import com.mapconductor.core.map.MapPaddingsInterface
import com.mapconductor.core.map.MapViewState
import com.mapconductor.core.map.MapViewStateBundle
import com.mapconductor.core.map.MapViewStateInterface
import java.util.UUID
import android.app.Activity
Expand Down Expand Up @@ -60,28 +62,49 @@ class HereMapViewSaver : BaseMapViewSaver<HereViewState>() {
state: HereViewState,
bundle: Bundle,
) {
bundle.putInt("id", state.mapDesignType.getValue().value)
val design = state.mapDesignType
val scheme = design.getValue().value
bundle.putInt("id", scheme)
// 定義済み(NormalDay など)以外は Custom として戻す。id だけだと Custom の
// attributionRules が落ちる。
bundle.putBoolean(KEY_CUSTOM, design !== presetOrNull(scheme))
MapViewStateBundle.putAttributionRules(bundle, design.attributionRules)
}

override fun createState(
stateId: String,
mapDesignBundle: Bundle?,
cameraPosition: MapCameraPosition,
): HereViewState =
HereViewState(
): HereViewState {
val scheme = mapDesignBundle?.getInt("id", HereMapDesign.NormalDay.id.value) ?: HereMapDesign.NormalDay.id.value
val preset = presetOrNull(scheme)
val mapScheme = MapScheme.values().firstOrNull { it.value == scheme }
val design =
if ((mapDesignBundle?.getBoolean(KEY_CUSTOM) == true || preset == null) && mapScheme != null) {
HereMapDesign.Custom(
id = mapScheme,
attributionRules = MapViewStateBundle.getAttributionRules(mapDesignBundle),
)
} else {
preset ?: HereMapDesign.NormalDay
}
return HereViewState(
id = stateId,
mapDesignType =
HereMapDesign.create(
id = mapDesignBundle?.getInt("id") ?: HereMapDesign.NormalDay.id.value,
),
mapDesignType = design,
cameraPosition = cameraPosition,
)
}

private fun presetOrNull(scheme: Int): HereMapDesign? = runCatching { HereMapDesign.create(scheme) }.getOrNull()

override fun getCameraPaddings(): MapPaddingsInterface? = MapPaddings.Zeros

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

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

@Composable
fun rememberHereMapViewState(
mapDesign: HereMapDesign = HereMapDesign.NormalDay,
Expand Down
Loading