Add customizable long press
This commit is contained in:
parent
72f81966df
commit
47ee21afc2
10 changed files with 120 additions and 82 deletions
|
@ -14,4 +14,4 @@ class AboutViewModelTest : IntentViewModelTest() {
|
|||
assertTrue(state.licenses.isNotEmpty())
|
||||
assertTrue(state.translators.isNotEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,20 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Test
|
||||
|
||||
class HistoryViewModelTest : IntentViewModelTest() {
|
||||
private val fakeMinefield = Minefield(9, 9, 9)
|
||||
private val allSaves = listOf(
|
||||
Save(0, 100, 0L, 100L, Minefield(9, 9, 9), Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()),
|
||||
Save(1, 200, 0L, 100L, Minefield(9, 9, 9), Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()),
|
||||
Save(2, 300, 0L, 100L, Minefield(9, 9, 9), Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf())
|
||||
Save(
|
||||
0, 1, 0L, 100L, fakeMinefield,
|
||||
Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()
|
||||
),
|
||||
Save(
|
||||
1, 2, 0L, 100L, fakeMinefield,
|
||||
Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()
|
||||
),
|
||||
Save(
|
||||
2, 3, 0L, 100L, fakeMinefield,
|
||||
Difficulty.Beginner, FirstOpen.Unknown, SaveStatus.ON_GOING, listOf()
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
|
|
|
@ -146,4 +146,4 @@ class ThemeViewModelTest : IntentViewModelTest() {
|
|||
|
||||
verify { themeRepository.setTheme(darkTheme) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,12 @@ buildscript {
|
|||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28.1-alpha'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72'
|
||||
|
||||
if (System.getenv('IS_GOOGLE_BUILD')) {
|
||||
classpath 'com.google.gms:google-services:4.3.3'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.lucasnlm.antimine.common.level.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
|
@ -33,7 +34,6 @@ class AreaAdapter(
|
|||
private val paintSettings: AreaPaintSettings
|
||||
|
||||
private var clickEnabled: Boolean = false
|
||||
private var longPressAt: Long = 0L
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
|
@ -56,103 +56,95 @@ class AreaAdapter(
|
|||
|
||||
override fun getItemCount(): Int = field.size
|
||||
|
||||
private fun AreaView.onClickablePosition(position: Int, action: suspend (Int) -> Unit): Boolean {
|
||||
return if (position == RecyclerView.NO_POSITION) {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
false
|
||||
} else if (clickEnabled) {
|
||||
requestFocus()
|
||||
GlobalScope.launch {
|
||||
action(position)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AreaViewHolder {
|
||||
val view = AreaView(parent.context)
|
||||
return AreaViewHolder(view).apply {
|
||||
view.setOnDoubleClickListener(object : GestureDetector.OnDoubleTapListener {
|
||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||
val position = adapterPosition
|
||||
return when {
|
||||
position == RecyclerView.NO_POSITION -> {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
false
|
||||
val style = preferencesRepository.controlStyle()
|
||||
if (style == ControlStyle.DoubleClick || style == ControlStyle.DoubleClickInverted) {
|
||||
view.isClickable = true
|
||||
view.setOnDoubleClickListener(object : GestureDetector.OnDoubleTapListener {
|
||||
override fun onDoubleTap(e: MotionEvent?): Boolean {
|
||||
return view.onClickablePosition(adapterPosition) {
|
||||
viewModel.onDoubleClick(it)
|
||||
}
|
||||
clickEnabled -> {
|
||||
GlobalScope.launch {
|
||||
viewModel.onDoubleClickArea(position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||
return view.onClickablePosition(adapterPosition) {
|
||||
viewModel.onSingleClick(it)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
view.setOnTouchListener { _, motionEvent ->
|
||||
when (motionEvent.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
view.isPressed = true
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP -> {
|
||||
view.isPressed = false
|
||||
val dt = motionEvent.eventTime - motionEvent.downTime
|
||||
|
||||
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
|
||||
val style = preferencesRepository.controlStyle()
|
||||
if (style == ControlStyle.DoubleClick || style == ControlStyle.DoubleClickInverted) {
|
||||
val position = adapterPosition
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
} else if (clickEnabled) {
|
||||
GlobalScope.launch {
|
||||
viewModel.onSingleClick(position)
|
||||
if (dt > preferencesRepository.customLongPressTimeout()) {
|
||||
view.onClickablePosition(adapterPosition) {
|
||||
viewModel.onLongClick(it)
|
||||
}
|
||||
} else {
|
||||
view.onClickablePosition(adapterPosition) {
|
||||
viewModel.onSingleClick(it)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
itemView.setOnLongClickListener { target ->
|
||||
target.requestFocus()
|
||||
|
||||
val position = adapterPosition
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
} else if (clickEnabled) {
|
||||
GlobalScope.launch {
|
||||
viewModel.onLongClick(position)
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
itemView.setOnClickListener {
|
||||
val style = preferencesRepository.controlStyle()
|
||||
if (style != ControlStyle.DoubleClick && style != ControlStyle.DoubleClickInverted) {
|
||||
val position = adapterPosition
|
||||
if (position == RecyclerView.NO_POSITION) {
|
||||
Log.d(TAG, "Item no longer exists.")
|
||||
} else if (clickEnabled) {
|
||||
GlobalScope.launch {
|
||||
viewModel.onSingleClick(position)
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
itemView.setOnKeyListener { _, keyCode, keyEvent ->
|
||||
view.setOnKeyListener { _, keyCode, keyEvent ->
|
||||
var handled = false
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
|
||||
when (keyEvent.action) {
|
||||
KeyEvent.ACTION_DOWN -> {
|
||||
longPressAt = System.currentTimeMillis()
|
||||
handled = true
|
||||
view.isPressed = true
|
||||
}
|
||||
KeyEvent.ACTION_UP -> {
|
||||
if (clickEnabled) {
|
||||
val value = System.currentTimeMillis() - longPressAt
|
||||
if (value > 300L) {
|
||||
view.performLongClick()
|
||||
} else {
|
||||
view.callOnClick()
|
||||
handled = true
|
||||
view.isPressed = false
|
||||
val dt = keyEvent.eventTime - keyEvent.downTime
|
||||
if (dt > preferencesRepository.customLongPressTimeout()) {
|
||||
view.onClickablePosition(adapterPosition) {
|
||||
viewModel.onLongClick(it)
|
||||
}
|
||||
} else {
|
||||
view.onClickablePosition(adapterPosition) {
|
||||
viewModel.onSingleClick(it)
|
||||
}
|
||||
}
|
||||
longPressAt = System.currentTimeMillis()
|
||||
handled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handled
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,9 @@ class AreaView : View {
|
|||
private lateinit var theme: AppTheme
|
||||
|
||||
private val gestureDetector: GestureDetector by lazy {
|
||||
GestureDetector(context, GestureDetector.SimpleOnGestureListener())
|
||||
GestureDetector(context, GestureDetector.SimpleOnGestureListener()).apply {
|
||||
setIsLongpressEnabled(false)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
@ -39,6 +41,7 @@ class AreaView : View {
|
|||
|
||||
init {
|
||||
isHapticFeedbackEnabled = true
|
||||
isClickable = true
|
||||
}
|
||||
|
||||
fun setOnDoubleClickListener(listener: GestureDetector.OnDoubleTapListener) {
|
||||
|
|
|
@ -244,10 +244,14 @@ class GameViewModel @ViewModelInject constructor(
|
|||
}
|
||||
}.also {
|
||||
onPostAction()
|
||||
|
||||
if (preferencesRepository.useHapticFeedback()) {
|
||||
hapticFeedbackManager.longPressFeedback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun onDoubleClickArea(index: Int) {
|
||||
suspend fun onDoubleClick(index: Int) {
|
||||
gameController.doubleClick(index).flatMapConcat { (action, flow) ->
|
||||
onFeedbackAnalytics(action, index)
|
||||
flow
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package dev.lucasnlm.antimine.core.preferences
|
||||
|
||||
import android.view.ViewConfiguration
|
||||
import dev.lucasnlm.antimine.common.level.models.Minefield
|
||||
import dev.lucasnlm.antimine.core.control.ControlStyle
|
||||
|
||||
|
@ -15,6 +16,8 @@ interface IPreferencesRepository {
|
|||
fun controlStyle(): ControlStyle
|
||||
fun useControlStyle(controlStyle: ControlStyle)
|
||||
|
||||
fun customLongPressTimeout(): Long
|
||||
|
||||
fun themeId(): Long
|
||||
fun useTheme(themeId: Long)
|
||||
|
||||
|
@ -93,6 +96,9 @@ class PreferencesRepository(
|
|||
putInt(PREFERENCE_CONTROL_STYLE, controlStyle.ordinal)
|
||||
}
|
||||
|
||||
override fun customLongPressTimeout(): Long =
|
||||
getInt(PREFERENCE_LONG_PRESS_TIMEOUT, ViewConfiguration.getLongPressTimeout()).toLong()
|
||||
|
||||
override fun themeId(): Long =
|
||||
getInt(PREFERENCE_CUSTOM_THEME, 0).toLong()
|
||||
|
||||
|
@ -143,6 +149,10 @@ class PreferencesRepository(
|
|||
if (!preferencesManager.contains(PREFERENCE_AREA_SIZE)) {
|
||||
preferencesManager.putInt(PREFERENCE_AREA_SIZE, 50)
|
||||
}
|
||||
|
||||
if (!preferencesManager.contains(PREFERENCE_LONG_PRESS_TIMEOUT)) {
|
||||
preferencesManager.putInt(PREFERENCE_LONG_PRESS_TIMEOUT, ViewConfiguration.getLongPressTimeout())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
@ -161,5 +171,6 @@ class PreferencesRepository(
|
|||
private const val PREFERENCE_STATS_BASE = "preference_stats_base"
|
||||
private const val PREFERENCE_OLD_LARGE_AREA = "preference_large_area"
|
||||
private const val PREFERENCE_PROGRESSIVE_VALUE = "preference_progressive_value"
|
||||
private const val PREFERENCE_LONG_PRESS_TIMEOUT = "preference_long_press_timeout"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,18 @@
|
|||
app:showSeekBarValue="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:key="preference_long_press_timeout"
|
||||
android:max="2000"
|
||||
android:min="100"
|
||||
android:defaultValue="500"
|
||||
android:title="@string/long_press"
|
||||
app:adjustable="true"
|
||||
app:min="100"
|
||||
app:seekBarIncrement="5"
|
||||
app:showSeekBarValue="true"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
|
||||
if (System.getenv('IS_GOOGLE_BUILD')) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
|
Loading…
Reference in a new issue