Skip to content

Commit 635cee1

Browse files
AL-SessionalansleyThomasSession
authored
fix/prevent_button_spam_on_scroll_to_replied_message - and VisibleMessageViews in general (#983)
* WIP * Minor tidyup * Removed some blank lines * Fix typo * Tweaks --------- Co-authored-by: alansley <aclansley@gmail.com> Co-authored-by: ThomasSession <thomas.r@getsession.org>
1 parent 58e142c commit 635cee1

File tree

6 files changed

+54
-6
lines changed

6 files changed

+54
-6
lines changed

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,5 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
155155
}
156156

157157
interface QuoteViewDelegate {
158-
159158
fun cancelQuoteDraft()
160159
}

app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import android.graphics.Rect
88
import android.graphics.drawable.ColorDrawable
99
import android.os.Handler
1010
import android.os.Looper
11+
import android.os.SystemClock
1112
import android.util.AttributeSet
1213
import android.view.Gravity
1314
import android.view.HapticFeedbackConstants
@@ -123,6 +124,10 @@ class VisibleMessageView : FrameLayout {
123124
var onLongPress: (() -> Unit)? = null
124125
val messageContentView: VisibleMessageContentView get() = binding.messageContentView.root
125126

127+
// Prevent button spam
128+
val MINIMUM_DURATION_BETWEEN_CLICKS_ON_SAME_VIEW_MS = 500L
129+
var lastClickTimestampMS = 0L
130+
126131
companion object {
127132
const val swipeToReplyThreshold = 64.0f // dp
128133
const val longPressMovementThreshold = 10.0f // dp
@@ -613,15 +618,22 @@ class VisibleMessageView : FrameLayout {
613618
onLongPress?.invoke()
614619
}
615620

616-
fun onContentClick(event: MotionEvent) {
617-
binding.messageContentView.root.onContentClick(event)
618-
}
621+
private fun clickedTooFast() = (SystemClock.elapsedRealtime() - lastClickTimestampMS < MINIMUM_DURATION_BETWEEN_CLICKS_ON_SAME_VIEW_MS)
619622

623+
// Note: `onPress` is called BEFORE `onContentClick` is called, so we only filter here rather than
624+
// in both places otherwise `onContentClick` will instantly fail the button spam test.
620625
private fun onPress(event: MotionEvent) {
626+
// Don't process the press if it's too soon after the last one..
627+
if (clickedTooFast()) return
628+
629+
// ..otherwise take note of the time and process the event.
630+
lastClickTimestampMS = SystemClock.elapsedRealtime()
621631
onPress?.invoke(event)
622632
pressCallback = null
623633
}
624634

635+
fun onContentClick(event: MotionEvent) = binding.messageContentView.root.onContentClick(event)
636+
625637
private fun maybeShowUserDetails(publicKey: String, threadID: Long) {
626638
UserDetailsBottomSheet().apply {
627639
arguments = bundleOf(

app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaSendActivity.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,13 @@ class MediaSendActivity : ScreenLockActionBarActivity(), MediaPickerFolderFragme
160160
}
161161

162162
override fun onMediaSelected(media: Media) {
163-
viewModel.onSingleMediaSelected(this, media)
164-
navigateToMediaSend(recipient!!)
163+
try {
164+
viewModel.onSingleMediaSelected(this, media)
165+
navigateToMediaSend(recipient!!)
166+
} catch (e: Exception){
167+
Log.e(TAG, "Error selecting media", e)
168+
Toast.makeText(this, R.string.errorUnknown, Toast.LENGTH_LONG).show()
169+
}
165170
}
166171

167172
override fun onAddMediaClicked(bucketId: String) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.thoughtcrime.securesms.util
2+
3+
import android.os.SystemClock
4+
import android.view.View
5+
6+
// Listener class that only accepts clicks at a given interval to prevent button spam.
7+
// Note: While this cannot be used on conversation views without interfering with motion events it may still be useful.
8+
class SafeClickListener(
9+
private var minimumClickIntervalMS: Long = 500L,
10+
private val onSafeClick: (View) -> Unit
11+
) : View.OnClickListener {
12+
private var lastClickTimestampMS: Long = 0L
13+
14+
override fun onClick(v: View) {
15+
// Ignore any follow-up clicks if the minimum interval has not passed
16+
if (SystemClock.elapsedRealtime() - lastClickTimestampMS < minimumClickIntervalMS) return
17+
18+
lastClickTimestampMS = SystemClock.elapsedRealtime()
19+
onSafeClick(v)
20+
}
21+
}

app/src/main/java/org/thoughtcrime/securesms/util/ViewUtilities.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,13 @@ fun EditText.addTextChangedListener(listener: (String) -> Unit) {
121121
}
122122
})
123123
}
124+
125+
// Listener class that only accepts clicks at given interval to prevent button spam - can be used instead
126+
// of a standard `onClickListener` in many places. A separate mechanism exists for VisibleMessageViews to
127+
// prevent interfering with gestures.
128+
fun View.setSafeOnClickListener(clickIntervalMS: Long = 1000L, onSafeClick: (View) -> Unit) {
129+
val safeClickListener = SafeClickListener(minimumClickIntervalMS = clickIntervalMS) {
130+
onSafeClick(it)
131+
}
132+
setOnClickListener(safeClickListener)
133+
}

libsession/src/main/java/org/session/libsession/messaging/messages/Message.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ abstract class Message {
1717
var recipient: String? = null
1818
var sender: String? = null
1919
var isSenderSelf: Boolean = false
20+
2021
var groupPublicKey: String? = null
2122
var openGroupServerMessageID: Long? = null
2223
var serverHash: String? = null

0 commit comments

Comments
 (0)