mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 11:59:12 +03:00
Merge branch 'develop' into feature/fga/voip_v1_start
This commit is contained in:
commit
f78c72db59
11 changed files with 263 additions and 11 deletions
|
@ -6,12 +6,11 @@ Features ✨:
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- VoIP : new tiles in timeline
|
- VoIP : new tiles in timeline
|
||||||
|
- Create a WidgetItemFactory and use it for better rendering of Jitsi widget change (video conference)
|
||||||
Bugfix 🐛:
|
|
||||||
- VoIP : fix audio devices output
|
|
||||||
- Open image from URL Preview (#2705)
|
- Open image from URL Preview (#2705)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
|
- VoIP : fix audio devices output
|
||||||
- Bug in WidgetContent.computeURL() (#2767)
|
- Bug in WidgetContent.computeURL() (#2767)
|
||||||
- Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776)
|
- Duplicate thumbs | Mobile reactions for 👍 and 👎 are not the same as web (#2776)
|
||||||
- Join room by alias other federation error (#2778)
|
- Join room by alias other federation error (#2778)
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.room.sender
|
package org.matrix.android.sdk.api.session.room.sender
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.util.replaceSpaceChars
|
||||||
|
|
||||||
data class SenderInfo(
|
data class SenderInfo(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
/**
|
/**
|
||||||
|
@ -27,8 +29,9 @@ data class SenderInfo(
|
||||||
) {
|
) {
|
||||||
val disambiguatedDisplayName: String
|
val disambiguatedDisplayName: String
|
||||||
get() = when {
|
get() = when {
|
||||||
displayName.isNullOrBlank() -> userId
|
displayName == null -> userId
|
||||||
isUniqueDisplayName -> displayName
|
displayName.replaceSpaceChars().isBlank() -> "$displayName ($userId)"
|
||||||
else -> "$displayName ($userId)"
|
isUniqueDisplayName -> displayName
|
||||||
|
else -> "$displayName ($userId)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,3 +71,10 @@ fun String.caseInsensitiveFind(subString: String): Boolean {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val spaceChars = "[\u00A0\u2000-\u200B\u2800\u3000]".toRegex()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip all the UTF-8 chars which are actually spaces
|
||||||
|
*/
|
||||||
|
internal fun String.replaceSpaceChars() = replace(spaceChars, "")
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -20,7 +20,6 @@ import im.vector.app.core.epoxy.EmptyItem_
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -32,7 +31,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 roomSummariesHolder: RoomSummariesHolder,
|
private val widgetItemFactory: WidgetItemFactory,
|
||||||
private val verificationConclusionItemFactory: VerificationItemFactory,
|
private val verificationConclusionItemFactory: VerificationItemFactory,
|
||||||
private val callItemFactory: CallItemFactory,
|
private val callItemFactory: CallItemFactory,
|
||||||
private val userPreferencesProvider: UserPreferencesProvider) {
|
private val userPreferencesProvider: UserPreferencesProvider) {
|
||||||
|
@ -59,11 +58,11 @@ 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.STATE_ROOM_POWER_LEVELS,
|
EventType.STATE_ROOM_POWER_LEVELS,
|
||||||
EventType.REACTION,
|
EventType.REACTION,
|
||||||
EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback)
|
EventType.REDACTION -> noticeItemFactory.create(event, highlight, 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)
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
// There is lot of other widget types we could improve here
|
||||||
|
else -> 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -143,12 +143,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 {
|
||||||
|
@ -156,6 +158,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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -83,7 +83,7 @@ abstract class BaseAttachmentProvider<Type>(
|
||||||
val dateString = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
val dateString = dateFormatter.format(timelineEvent.root.originServerTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||||
overlayView?.updateWith(
|
overlayView?.updateWith(
|
||||||
counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, getItemCount()),
|
counter = stringProvider.getString(R.string.attachment_viewer_item_x_of_y, position + 1, getItemCount()),
|
||||||
senderInfo = "${timelineEvent.senderInfo.displayName} $dateString"
|
senderInfo = "${timelineEvent.senderInfo.disambiguatedDisplayName} $dateString"
|
||||||
)
|
)
|
||||||
overlayView?.views?.overlayVideoControlsGroup?.isVisible = timelineEvent.root.isVideoMessage()
|
overlayView?.views?.overlayVideoControlsGroup?.isVisible = timelineEvent.root.isVideoMessage()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -60,6 +60,13 @@
|
||||||
android:layout="@layout/item_timeline_event_status_tile_stub"
|
android:layout="@layout/item_timeline_event_status_tile_stub"
|
||||||
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>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue