Added swipe actions on chat message (reply / delete), improved chat message redraw condition
This commit is contained in:
parent
0378848f10
commit
9963381419
10 changed files with 135 additions and 75 deletions
|
@ -14,6 +14,7 @@ Group changes to describe their impact on the project, as follows:
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Reply to chat message feature (with original message preview)
|
- Reply to chat message feature (with original message preview)
|
||||||
|
- Swipe action on chat messages to reply / delete
|
||||||
- Voice recordings in chat feature
|
- Voice recordings in chat feature
|
||||||
- Allow video recording in chat file sharing
|
- Allow video recording in chat file sharing
|
||||||
- Unread messages indicator in chat conversation that separates read & unread messages
|
- Unread messages indicator in chat conversation that separates read & unread messages
|
||||||
|
|
|
@ -452,7 +452,11 @@ private class ChatMessageDiffCallback : DiffUtil.ItemCallback<EventLogData>() {
|
||||||
newItem: EventLogData
|
newItem: EventLogData
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
return if (newItem.eventLog.type == EventLog.Type.ConferenceChatMessage) {
|
||||||
newItem.eventLog.chatMessage?.state == ChatMessage.State.Displayed
|
val oldData = (oldItem.data as ChatMessageData)
|
||||||
|
val newData = (newItem.data as ChatMessageData)
|
||||||
|
val previous = oldData.hasPreviousMessage == newData.hasPreviousMessage
|
||||||
|
val next = oldData.hasNextMessage == newData.hasNextMessage
|
||||||
|
newItem.eventLog.chatMessage?.state == ChatMessage.State.Displayed && previous && next
|
||||||
} else true
|
} else true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,9 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
||||||
|
|
||||||
val replyData = MutableLiveData<ChatMessageData>()
|
val replyData = MutableLiveData<ChatMessageData>()
|
||||||
|
|
||||||
|
var hasPreviousMessage = false
|
||||||
|
var hasNextMessage = false
|
||||||
|
|
||||||
private var countDownTimer: CountDownTimer? = null
|
private var countDownTimer: CountDownTimer? = null
|
||||||
|
|
||||||
private val listener = object : ChatMessageListenerStub() {
|
private val listener = object : ChatMessageListenerStub() {
|
||||||
|
@ -106,6 +109,11 @@ class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateBubbleBackground(hasPrevious: Boolean, hasNext: Boolean) {
|
fun updateBubbleBackground(hasPrevious: Boolean, hasNext: Boolean) {
|
||||||
|
hasPreviousMessage = hasPrevious
|
||||||
|
hasNextMessage = hasNext
|
||||||
|
hideTime.value = false
|
||||||
|
hideAvatar.value = false
|
||||||
|
|
||||||
if (hasPrevious) {
|
if (hasPrevious) {
|
||||||
hideTime.value = true
|
hideTime.value = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import android.provider.MediaStore
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||||
import android.widget.PopupWindow
|
import android.widget.PopupWindow
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.view.doOnPreDraw
|
import androidx.core.view.doOnPreDraw
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
|
@ -226,32 +227,48 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (corePreferences.allowSwipeActionOnChatMessage) {
|
// Swipe action
|
||||||
// Swipe action
|
val swipeConfiguration = RecyclerViewSwipeConfiguration()
|
||||||
val swipeConfiguration = RecyclerViewSwipeConfiguration()
|
// Reply action can only be done on a ChatMessageEventLog
|
||||||
swipeConfiguration.leftToRightAction = RecyclerViewSwipeConfiguration.Action(
|
swipeConfiguration.leftToRightAction = RecyclerViewSwipeConfiguration.Action(
|
||||||
icon = R.drawable.menu_reply,
|
text = requireContext().getString(R.string.chat_message_context_menu_reply),
|
||||||
preventFor = ChatMessagesListAdapter.EventViewHolder::class.java
|
backgroundColor = ContextCompat.getColor(requireContext(), R.color.light_grey_color),
|
||||||
)
|
preventFor = ChatMessagesListAdapter.EventViewHolder::class.java
|
||||||
val swipeListener = object : RecyclerViewSwipeListener {
|
)
|
||||||
override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) {
|
// Delete action can be done on any EventLog
|
||||||
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
swipeConfiguration.rightToLeftAction = RecyclerViewSwipeConfiguration.Action(
|
||||||
|
text = requireContext().getString(R.string.chat_message_context_menu_delete),
|
||||||
|
backgroundColor = ContextCompat.getColor(requireContext(), R.color.red_color)
|
||||||
|
)
|
||||||
|
val swipeListener = object : RecyclerViewSwipeListener {
|
||||||
|
override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
adapter.notifyItemChanged(viewHolder.bindingAdapterPosition)
|
||||||
|
|
||||||
val chatMessageEventLog = adapter.currentList[viewHolder.bindingAdapterPosition]
|
val chatMessageEventLog = adapter.currentList[viewHolder.bindingAdapterPosition]
|
||||||
val chatMessage = chatMessageEventLog.eventLog.chatMessage
|
val chatMessage = chatMessageEventLog.eventLog.chatMessage
|
||||||
if (chatMessage != null) {
|
if (chatMessage != null) {
|
||||||
chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy()
|
chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy()
|
||||||
chatSendingViewModel.pendingChatMessageToReplyTo.value =
|
chatSendingViewModel.pendingChatMessageToReplyTo.value =
|
||||||
ChatMessageData(chatMessage)
|
ChatMessageData(chatMessage)
|
||||||
chatSendingViewModel.isPendingAnswer.value = true
|
chatSendingViewModel.isPendingAnswer.value = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) {
|
||||||
|
val position = viewHolder.bindingAdapterPosition
|
||||||
|
val eventLog = adapter.currentList[position]
|
||||||
|
val chatMessage = eventLog.eventLog.chatMessage
|
||||||
|
if (chatMessage != null) {
|
||||||
|
Log.i("[Chat Room] Deleting message $chatMessage at position $position")
|
||||||
|
listViewModel.deleteMessage(chatMessage)
|
||||||
|
} else {
|
||||||
|
Log.i("[Chat Room] Deleting event $eventLog at position $position")
|
||||||
|
listViewModel.deleteEventLogs(arrayListOf(eventLog))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) {}
|
|
||||||
}
|
}
|
||||||
RecyclerViewSwipeUtils(ItemTouchHelper.RIGHT, swipeConfiguration, swipeListener)
|
|
||||||
.attachToRecyclerView(binding.chatMessagesList)
|
|
||||||
}
|
}
|
||||||
|
RecyclerViewSwipeUtils(ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT, swipeConfiguration, swipeListener)
|
||||||
|
.attachToRecyclerView(binding.chatMessagesList)
|
||||||
|
|
||||||
val chatScrollListener = object : ChatScrollListener(layoutManager) {
|
val chatScrollListener = object : ChatScrollListener(layoutManager) {
|
||||||
override fun onLoadMore(totalItemsCount: Int) {
|
override fun onLoadMore(totalItemsCount: Int) {
|
||||||
|
|
|
@ -158,27 +158,21 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteMessage(chatMessage: ChatMessage) {
|
fun deleteMessage(chatMessage: ChatMessage) {
|
||||||
val position: Int = chatMessage.userData as Int
|
|
||||||
LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage)
|
LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage)
|
||||||
chatRoom.deleteMessage(chatMessage)
|
chatRoom.deleteMessage(chatMessage)
|
||||||
|
|
||||||
val list = arrayListOf<EventLogData>()
|
events.value.orEmpty().forEach(EventLogData::destroy)
|
||||||
list.addAll(events.value.orEmpty())
|
events.value = getEvents()
|
||||||
list.removeAt(position)
|
|
||||||
events.value = list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteEventLogs(listToDelete: ArrayList<EventLogData>) {
|
fun deleteEventLogs(listToDelete: ArrayList<EventLogData>) {
|
||||||
val list = arrayListOf<EventLogData>()
|
|
||||||
list.addAll(events.value.orEmpty())
|
|
||||||
|
|
||||||
for (eventLog in listToDelete) {
|
for (eventLog in listToDelete) {
|
||||||
LinphoneUtils.deleteFilesAttachedToEventLog(eventLog.eventLog)
|
LinphoneUtils.deleteFilesAttachedToEventLog(eventLog.eventLog)
|
||||||
eventLog.eventLog.deleteFromDatabase()
|
eventLog.eventLog.deleteFromDatabase()
|
||||||
list.remove(eventLog)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
events.value = list
|
events.value.orEmpty().forEach(EventLogData::destroy)
|
||||||
|
events.value = getEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadMoreData(totalItemsCount: Int) {
|
fun loadMoreData(totalItemsCount: Int) {
|
||||||
|
@ -248,6 +242,8 @@ class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() {
|
||||||
LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage)
|
LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage)
|
||||||
chatRoom.deleteMessage(chatMessage)
|
chatRoom.deleteMessage(chatMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
events.value.orEmpty().forEach(EventLogData::destroy)
|
||||||
events.value = getEvents()
|
events.value = getEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
||||||
override fun onContactsUpdated() {
|
override fun onContactsUpdated() {
|
||||||
Log.i("[Chat Room] Contacts have changed")
|
Log.i("[Chat Room] Contacts have changed")
|
||||||
contactLookup()
|
contactLookup()
|
||||||
lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory)
|
updateLastMessageToDisplay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
||||||
|
|
||||||
override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) {
|
override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||||
Log.i("[Chat Room] Ephemeral message deleted, updated last message displayed")
|
Log.i("[Chat Room] Ephemeral message deleted, updated last message displayed")
|
||||||
lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory)
|
updateLastMessageToDisplay()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) {
|
override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) {
|
||||||
|
@ -226,7 +226,7 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
||||||
|
|
||||||
contactLookup()
|
contactLookup()
|
||||||
updateParticipants()
|
updateParticipants()
|
||||||
lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory)
|
updateLastMessageToDisplay()
|
||||||
|
|
||||||
callInProgress.value = chatRoom.core.callsNb > 0
|
callInProgress.value = chatRoom.core.callsNb > 0
|
||||||
updateRemotesComposing()
|
updateRemotesComposing()
|
||||||
|
@ -276,6 +276,10 @@ class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateLastMessageToDisplay() {
|
||||||
|
lastMessageText.value = formatLastMessage(chatRoom.lastMessageInHistory)
|
||||||
|
}
|
||||||
|
|
||||||
private fun formatLastMessage(msg: ChatMessage?): String {
|
private fun formatLastMessage(msg: ChatMessage?): String {
|
||||||
if (msg == null) return ""
|
if (msg == null) return ""
|
||||||
|
|
||||||
|
|
|
@ -465,9 +465,6 @@ class CorePreferences constructor(private val context: Context) {
|
||||||
val showAllRingtones: Boolean
|
val showAllRingtones: Boolean
|
||||||
get() = config.getBool("app", "show_all_available_ringtones", false)
|
get() = config.getBool("app", "show_all_available_ringtones", false)
|
||||||
|
|
||||||
val allowSwipeActionOnChatMessage: Boolean
|
|
||||||
get() = config.getBool("app", "swipe_action_on_chat_messages", false)
|
|
||||||
|
|
||||||
/* Default values related */
|
/* Default values related */
|
||||||
|
|
||||||
val echoCancellerCalibration: Int
|
val echoCancellerCalibration: Int
|
||||||
|
|
|
@ -84,26 +84,38 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
background.draw(canvas)
|
background.draw(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
val iconHorizontalMargin: Int = TypedValue.applyDimension(
|
val horizontalMargin: Int = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
configuration.iconMargin,
|
configuration.iconMargin,
|
||||||
recyclerView.context.resources.displayMetrics
|
recyclerView.context.resources.displayMetrics
|
||||||
).toInt()
|
).toInt()
|
||||||
var iconSize = 0
|
var iconWidth = 0
|
||||||
|
|
||||||
if (configuration.leftToRightAction.icon != 0 && dX > iconHorizontalMargin) {
|
if (configuration.leftToRightAction.icon != 0) {
|
||||||
val icon =
|
val icon =
|
||||||
ContextCompat.getDrawable(recyclerView.context, configuration.leftToRightAction.icon)
|
ContextCompat.getDrawable(
|
||||||
if (icon != null) {
|
recyclerView.context,
|
||||||
iconSize = icon.intrinsicHeight
|
configuration.leftToRightAction.icon
|
||||||
val halfIcon = iconSize / 2
|
)
|
||||||
|
iconWidth = icon?.intrinsicWidth ?: 0
|
||||||
|
if (icon != null && dX > iconWidth) {
|
||||||
|
val halfIcon = icon.intrinsicHeight / 2
|
||||||
val top =
|
val top =
|
||||||
viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon)
|
viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
} else {
|
||||||
|
viewHolder.itemView.left + threshold.toInt() - iconWidth
|
||||||
|
}
|
||||||
|
|
||||||
icon.setBounds(
|
icon.setBounds(
|
||||||
viewHolder.itemView.left + iconHorizontalMargin,
|
left,
|
||||||
top,
|
top,
|
||||||
viewHolder.itemView.left + iconHorizontalMargin + icon.intrinsicWidth,
|
left + iconWidth,
|
||||||
top + icon.intrinsicHeight
|
top + icon.intrinsicHeight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -116,7 +128,7 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configuration.leftToRightAction.text.isNotEmpty() && dX > iconHorizontalMargin + iconSize) {
|
if (configuration.leftToRightAction.text.isNotEmpty() && dX > horizontalMargin + iconWidth) {
|
||||||
val textPaint = TextPaint()
|
val textPaint = TextPaint()
|
||||||
textPaint.isAntiAlias = true
|
textPaint.isAntiAlias = true
|
||||||
textPaint.textSize = TypedValue.applyDimension(
|
textPaint.textSize = TypedValue.applyDimension(
|
||||||
|
@ -127,9 +139,9 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
textPaint.color = configuration.leftToRightAction.textColor
|
textPaint.color = configuration.leftToRightAction.textColor
|
||||||
textPaint.typeface = configuration.actionTextFont
|
textPaint.typeface = configuration.actionTextFont
|
||||||
|
|
||||||
val margin = if (iconSize > 0) iconHorizontalMargin / 2 else 0
|
val margin = if (iconWidth > 0) horizontalMargin / 2 else 0
|
||||||
val textX =
|
val textX =
|
||||||
(viewHolder.itemView.left + iconHorizontalMargin + iconSize + margin).toFloat()
|
(viewHolder.itemView.left + horizontalMargin + iconWidth + margin).toFloat()
|
||||||
val textY =
|
val textY =
|
||||||
(viewHolder.itemView.top + (viewHolder.itemView.bottom - viewHolder.itemView.top) / 2.0 + textPaint.textSize / 2).toFloat()
|
(viewHolder.itemView.top + (viewHolder.itemView.bottom - viewHolder.itemView.top) / 2.0 + textPaint.textSize / 2).toFloat()
|
||||||
canvas.drawText(
|
canvas.drawText(
|
||||||
|
@ -158,30 +170,44 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
background.draw(canvas)
|
background.draw(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
val iconHorizontalMargin: Int = TypedValue.applyDimension(
|
val horizontalMargin: Int = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
configuration.iconMargin,
|
configuration.iconMargin,
|
||||||
recyclerView.context.resources.displayMetrics
|
recyclerView.context.resources.displayMetrics
|
||||||
).toInt()
|
).toInt()
|
||||||
var iconSize = 0
|
var iconWidth = 0
|
||||||
var imageLeftBorder = viewHolder.itemView.right
|
var imageLeftBorder = viewHolder.itemView.right
|
||||||
|
|
||||||
if (configuration.rightToLeftAction.icon != 0 && dX < -iconHorizontalMargin) {
|
if (configuration.rightToLeftAction.icon != 0) {
|
||||||
val icon =
|
val icon =
|
||||||
ContextCompat.getDrawable(recyclerView.context, configuration.rightToLeftAction.icon)
|
ContextCompat.getDrawable(
|
||||||
if (icon != null) {
|
recyclerView.context,
|
||||||
iconSize = icon.intrinsicHeight
|
configuration.rightToLeftAction.icon
|
||||||
val halfIcon = iconSize / 2
|
)
|
||||||
|
iconWidth = icon?.intrinsicWidth ?: 0
|
||||||
|
if (icon != null && dX < viewHolder.itemView.right - iconWidth) {
|
||||||
|
val halfIcon = icon.intrinsicHeight / 2
|
||||||
val top =
|
val top =
|
||||||
viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon)
|
viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon)
|
||||||
imageLeftBorder =
|
|
||||||
viewHolder.itemView.right - iconHorizontalMargin - halfIcon * 2
|
// 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 right = if (dX > threshold) {
|
||||||
|
viewHolder.itemView.right + dX.toInt()
|
||||||
|
} else {
|
||||||
|
viewHolder.itemView.right + threshold.toInt()
|
||||||
|
}
|
||||||
|
imageLeftBorder = right - icon.intrinsicWidth
|
||||||
|
|
||||||
icon.setBounds(
|
icon.setBounds(
|
||||||
imageLeftBorder,
|
imageLeftBorder,
|
||||||
top,
|
top,
|
||||||
viewHolder.itemView.right - iconHorizontalMargin,
|
right,
|
||||||
top + icon.intrinsicHeight
|
top + icon.intrinsicHeight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
if (configuration.rightToLeftAction.iconTint != 0) icon.setColorFilter(
|
if (configuration.rightToLeftAction.iconTint != 0) icon.setColorFilter(
|
||||||
configuration.rightToLeftAction.iconTint,
|
configuration.rightToLeftAction.iconTint,
|
||||||
PorterDuff.Mode.SRC_IN
|
PorterDuff.Mode.SRC_IN
|
||||||
|
@ -189,7 +215,8 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
icon.draw(canvas)
|
icon.draw(canvas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (configuration.rightToLeftAction.text.isNotEmpty() && dX < -iconHorizontalMargin - iconSize) {
|
|
||||||
|
if (configuration.rightToLeftAction.text.isNotEmpty() && dX < -horizontalMargin - iconWidth) {
|
||||||
val textPaint = TextPaint()
|
val textPaint = TextPaint()
|
||||||
textPaint.isAntiAlias = true
|
textPaint.isAntiAlias = true
|
||||||
textPaint.textSize = TypedValue.applyDimension(
|
textPaint.textSize = TypedValue.applyDimension(
|
||||||
|
@ -201,7 +228,7 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
textPaint.typeface = configuration.actionTextFont
|
textPaint.typeface = configuration.actionTextFont
|
||||||
|
|
||||||
val margin =
|
val margin =
|
||||||
if (imageLeftBorder == viewHolder.itemView.right) iconHorizontalMargin else iconHorizontalMargin / 2
|
if (imageLeftBorder == viewHolder.itemView.right) horizontalMargin else horizontalMargin / 2
|
||||||
val textX =
|
val textX =
|
||||||
imageLeftBorder - textPaint.measureText(configuration.rightToLeftAction.text) - margin
|
imageLeftBorder - textPaint.measureText(configuration.rightToLeftAction.text) - margin
|
||||||
val textY =
|
val textY =
|
||||||
|
@ -239,6 +266,8 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
viewHolder: RecyclerView.ViewHolder
|
viewHolder: RecyclerView.ViewHolder
|
||||||
): Int {
|
): Int {
|
||||||
|
// Prevent swipe actions for a specific ViewHolder class if needed
|
||||||
|
// Used to allow swipe actions on chat messages but not events
|
||||||
var dirFlags = direction
|
var dirFlags = direction
|
||||||
if (direction and ItemTouchHelper.RIGHT != 0) {
|
if (direction and ItemTouchHelper.RIGHT != 0) {
|
||||||
val classToPrevent = configuration.leftToRightAction.preventFor
|
val classToPrevent = configuration.leftToRightAction.preventFor
|
||||||
|
@ -278,6 +307,10 @@ private class RecyclerViewSwipeUtilsCallback(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float {
|
||||||
|
return .33f // A third of the screen is required to validate swipe move (default is .5f)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onChildDraw(
|
override fun onChildDraw(
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
|
|
|
@ -37,27 +37,27 @@
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="35dp"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:layout_marginBottom="5dp"
|
android:layout_marginBottom="5dp"
|
||||||
android:layout_toLeftOf="@id/select"
|
android:layout_toLeftOf="@id/select"
|
||||||
android:gravity="center"
|
|
||||||
android:background="@{data.security || data.groupLeft ? @drawable/event_decoration_red : @drawable/event_decoration_gray, default=@drawable/event_decoration_gray}"
|
android:background="@{data.security || data.groupLeft ? @drawable/event_decoration_red : @drawable/event_decoration_gray, default=@drawable/event_decoration_gray}"
|
||||||
|
android:gravity="center"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<!-- Ugly hack to prevent last character to be missing half of it, don't know why yet -->
|
<!-- Ugly hack to prevent last character to be missing half of it, don't know why yet -->
|
||||||
<TextView
|
<TextView
|
||||||
android:text="@{data.text + ' '}"
|
|
||||||
android:textSize="13sp"
|
|
||||||
android:fontFamily="sans-serif"
|
|
||||||
android:lineSpacingExtra="0sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:textColor="@{data.security || data.groupLeft ? @color/red_color : @color/light_grey_color}"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingRight="5dp"
|
android:background="?attr/backgroundColor"
|
||||||
|
android:fontFamily="sans-serif"
|
||||||
|
android:lineSpacingExtra="0sp"
|
||||||
android:paddingLeft="5dp"
|
android:paddingLeft="5dp"
|
||||||
android:background="?attr/backgroundColor" />
|
android:paddingRight="5dp"
|
||||||
|
android:text="@{data.text + ' '}"
|
||||||
|
android:textColor="@{data.security || data.groupLeft ? @color/red_color : @color/light_grey_color}"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="italic" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -282,12 +282,12 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_above="@id/footer"
|
android:layout_above="@id/footer"
|
||||||
android:layout_below="@+id/top_bar"
|
android:layout_below="@+id/top_bar"
|
||||||
android:paddingBottom="20dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:cacheColorHint="@color/transparent_color"
|
android:cacheColorHint="@color/transparent_color"
|
||||||
android:choiceMode="multipleChoice"
|
android:choiceMode="multipleChoice"
|
||||||
|
android:clipToPadding="false"
|
||||||
android:divider="@android:color/transparent"
|
android:divider="@android:color/transparent"
|
||||||
android:listSelector="@color/transparent_color"
|
android:listSelector="@color/transparent_color"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
android:transcriptMode="normal" />
|
android:transcriptMode="normal" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
Loading…
Reference in a new issue