Add customizable long press

This commit is contained in:
Lucas Lima 2020-08-17 09:56:29 -03:00
parent 72f81966df
commit 47ee21afc2
No known key found for this signature in database
GPG key ID: C5EEF4C30BFBF8D7
10 changed files with 120 additions and 82 deletions

View file

@ -14,4 +14,4 @@ class AboutViewModelTest : IntentViewModelTest() {
assertTrue(state.licenses.isNotEmpty())
assertTrue(state.translators.isNotEmpty())
}
}
}

View file

@ -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

View file

@ -146,4 +146,4 @@ class ThemeViewModelTest : IntentViewModelTest() {
verify { themeRepository.setTheme(darkTheme) }
}
}
}

View file

@ -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'
}
}
}

View file

@ -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
}
}

View file

@ -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) {

View file

@ -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

View file

@ -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"
}
}

View file

@ -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>

View file

@ -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