diff --git a/app/src/test/java/dev/lucasnlm/antimine/about/AboutViewModelTest.kt b/app/src/test/java/dev/lucasnlm/antimine/about/AboutViewModelTest.kt index cc2f1bd7..4901ef38 100644 --- a/app/src/test/java/dev/lucasnlm/antimine/about/AboutViewModelTest.kt +++ b/app/src/test/java/dev/lucasnlm/antimine/about/AboutViewModelTest.kt @@ -14,4 +14,4 @@ class AboutViewModelTest : IntentViewModelTest() { assertTrue(state.licenses.isNotEmpty()) assertTrue(state.translators.isNotEmpty()) } -} \ No newline at end of file +} diff --git a/app/src/test/java/dev/lucasnlm/antimine/history/HistoryViewModelTest.kt b/app/src/test/java/dev/lucasnlm/antimine/history/HistoryViewModelTest.kt index 8b7d3383..31fc1bbd 100644 --- a/app/src/test/java/dev/lucasnlm/antimine/history/HistoryViewModelTest.kt +++ b/app/src/test/java/dev/lucasnlm/antimine/history/HistoryViewModelTest.kt @@ -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 diff --git a/app/src/test/java/dev/lucasnlm/antimine/theme/ThemeViewModelTest.kt b/app/src/test/java/dev/lucasnlm/antimine/theme/ThemeViewModelTest.kt index b9fe098a..b2561fbe 100644 --- a/app/src/test/java/dev/lucasnlm/antimine/theme/ThemeViewModelTest.kt +++ b/app/src/test/java/dev/lucasnlm/antimine/theme/ThemeViewModelTest.kt @@ -146,4 +146,4 @@ class ThemeViewModelTest : IntentViewModelTest() { verify { themeRepository.setTheme(darkTheme) } } -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index 6e2cb297..fd58c78d 100644 --- a/build.gradle +++ b/build.gradle @@ -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' + } } } diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaAdapter.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaAdapter.kt index df1a6992..38709908 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaAdapter.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaAdapter.kt @@ -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 } } diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaView.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaView.kt index 20f96f77..05943a3c 100755 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaView.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/view/AreaView.kt @@ -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) { diff --git a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt index 5b75499b..88ce081d 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/common/level/viewmodel/GameViewModel.kt @@ -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 diff --git a/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt b/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt index 25059182..39f80ff8 100644 --- a/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt +++ b/common/src/main/java/dev/lucasnlm/antimine/core/preferences/PreferencesRepository.kt @@ -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" } } diff --git a/common/src/main/res/xml/preferences.xml b/common/src/main/res/xml/preferences.xml index ad49abbd..0e281cc0 100644 --- a/common/src/main/res/xml/preferences.xml +++ b/common/src/main/res/xml/preferences.xml @@ -65,6 +65,18 @@ app:showSeekBarValue="true" app:iconSpaceReserved="false" /> + + diff --git a/proprietary/build.gradle b/proprietary/build.gradle index 1dee3639..a40e2150 100644 --- a/proprietary/build.gradle +++ b/proprietary/build.gradle @@ -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