Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference)

This commit is contained in:
Benoit Marty 2021-02-06 16:52:46 +01:00
parent 7984d021f6
commit d47ba6bd11
8 changed files with 257 additions and 3 deletions

View file

@ -5,7 +5,7 @@ Features ✨:
- -
Improvements 🙌: Improvements 🙌:
- - Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference)
Bugfix 🐛: Bugfix 🐛:
- Bug in WidgetContent.computeURL() (#2767) - Bug in WidgetContent.computeURL() (#2767)

View file

@ -126,6 +126,13 @@
<string name="notice_widget_modified">%1$s modified %2$s widget</string> <string name="notice_widget_modified">%1$s modified %2$s widget</string>
<string name="notice_widget_modified_by_you">You modified %1$s widget</string> <string name="notice_widget_modified_by_you">You modified %1$s widget</string>
<string name="notice_widget_jitsi_added">Video conference started by %1$s</string>
<string name="notice_widget_jitsi_added_by_you">You started video conference</string>
<string name="notice_widget_jitsi_removed">Video conference ended by %1$s</string>
<string name="notice_widget_jitsi_removed_by_you">You ended video conference</string>
<string name="notice_widget_jitsi_modified">Video conference modified by %1$s</string>
<string name="notice_widget_jitsi_modified_by_you">You modified video conference</string>
<string name="power_level_admin">Admin</string> <string name="power_level_admin">Admin</string>
<string name="power_level_moderator">Moderator</string> <string name="power_level_moderator">Moderator</string>
<string name="power_level_default">Default</string> <string name="power_level_default">Default</string>

View file

@ -32,6 +32,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
private val defaultItemFactory: DefaultItemFactory, private val defaultItemFactory: DefaultItemFactory,
private val encryptionItemFactory: EncryptionItemFactory, private val encryptionItemFactory: EncryptionItemFactory,
private val roomCreateItemFactory: RoomCreateItemFactory, private val roomCreateItemFactory: RoomCreateItemFactory,
private val widgetItemFactory: WidgetItemFactory,
private val roomSummaryHolder: RoomSummaryHolder, private val roomSummaryHolder: RoomSummaryHolder,
private val verificationConclusionItemFactory: VerificationItemFactory, private val verificationConclusionItemFactory: VerificationItemFactory,
private val userPreferencesProvider: UserPreferencesProvider) { private val userPreferencesProvider: UserPreferencesProvider) {
@ -58,14 +59,14 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.STATE_ROOM_HISTORY_VISIBILITY,
EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_SERVER_ACL,
EventType.STATE_ROOM_GUEST_ACCESS, EventType.STATE_ROOM_GUEST_ACCESS,
EventType.STATE_ROOM_WIDGET_LEGACY,
EventType.STATE_ROOM_WIDGET,
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_ANSWER, EventType.CALL_ANSWER,
EventType.STATE_ROOM_POWER_LEVELS, EventType.STATE_ROOM_POWER_LEVELS,
EventType.REACTION, EventType.REACTION,
EventType.REDACTION -> noticeItemFactory.create(event, highlight, roomSummaryHolder.roomSummary, callback) 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) EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback)
// State room create // State room create
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback)

View file

@ -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)
}
}

View file

@ -140,12 +140,14 @@ class NoticeEventFormatter @Inject constructor(
return if (widgetContent.isActive()) { return if (widgetContent.isActive()) {
val widgetName = widgetContent.getHumanName() val widgetName = widgetContent.getHumanName()
if (previousWidgetContent?.isActive().orFalse()) { if (previousWidgetContent?.isActive().orFalse()) {
// Widget has been modified
if (event.isSentByCurrentUser()) { if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_widget_modified_by_you, widgetName) sp.getString(R.string.notice_widget_modified_by_you, widgetName)
} else { } else {
sp.getString(R.string.notice_widget_modified, disambiguatedDisplayName, widgetName) sp.getString(R.string.notice_widget_modified, disambiguatedDisplayName, widgetName)
} }
} else { } else {
// Widget has been added
if (event.isSentByCurrentUser()) { if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_widget_added_by_you, widgetName) sp.getString(R.string.notice_widget_added_by_you, widgetName)
} else { } else {
@ -153,6 +155,7 @@ class NoticeEventFormatter @Inject constructor(
} }
} }
} else { } else {
// Widget has been removed
val widgetName = previousWidgetContent?.getHumanName() val widgetName = previousWidgetContent?.getHumanName()
if (event.isSentByCurrentUser()) { if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_widget_removed_by_you, widgetName) sp.getString(R.string.notice_widget_removed_by_you, widgetName)

View file

@ -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<WidgetTileTimelineItem.Holder>() {
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<RelativeLayout.LayoutParams> {
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<TextView>(R.id.itemWidgetTitle)
val endGuideline by bind<View>(R.id.messageEndGuideline)
val failedToSendIndicator by bind<ImageView>(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
}

View file

@ -53,6 +53,13 @@
tools:layout_marginTop="180dp" tools:layout_marginTop="180dp"
tools:visibility="visible" /> tools:visibility="visible" />
<ViewStub
android:id="@+id/messageWidgetStub"
style="@style/TimelineContentStubBaseParams"
android:layout="@layout/item_timeline_event_widget_stub"
tools:layout_marginTop="280dp"
tools:visibility="visible" />
</FrameLayout> </FrameLayout>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Add a FrameLayout parent for better alignment -->
<FrameLayout 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"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/itemWidgetTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="6dp"
android:gravity="center"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:drawableTint="?riotx_text_primary"
tools:drawableStart="@drawable/ic_video"
tools:text="@string/notice_widget_jitsi_added_by_you" />
</FrameLayout>