Merge pull request #891 from vector-im/feature/event-unknown

Feature/event unknown
This commit is contained in:
Benoit Marty 2020-01-28 17:30:02 +01:00 committed by GitHub
commit e12de3fba0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 133 additions and 67 deletions

View file

@ -46,17 +46,14 @@ class MessageActionsEpoxyController @Inject constructor(
override fun buildModels(state: MessageActionState) { override fun buildModels(state: MessageActionState) {
// Message preview // Message preview
val body = state.messageBody bottomSheetMessagePreviewItem {
if (body != null) { id("preview")
bottomSheetMessagePreviewItem { avatarRenderer(avatarRenderer)
id("preview") matrixItem(state.informationData.matrixItem)
avatarRenderer(avatarRenderer) movementMethod(createLinkMovementMethod(listener))
matrixItem(state.informationData.matrixItem) userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
movementMethod(createLinkMovementMethod(listener)) body(state.messageBody.linkify(listener))
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } time(state.time())
body(body.linkify(listener))
time(state.time())
}
} }
// Send state // Send state

View file

@ -15,7 +15,12 @@
*/ */
package im.vector.riotx.features.home.room.detail.timeline.action package im.vector.riotx.features.home.room.detail.timeline.action
import com.airbnb.mvrx.* import com.airbnb.mvrx.Async
import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import dagger.Lazy import dagger.Lazy
@ -45,7 +50,8 @@ import im.vector.riotx.features.html.VectorHtmlCompressor
import im.vector.riotx.features.reactions.data.EmojiDataSource import im.vector.riotx.features.reactions.data.EmojiDataSource
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Date
import java.util.Locale
/** /**
* Quick reactions state * Quick reactions state
@ -60,7 +66,7 @@ data class MessageActionState(
val eventId: String, val eventId: String,
val informationData: MessageInformationData, val informationData: MessageInformationData,
val timelineEvent: Async<TimelineEvent> = Uninitialized, val timelineEvent: Async<TimelineEvent> = Uninitialized,
val messageBody: CharSequence? = null, val messageBody: CharSequence = "",
// For quick reactions // For quick reactions
val quickStates: Async<List<ToggleState>> = Uninitialized, val quickStates: Async<List<ToggleState>> = Uninitialized,
// For actions // For actions
@ -155,13 +161,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
private fun observeTimelineEventState() { private fun observeTimelineEventState() {
asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent -> asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent ->
val computedMessage = computeMessageBody(timelineEvent) setState {
val actions = actionsForEvent(timelineEvent) copy(
setState { copy(messageBody = computedMessage, actions = actions) } messageBody = computeMessageBody(timelineEvent),
actions = actionsForEvent(timelineEvent)
)
}
} }
} }
private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? { private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence {
return when (timelineEvent.root.getClearType()) { return when (timelineEvent.root.getClearType()) {
EventType.MESSAGE, EventType.MESSAGE,
EventType.STICKER -> { EventType.STICKER -> {
@ -189,7 +198,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
noticeEventFormatter.format(timelineEvent) noticeEventFormatter.format(timelineEvent)
} }
else -> null else -> null
} } ?: ""
} }
private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> { private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> {

View file

@ -16,10 +16,13 @@
package im.vector.riotx.features.home.room.detail.timeline.factory package im.vector.riotx.features.home.room.detail.timeline.factory
import android.view.View
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_ import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_
@ -28,20 +31,26 @@ import javax.inject.Inject
class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider, class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider,
private val avatarRenderer: AvatarRenderer, private val avatarRenderer: AvatarRenderer,
private val stringProvider: StringProvider,
private val informationDataFactory: MessageInformationDataFactory) { private val informationDataFactory: MessageInformationDataFactory) {
fun create(text: String, fun create(text: String,
informationData: MessageInformationData, informationData: MessageInformationData,
highlight: Boolean, highlight: Boolean,
callback: TimelineEventController.Callback?): DefaultItem { callback: TimelineEventController.Callback?): DefaultItem {
val attributes = DefaultItem.Attributes(
avatarRenderer = avatarRenderer,
informationData = informationData,
text = text,
itemLongClickListener = View.OnLongClickListener { view ->
callback?.onEventLongClicked(informationData, null, view) ?: false
},
readReceiptsCallback = callback
)
return DefaultItem_() return DefaultItem_()
.leftGuideline(avatarSizeProvider.leftGuideline) .leftGuideline(avatarSizeProvider.leftGuideline)
.highlighted(highlight) .highlighted(highlight)
.text(text) .attributes(attributes)
.avatarRenderer(avatarRenderer)
.informationData(informationData)
.baseCallback(callback)
.readReceiptsCallback(callback)
} }
fun create(event: TimelineEvent, fun create(event: TimelineEvent,
@ -49,9 +58,9 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava
callback: TimelineEventController.Callback?, callback: TimelineEventController.Callback?,
throwable: Throwable? = null): DefaultItem { throwable: Throwable? = null): DefaultItem {
val text = if (throwable == null) { val text = if (throwable == null) {
"${event.root.getClearType()} events are not yet handled" stringProvider.getString(R.string.rendering_event_error_type_of_event_not_handled, event.root.getClearType())
} else { } else {
"an exception occurred when rendering the event ${event.root.eventId}" stringProvider.getString(R.string.rendering_event_error_exception, event.root.eventId)
} }
val informationData = informationDataFactory.create(event, null) val informationData = informationDataFactory.create(event, null)
return create(text, informationData, highlight, callback) return create(text, informationData, highlight, callback)

View file

@ -26,7 +26,16 @@ import android.view.View
import dagger.Lazy import dagger.Lazy
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.* import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageInfoContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
@ -40,8 +49,24 @@ import im.vector.riotx.core.utils.DimensionConverter
import im.vector.riotx.core.utils.containsOnlyEmojis import im.vector.riotx.core.utils.containsOnlyEmojis
import im.vector.riotx.core.utils.isLocalFile import im.vector.riotx.core.utils.isLocalFile
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.* import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.item.* import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageBlockCodeItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem_
import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
import im.vector.riotx.features.home.room.detail.timeline.tools.linkify import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
import im.vector.riotx.features.html.CodeVisitor import im.vector.riotx.features.html.CodeVisitor
@ -153,7 +178,7 @@ class MessageItemFactory @Inject constructor(
informationData: MessageInformationData, informationData: MessageInformationData,
highlight: Boolean, highlight: Boolean,
callback: TimelineEventController.Callback?): DefaultItem? { callback: TimelineEventController.Callback?): DefaultItem? {
val text = "${messageContent.type} message events are not yet handled" val text = stringProvider.getString(R.string.rendering_event_error_type_of_message_not_handled, messageContent.type)
return defaultItemFactory.create(text, informationData, highlight, callback) return defaultItemFactory.create(text, informationData, highlight, callback)
} }

View file

@ -18,6 +18,7 @@ package im.vector.riotx.features.home.room.detail.timeline.item
import android.view.View import android.view.View
import android.view.ViewStub import android.view.ViewStub
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.annotation.CallSuper
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
@ -42,6 +43,7 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
@EpoxyAttribute @EpoxyAttribute
lateinit var dimensionConverter: DimensionConverter lateinit var dimensionConverter: DimensionConverter
@CallSuper
override fun bind(holder: H) { override fun bind(holder: H) {
super.bind(holder) super.bind(holder)
holder.leftGuideline.updateLayoutParams<RelativeLayout.LayoutParams> { holder.leftGuideline.updateLayoutParams<RelativeLayout.LayoutParams> {

View file

@ -17,6 +17,7 @@
package im.vector.riotx.features.home.room.detail.timeline.item package im.vector.riotx.features.home.room.detail.timeline.item
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
@ -29,42 +30,39 @@ import im.vector.riotx.features.home.room.detail.timeline.TimelineEventControlle
abstract class DefaultItem : BaseEventItem<DefaultItem.Holder>() { abstract class DefaultItem : BaseEventItem<DefaultItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute
lateinit var informationData: MessageInformationData lateinit var attributes: Attributes
@EpoxyAttribute
lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute
var baseCallback: TimelineEventController.BaseCallback? = null
private var longClickListener = View.OnLongClickListener {
return@OnLongClickListener baseCallback?.onEventLongClicked(informationData, null, it) == true
}
@EpoxyAttribute
var readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener { private val _readReceiptsClickListener = DebouncedClickListener(View.OnClickListener {
readReceiptsCallback?.onReadReceiptsClicked(informationData.readReceipts) attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts)
}) })
@EpoxyAttribute
var text: CharSequence? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
holder.messageView.text = text super.bind(holder)
holder.view.setOnLongClickListener(longClickListener) holder.messageTextView.text = attributes.text
holder.readReceiptsView.render(informationData.readReceipts, avatarRenderer, _readReceiptsClickListener) attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
holder.view.setOnLongClickListener(attributes.itemLongClickListener)
holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener)
} }
override fun getEventIds(): List<String> { override fun getEventIds(): List<String> {
return listOf(informationData.eventId) return listOf(attributes.informationData.eventId)
} }
override fun getViewType() = STUB_ID override fun getViewType() = STUB_ID
class Holder : BaseHolder(STUB_ID) { class Holder : BaseHolder(STUB_ID) {
val messageView by bind<TextView>(R.id.stateMessageView) val avatarImageView by bind<ImageView>(R.id.itemDefaultAvatarView)
val messageTextView by bind<TextView>(R.id.itemDefaultTextView)
} }
data class Attributes(
val avatarRenderer: AvatarRenderer,
val informationData: MessageInformationData,
val text: CharSequence,
val itemLongClickListener: View.OnLongClickListener? = null,
val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null
)
companion object { companion object {
private const val STUB_ID = R.id.messageContentDefaultStub private const val STUB_ID = R.id.messageContentDefaultStub
} }

View file

@ -36,8 +36,9 @@
<ViewStub <ViewStub
android:id="@+id/messageContentDefaultStub" android:id="@+id/messageContentDefaultStub"
style="@style/TimelineContentStubBaseParams" style="@style/TimelineContentStubBaseParams"
android:inflatedId="@+id/stateMessageView" android:layout="@layout/item_timeline_event_default_stub"
android:layout="@layout/item_timeline_event_default_stub" /> tools:layout_marginTop="80dp"
tools:visibility="visible" />
<ViewStub <ViewStub
android:id="@+id/messageContentBlankStub" android:id="@+id/messageContentBlankStub"
@ -49,7 +50,9 @@
<ViewStub <ViewStub
android:id="@+id/messageContentMergedHeaderStub" android:id="@+id/messageContentMergedHeaderStub"
style="@style/TimelineContentStubBaseParams" style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_merged_header_stub" /> android:layout="@layout/item_timeline_event_merged_header_stub"
tools:layout_marginTop="160dp"
tools:visibility="visible" />
</FrameLayout> </FrameLayout>

View file

@ -1,12 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/stateMessageView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:orientation="horizontal">
android:padding="8dp"
android:textColor="?attr/colorAccent" <ImageView
android:textSize="14sp" android:id="@+id/itemDefaultAvatarView"
android:textStyle="italic" android:layout_width="24dp"
tools:text="Mon item" /> android:layout_height="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/itemDefaultTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:textColor="?attr/colorAccent"
android:textSize="14sp"
android:textStyle="italic"
tools:text="@string/rendering_event_error_type_of_event_not_handled" />
</LinearLayout>

View file

@ -3,8 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:orientation="horizontal" android:layout_height="wrap_content"
android:layout_height="wrap_content"> android:orientation="horizontal">
<ImageView <ImageView
android:id="@+id/itemNoticeAvatarView" android:id="@+id/itemNoticeAvatarView"
@ -15,16 +15,16 @@
tools:srcCompat="@tools:sample/avatars" /> tools:srcCompat="@tools:sample/avatars" />
<TextView <TextView
android:layout_gravity="top"
android:id="@+id/itemNoticeTextView" android:id="@+id/itemNoticeTextView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:textColor="?riotx_text_secondary" android:textColor="?riotx_text_secondary"
android:textSize="14sp" android:textSize="14sp"
android:textStyle="italic" android:textStyle="italic"
tools:text="John doe changed their avatar" /> tools:text="@string/notice_avatar_url_changed" />
</LinearLayout> </LinearLayout>

View file

@ -32,6 +32,10 @@
<string name="room_member_jump_to_read_receipt">Jump to read receipt</string> <string name="room_member_jump_to_read_receipt">Jump to read receipt</string>
<string name="rendering_event_error_type_of_event_not_handled">"RiotX does not handle events of type '%1$s' (yet)"</string>
<string name="rendering_event_error_type_of_message_not_handled">"RiotX does not handle message of type '%1$s' (yet)"</string>
<string name="rendering_event_error_exception">"RiotX encountered an issue when rendering content of event with id '%1$s'"</string>
<string name="unignore">Unignore</string> <string name="unignore">Unignore</string>
<string name="room_list_sharing_header_recent_rooms">Recent rooms</string> <string name="room_list_sharing_header_recent_rooms">Recent rooms</string>