Commit 09ebd8e3 authored by Lucio Maciel's avatar Lucio Maciel

Improve the long click to actions on messages list

parent 232da165
......@@ -6,11 +6,15 @@ import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.extensions.setVisible
import kotlinx.android.synthetic.main.message_attachment.view.*
class AudioAttachmentViewHolder(itemView: View) : BaseViewHolder<AudioAttachmentViewModel>(itemView) {
class AudioAttachmentViewHolder(itemView: View, listener: ActionsListener)
: BaseViewHolder<AudioAttachmentViewModel>(itemView, listener) {
init {
with(itemView) {
image_attachment.setVisible(false)
audio_video_attachment.setVisible(true)
setupActionMenu(attachment_container)
setupActionMenu(audio_video_attachment)
}
}
......
package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView
import android.view.MenuItem
import android.view.View
import chat.rocket.android.R
import chat.rocket.android.chatroom.ui.bottomsheet.BottomSheetMenu
import chat.rocket.android.chatroom.ui.bottomsheet.adapter.ActionListAdapter
import chat.rocket.android.chatroom.viewmodel.BaseViewModel
import chat.rocket.core.model.Message
import chat.rocket.core.model.isSystemMessage
import ru.whalemare.sheetmenu.extension.inflate
import ru.whalemare.sheetmenu.extension.toList
abstract class BaseViewHolder<T>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract class BaseViewHolder<T : BaseViewModel<*>>(
itemView: View,
private val listener: ActionsListener
) : RecyclerView.ViewHolder(itemView),
MenuItem.OnMenuItemClickListener {
var data: T? = null
init {
setupActionMenu(itemView)
}
fun bind(data: T) {
this.data = data
bindViews(data)
}
abstract fun bindViews(data: T)
interface ActionsListener {
fun isActionsEnabled(): Boolean
fun onActionSelected(item: MenuItem, message: Message)
}
val longClickListener = { view: View ->
if (data?.message?.isSystemMessage() == false) {
val menuItems = view.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_menu_msg_pin_unpin }?.apply {
val isPinned = data?.message?.pinned ?: false
setTitle(if (isPinned) R.string.action_msg_unpin else R.string.action_msg_pin)
isChecked = isPinned
}
val adapter = ActionListAdapter(menuItems, this@BaseViewHolder)
BottomSheetMenu(adapter).show(view.context)
}
true
}
internal fun setupActionMenu(view: View) {
if (listener.isActionsEnabled()) {
view.setOnLongClickListener(longClickListener)
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
data?.let {
listener.onActionSelected(item, it.message)
}
return true
}
}
\ No newline at end of file
package chat.rocket.android.chatroom.adapter
import android.support.v7.widget.RecyclerView
import android.view.MenuItem
import android.view.ViewGroup
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.viewmodel.*
import chat.rocket.android.util.extensions.inflate
import chat.rocket.core.model.Message
import timber.log.Timber
import java.security.InvalidParameterException
......@@ -26,23 +28,23 @@ class ChatRoomAdapter(
return when(viewType.toViewType()) {
BaseViewModel.ViewType.MESSAGE -> {
val view = parent.inflate(R.layout.item_message)
MessageViewHolder(view, roomName, roomType, presenter, enableActions)
MessageViewHolder(view, actionsListener)
}
BaseViewModel.ViewType.IMAGE_ATTACHMENT -> {
val view = parent.inflate(R.layout.message_attachment)
ImageAttachmentViewHolder(view)
ImageAttachmentViewHolder(view, actionsListener)
}
BaseViewModel.ViewType.AUDIO_ATTACHMENT -> {
val view = parent.inflate(R.layout.message_attachment)
AudioAttachmentViewHolder(view)
AudioAttachmentViewHolder(view, actionsListener)
}
BaseViewModel.ViewType.VIDEO_ATTACHMENT -> {
val view = parent.inflate(R.layout.message_attachment)
VideoAttachmentViewHolder(view)
VideoAttachmentViewHolder(view, actionsListener)
}
BaseViewModel.ViewType.URL_PREVIEW -> {
val view = parent.inflate(R.layout.message_url_preview)
UrlPreviewViewHolder(view)
UrlPreviewViewHolder(view, actionsListener)
}
else -> {
throw InvalidParameterException("TODO - implement for ${viewType.toViewType()}")
......@@ -108,4 +110,30 @@ class ChatRoomAdapter(
notifyItemRangeRemoved(index, oldSize - newSize)
}
}
val actionsListener = object : BaseViewHolder.ActionsListener {
override fun isActionsEnabled(): Boolean = enableActions
override fun onActionSelected(item: MenuItem, message: Message) {
message.apply {
when (item.itemId) {
R.id.action_menu_msg_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_quote -> presenter?.citeMessage(roomType, roomName, id, false)
R.id.action_menu_msg_reply -> presenter?.citeMessage(roomType, roomName, id, true)
R.id.action_menu_msg_copy -> presenter?.copyMessage(id)
R.id.action_menu_msg_edit -> presenter?.editMessage(roomId, id, message.message)
R.id.action_menu_msg_pin_unpin -> {
with(item) {
if (!isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
}
}
else -> TODO("Not implemented")
}
}
}
}
}
\ No newline at end of file
......@@ -5,7 +5,16 @@ import chat.rocket.android.chatroom.viewmodel.ImageAttachmentViewModel
import com.stfalcon.frescoimageviewer.ImageViewer
import kotlinx.android.synthetic.main.message_attachment.view.*
class ImageAttachmentViewHolder(itemView: View) : BaseViewHolder<ImageAttachmentViewModel>(itemView) {
class ImageAttachmentViewHolder(itemView: View, listener: ActionsListener)
: BaseViewHolder<ImageAttachmentViewModel>(itemView, listener) {
init {
with(itemView) {
setupActionMenu(attachment_container)
setupActionMenu(image_attachment)
}
}
override fun bindViews(data: ImageAttachmentViewModel) {
with(itemView) {
image_attachment.setImageURI(data.attachmentUrl)
......
package chat.rocket.android.chatroom.adapter
import android.text.method.LinkMovementMethod
import android.view.MenuItem
import android.view.View
import chat.rocket.android.R
import chat.rocket.android.chatroom.presentation.ChatRoomPresenter
import chat.rocket.android.chatroom.ui.bottomsheet.BottomSheetMenu
import chat.rocket.android.chatroom.ui.bottomsheet.adapter.ActionListAdapter
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import kotlinx.android.synthetic.main.avatar.view.*
import kotlinx.android.synthetic.main.item_message.view.*
import ru.whalemare.sheetmenu.extension.inflate
import ru.whalemare.sheetmenu.extension.toList
class MessageViewHolder(
itemView: View,
private val roomType: String,
private val roomName: String,
private val presenter: ChatRoomPresenter?,
enableActions: Boolean
) : BaseViewHolder<MessageViewModel>(itemView),
MenuItem.OnMenuItemClickListener {
listener: ActionsListener
) : BaseViewHolder<MessageViewModel>(itemView, listener) {
init {
itemView.text_content.movementMethod = LinkMovementMethod()
if (enableActions) {
itemView.setOnLongClickListener {
if (data?.isSystemMessage == false) {
val menuItems = it.context.inflate(R.menu.message_actions).toList()
menuItems.find { it.itemId == R.id.action_menu_msg_pin_unpin }?.apply {
val isPinned = data?.isPinned ?: false
setTitle(if (isPinned) R.string.action_msg_unpin else R.string.action_msg_pin)
isChecked = isPinned
}
val adapter = ActionListAdapter(menuItems, this@MessageViewHolder)
BottomSheetMenu(adapter).apply {
}.show(it.context)
}
true
}
// Manually add the long click listener to the text content
if (listener.isActionsEnabled()) {
itemView.text_content.setOnLongClickListener(longClickListener)
}
}
......@@ -52,27 +28,4 @@ class MessageViewHolder(
image_avatar.setImageURI(data.avatar)
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {
data?.rawData?.apply {
when (item.itemId) {
R.id.action_menu_msg_delete -> presenter?.deleteMessage(roomId, id)
R.id.action_menu_msg_quote -> presenter?.citeMessage(roomType, roomName, id, false)
R.id.action_menu_msg_reply -> presenter?.citeMessage(roomType, roomName, id, true)
R.id.action_menu_msg_copy -> presenter?.copyMessage(id)
R.id.action_menu_msg_edit -> presenter?.editMessage(roomId, id, message)
R.id.action_menu_msg_pin_unpin -> {
with(item) {
if (!isChecked) {
presenter?.pinMessage(id)
} else {
presenter?.unpinMessage(id)
}
}
}
else -> TODO("Not implemented")
}
}
return true
}
}
\ No newline at end of file
......@@ -8,7 +8,15 @@ import chat.rocket.android.util.extensions.content
import chat.rocket.android.util.extensions.setVisible
import kotlinx.android.synthetic.main.message_url_preview.view.*
class UrlPreviewViewHolder(itemView: View) : BaseViewHolder<UrlPreviewViewModel>(itemView) {
class UrlPreviewViewHolder(itemView: View, listener: ActionsListener)
: BaseViewHolder<UrlPreviewViewModel>(itemView, listener) {
init {
with(itemView) {
setupActionMenu(url_preview_layout)
}
}
override fun bindViews(data: UrlPreviewViewModel) {
with(itemView) {
if (data.thumbUrl.isNullOrEmpty()) {
......
......@@ -6,11 +6,15 @@ import chat.rocket.android.player.PlayerActivity
import chat.rocket.android.util.extensions.setVisible
import kotlinx.android.synthetic.main.message_attachment.view.*
class VideoAttachmentViewHolder(itemView: View) : BaseViewHolder<VideoAttachmentViewModel>(itemView) {
class VideoAttachmentViewHolder(itemView: View, listener: ActionsListener)
: BaseViewHolder<VideoAttachmentViewModel>(itemView, listener) {
init {
with(itemView) {
image_attachment.setVisible(false)
audio_video_attachment.setVisible(true)
setupActionMenu(attachment_container)
setupActionMenu(audio_video_attachment)
}
}
......
package chat.rocket.android.chatroom.presentation
import chat.rocket.android.chatroom.viewmodel.MessageViewModel
import chat.rocket.android.chatroom.viewmodel.ViewModelMapper
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.server.domain.GetChatRoomsInteractor
......@@ -12,6 +11,7 @@ import chat.rocket.common.RocketChatException
import chat.rocket.common.util.ifNull
import chat.rocket.core.internal.rest.getRoomPinnedMessages
import chat.rocket.core.model.Value
import chat.rocket.core.model.isSystemMessage
import timber.log.Timber
import javax.inject.Inject
......@@ -42,8 +42,7 @@ class PinnedMessagesPresenter @Inject constructor(private val view: PinnedMessag
val pinnedMessages =
client.getRoomPinnedMessages(roomId, room.type, pinnedMessagesListOffset)
pinnedMessagesListOffset = pinnedMessages.offset.toInt()
val messageList = mapper.map(pinnedMessages.result)
.filter { it is MessageViewModel}.filterNot { (it as MessageViewModel).isSystemMessage }
val messageList = mapper.map(pinnedMessages.result.filterNot { it.isSystemMessage() })
view.showPinnedMessages(messageList)
view.hideLoading()
}.ifNull {
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.AudioAttachment
data class AudioAttachmentViewModel(
override val message: Message,
override val rawData: AudioAttachment,
override val messageId: String,
override val attachmentUrl: String,
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.core.model.Message
import java.security.InvalidParameterException
interface BaseViewModel<out T> {
val message: Message
val rawData: T
val messageId: String
val viewType: Int
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.ImageAttachment
data class ImageAttachmentViewModel(
override val message: Message,
override val rawData: ImageAttachment,
override val messageId: String,
override val attachmentUrl: String,
......
......@@ -4,14 +4,14 @@ import chat.rocket.android.R
import chat.rocket.core.model.Message
data class MessageViewModel(
override val message: Message,
override val rawData: Message,
override val messageId: String,
override val avatar: String,
override val time: CharSequence,
override val senderName: CharSequence,
override val content: CharSequence,
override val isPinned: Boolean,
val isSystemMessage: Boolean
override val isPinned: Boolean
) : BaseMessageViewModel<Message> {
override val viewType: Int
get() = BaseViewModel.ViewType.MESSAGE.viewType
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.url.Url
data class UrlPreviewViewModel(
override val message: Message,
override val rawData: Url,
override val messageId: String,
val title: CharSequence?,
......
package chat.rocket.android.chatroom.viewmodel
import chat.rocket.android.R
import chat.rocket.core.model.Message
import chat.rocket.core.model.attachment.VideoAttachment
data class VideoAttachmentViewModel(
override val message: Message,
override val rawData: VideoAttachment,
override val messageId: String,
override val attachmentUrl: String,
......
......@@ -19,11 +19,13 @@ import chat.rocket.core.model.Message
import chat.rocket.core.model.MessageType
import chat.rocket.core.model.Value
import chat.rocket.core.model.attachment.*
import chat.rocket.core.model.isSystemMessage
import chat.rocket.core.model.url.Url
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
import okhttp3.HttpUrl
import timber.log.Timber
import java.security.InvalidParameterException
import javax.inject.Inject
class ViewModelMapper @Inject constructor(private val context: Context,
......@@ -81,7 +83,7 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val title = url.meta?.title
val description = url.meta?.description
return UrlPreviewViewModel(url, message.id, title, hostname, description, thumb)
return UrlPreviewViewModel(message, url, message.id, title, hostname, description, thumb)
}
private fun mapAttachment(message: Message, attachment: Attachment): BaseViewModel<*>? {
......@@ -96,12 +98,12 @@ class ViewModelMapper @Inject constructor(private val context: Context,
val attachmentTitle = attachment.title
val id = "${message.id}_${attachment.titleLink}".hashCode().toLong()
return when (attachment) {
is ImageAttachment -> ImageAttachmentViewModel(attachment, message.id, attachmentUrl,
attachmentTitle ?: "", id)
is VideoAttachment -> VideoAttachmentViewModel(attachment, message.id,
is ImageAttachment -> ImageAttachmentViewModel(message, attachment, message.id,
attachmentUrl, attachmentTitle ?: "", id)
is VideoAttachment -> VideoAttachmentViewModel(message, attachment, message.id,
attachmentUrl, attachmentTitle ?: "", id)
is AudioAttachment -> AudioAttachmentViewModel(message, attachment, message.id,
attachmentUrl, attachmentTitle ?: "", id)
is AudioAttachment -> AudioAttachmentViewModel(attachment,
message.id, attachmentUrl, attachmentTitle ?: "", id)
else -> null
}
}
......@@ -143,10 +145,9 @@ class ViewModelMapper @Inject constructor(private val context: Context,
}
val content = getContent(context, message, quote)
MessageViewModel(rawData = message, messageId = message.id,
MessageViewModel(message = message, rawData = message, messageId = message.id,
avatar = avatar!!, time = time, senderName = sender,
content = content.first, isPinned = message.pinned,
isSystemMessage = content.second)
content = content, isPinned = message.pinned)
}
private fun getSenderName(message: Message): CharSequence {
......@@ -175,24 +176,11 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return null
}
private suspend fun getContent(context: Context, message: Message, quote: Message?): Pair<CharSequence, Boolean> {
var systemMessage = true
val content = when (message.type) {
//TODO: Add implementation for Welcome type.
is MessageType.MessageRemoved -> getSystemMessage(context.getString(R.string.message_removed))
is MessageType.UserJoined -> getSystemMessage(context.getString(R.string.message_user_joined_channel))
is MessageType.UserLeft -> getSystemMessage(context.getString(R.string.message_user_left))
is MessageType.UserAdded -> getSystemMessage(context.getString(R.string.message_user_added_by, message.message, message.sender?.username))
is MessageType.RoomNameChanged -> getSystemMessage(context.getString(R.string.message_room_name_changed, message.message, message.sender?.username))
is MessageType.UserRemoved -> getSystemMessage(context.getString(R.string.message_user_removed_by, message.message, message.sender?.username))
is MessageType.MessagePinned -> getSystemMessage(context.getString(R.string.message_pinned))
else -> {
systemMessage = false
getNormalMessage(message, quote)
}
private suspend fun getContent(context: Context, message: Message, quote: Message?): CharSequence {
return when (message.isSystemMessage()) {
true -> getSystemMessage(message, context)
false -> getNormalMessage(message, quote)
}
return Pair(content, systemMessage)
}
private suspend fun getNormalMessage(message: Message, quote: Message?): CharSequence {
......@@ -204,7 +192,20 @@ class ViewModelMapper @Inject constructor(private val context: Context,
return parser.renderMarkdown(message.message, quoteViewModel, currentUsername)
}
private fun getSystemMessage(content: String): CharSequence {
private fun getSystemMessage(message: Message, context: Context): CharSequence {
val content = when (message.type) {
//TODO: Add implementation for Welcome type.
is MessageType.MessageRemoved -> context.getString(R.string.message_removed)
is MessageType.UserJoined -> context.getString(R.string.message_user_joined_channel)
is MessageType.UserLeft -> context.getString(R.string.message_user_left)
is MessageType.UserAdded -> context.getString(R.string.message_user_added_by, message.message, message.sender?.username)
is MessageType.RoomNameChanged -> context.getString(R.string.message_room_name_changed, message.message, message.sender?.username)
is MessageType.UserRemoved -> context.getString(R.string.message_user_removed_by, message.message, message.sender?.username)
is MessageType.MessagePinned -> context.getString(R.string.message_pinned)
else -> {
throw InvalidParameterException("Invalid message type: ${message.type}")
}
}
//isSystemMessage = true
val spannableMsg = SpannableStringBuilder(content)
spannableMsg.setSpan(StyleSpan(Typeface.ITALIC), 0, spannableMsg.length,
......
......@@ -5,8 +5,8 @@
android:id="@+id/attachment_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="72dp"
android:layout_marginEnd="@dimen/screen_edge_left_and_right_margins"
android:paddingStart="72dp"
android:paddingEnd="@dimen/screen_edge_left_and_right_margins"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
......
......@@ -6,8 +6,8 @@
android:id="@+id/url_preview_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="72dp"
android:layout_marginEnd="24dp">
android:paddingStart="72dp"
android:paddingEnd="24dp">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/image_preview"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment