Added swipe left to right on a chat bubble to reply

This commit is contained in:
Sylvain Berfini 2023-11-22 11:34:09 +01:00
parent f35df5e418
commit 5f9edb4fcc
2 changed files with 72 additions and 60 deletions

View file

@ -76,6 +76,8 @@ import org.linphone.utils.AppUtils
import org.linphone.utils.Event
import org.linphone.utils.FileUtils
import org.linphone.utils.LinphoneUtils
import org.linphone.utils.RecyclerViewSwipeUtils
import org.linphone.utils.RecyclerViewSwipeUtilsCallback
import org.linphone.utils.addCharacterAtPosition
import org.linphone.utils.hideKeyboard
import org.linphone.utils.setKeyboardInsetListener
@ -218,6 +220,29 @@ class ConversationFragment : GenericFragment() {
binding.eventsList.setHasFixedSize(true)
binding.eventsList.layoutManager = LinearLayoutManager(requireContext())
val callbacks = RecyclerViewSwipeUtilsCallback(
R.drawable.reply,
ConversationEventAdapter.EventViewHolder::class.java
) { viewHolder ->
val index = viewHolder.bindingAdapterPosition
if (index < 0 || index >= adapter.currentList.size) {
Log.e("$TAG Swipe viewHolder index [$index] is out of bounds!")
} else {
adapter.notifyItemChanged(index)
val chatMessageEventLog = adapter.currentList[index]
val chatMessageModel = (chatMessageEventLog.model as? ChatMessageModel)
if (chatMessageModel != null) {
sendMessageViewModel.replyToMessage(chatMessageModel)
} else {
Log.e(
"$TAG Can't reply, failed to get a ChatMessageModel from adapter item #[$index]"
)
}
}
}
RecyclerViewSwipeUtils(callbacks).attachToRecyclerView(binding.eventsList)
val localSipUri = args.localSipUri
val remoteSipUri = args.remoteSipUri
Log.i(

View file

@ -21,41 +21,30 @@ package org.linphone.utils
import android.annotation.SuppressLint
import android.graphics.Canvas
import android.view.MotionEvent
import android.view.View
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_SWIPE
import androidx.recyclerview.widget.ItemTouchHelper.LEFT
import androidx.recyclerview.widget.ItemTouchHelper.RIGHT
import androidx.recyclerview.widget.RecyclerView
class RecyclerViewSwipeUtils(callbacks: RecyclerViewSwipeUtilsCallback) : ItemTouchHelper(callbacks)
class RecyclerViewSwipeUtilsCallback(val rightButton: View? = null) : ItemTouchHelper.Callback() {
companion object {
private const val TAG = "[RecyclerViewSwipeUtilsCallback]"
}
private var swipeBack: Boolean = false
private var rightButtonWidth: Int = 0
init {
if (rightButton != null) {
val widthSpec = View.MeasureSpec.makeMeasureSpec(
0,
View.MeasureSpec.UNSPECIFIED
)
val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
rightButton.measure(widthSpec, heightSpec)
rightButtonWidth = rightButton.measuredWidth
}
}
class RecyclerViewSwipeUtils(
callbacks: RecyclerViewSwipeUtilsCallback
) : ItemTouchHelper(callbacks)
class RecyclerViewSwipeUtilsCallback(
@DrawableRes private val icon: Int,
private val disableActionForViewHolderClass: Class<*>? = null,
private val onSwiped: ((viewHolder: RecyclerView.ViewHolder) -> Unit)? = null
) : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
return makeMovementFlags(0, LEFT or RIGHT)
if (disableActionForViewHolderClass?.isInstance(viewHolder) == true) {
return makeMovementFlags(0, 0)
}
return makeMovementFlags(0, RIGHT)
}
override fun onMove(
@ -66,7 +55,17 @@ class RecyclerViewSwipeUtilsCallback(val rightButton: View? = null) : ItemTouchH
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
override fun onSwiped(
viewHolder: RecyclerView.ViewHolder,
direction: Int
) {
if (direction == RIGHT) {
onSwiped?.invoke(viewHolder)
}
}
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
return .2f // Percentage of the screen width the swipe action has to reach to validate swipe move (default is .5f)
}
@SuppressLint("ClickableViewAccessibility")
@ -80,45 +79,33 @@ class RecyclerViewSwipeUtilsCallback(val rightButton: View? = null) : ItemTouchH
isCurrentlyActive: Boolean
) {
if (actionState == ACTION_STATE_SWIPE) {
recyclerView.setOnTouchListener { _, event ->
swipeBack =
event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP
val iconDrawable = ContextCompat.getDrawable(recyclerView.context, icon)
val iconWidth = iconDrawable?.intrinsicWidth ?: 0
val margin = 20
if (iconDrawable != null && dX > iconWidth + margin) {
val halfIcon = iconDrawable.intrinsicHeight / 2
val top =
viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon)
val showRightButton = (rightButtonWidth != 0 && dX < -rightButtonWidth)
val position = viewHolder.bindingAdapterPosition
val clickable = !showRightButton || swipeBack
try {
recyclerView.getChildAt(position).isClickable = clickable
} catch (e: IndexOutOfBoundsException) {
}
if (rightButton != null && showRightButton) {
val itemView = viewHolder.itemView
val left = (itemView.right - rightButton.measuredWidth)
val top = itemView.top
canvas.save()
canvas.translate(left.toFloat(), top.toFloat())
rightButton.layout(
0,
0,
rightButton.width,
rightButton.height
)
rightButton.draw(canvas)
canvas.restore()
// Icon won't move past the swipe threshold, thus indicating to the user
// it has reached the required distance for swipe action to be done
val threshold = getSwipeThreshold(viewHolder) * viewHolder.itemView.right
val left = if (dX < threshold) {
viewHolder.itemView.left + dX.toInt() - iconWidth - margin
} else {
viewHolder.itemView.left + threshold.toInt() - iconWidth - margin
}
false
iconDrawable.setBounds(
left,
top,
left + iconWidth,
top + iconDrawable.intrinsicHeight
)
iconDrawable.draw(canvas)
}
}
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
}
override fun convertToAbsoluteDirection(flags: Int, layoutDirection: Int): Int {
if (swipeBack) {
swipeBack = false
return 0
}
return super.convertToAbsoluteDirection(flags, layoutDirection)
}
}