mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-27 12:00:03 +03:00
Bubbles: start adding "theming" mechanism
This commit is contained in:
parent
f7c9b36cef
commit
af542a8243
10 changed files with 212 additions and 49 deletions
|
@ -108,6 +108,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
|||
val informationData = messageInformationDataFactory.create(params)
|
||||
val attributes = attributesFactory.create(event.root.content.toModel<EncryptedEventContent>(), informationData, params.callback)
|
||||
return MessageTextItem_()
|
||||
.layout(informationData.messageLayout.layoutRes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.highlighted(params.isHighlighted)
|
||||
.attributes(attributes)
|
||||
|
|
|
@ -154,7 +154,7 @@ class MessageItemFactory @Inject constructor(
|
|||
|
||||
// val all = event.root.toContent()
|
||||
// val ev = all.toModel<Event>()
|
||||
return when (messageContent) {
|
||||
val messageItem = when (messageContent) {
|
||||
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageTextContent -> buildItemForTextContent(messageContent, informationData, highlight, callback, attributes)
|
||||
is MessageImageInfoContent -> buildImageMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
|
@ -172,6 +172,9 @@ class MessageItemFactory @Inject constructor(
|
|||
is MessagePollContent -> buildPollContent(messageContent, informationData, highlight, callback, attributes)
|
||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||
}
|
||||
return messageItem?.apply {
|
||||
layout(informationData.messageLayout.layoutRes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPollContent(pollContent: MessagePollContent,
|
||||
|
@ -389,13 +392,6 @@ class MessageItemFactory @Inject constructor(
|
|||
allowNonMxcUrls = informationData.sendState.isSending()
|
||||
)
|
||||
return MessageImageVideoItem_()
|
||||
.layout(
|
||||
if (informationData.sentByMe) {
|
||||
R.layout.item_timeline_event_bubble_outgoing_base
|
||||
} else {
|
||||
R.layout.item_timeline_event_bubble_incoming_base
|
||||
}
|
||||
)
|
||||
.attributes(attributes)
|
||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||
.imageContentRenderer(imageContentRenderer)
|
||||
|
@ -517,13 +513,6 @@ class MessageItemFactory @Inject constructor(
|
|||
linkifiedBody
|
||||
}.toEpoxyCharSequence()
|
||||
)
|
||||
.layout(
|
||||
if (informationData.sentByMe) {
|
||||
R.layout.item_timeline_event_bubble_outgoing_base
|
||||
} else {
|
||||
R.layout.item_timeline_event_bubble_incoming_base
|
||||
}
|
||||
)
|
||||
.useBigFont(linkifiedBody.length <= MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT * 2 && containsOnlyEmojis(linkifiedBody.toString()))
|
||||
.bindingOptions(bindingOptions)
|
||||
.searchForPills(isFormatted)
|
||||
|
|
|
@ -17,14 +17,22 @@
|
|||
package im.vector.app.features.home.room.detail.timeline.helper
|
||||
|
||||
import im.vector.app.core.utils.DimensionConverter
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettings
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineLayoutSettingsProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter) {
|
||||
class AvatarSizeProvider @Inject constructor(private val dimensionConverter: DimensionConverter,
|
||||
private val layoutSettingsProvider: TimelineLayoutSettingsProvider) {
|
||||
|
||||
private val avatarStyle = AvatarStyle.X_SMALL
|
||||
private val avatarStyle by lazy {
|
||||
when (layoutSettingsProvider.getLayoutSettings()) {
|
||||
TimelineLayoutSettings.MODERN -> AvatarStyle.SMALL
|
||||
TimelineLayoutSettings.BUBBLE -> AvatarStyle.BUBBLE
|
||||
}
|
||||
}
|
||||
|
||||
val leftGuideline: Int by lazy {
|
||||
dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + 4)
|
||||
dimensionConverter.dpToPx(avatarStyle.avatarSizeDP + avatarStyle.marginDP)
|
||||
}
|
||||
|
||||
val avatarSize: Int by lazy {
|
||||
|
@ -33,12 +41,12 @@ class AvatarSizeProvider @Inject constructor(private val dimensionConverter: Dim
|
|||
|
||||
companion object {
|
||||
|
||||
enum class AvatarStyle(val avatarSizeDP: Int) {
|
||||
BIG(50),
|
||||
MEDIUM(40),
|
||||
SMALL(30),
|
||||
X_SMALL(28),
|
||||
NONE(0)
|
||||
enum class AvatarStyle(val avatarSizeDP: Int, val marginDP: Int) {
|
||||
BIG(50, 8),
|
||||
MEDIUM(40, 8),
|
||||
SMALL(30, 8),
|
||||
BUBBLE(28, 4),
|
||||
NONE(0, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
|
|||
import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
|
||||
import org.matrix.android.sdk.api.crypto.VerificationState
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.room.send.SendState
|
|||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
|
||||
import org.matrix.android.sdk.api.session.room.timeline.isEdition
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -51,8 +50,7 @@ import javax.inject.Inject
|
|||
*/
|
||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||
private val dateFormatter: VectorDateFormatter,
|
||||
private val visibilityHelper: TimelineEventVisibilityHelper,
|
||||
private val vectorPreferences: VectorPreferences) {
|
||||
private val messageLayoutFactory: TimelineMessageLayoutFactory) {
|
||||
|
||||
fun create(params: TimelineItemFactoryParams): MessageInformationData {
|
||||
val event = params.event
|
||||
|
@ -66,21 +64,9 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
val nextDate = nextDisplayableEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
|
||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||
?: false
|
||||
|
||||
val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
|
||||
val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
|
||||
|
||||
val showInformation =
|
||||
(addDaySeparator ||
|
||||
event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
|
||||
event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
|
||||
nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
|
||||
isNextMessageReceivedMoreThanOneHourAgo ||
|
||||
isTileTypeMessage(nextDisplayableEvent) ||
|
||||
nextDisplayableEvent.isEdition()) && !isSentByMe
|
||||
|
||||
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
|
||||
val e2eDecoration = getE2EDecoration(roomSummary, event)
|
||||
|
||||
|
@ -95,6 +81,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
SendStateDecoration.NONE
|
||||
}
|
||||
|
||||
val messageLayout = messageLayoutFactory.create(params)
|
||||
|
||||
return MessageInformationData(
|
||||
eventId = eventId,
|
||||
senderId = event.root.senderId ?: "",
|
||||
|
@ -103,9 +91,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||
ageLocalTS = event.root.ageLocalTs,
|
||||
avatarUrl = event.senderInfo.avatarUrl,
|
||||
memberName = event.senderInfo.disambiguatedDisplayName,
|
||||
showAvatar = showInformation,
|
||||
showDisplayName = showInformation,
|
||||
showTimestamp = true,
|
||||
messageLayout = messageLayout,
|
||||
orderedReactionList = event.annotations?.reactionsSummary
|
||||
// ?.filter { isSingleEmoji(it.key) }
|
||||
?.map {
|
||||
|
|
|
@ -62,7 +62,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
|
||||
override fun bind(holder: H) {
|
||||
super.bind(holder)
|
||||
if (attributes.informationData.showAvatar) {
|
||||
if (attributes.informationData.messageLayout.showAvatar) {
|
||||
holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
|
||||
height = attributes.avatarSize
|
||||
width = attributes.avatarSize
|
||||
|
@ -76,7 +76,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
holder.avatarImageView.setOnLongClickListener(null)
|
||||
holder.avatarImageView.isVisible = false
|
||||
}
|
||||
if (attributes.informationData.showDisplayName) {
|
||||
if (attributes.informationData.messageLayout.showDisplayName) {
|
||||
holder.memberNameView.isVisible = true
|
||||
holder.memberNameView.text = attributes.informationData.memberName
|
||||
holder.memberNameView.setTextColor(attributes.getMemberNameColor())
|
||||
|
@ -87,7 +87,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
holder.memberNameView.setOnLongClickListener(null)
|
||||
holder.memberNameView.isVisible = false
|
||||
}
|
||||
if (attributes.informationData.showTimestamp) {
|
||||
if (attributes.informationData.messageLayout.showTimestamp) {
|
||||
holder.timeView.isVisible = true
|
||||
holder.timeView.text = attributes.informationData.time
|
||||
} else {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.home.room.detail.timeline.item
|
||||
|
||||
import android.os.Parcelable
|
||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.crypto.VerificationState
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
|
@ -31,9 +32,7 @@ data class MessageInformationData(
|
|||
val ageLocalTS: Long?,
|
||||
val avatarUrl: String?,
|
||||
val memberName: CharSequence? = null,
|
||||
val showAvatar: Boolean,
|
||||
val showDisplayName: Boolean,
|
||||
val showTimestamp: Boolean,
|
||||
val messageLayout: TimelineMessageLayout,
|
||||
/*List of reactions (emoji,count,isSelected)*/
|
||||
val orderedReactionList: List<ReactionInfoData>? = null,
|
||||
val pollResponseAggregatedSummary: PollResponseData? = null,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.style
|
||||
|
||||
enum class TimelineLayoutSettings {
|
||||
MODERN,
|
||||
BUBBLE
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.style
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineLayoutSettingsProvider @Inject constructor() {
|
||||
|
||||
fun getLayoutSettings(): TimelineLayoutSettings {
|
||||
return TimelineLayoutSettings.BUBBLE
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.style
|
||||
|
||||
import android.os.Parcelable
|
||||
import im.vector.app.R
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
sealed interface TimelineMessageLayout : Parcelable {
|
||||
val layoutRes: Int
|
||||
val showAvatar: Boolean
|
||||
val showDisplayName: Boolean
|
||||
val showTimestamp: Boolean
|
||||
|
||||
@Parcelize
|
||||
data class Modern(override val showAvatar: Boolean,
|
||||
override val showDisplayName: Boolean,
|
||||
override val showTimestamp: Boolean,
|
||||
override val layoutRes: Int = R.layout.item_timeline_event_base) : TimelineMessageLayout
|
||||
|
||||
@Parcelize
|
||||
data class Bubble(override val showAvatar: Boolean,
|
||||
override val showDisplayName: Boolean,
|
||||
override val showTimestamp: Boolean = true,
|
||||
val isIncoming: Boolean,
|
||||
val isFirstFromThisSender: Boolean,
|
||||
val isLastFromThisSender: Boolean,
|
||||
override val layoutRes: Int = if (isIncoming) R.layout.item_timeline_event_bubble_incoming_base else R.layout.item_timeline_event_bubble_outgoing_base,
|
||||
) : TimelineMessageLayout
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.style
|
||||
|
||||
import im.vector.app.core.extensions.localDateTime
|
||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.isEdition
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineMessageLayoutFactory @Inject constructor(private val session: Session,
|
||||
private val layoutSettingsProvider: TimelineLayoutSettingsProvider,
|
||||
private val vectorPreferences: VectorPreferences) {
|
||||
|
||||
fun create(params: TimelineItemFactoryParams): TimelineMessageLayout {
|
||||
|
||||
val event = params.event
|
||||
val nextDisplayableEvent = params.nextDisplayableEvent
|
||||
val prevDisplayableEvent = params.prevDisplayableEvent
|
||||
val isSentByMe = event.root.senderId == session.myUserId
|
||||
|
||||
val date = event.root.localDateTime()
|
||||
val nextDate = nextDisplayableEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
|
||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||
?: false
|
||||
|
||||
val showInformation =
|
||||
(addDaySeparator ||
|
||||
event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl ||
|
||||
event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName ||
|
||||
nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED) ||
|
||||
isNextMessageReceivedMoreThanOneHourAgo ||
|
||||
isTileTypeMessage(nextDisplayableEvent) ||
|
||||
nextDisplayableEvent.isEdition()) && !isSentByMe
|
||||
|
||||
val messageLayout = when (layoutSettingsProvider.getLayoutSettings()) {
|
||||
TimelineLayoutSettings.MODERN -> TimelineMessageLayout.Modern(showInformation, showInformation, showInformation || vectorPreferences.alwaysShowTimeStamps())
|
||||
TimelineLayoutSettings.BUBBLE -> {
|
||||
val isFirstFromThisSender = nextDisplayableEvent?.root?.senderId != event.root.senderId || addDaySeparator
|
||||
val isLastFromThisSender = prevDisplayableEvent?.root?.senderId != event.root.senderId || prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
|
||||
TimelineMessageLayout.Bubble(
|
||||
showAvatar = showInformation,
|
||||
showDisplayName = showInformation,
|
||||
isIncoming = !isSentByMe,
|
||||
isFirstFromThisSender = isFirstFromThisSender,
|
||||
isLastFromThisSender = isLastFromThisSender
|
||||
)
|
||||
}
|
||||
}
|
||||
return messageLayout
|
||||
}
|
||||
|
||||
/**
|
||||
* Tiles type message never show the sender information (like verification request), so we should repeat it for next message
|
||||
* even if same sender
|
||||
*/
|
||||
private fun isTileTypeMessage(event: TimelineEvent?): Boolean {
|
||||
return when (event?.root?.getClearType()) {
|
||||
EventType.KEY_VERIFICATION_DONE,
|
||||
EventType.KEY_VERIFICATION_CANCEL -> true
|
||||
EventType.MESSAGE -> {
|
||||
event.getLastMessageContent() is MessageVerificationRequestContent
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue