From d47ba6bd1187f0aa4960abb85d240e6eee7f9058 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Sat, 6 Feb 2021 16:52:46 +0100 Subject: [PATCH] Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference) --- CHANGES.md | 2 +- .../src/main/res/values/strings.xml | 7 + .../timeline/factory/TimelineItemFactory.kt | 5 +- .../timeline/factory/WidgetItemFactory.kt | 126 ++++++++++++++++++ .../timeline/format/NoticeEventFormatter.kt | 3 + .../timeline/item/WidgetTileTimelineItem.kt | 87 ++++++++++++ .../layout/item_timeline_event_base_state.xml | 7 + .../item_timeline_event_widget_stub.xml | 23 ++++ 8 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt create mode 100644 vector/src/main/res/layout/item_timeline_event_widget_stub.xml diff --git a/CHANGES.md b/CHANGES.md index dfce79d260..9af20d61d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference) Bugfix 🐛: - Bug in WidgetContent.computeURL() (#2767) diff --git a/matrix-sdk-android/src/main/res/values/strings.xml b/matrix-sdk-android/src/main/res/values/strings.xml index 7a0fe1d735..26b9bc19d9 100644 --- a/matrix-sdk-android/src/main/res/values/strings.xml +++ b/matrix-sdk-android/src/main/res/values/strings.xml @@ -126,6 +126,13 @@ %1$s modified %2$s widget You modified %1$s widget + Video conference started by %1$s + You started video conference + Video conference ended by %1$s + You ended video conference + Video conference modified by %1$s + You modified video conference + Admin Moderator Default diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 243cbbd0e6..943e78ae35 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -32,6 +32,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val defaultItemFactory: DefaultItemFactory, private val encryptionItemFactory: EncryptionItemFactory, private val roomCreateItemFactory: RoomCreateItemFactory, + private val widgetItemFactory: WidgetItemFactory, private val roomSummaryHolder: RoomSummaryHolder, private val verificationConclusionItemFactory: VerificationItemFactory, private val userPreferencesProvider: UserPreferencesProvider) { @@ -58,14 +59,14 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_GUEST_ACCESS, - EventType.STATE_ROOM_WIDGET_LEGACY, - EventType.STATE_ROOM_WIDGET, EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER, EventType.STATE_ROOM_POWER_LEVELS, EventType.REACTION, EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + EventType.STATE_ROOM_WIDGET_LEGACY, + EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback) // State room create EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt new file mode 100644 index 0000000000..b20fa72012 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.detail.timeline.factory + +import im.vector.app.ActiveSessionDataSource +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider +import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory +import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory +import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder +import im.vector.app.features.home.room.detail.timeline.item.WidgetTileTimelineItem +import im.vector.app.features.home.room.detail.timeline.item.WidgetTileTimelineItem_ +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.widgets.model.WidgetContent +import org.matrix.android.sdk.api.session.widgets.model.WidgetType +import javax.inject.Inject + +class WidgetItemFactory @Inject constructor( + private val sp: StringProvider, + private val roomSummaryHolder: RoomSummaryHolder, + private val messageItemAttributesFactory: MessageItemAttributesFactory, + private val informationDataFactory: MessageInformationDataFactory, + private val noticeItemFactory: NoticeItemFactory, + private val avatarSizeProvider: AvatarSizeProvider, + private val activeSessionDataSource: ActiveSessionDataSource +) { + private val currentUserId: String? + get() = activeSessionDataSource.currentValue?.orNull()?.myUserId + + private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId + + fun create(event: TimelineEvent, + highlight: Boolean, + callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + val widgetContent: WidgetContent = event.root.getClearContent().toModel() ?: return null + val previousWidgetContent: WidgetContent? = event.root.resolvedPrevContent().toModel() + + return when (WidgetType.fromString(widgetContent.type ?: previousWidgetContent?.type ?: "")) { + WidgetType.Jitsi -> createJitsiItem(event, callback, widgetContent, previousWidgetContent) + WidgetType.TradingView, + WidgetType.Spotify, + WidgetType.Video, + WidgetType.GoogleDoc, + WidgetType.GoogleCalendar, + WidgetType.Etherpad, + WidgetType.StickerPicker, + WidgetType.Grafana, + WidgetType.Custom, + WidgetType.IntegrationManager, + is WidgetType.Fallback -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) + } + } + + private fun createJitsiItem(timelineEvent: TimelineEvent, + callback: TimelineEventController.Callback?, + widgetContent: WidgetContent, + previousWidgetContent: WidgetContent?): VectorEpoxyModel<*> { + val informationData = informationDataFactory.create(timelineEvent, null) + val attributes = messageItemAttributesFactory.create(null, informationData, callback) + + val disambiguatedDisplayName = timelineEvent.senderInfo.disambiguatedDisplayName + val message = if (widgetContent.isActive()) { + val widgetName = widgetContent.getHumanName() + if (previousWidgetContent?.isActive().orFalse()) { + // Widget has been modified + if (timelineEvent.root.isSentByCurrentUser()) { + sp.getString(R.string.notice_widget_jitsi_modified_by_you, widgetName) + } else { + sp.getString(R.string.notice_widget_jitsi_modified, disambiguatedDisplayName, widgetName) + } + } else { + // Widget has been added + if (timelineEvent.root.isSentByCurrentUser()) { + sp.getString(R.string.notice_widget_jitsi_added_by_you, widgetName) + } else { + sp.getString(R.string.notice_widget_jitsi_added, disambiguatedDisplayName, widgetName) + } + } + } else { + // Widget has been removed + val widgetName = previousWidgetContent?.getHumanName() + if (timelineEvent.root.isSentByCurrentUser()) { + sp.getString(R.string.notice_widget_jitsi_removed_by_you, widgetName) + } else { + sp.getString(R.string.notice_widget_jitsi_removed, disambiguatedDisplayName, widgetName) + } + } + + return WidgetTileTimelineItem_() + .attributes( + WidgetTileTimelineItem.Attributes( + title = message, + drawableStart = R.drawable.ic_video, + informationData = informationData, + avatarRenderer = attributes.avatarRenderer, + messageColorProvider = attributes.messageColorProvider, + itemLongClickListener = attributes.itemLongClickListener, + itemClickListener = attributes.itemClickListener, + reactionPillCallback = attributes.reactionPillCallback, + readReceiptsCallback = attributes.readReceiptsCallback, + emojiTypeFace = attributes.emojiTypeFace + ) + ) + .leftGuideline(avatarSizeProvider.leftGuideline) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt index c725f5b7dc..8204ce39ec 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/NoticeEventFormatter.kt @@ -140,12 +140,14 @@ class NoticeEventFormatter @Inject constructor( return if (widgetContent.isActive()) { val widgetName = widgetContent.getHumanName() if (previousWidgetContent?.isActive().orFalse()) { + // Widget has been modified if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_widget_modified_by_you, widgetName) } else { sp.getString(R.string.notice_widget_modified, disambiguatedDisplayName, widgetName) } } else { + // Widget has been added if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_widget_added_by_you, widgetName) } else { @@ -153,6 +155,7 @@ class NoticeEventFormatter @Inject constructor( } } } else { + // Widget has been removed val widgetName = previousWidgetContent?.getHumanName() if (event.isSentByCurrentUser()) { sp.getString(R.string.notice_widget_removed_by_you, widgetName) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt new file mode 100644 index 0000000000..33f59ca22a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/WidgetTileTimelineItem.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.app.features.home.room.detail.timeline.item + +import android.annotation.SuppressLint +import android.graphics.Typeface +import android.view.View +import android.widget.ImageView +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.core.view.updateLayoutParams +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.MessageColorProvider +import im.vector.app.features.home.room.detail.timeline.TimelineEventController + +@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state) +abstract class WidgetTileTimelineItem : AbsBaseMessageItem() { + + override val baseAttributes: AbsBaseMessageItem.Attributes + get() = attributes + + @EpoxyAttribute + lateinit var attributes: Attributes + + override fun getViewType() = STUB_ID + + @SuppressLint("SetTextI18n") + override fun bind(holder: Holder) { + super.bind(holder) + holder.endGuideline.updateLayoutParams { + this.marginEnd = leftGuideline + } + + holder.titleView.text = attributes.title + holder.titleView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(holder.view.context, attributes.drawableStart), + null, null, null + ) + + renderSendState(holder.view, null, holder.failedToSendIndicator) + } + + class Holder : AbsBaseMessageItem.Holder(STUB_ID) { + val titleView by bind(R.id.itemWidgetTitle) + val endGuideline by bind(R.id.messageEndGuideline) + val failedToSendIndicator by bind(R.id.messageFailToSendIndicator) + } + + companion object { + private const val STUB_ID = R.id.messageWidgetStub + } + + /** + * This class holds all the common attributes for timeline items. + */ + data class Attributes( + val title: CharSequence, + @DrawableRes + val drawableStart: Int, + override val informationData: MessageInformationData, + override val avatarRenderer: AvatarRenderer, + override val messageColorProvider: MessageColorProvider, + override val itemLongClickListener: View.OnLongClickListener? = null, + override val itemClickListener: View.OnClickListener? = null, + override val reactionPillCallback: TimelineEventController.ReactionPillCallback? = null, + override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, + val emojiTypeFace: Typeface? = null + ) : AbsBaseMessageItem.Attributes +} diff --git a/vector/src/main/res/layout/item_timeline_event_base_state.xml b/vector/src/main/res/layout/item_timeline_event_base_state.xml index 38fb3af07b..3f44b11aa7 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_state.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_state.xml @@ -53,6 +53,13 @@ tools:layout_marginTop="180dp" tools:visibility="visible" /> + + diff --git a/vector/src/main/res/layout/item_timeline_event_widget_stub.xml b/vector/src/main/res/layout/item_timeline_event_widget_stub.xml new file mode 100644 index 0000000000..573fecc4e8 --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_widget_stub.xml @@ -0,0 +1,23 @@ + + + + + + + +