diff --git a/app/build.gradle b/app/build.gradle index 34f8de3..d031b50 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -166,4 +166,5 @@ dependencies { classifier = "sources" } } + implementation libs.kotlin.reflect } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ec9d7f4..29169f3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - + @@ -15,11 +15,19 @@ android:roundIcon="@mipmap/icon_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + + + + android:theme="@style/SplashTheme"> @@ -38,15 +46,17 @@ tools:ignore="LockedOrientationActivity" /> + android:theme="@style/MapStyleActivityTheme" /> - - + + diff --git a/app/src/main/java/ru/dgis/sdk/demo/GesturesActivity.kt b/app/src/main/java/ru/dgis/sdk/demo/GesturesActivity.kt new file mode 100644 index 0000000..b98dc5c --- /dev/null +++ b/app/src/main/java/ru/dgis/sdk/demo/GesturesActivity.kt @@ -0,0 +1,185 @@ +package ru.dgis.sdk.demo + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.widget.addTextChangedListener +import ru.dgis.sdk.demo.common.addSettingsLayout +import ru.dgis.sdk.demo.databinding.ActivityGesturesBinding +import ru.dgis.sdk.demo.databinding.ActivityGesturesSettingsBinding +import ru.dgis.sdk.map.Gesture +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.functions +import kotlin.reflect.full.instanceParameter + +/** + * Sample activity for demonstration of maps's Gesture Manager possibilities in terms of enabling / disabling gestures and modifying their settings + * For further info check [SDK Documentation](https://docs.2gis.com/en/android/sdk/reference/7.0/ru.dgis.sdk.map.GestureManager) + * + * Some reflection used here to reduce boilerplate code, see [readSettingsPropertyByName] and [copySettingsByName] + * Lets see on example: + * + * 1. readSettingsPropertyByName() is equivalent for property accessor in data class, hence + * settings = TiltSettings(...) + * assertEquals(settings.lenOnDegreeMm, readSettingsPropertyByName(settings, "lenOnDegreeMm")) // will pass + * + * 2. copySettingsByName() is equivalent for copy() function of data class, hence + * settings = TiltSettings(...) + * assertEquals(settings.copy(lenOnDegreeMm = 42.0), copySettingsByName((settings), mapOf("lenOnDegreeMm" to 42.0)) // will pass + * + * Be sure to not use this in production code because reflection is slow. Use strict data class methods instead. + */ +class GesturesActivity : AppCompatActivity() { + private val binding: ActivityGesturesBinding by lazy { ActivityGesturesBinding.inflate(layoutInflater) } + private val mapView by lazy { binding.mapView } + private val gestureManager by lazy { mapView.gestureManager } + private val settingsBinding by lazy { prepareSettingsBinding() } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + binding.addSettingsLayout().apply { + settingsDrawerInnerLayout.addView(settingsBinding.root) + } + + /** + * Using hack here to delay settings initialization until map is ready and gestureManager is not null for sure + */ + mapView.getMapAsync { + initSwitches() + initRotationSettings() + initTiltSettings() + initScailingSettings() + initMultiTouchShiftSettings() + } + } + + private fun prepareSettingsBinding(): ActivityGesturesSettingsBinding { + return ActivityGesturesSettingsBinding.inflate(layoutInflater) + } + + private fun initSwitches() { + val gestureManager = this.gestureManager!! + + mapOf( + settingsBinding.scailingSwitch to Gesture.SCALING, + settingsBinding.shiftSwitch to Gesture.SHIFT, + settingsBinding.multiTouchShiftSwitch to Gesture.MULTI_TOUCH_SHIFT, + settingsBinding.tiltSwitch to Gesture.TILT, + settingsBinding.rotationSwitch to Gesture.ROTATION + ).forEach { (switchMaterial, gesture) -> + switchMaterial.isChecked = gestureManager.gestureEnabled(gesture) + switchMaterial.setOnCheckedChangeListener { _, isChecked -> + /** + * Here is main functions for enabling / disabling gestures + */ + if (isChecked) { + gestureManager.enableGesture(gesture) + } else { + gestureManager.disableGesture(gesture) + } + } + } + } + + private fun initRotationSettings() { + val gestureManager = this.gestureManager!! + val rotationSettings = gestureManager.rotationSettings + + mapOf( + settingsBinding.rotationAnglediffinscalingdegEditText to "angleDiffInScalingDeg", + settingsBinding.rotationDistancediffmmEditText to "distanceDiffMm", + settingsBinding.rotationAnglediffdegEditText to "angleDiffDeg", + settingsBinding.rotationDistancediffinscalingmmEditText to "distanceDiffInScalingMm" + ).forEach { (editText, setting) -> + val propertyValue = readSettingsPropertyByName(rotationSettings, setting) + editText.apply { + setText(propertyValue.toString()) + addTextChangedListener { + gestureManager.rotationSettings = copySettingsByName( + gestureManager.rotationSettings, + mapOf(setting to it.toString().toFloat()) + ) + } + } + } + } + + private fun initTiltSettings() { + val gestureManager = this.gestureManager!! + val tiltSettings = gestureManager.tiltSettings + + mapOf( + settingsBinding.tiltHorizontalswervedegEditText to "horizontalSwerveDeg", + settingsBinding.tiltLenondegreemmEditText to "lenOnDegreeMm", + settingsBinding.tiltThresholdmmEditText to "thresholdMm", + settingsBinding.tiltVerticalswervedegEditText to "verticalSwerveDeg" + ).forEach { (editText, setting) -> + val propertyValue = readSettingsPropertyByName(tiltSettings, setting) + editText.apply { + setText(propertyValue.toString()) + addTextChangedListener { + gestureManager.tiltSettings = copySettingsByName( + gestureManager.tiltSettings, + mapOf(setting to it.toString().toFloat()) + ) + } + } + } + } + + private fun initScailingSettings() { + val gestureManager = this.gestureManager!! + val scalingSettings = gestureManager.scalingSettings + + mapOf( + settingsBinding.scailingScaleratiothresholdEditText to "scaleRatioThreshold", + settingsBinding.scailingScaleratiothresholdinrotationEditText to "scaleRatioThresholdInRotation" + ).forEach { (editText, setting) -> + val propertyValue = readSettingsPropertyByName(scalingSettings, setting) + editText.apply { + setText(propertyValue.toString()) + addTextChangedListener { + gestureManager.scalingSettings = copySettingsByName( + gestureManager.scalingSettings, + mapOf(setting to it.toString().toFloat()) + ) + } + } + } + } + private fun initMultiTouchShiftSettings() { + val gestureManager = this.gestureManager!! + val multiTouchShiftSettings = gestureManager.multitouchShiftSettings + + mapOf( + settingsBinding.multiTouchShiftThresholdmmEditText to "thresholdMm" + ).forEach { (editText, setting) -> + val propertyValue = readSettingsPropertyByName(multiTouchShiftSettings, setting) + editText.apply { + setText(propertyValue.toString()) + addTextChangedListener { + gestureManager.multitouchShiftSettings = copySettingsByName( + gestureManager.multitouchShiftSettings, + mapOf(setting to it.toString().toFloat()) + ) + } + } + } + } + + @Suppress("UNCHECKED_CAST") + private fun readSettingsPropertyByName(instance: Any, propertyName: String): T { + val property = instance::class.members.first { it.name == propertyName } as KProperty1 + return property.get(instance) as T + } + + @Suppress("UNCHECKED_CAST") + private fun copySettingsByName(instance: T, newValues: Map): T { + val clazz = instance::class + val copyFunction = clazz.functions.first { it.name == "copy" } + val args = copyFunction.parameters + .filter { param -> newValues.keys.contains(param.name) } + .map { param -> param to newValues[param.name] } + + return copyFunction.callBy(mapOf(copyFunction.instanceParameter!! to instance) + args) as? T ?: error("error") + } +} diff --git a/app/src/main/java/ru/dgis/sdk/demo/GesturesMapPointActivity.kt b/app/src/main/java/ru/dgis/sdk/demo/GesturesMapPointActivity.kt new file mode 100644 index 0000000..b2ff01f --- /dev/null +++ b/app/src/main/java/ru/dgis/sdk/demo/GesturesMapPointActivity.kt @@ -0,0 +1,72 @@ +package ru.dgis.sdk.demo + +import android.os.Bundle +import android.widget.ArrayAdapter +import androidx.appcompat.app.AppCompatActivity +import androidx.core.widget.doAfterTextChanged +import ru.dgis.sdk.demo.common.addSettingsLayout +import ru.dgis.sdk.demo.databinding.ActivityGesturesBinding +import ru.dgis.sdk.demo.databinding.ActivityGesturesMapPointSettingsBinding +import ru.dgis.sdk.map.EventsProcessingSettings +import ru.dgis.sdk.map.RotationCenter +import ru.dgis.sdk.map.ScalingCenter + +/** + * Sample activity for demonstration of maps's Gesture Manager possibilities in terms of setting map point, which gestures will be relative to + * It's hard to test these cases on an emulator since all multitouch gestures will use center of screen as a center of segment between 2 touch points, + * so we recommend to use real smartphone here. + * + * For further details check [SDK Documentation](https://docs.2gis.com/en/android/sdk/reference/7.0/ru.dgis.sdk.map.GestureManager#nav-lvl1--setSettingsAboutMapPositionPoint) + */ +class GesturesMapPointActivity : AppCompatActivity() { + + private val binding by lazy { ActivityGesturesBinding.inflate(layoutInflater) } + private val mapView by lazy { binding.mapView } + private val gestureManager by lazy { mapView.gestureManager } + private val settingsBinding by lazy { prepareSettingsBinding() } + private var eventProcessingSettings = EventsProcessingSettings( + RotationCenter.MAP_POSITION, + ScalingCenter.MAP_POSITION + ) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + binding.addSettingsLayout().apply { + settingsDrawerInnerLayout.addView(settingsBinding.root) + } + + /** + * Using hack here to delay settings initialization until map is ready and gestureManager is not null for sure + */ + mapView.getMapAsync { + initSettings() + } + } + + private fun prepareSettingsBinding(): ActivityGesturesMapPointSettingsBinding { + return ActivityGesturesMapPointSettingsBinding.inflate(layoutInflater).apply { + val options = resources.getStringArray(R.array.events_processing_settings) + val adapter = ArrayAdapter(this@GesturesMapPointActivity, R.layout.dropdown_item, options) + rotationCenterTextView.setAdapter(adapter) + scailingCenterTextView.setAdapter(adapter) + } + } + + private fun initSettings() { + val gestureManager = this.gestureManager!! + + settingsBinding.rotationCenterTextView.setText(eventProcessingSettings.rotationCenter.toString(), false) + settingsBinding.rotationCenterTextView.doAfterTextChanged { editable -> + val newRotationCenter = RotationCenter.entries.first { it.name == editable.toString() } + eventProcessingSettings = eventProcessingSettings.copy(rotationCenter = newRotationCenter) + gestureManager.setSettingsAboutMapPositionPoint(eventProcessingSettings) + } + + settingsBinding.scailingCenterTextView.setText(eventProcessingSettings.scalingCenter.toString(), false) + settingsBinding.scailingCenterTextView.doAfterTextChanged { editable -> + val newScailingCenter = ScalingCenter.entries.first { it.name == editable.toString() } + eventProcessingSettings = eventProcessingSettings.copy(scalingCenter = newScailingCenter) + gestureManager.setSettingsAboutMapPositionPoint(eventProcessingSettings) + } + } +} diff --git a/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt b/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt index 9c480fa..25f976b 100644 --- a/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt +++ b/app/src/main/java/ru/dgis/sdk/demo/MainActivity.kt @@ -45,6 +45,18 @@ class MainActivity : AppCompatActivity() { Page("Parkings on map") { val intent = Intent(this@MainActivity, ParkingActivity::class.java) startActivity(intent) + }, + Page("Gestures") { + val intent = Intent(this@MainActivity, GesturesActivity::class.java) + startActivity(intent) + }, + Page("Gestures: center point") { + val intent = Intent(this@MainActivity, GesturesMapPointActivity::class.java) + startActivity(intent) + }, + Page("Mutually exclusive gestures") { + val intent = Intent(this@MainActivity, MutuallyExclusiveGesturesActivity::class.java) + startActivity(intent) } ) diff --git a/app/src/main/java/ru/dgis/sdk/demo/MutuallyExclusiveGesturesActivity.kt b/app/src/main/java/ru/dgis/sdk/demo/MutuallyExclusiveGesturesActivity.kt new file mode 100644 index 0000000..d78e23d --- /dev/null +++ b/app/src/main/java/ru/dgis/sdk/demo/MutuallyExclusiveGesturesActivity.kt @@ -0,0 +1,78 @@ +package ru.dgis.sdk.demo + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import ru.dgis.sdk.demo.common.addSettingsLayout +import ru.dgis.sdk.demo.databinding.ActivityGesturesBinding +import ru.dgis.sdk.demo.databinding.ActivityMutuallyExclusiveGesturesSettingsBinding +import ru.dgis.sdk.map.Gesture +import java.util.EnumSet + +/** + * Sample activity for demonstration of maps's Gesture Manager possibilities in terms of setting rules for gestures + * + * For further details check [SDK Documentation](https://docs.2gis.com/en/android/sdk/reference/7.0/ru.dgis.sdk.map.GestureManager#nav-lvl1--setMutuallyExclusiveGestures) + */ +class MutuallyExclusiveGesturesActivity : AppCompatActivity() { + + private val binding by lazy { ActivityGesturesBinding.inflate(layoutInflater) } + private val settingsBinding by lazy { ActivityMutuallyExclusiveGesturesSettingsBinding.inflate(layoutInflater) } + private val mapView by lazy { binding.mapView } + private val gestureManager by lazy { mapView.gestureManager } + + private var checkedGestures = EnumSet.noneOf(Gesture::class.java) + private var addedRules = mutableListOf>() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + binding.addSettingsLayout().apply { + settingsDrawerInnerLayout.addView(settingsBinding.root) + } + + /** + * Using hack here to delay settings initialization until map is ready and gestureManager is not null for sure + */ + mapView.getMapAsync { + initSettings() + } + } + + private fun initSettings() { + val gestureManager = this.gestureManager!! + settingsBinding.apply { + val checkboxes = listOf( + tiltCheckbox, + rotationCheckbox, + scailingCheckbox, + multiTouchShiftCheckbox + ) + checkboxes.forEach { checkBox -> + checkBox.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + checkedGestures.add(ru.dgis.sdk.map.Gesture.entries.first { it.name == checkBox.tag }) + } else { + checkedGestures.remove(ru.dgis.sdk.map.Gesture.entries.first { it.name == checkBox.tag }) + } + } + } + + applyRuleButton.setOnClickListener { + addedRules += listOf(checkedGestures) + gestureManager.setMutuallyExclusiveGestures(addedRules) + checkedGestures = EnumSet.noneOf(Gesture::class.java) + checkboxes.forEach { + it.isChecked = false + } + } + + cleanRulesButton.setOnClickListener { + gestureManager.setMutuallyExclusiveGestures(listOf()) + addedRules.clear() + checkedGestures = EnumSet.noneOf(Gesture::class.java) + checkboxes.forEach { + it.isChecked = false + } + } + } + } +} diff --git a/app/src/main/res/layout/activity_gestures.xml b/app/src/main/res/layout/activity_gestures.xml new file mode 100644 index 0000000..a6bf3bb --- /dev/null +++ b/app/src/main/res/layout/activity_gestures.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_gestures_map_point_settings.xml b/app/src/main/res/layout/activity_gestures_map_point_settings.xml new file mode 100644 index 0000000..de790c6 --- /dev/null +++ b/app/src/main/res/layout/activity_gestures_map_point_settings.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_gestures_settings.xml b/app/src/main/res/layout/activity_gestures_settings.xml new file mode 100644 index 0000000..182c717 --- /dev/null +++ b/app/src/main/res/layout/activity_gestures_settings.xml @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_map_fps_settings.xml b/app/src/main/res/layout/activity_map_fps_settings.xml index 5cfea41..bf1d529 100644 --- a/app/src/main/res/layout/activity_map_fps_settings.xml +++ b/app/src/main/res/layout/activity_map_fps_settings.xml @@ -15,10 +15,10 @@ android:text="@string/max_fps_limit"/> @@ -33,10 +33,10 @@ android:text="@string/power_saving_fps_limit"/> diff --git a/app/src/main/res/layout/activity_mutually_exclusive_gestures_settings.xml b/app/src/main/res/layout/activity_mutually_exclusive_gestures_settings.xml new file mode 100644 index 0000000..723f7db --- /dev/null +++ b/app/src/main/res/layout/activity_mutually_exclusive_gestures_settings.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +