forked from GitHub-Mirror/riotX-android
Merge branch 'develop' into feature/room_update
This commit is contained in:
@ -21,5 +21,5 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
|
||||
fun TimelineEvent.canReact(): Boolean {
|
||||
// Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
||||
return root.getClearType() == EventType.MESSAGE && sendState.isSent() && !root.isRedacted()
|
||||
return root.getClearType() == EventType.MESSAGE && root.sendState.isSent() && !root.isRedacted()
|
||||
}
|
||||
|
@ -42,6 +42,10 @@ sealed class RoomDetailActions {
|
||||
data class EnterEditMode(val eventId: String) : RoomDetailActions()
|
||||
data class EnterQuoteMode(val eventId: String) : RoomDetailActions()
|
||||
data class EnterReplyMode(val eventId: String) : RoomDetailActions()
|
||||
data class ResendMessage(val eventId: String) : RoomDetailActions()
|
||||
data class RemoveFailedEcho(val eventId: String) : RoomDetailActions()
|
||||
object ClearSendQueue : RoomDetailActions()
|
||||
object ResendAll : RoomDetailActions()
|
||||
|
||||
|
||||
}
|
@ -19,7 +19,12 @@ package im.vector.riotx.features.home.room.detail
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.replaceFragment
|
||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||
|
@ -27,9 +27,7 @@ import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.Spannable
|
||||
import android.text.TextUtils
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.*
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
@ -38,6 +36,7 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.forEach
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@ -188,6 +187,8 @@ class RoomDetailFragment :
|
||||
|
||||
override fun getLayoutResId() = R.layout.fragment_room_detail
|
||||
|
||||
override fun getMenuRes() = R.menu.menu_timeline
|
||||
|
||||
private lateinit var actionViewModel: ActionsHandler
|
||||
|
||||
@BindView(R.id.composerLayout)
|
||||
@ -271,6 +272,27 @@ class RoomDetailFragment :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
menu.forEach {
|
||||
it.isVisible = roomDetailViewModel.isMenuItemVisible(it.itemId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.clear_message_queue) {
|
||||
//This a temporary option during dev as it is not super stable
|
||||
//Cancel all pending actions in room queue and post a dummy
|
||||
//Then mark all sending events as undelivered
|
||||
roomDetailViewModel.process(RoomDetailActions.ClearSendQueue)
|
||||
return true
|
||||
}
|
||||
if (item.itemId == R.id.resend_all) {
|
||||
roomDetailViewModel.process(RoomDetailActions.ResendAll)
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun exitSpecialMode() {
|
||||
commandAutocompletePolicy.enabled = true
|
||||
composerLayout.collapse()
|
||||
@ -874,6 +896,14 @@ class RoomDetailFragment :
|
||||
showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||
|
||||
}
|
||||
MessageMenuViewModel.ACTION_RESEND -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.ResendMessage(eventId))
|
||||
}
|
||||
MessageMenuViewModel.ACTION_REMOVE -> {
|
||||
val eventId = actionData.data.toString()
|
||||
roomDetailViewModel.process(RoomDetailActions.RemoveFailedEcho(eventId))
|
||||
}
|
||||
else -> {
|
||||
Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.airbnb.mvrx.FragmentViewModelContext
|
||||
@ -32,6 +33,8 @@ import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.content.ContentAttachmentData
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.isImageMessage
|
||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.file.FileService
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
@ -43,6 +46,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.extensions.postLiveEvent
|
||||
import im.vector.riotx.core.intent.getFilenameFromUri
|
||||
import im.vector.riotx.core.platform.VectorViewModel
|
||||
@ -123,6 +127,10 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
is RoomDetailActions.DownloadFile -> handleDownloadFile(action)
|
||||
is RoomDetailActions.NavigateToEvent -> handleNavigateToEvent(action)
|
||||
is RoomDetailActions.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||
is RoomDetailActions.ResendMessage -> handleResendEvent(action)
|
||||
is RoomDetailActions.RemoveFailedEcho -> handleRemove(action)
|
||||
is RoomDetailActions.ClearSendQueue -> handleClearSendQueue()
|
||||
is RoomDetailActions.ResendAll -> handleResendAll()
|
||||
else -> Timber.e("Unhandled Action: $action")
|
||||
}
|
||||
}
|
||||
@ -186,6 +194,20 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
get() = _downloadedFileEvent
|
||||
|
||||
|
||||
fun isMenuItemVisible(@IdRes itemId: Int): Boolean {
|
||||
if (itemId == R.id.clear_message_queue) {
|
||||
//For now always disable, woker cancellation is not working properly
|
||||
return false//timeline.pendingEventCount() > 0
|
||||
}
|
||||
if (itemId == R.id.resend_all) {
|
||||
return timeline.failedToDeliverEventCount() > 0
|
||||
}
|
||||
if (itemId == R.id.clear_all) {
|
||||
return timeline.failedToDeliverEventCount() > 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSendMessage(action: RoomDetailActions.SendMessage) {
|
||||
@ -419,7 +441,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
|
||||
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
||||
if (action.event.sendState.isSent()) { //ignore pending/local events
|
||||
if (action.event.root.sendState.isSent()) { //ignore pending/local events
|
||||
displayedEventsObservable.accept(action)
|
||||
}
|
||||
//We need to update this with the related m.replace also (to move read receipt)
|
||||
@ -553,6 +575,46 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResendEvent(action: RoomDetailActions.ResendMessage) {
|
||||
val targetEventId = action.eventId
|
||||
room.getTimeLineEvent(targetEventId)?.let {
|
||||
//State must be UNDELIVERED or Failed
|
||||
if (!it.root.sendState.hasFailed()) {
|
||||
Timber.e("Cannot resend message, it is not failed, Cancel first")
|
||||
return
|
||||
}
|
||||
if (it.root.isTextMessage()) {
|
||||
room.resendTextMessage(it)
|
||||
} else if (it.root.isImageMessage()) {
|
||||
room.resendMediaMessage(it)
|
||||
} else {
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleRemove(action: RoomDetailActions.RemoveFailedEcho) {
|
||||
val targetEventId = action.eventId
|
||||
room.getTimeLineEvent(targetEventId)?.let {
|
||||
//State must be UNDELIVERED or Failed
|
||||
if (!it.root.sendState.hasFailed()) {
|
||||
Timber.e("Cannot resend message, it is not failed, Cancel first")
|
||||
return
|
||||
}
|
||||
room.deleteFailedEcho(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClearSendQueue() {
|
||||
room.clearSendingQueue()
|
||||
}
|
||||
|
||||
private fun handleResendAll() {
|
||||
room.resendAllFailedMessages()
|
||||
}
|
||||
|
||||
|
||||
private fun observeEventDisplayedActions() {
|
||||
// We are buffering scroll events for one second
|
||||
// and keep the most recent one to set the read receipt on.
|
||||
|
@ -130,8 +130,7 @@ class RoomMessageTouchHelperCallback(private val context: Context,
|
||||
|
||||
|
||||
private fun drawReplyButton(canvas: Canvas, itemView: View) {
|
||||
|
||||
Timber.v("drawReplyButton")
|
||||
//Timber.v("drawReplyButton")
|
||||
val translationX = Math.abs(itemView.translationX)
|
||||
val newTime = System.currentTimeMillis()
|
||||
val dt = Math.min(17, newTime - lastReplyButtonAnimationTime)
|
||||
|
@ -138,6 +138,19 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment() {
|
||||
}
|
||||
quickReactBottomDivider.isVisible = it.canReact()
|
||||
bottom_sheet_quick_reaction_container.isVisible = it.canReact()
|
||||
if (it.informationData.sendState.isSending()) {
|
||||
messageStatusInfo.isVisible = true
|
||||
messageStatusProgress.isVisible = true
|
||||
messageStatusText.text = getString(R.string.event_status_sending_message)
|
||||
messageStatusText.setCompoundDrawables(null, null, null, null)
|
||||
} else if (it.informationData.sendState.hasFailed()) {
|
||||
messageStatusInfo.isVisible = true
|
||||
messageStatusProgress.isVisible = false
|
||||
messageStatusText.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_warning_small, 0, 0, 0)
|
||||
messageStatusText.text = getString(R.string.unable_to_send_message)
|
||||
} else {
|
||||
messageStatusInfo.isVisible = false
|
||||
}
|
||||
return@withState
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.isTextMessage
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||
@ -75,7 +76,9 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
const val ACTION_REPLY = "reply"
|
||||
const val ACTION_SHARE = "share"
|
||||
const val ACTION_RESEND = "resend"
|
||||
const val ACTION_REMOVE = "remove"
|
||||
const val ACTION_DELETE = "delete"
|
||||
const val ACTION_CANCEL = "cancel"
|
||||
const val VIEW_SOURCE = "VIEW_SOURCE"
|
||||
const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
|
||||
const val ACTION_COPY_PERMALINK = "ACTION_COPY_PERMALINK"
|
||||
@ -110,56 +113,57 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
?: event.root.getClearContent().toModel()
|
||||
val type = messageContent?.type
|
||||
|
||||
val actions = if (!event.sendState.isSent()) {
|
||||
//Resend and Delete
|
||||
listOf<SimpleAction>(
|
||||
// SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId),
|
||||
// //TODO delete icon
|
||||
// SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
|
||||
)
|
||||
return if (event.root.sendState.hasFailed()) {
|
||||
arrayListOf<SimpleAction>().apply {
|
||||
if (canRetry(event)) {
|
||||
this.add(SimpleAction(ACTION_RESEND, R.string.global_retry, R.drawable.ic_refresh_cw, eventId))
|
||||
}
|
||||
this.add(SimpleAction(ACTION_REMOVE, R.string.remove, R.drawable.ic_trash, eventId))
|
||||
}
|
||||
} else if (event.root.sendState.isSending()) {
|
||||
//TODO is uploading attachment?
|
||||
arrayListOf<SimpleAction>().apply {
|
||||
if (canCancel(event)) {
|
||||
this.add(SimpleAction(ACTION_CANCEL, R.string.cancel, R.drawable.ic_close_round, eventId))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arrayListOf<SimpleAction>().apply {
|
||||
|
||||
if (event.sendState == SendState.SENDING) {
|
||||
//TODO add cancel?
|
||||
return@apply
|
||||
}
|
||||
//TODO is downloading attachement?
|
||||
|
||||
if (!event.root.isRedacted()) {
|
||||
|
||||
if (canReply(event, messageContent)) {
|
||||
this.add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId))
|
||||
add(SimpleAction(ACTION_REPLY, R.string.reply, R.drawable.ic_reply, eventId))
|
||||
}
|
||||
|
||||
if (canEdit(event, session.myUserId)) {
|
||||
this.add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId))
|
||||
add(SimpleAction(ACTION_EDIT, R.string.edit, R.drawable.ic_edit, eventId))
|
||||
}
|
||||
|
||||
if (canRedact(event, session.myUserId)) {
|
||||
this.add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId))
|
||||
add(SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, eventId))
|
||||
}
|
||||
|
||||
if (canCopy(type)) {
|
||||
//TODO copy images? html? see ClipBoard
|
||||
this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body))
|
||||
add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body))
|
||||
}
|
||||
|
||||
if (event.canReact()) {
|
||||
this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, eventId))
|
||||
add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, eventId))
|
||||
}
|
||||
|
||||
if (canQuote(event, messageContent)) {
|
||||
this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId))
|
||||
add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, eventId))
|
||||
}
|
||||
|
||||
if (canViewReactions(event)) {
|
||||
this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, informationData))
|
||||
add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, informationData))
|
||||
}
|
||||
|
||||
if (canShare(type)) {
|
||||
if (messageContent is MessageImageContent) {
|
||||
this.add(
|
||||
add(
|
||||
SimpleAction(ACTION_SHARE,
|
||||
R.string.share, R.drawable.ic_share,
|
||||
session.contentUrlResolver().resolveFullSize(messageContent.url))
|
||||
@ -169,7 +173,7 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
}
|
||||
|
||||
|
||||
if (event.sendState == SendState.SENT) {
|
||||
if (event.root.sendState == SendState.SENT) {
|
||||
|
||||
//TODO Can be redacted
|
||||
|
||||
@ -177,23 +181,25 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
}
|
||||
}
|
||||
|
||||
this.add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, event.root.toContentStringWithIndent()))
|
||||
add(SimpleAction(VIEW_SOURCE, R.string.view_source, R.drawable.ic_view_source, event.root.toContentStringWithIndent()))
|
||||
if (event.isEncrypted()) {
|
||||
val decryptedContent = event.root.toClearContentStringWithIndent()
|
||||
?: stringProvider.getString(R.string.encryption_information_decryption_error)
|
||||
this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))
|
||||
add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, decryptedContent))
|
||||
}
|
||||
this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
|
||||
add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, event.root.eventId))
|
||||
|
||||
if (session.myUserId != event.root.senderId && event.root.getClearType() == EventType.MESSAGE) {
|
||||
//not sent by me
|
||||
this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId))
|
||||
add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, event.root.eventId))
|
||||
}
|
||||
}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
private fun canCancel(event: TimelineEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean {
|
||||
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
||||
@ -232,6 +238,11 @@ class MessageMenuViewModel @AssistedInject constructor(@Assisted initialState: M
|
||||
return event.root.senderId == myUserId
|
||||
}
|
||||
|
||||
private fun canRetry(event: TimelineEvent): Boolean {
|
||||
return event.root.sendState.hasFailed() && event.root.isTextMessage()
|
||||
}
|
||||
|
||||
|
||||
private fun canViewReactions(event: TimelineEvent): Boolean {
|
||||
//Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
|
||||
if (event.root.getClearType() != EventType.MESSAGE) return false
|
||||
|
@ -43,7 +43,7 @@ class EncryptionItemFactory @Inject constructor(private val stringProvider: Stri
|
||||
val informationData = MessageInformationData(
|
||||
eventId = event.root.eventId ?: "?",
|
||||
senderId = event.root.senderId ?: "",
|
||||
sendState = event.sendState,
|
||||
sendState = event.root.sendState,
|
||||
avatarUrl = event.senderAvatar(),
|
||||
memberName = event.senderName(),
|
||||
showInformation = false
|
||||
|
@ -97,7 +97,7 @@ class MessageItemFactory @Inject constructor(
|
||||
val informationData = MessageInformationData(
|
||||
eventId = event.root.eventId ?: "?",
|
||||
senderId = event.root.senderId ?: "",
|
||||
sendState = event.sendState,
|
||||
sendState = event.root.sendState,
|
||||
time = "",
|
||||
avatarUrl = event.senderAvatar(),
|
||||
memberName = "",
|
||||
@ -121,7 +121,7 @@ class MessageItemFactory @Inject constructor(
|
||||
event.annotations?.editSummary,
|
||||
highlight,
|
||||
callback)
|
||||
is MessageTextContent -> buildTextMessageItem(event.sendState,
|
||||
is MessageTextContent -> buildTextMessageItem(event.root.sendState,
|
||||
messageContent,
|
||||
informationData,
|
||||
event.annotations?.editSummary,
|
||||
|
@ -37,7 +37,7 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv
|
||||
val informationData = MessageInformationData(
|
||||
eventId = event.root.eventId ?: "?",
|
||||
senderId = event.root.senderId ?: "",
|
||||
sendState = event.sendState,
|
||||
sendState = event.root.sendState,
|
||||
avatarUrl = event.senderAvatar(),
|
||||
memberName = event.senderName(),
|
||||
showInformation = false
|
||||
|
@ -76,7 +76,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
||||
val informationData = MessageInformationData(
|
||||
eventId = event.root.eventId ?: "?",
|
||||
senderId = event.root.senderId ?: "",
|
||||
sendState = event.sendState,
|
||||
sendState = event.root.sendState,
|
||||
time = "",
|
||||
avatarUrl = event.senderAvatar(),
|
||||
memberName = "",
|
||||
|
@ -162,10 +162,15 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
|
||||
return true
|
||||
}
|
||||
|
||||
protected fun renderSendState(root: View, textView: TextView?) {
|
||||
protected open fun renderSendState(root: View, textView: TextView?, failureIndicator: ImageView? = null) {
|
||||
root.isClickable = informationData.sendState.isSent()
|
||||
val state = if (informationData.hasPendingEdits) SendState.UNSENT else informationData.sendState
|
||||
textView?.setTextColor(colorProvider.getMessageTextColor(state))
|
||||
failureIndicator?.isVisible = when (informationData.sendState) {
|
||||
SendState.UNDELIVERED,
|
||||
SendState.FAILED_UNKNOWN_DEVICES -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Holder(@IdRes stubId: Int) : BaseHolder(stubId) {
|
||||
|
@ -43,14 +43,20 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||
override fun bind(holder: Holder) {
|
||||
super.bind(holder)
|
||||
imageContentRenderer.render(mediaData, ImageContentRenderer.Mode.THUMBNAIL, holder.imageView)
|
||||
contentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
|
||||
if (!informationData.sendState.hasFailed()) {
|
||||
contentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
|
||||
}
|
||||
holder.imageView.setOnClickListener(clickListener)
|
||||
holder.imageView.setOnLongClickListener(longClickListener)
|
||||
ViewCompat.setTransitionName(holder.imageView,"imagePreview_${id()}")
|
||||
holder.mediaContentView.setOnClickListener(cellClickListener)
|
||||
holder.mediaContentView.setOnLongClickListener(longClickListener)
|
||||
// The sending state color will be apply to the progress text
|
||||
renderSendState(holder.imageView, null)
|
||||
renderSendState(holder.imageView, null, holder.failedToSendIndicator)
|
||||
holder.progressLayout
|
||||
if (informationData.sendState.hasFailed()) {
|
||||
|
||||
}
|
||||
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
@ -67,6 +73,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||
val playContentView by bind<ImageView>(R.id.messageMediaPlayView)
|
||||
|
||||
val mediaContentView by bind<ViewGroup>(R.id.messageContentMedia)
|
||||
val failedToSendIndicator by bind<ImageView>(R.id.messageFailToSendIndicator)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -64,7 +64,7 @@ class MessageInformationDataFactory @Inject constructor(private val timelineDate
|
||||
return MessageInformationData(
|
||||
eventId = eventId,
|
||||
senderId = event.root.senderId ?: "",
|
||||
sendState = event.sendState,
|
||||
sendState = event.root.sendState,
|
||||
time = time,
|
||||
avatarUrl = avatarUrl,
|
||||
memberName = formattedMemberName,
|
||||
|
22
vector/src/main/res/drawable/ic_refresh_cw.xml
Normal file
22
vector/src/main/res/drawable/ic_refresh_cw.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="22dp"
|
||||
android:height="19dp"
|
||||
android:viewportWidth="22"
|
||||
android:viewportHeight="19">
|
||||
<path
|
||||
android:pathData="M21,2.741v5.333h-5.455M1,16.963V11.63h5.455"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#9E9E9E"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M3.282,7.185c0.937,-2.589 3.167,-4.527 5.907,-5.133 2.74,-0.607 5.607,0.204 7.593,2.147L21,8.074M1,11.63l4.218,3.875c1.986,1.943 4.853,2.754 7.593,2.148 2.74,-0.606 4.97,-2.545 5.907,-5.134"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#9E9E9E"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
14
vector/src/main/res/drawable/ic_trash.xml
Normal file
14
vector/src/main/res/drawable/ic_trash.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="23dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="23">
|
||||
<path
|
||||
android:pathData="M1,5.852h18M17,5.852v14a2,2 0,0 1,-2 2H5a2,2 0,0 1,-2 -2v-14m3,0v-2a2,2 0,0 1,2 -2h4a2,2 0,0 1,2 2v2M8,10.852v6M12,10.852v6"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#9E9E9E"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
14
vector/src/main/res/drawable/ic_warning_small.xml
Normal file
14
vector/src/main/res/drawable/ic_warning_small.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="14dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="14"
|
||||
android:viewportHeight="14">
|
||||
<path
|
||||
android:pathData="M7,12.852A6,6 0,1 0,7 0.852a6,6 0,0 0,0 12zM7,1.452a1.8,1.8 0,0 1,1.8 1.8L8.8,6.852a1.8,1.8 0,1 1,-3.6 0L5.2,3.252A1.8,1.8 0,0 1,7 1.452zM7,12.252a1.8,1.8 0,1 0,0 -3.6,1.8 1.8,0 0,0 0,3.6z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1.44"
|
||||
android:fillColor="#FF4B55"
|
||||
android:fillType="evenOdd"
|
||||
android:strokeColor="#FF4B55"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
@ -87,6 +87,38 @@
|
||||
tools:text="Friday 8pm" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/messageStatusInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginEnd="16dp">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/messageStatusProgress"
|
||||
style="?android:attr/progressBarStyleSmall"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageStatusText"
|
||||
android:textColor="?riotx_text_secondary"
|
||||
android:textStyle="bold"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableStart="@drawable/ic_warning_small"
|
||||
android:drawablePadding="4dp"
|
||||
tools:text="@string/unable_to_send_message" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/quickReactTopDivider"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -93,8 +93,6 @@
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollView" />
|
||||
|
||||
@ -113,7 +111,6 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:minHeight="@dimen/layout_touch_size"
|
||||
|
@ -15,7 +15,19 @@
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:layout_height="300dp" />
|
||||
tools:layout_height="300dp"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageFailToSendIndicator"
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:src="@drawable/ic_warning_small"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toEndOf="@id/messageThumbnailView"
|
||||
app:layout_constraintTop_toTopOf="@id/messageThumbnailView"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageMediaPlayView"
|
||||
|
29
vector/src/main/res/menu/menu_timeline.xml
Normal file
29
vector/src/main/res/menu/menu_timeline.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<item
|
||||
android:id="@+id/resend_all"
|
||||
android:icon="@drawable/ic_refresh_cw"
|
||||
android:title="@string/room_prompt_resend"
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/clear_all"
|
||||
android:icon="@drawable/ic_trash"
|
||||
android:title="@string/room_prompt_cancel"
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true" />
|
||||
|
||||
<item
|
||||
android:id="@+id/clear_message_queue"
|
||||
android:title="@string/clear_timeline_send_queue"
|
||||
android:visible="false"
|
||||
app:showAsAction="never"
|
||||
tools:visible="true" />
|
||||
|
||||
</menu>
|
@ -1,89 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_resend_message"
|
||||
android:icon="@drawable/ic_material_send_black"
|
||||
android:title="@string/resend"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_cancel_upload"
|
||||
android:icon="@drawable/vector_cancel_upload_download"
|
||||
android:title="@string/room_event_action_cancel_upload"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_cancel_download"
|
||||
android:icon="@drawable/vector_cancel_upload_download"
|
||||
android:title="@string/room_event_action_cancel_download"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_redact_message"
|
||||
android:icon="@drawable/ic_material_delete"
|
||||
android:title="@string/redact"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_copy"
|
||||
android:icon="@drawable/ic_material_copy"
|
||||
android:title="@string/copy"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_quote"
|
||||
android:icon="@drawable/ic_material_quote"
|
||||
android:title="@string/quote"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_share"
|
||||
android:icon="@drawable/ic_material_share"
|
||||
android:title="@string/share"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_forward"
|
||||
android:icon="@drawable/ic_material_forward"
|
||||
android:title="@string/forward"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_save"
|
||||
android:icon="@drawable/ic_material_save"
|
||||
android:title="@string/save"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_view_source"
|
||||
android:icon="@drawable/ic_material_message_black"
|
||||
android:title="@string/view_source"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_view_decrypted_source"
|
||||
android:icon="@drawable/ic_material_message_black"
|
||||
android:title="@string/view_decrypted_source"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_permalink"
|
||||
android:icon="@drawable/ic_material_link_black"
|
||||
android:title="@string/permalink"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_vector_report"
|
||||
android:icon="@drawable/ic_report_black"
|
||||
android:title="@string/report_content"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ic_action_device_verification"
|
||||
android:icon="@drawable/ic_perm_device_information_black"
|
||||
android:title="@string/device_information"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
@ -507,7 +507,7 @@
|
||||
<string name="room_unsent_messages_notification">Messages not sent. %1$s or %2$s now?</string>
|
||||
<string name="room_unknown_devices_messages_notification">Messages not sent due to unknown devices being present. %1$s or %2$s now?</string>
|
||||
<string name="room_prompt_resend">Resend all</string>
|
||||
<string name="room_prompt_cancel">cancel all</string>
|
||||
<string name="room_prompt_cancel">Cancel all</string>
|
||||
<string name="room_resend_unsent_messages">Resend unsent messages</string>
|
||||
<string name="room_delete_unsent_messages">Delete unsent messages</string>
|
||||
<string name="room_message_file_not_found">File not found</string>
|
||||
|
Reference in New Issue
Block a user