Message bubble footer area
For use with - bottom timestamps in message bubbles - unpersonal read receipts: maybe in the future, as we can not really update all relevant messages with the current db update mechanism. But we can use it for send-status for now. Change-Id: I2de909362394e336f9aaba9f0d157e7c6fe8f9b1
This commit is contained in:
parent
5853709c97
commit
8602f1345b
@ -0,0 +1,68 @@
|
|||||||
|
package im.vector.app.core.ui.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView that reserves space at the bottom for overlaying it with a footer, e.g. in a FrameLayout or RelativeLayout
|
||||||
|
*/
|
||||||
|
class FooteredTextView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
): AppCompatTextView(context, attrs, defStyleAttr) {
|
||||||
|
|
||||||
|
var footerHeight: Int = 0
|
||||||
|
var footerWidth: Int = 0
|
||||||
|
//var widthLimit: Float = 0f
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
// First, let super measure the content for our normal TextView use
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
|
// Get max available width
|
||||||
|
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||||
|
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
val widthLimit = if (widthMode == MeasureSpec.AT_MOST) { widthSize.toFloat() } else { Float.MAX_VALUE }
|
||||||
|
/*
|
||||||
|
// Sometimes, widthLimit is not the actual limit, so remember it... ?
|
||||||
|
if (this.widthLimit > widthLimit) {
|
||||||
|
widthLimit = this.widthLimit
|
||||||
|
} else {
|
||||||
|
this.widthLimit = widthLimit
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get required width for all lines
|
||||||
|
var maxLineWidth = 0f
|
||||||
|
for (i in 0 until layout.lineCount) {
|
||||||
|
maxLineWidth = max(layout.getLineWidth(i), maxLineWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix wrap_content in multi-line texts by using maxLineWidth instead of measuredWidth here
|
||||||
|
// (compare WrapWidthTextView.kt)
|
||||||
|
var newWidth = ceil(maxLineWidth).toInt()
|
||||||
|
var newHeight = measuredHeight
|
||||||
|
|
||||||
|
val widthLastLine = layout.getLineWidth(layout.lineCount-1)
|
||||||
|
|
||||||
|
// Required width if putting footer in the same line as the last line
|
||||||
|
val widthWithHorizontalFooter = widthLastLine + footerWidth
|
||||||
|
|
||||||
|
// Is there space for a horizontal footer?
|
||||||
|
if (widthWithHorizontalFooter <= widthLimit) {
|
||||||
|
// Reserve extra horizontal footer space if necessary
|
||||||
|
if (widthWithHorizontalFooter > newWidth) {
|
||||||
|
newWidth = ceil(widthWithHorizontalFooter).toInt()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Reserve vertical footer space
|
||||||
|
newHeight += footerHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
setMeasuredDimension(newWidth, newHeight)
|
||||||
|
}
|
||||||
|
}
|
@ -18,9 +18,11 @@
|
|||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.helper
|
package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.AnonymousReadReceipt
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
|
||||||
@ -28,6 +30,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
|
|||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import im.vector.app.features.themes.BubbleThemeUtils
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
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.events.model.EventType
|
||||||
@ -49,7 +52,8 @@ import javax.inject.Inject
|
|||||||
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
class MessageInformationDataFactory @Inject constructor(private val session: Session,
|
||||||
private val roomSummaryHolder: RoomSummaryHolder,
|
private val roomSummaryHolder: RoomSummaryHolder,
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val vectorPreferences: VectorPreferences) {
|
private val vectorPreferences: VectorPreferences,
|
||||||
|
private val context: Context) {
|
||||||
|
|
||||||
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
fun create(event: TimelineEvent, nextEvent: TimelineEvent?): MessageInformationData {
|
||||||
// Non nullability has been tested before
|
// Non nullability has been tested before
|
||||||
@ -81,7 +85,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||||||
avatarUrl = event.senderInfo.avatarUrl,
|
avatarUrl = event.senderInfo.avatarUrl,
|
||||||
memberName = event.senderInfo.disambiguatedDisplayName,
|
memberName = event.senderInfo.disambiguatedDisplayName,
|
||||||
showInformation = showInformation,
|
showInformation = showInformation,
|
||||||
forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps(),
|
forceShowTimestamp = vectorPreferences.alwaysShowTimeStamps() || BubbleThemeUtils.forceAlwaysShowTimestamps(context),
|
||||||
orderedReactionList = event.annotations?.reactionsSummary
|
orderedReactionList = event.annotations?.reactionsSummary
|
||||||
// ?.filter { isSingleEmoji(it.key) }
|
// ?.filter { isSingleEmoji(it.key) }
|
||||||
?.map {
|
?.map {
|
||||||
@ -113,6 +117,16 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|
|||||||
ReferencesInfoData(verificationState)
|
ReferencesInfoData(verificationState)
|
||||||
},
|
},
|
||||||
sentByMe = event.root.senderId == session.myUserId,
|
sentByMe = event.root.senderId == session.myUserId,
|
||||||
|
readReceiptAnonymous = if (event.root.sendState == SendState.SYNCED) {
|
||||||
|
/*if (event.readByOther) {
|
||||||
|
AnonymousReadReceipt.READ
|
||||||
|
} else {
|
||||||
|
AnonymousReadReceipt.SENT
|
||||||
|
}*/
|
||||||
|
AnonymousReadReceipt.NONE
|
||||||
|
} else {
|
||||||
|
AnonymousReadReceipt.PROCESSING
|
||||||
|
},
|
||||||
isDirect = roomSummaryHolder.roomSummary?.isDirect ?: false,
|
isDirect = roomSummaryHolder.roomSummary?.isDirect ?: false,
|
||||||
e2eDecoration = e2eDecoration
|
e2eDecoration = e2eDecoration
|
||||||
)
|
)
|
||||||
|
@ -18,18 +18,16 @@ package im.vector.app.features.home.room.detail.timeline.item
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
import android.widget.RelativeLayout
|
import android.widget.RelativeLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
import androidx.core.view.isInvisible
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
@ -40,6 +38,7 @@ import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
|||||||
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.themes.BubbleThemeUtils
|
import im.vector.app.features.themes.BubbleThemeUtils
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base timeline item that adds an optional information bar with the sender avatar, name and time
|
* Base timeline item that adds an optional information bar with the sender avatar, name and time
|
||||||
@ -66,7 +65,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
|
|
||||||
val avatarImageView: ImageView?
|
val avatarImageView: ImageView?
|
||||||
val memberNameView: TextView?
|
val memberNameView: TextView?
|
||||||
val timeView: TextView?
|
var timeView: TextView?
|
||||||
val hiddenViews = ArrayList<View>()
|
val hiddenViews = ArrayList<View>()
|
||||||
val invisibleViews = ArrayList<View>()
|
val invisibleViews = ArrayList<View>()
|
||||||
|
|
||||||
@ -108,6 +107,24 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
hiddenViews.add(holder.timeView)
|
hiddenViews.add(holder.timeView)
|
||||||
hiddenViews.add(holder.bubbleTimeView)
|
hiddenViews.add(holder.bubbleTimeView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timeView === holder.bubbleTimeView) {
|
||||||
|
// We have two possible bubble time view locations
|
||||||
|
// For code readability, we don't inline this setting in the above cases
|
||||||
|
if (BubbleThemeUtils.getBubbleTimeLocation(holder.bubbleTimeView.context) == BubbleThemeUtils.BUBBLE_TIME_BOTTOM) {
|
||||||
|
timeView = holder.bubbleFooterTimeView
|
||||||
|
if (attributes.informationData.showInformation) {
|
||||||
|
// Don't hide, so our relative layout rules still work
|
||||||
|
invisibleViews.add(holder.bubbleTimeView)
|
||||||
|
} else {
|
||||||
|
// Do hide, or we accidentally reserve space
|
||||||
|
hiddenViews.add(holder.bubbleTimeView)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hiddenViews.add(holder.bubbleFooterTimeView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dual-side bubbles: hide own avatar, and all in direct chats
|
// Dual-side bubbles: hide own avatar, and all in direct chats
|
||||||
if ((!attributes.informationData.showInformation) ||
|
if ((!attributes.informationData.showInformation) ||
|
||||||
(contentInBubble && (attributes.informationData.sentByMe || attributes.informationData.isDirect))) {
|
(contentInBubble && (attributes.informationData.sentByMe || attributes.informationData.isDirect))) {
|
||||||
@ -117,15 +134,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
avatarImageView = holder.avatarImageView
|
avatarImageView = holder.avatarImageView
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenViews.forEach {
|
|
||||||
// Same as it.isVisible = false
|
|
||||||
it.visibility = View.GONE
|
|
||||||
}
|
|
||||||
invisibleViews.forEach {
|
|
||||||
// Same as it.isInvisible = true
|
|
||||||
it.visibility = View.INVISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
// Views available in upstream Element
|
// Views available in upstream Element
|
||||||
avatarImageView?.layoutParams = avatarImageView?.layoutParams?.apply {
|
avatarImageView?.layoutParams = avatarImageView?.layoutParams?.apply {
|
||||||
height = attributes.avatarSize
|
height = attributes.avatarSize
|
||||||
@ -143,8 +151,23 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
avatarImageView?.setOnLongClickListener(attributes.itemLongClickListener)
|
avatarImageView?.setOnLongClickListener(attributes.itemLongClickListener)
|
||||||
memberNameView?.setOnLongClickListener(attributes.itemLongClickListener)
|
memberNameView?.setOnLongClickListener(attributes.itemLongClickListener)
|
||||||
|
|
||||||
// Views added by Schildi
|
// More extra views added by Schildi
|
||||||
holder.viewStubContainer.minimumWidth = getViewStubMinimumWidth(holder, contentInBubble, attributes.informationData.showInformation)
|
holder.viewStubContainer.minimumWidth = getViewStubMinimumWidth(holder, contentInBubble, attributes.informationData.showInformation)
|
||||||
|
if (contentInBubble) {
|
||||||
|
holder.bubbleFootView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
hiddenViews.add(holder.bubbleFootView)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually hide all unnecessary views
|
||||||
|
hiddenViews.forEach {
|
||||||
|
// Same as it.isVisible = false
|
||||||
|
it.visibility = View.GONE
|
||||||
|
}
|
||||||
|
invisibleViews.forEach {
|
||||||
|
// Same as it.isInvisible = true
|
||||||
|
it.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: H) {
|
override fun unbind(holder: H) {
|
||||||
@ -166,6 +189,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
val bubbleView by bind<View>(R.id.bubbleView)
|
val bubbleView by bind<View>(R.id.bubbleView)
|
||||||
val bubbleMemberNameView by bind<TextView>(R.id.bubbleMessageMemberNameView)
|
val bubbleMemberNameView by bind<TextView>(R.id.bubbleMessageMemberNameView)
|
||||||
val bubbleTimeView by bind<TextView>(R.id.bubbleMessageTimeView)
|
val bubbleTimeView by bind<TextView>(R.id.bubbleMessageTimeView)
|
||||||
|
val bubbleFootView by bind<LinearLayout>(R.id.bubbleFootView)
|
||||||
|
val bubbleFooterTimeView by bind<TextView>(R.id.bubbleFooterMessageTimeView)
|
||||||
|
val bubbleFooterReadReceipt by bind<ImageView>(R.id.bubbleFooterReadReceipt)
|
||||||
val viewStubContainer by bind<FrameLayout>(R.id.viewStubContainer)
|
val viewStubContainer by bind<FrameLayout>(R.id.viewStubContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,20 +237,33 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun getViewStubMinimumWidth(holder: H, contentInBubble: Boolean, showInformation: Boolean): Int {
|
open fun getViewStubMinimumWidth(holder: H, contentInBubble: Boolean, showInformation: Boolean): Int {
|
||||||
return if (contentInBubble && (attributes.informationData.showInformation || attributes.informationData.forceShowTimestamp)) {
|
return if (contentInBubble) {
|
||||||
// Guess text width for name and time
|
if (BubbleThemeUtils.getBubbleTimeLocation(holder.bubbleTimeView.context) == BubbleThemeUtils.BUBBLE_TIME_BOTTOM) {
|
||||||
val text = if (attributes.informationData.showInformation) {
|
if (attributes.informationData.showInformation) {
|
||||||
holder.bubbleMemberNameView.text.toString() + " " + holder.bubbleTimeView.text.toString()
|
// Since timeView automatically gets enough space, either within or outside the viewStub, we just need to ensure the member name view has enough space
|
||||||
|
// Somehow not enough without extra space...
|
||||||
|
ceil(BubbleThemeUtils.guessTextWidth(holder.bubbleMemberNameView, holder.bubbleMemberNameView.text.toString() + " ")).toInt()
|
||||||
|
} else {
|
||||||
|
// wrap_content works!
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else if (attributes.informationData.showInformation || attributes.informationData.forceShowTimestamp) {
|
||||||
|
// Guess text width for name and time next to each other
|
||||||
|
val text = if (attributes.informationData.showInformation) {
|
||||||
|
holder.bubbleMemberNameView.text.toString() + " " + holder.bubbleTimeView.text.toString()
|
||||||
|
} else {
|
||||||
|
holder.bubbleTimeView.text.toString()
|
||||||
|
}
|
||||||
|
val textSize = if (attributes.informationData.showInformation) {
|
||||||
|
max(holder.bubbleMemberNameView.textSize, holder.bubbleTimeView.textSize)
|
||||||
|
} else {
|
||||||
|
holder.bubbleTimeView.textSize
|
||||||
|
}
|
||||||
|
ceil(BubbleThemeUtils.guessTextWidth(textSize, text)).toInt()
|
||||||
} else {
|
} else {
|
||||||
holder.bubbleTimeView.text.toString()
|
// Not showing any header, use wrap_content of content only
|
||||||
|
0
|
||||||
}
|
}
|
||||||
val paint = Paint()
|
|
||||||
paint.textSize = if (attributes.informationData.showInformation) {
|
|
||||||
max(holder.bubbleMemberNameView.textSize, holder.bubbleTimeView.textSize)
|
|
||||||
} else {
|
|
||||||
holder.bubbleTimeView.textSize
|
|
||||||
}
|
|
||||||
round(paint.measureText(text)).toInt()
|
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
@ -243,11 +282,23 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
return round(96*density).toInt()
|
return round(96*density).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun allowFooterOverlay(holder: H): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun needsFooterReservation(holder: H): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun reserveFooterSpace(holder: H, width: Int, height: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
|
override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
|
||||||
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
|
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
|
||||||
|
|
||||||
//val bubbleView = holder.eventBaseView
|
//val bubbleView = holder.eventBaseView
|
||||||
val bubbleView = holder.bubbleView
|
val bubbleView = holder.bubbleView
|
||||||
|
val contentInBubble = infoInBubbles(holder.memberNameView.context)
|
||||||
|
|
||||||
when (bubbleStyle) {
|
when (bubbleStyle) {
|
||||||
BubbleThemeUtils.BUBBLE_STYLE_NONE -> {
|
BubbleThemeUtils.BUBBLE_STYLE_NONE -> {
|
||||||
@ -272,7 +323,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
} else {
|
} else {
|
||||||
bubbleView.setBackgroundResource(if (reverseBubble) R.drawable.msg_bubble2_outgoing else R.drawable.msg_bubble2_incoming)
|
bubbleView.setBackgroundResource(if (reverseBubble) R.drawable.msg_bubble2_outgoing else R.drawable.msg_bubble2_incoming)
|
||||||
}
|
}
|
||||||
var tintColor = ColorStateList(
|
val tintColor = ColorStateList(
|
||||||
arrayOf(intArrayOf(0)),
|
arrayOf(intArrayOf(0)),
|
||||||
intArrayOf(ThemeUtils.getColor(bubbleView.context,
|
intArrayOf(ThemeUtils.getColor(bubbleView.context,
|
||||||
if (attributes.informationData.sentByMe) R.attr.sc_message_bg_outgoing else R.attr.sc_message_bg_incoming)
|
if (attributes.informationData.sentByMe) R.attr.sc_message_bg_outgoing else R.attr.sc_message_bg_incoming)
|
||||||
@ -315,6 +366,62 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||||||
round(shortPadding * density).toInt()
|
round(shortPadding * density).toInt()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (contentInBubble) {
|
||||||
|
val anonymousReadReceipt = BubbleThemeUtils.getVisibleAnonymousReadReceipts(holder.bubbleFootView.context,
|
||||||
|
attributes.informationData.readReceiptAnonymous, attributes.informationData.sentByMe)
|
||||||
|
|
||||||
|
when (anonymousReadReceipt) {
|
||||||
|
AnonymousReadReceipt.PROCESSING -> {
|
||||||
|
holder.bubbleFooterReadReceipt.visibility = View.VISIBLE
|
||||||
|
holder.bubbleFooterReadReceipt.setImageResource(R.drawable.ic_processing_msg)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
holder.bubbleFooterReadReceipt.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowFooterOverlay(holder)) {
|
||||||
|
(holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
|
||||||
|
(holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.BELOW)
|
||||||
|
if (needsFooterReservation(holder)) {
|
||||||
|
// Calculate required footer space
|
||||||
|
val timeWidth: Int
|
||||||
|
val timeHeight: Int
|
||||||
|
if (BubbleThemeUtils.getBubbleTimeLocation(holder.bubbleTimeView.context) == BubbleThemeUtils.BUBBLE_TIME_BOTTOM) {
|
||||||
|
// Guess text width for name and time
|
||||||
|
timeWidth = ceil(BubbleThemeUtils.guessTextWidth(holder.bubbleFooterTimeView, attributes.informationData.time.toString())).toInt() + holder.bubbleFooterTimeView.paddingLeft + holder.bubbleFooterTimeView.paddingRight
|
||||||
|
timeHeight = ceil(holder.bubbleFooterTimeView.textSize).toInt() + holder.bubbleFooterTimeView.paddingTop + holder.bubbleFooterTimeView.paddingBottom
|
||||||
|
} else {
|
||||||
|
timeWidth = 0
|
||||||
|
timeHeight = 0
|
||||||
|
}
|
||||||
|
val readReceiptWidth: Int
|
||||||
|
val readReceiptHeight: Int
|
||||||
|
if (anonymousReadReceipt == AnonymousReadReceipt.NONE) {
|
||||||
|
readReceiptWidth = 0
|
||||||
|
readReceiptHeight = 0
|
||||||
|
} else {
|
||||||
|
readReceiptWidth = holder.bubbleFooterReadReceipt.maxWidth + holder.bubbleFooterReadReceipt.paddingLeft + holder.bubbleFooterReadReceipt.paddingRight
|
||||||
|
readReceiptHeight = holder.bubbleFooterReadReceipt.maxHeight + holder.bubbleFooterReadReceipt.paddingTop + holder.bubbleFooterReadReceipt.paddingBottom
|
||||||
|
}
|
||||||
|
|
||||||
|
var footerWidth = timeWidth + readReceiptWidth
|
||||||
|
var footerHeight = max(timeHeight, readReceiptHeight)
|
||||||
|
// Reserve extra padding, if we do have actual content
|
||||||
|
if (footerWidth > 0) {
|
||||||
|
footerWidth += holder.bubbleFootView.paddingLeft + holder.bubbleFootView.paddingRight
|
||||||
|
}
|
||||||
|
if (footerHeight > 0) {
|
||||||
|
footerHeight += holder.bubbleFootView.paddingTop + holder.bubbleFootView.paddingBottom
|
||||||
|
}
|
||||||
|
reserveFooterSpace(holder, footerWidth, footerHeight)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.BELOW, R.id.viewStubContainer)
|
||||||
|
(holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.ALIGN_BOTTOM)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,11 +27,12 @@ import androidx.core.view.isVisible
|
|||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.round
|
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
|
||||||
|
import im.vector.app.features.themes.BubbleThemeUtils
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import kotlin.math.ceil
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
|
abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
|
||||||
@ -115,12 +116,10 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
|
|||||||
val superVal = super.getViewStubMinimumWidth(holder, contentInBubble, showInformation)
|
val superVal = super.getViewStubMinimumWidth(holder, contentInBubble, showInformation)
|
||||||
|
|
||||||
// Guess text width for name and time
|
// Guess text width for name and time
|
||||||
val paint = Paint()
|
|
||||||
paint.textSize = holder.filenameView.textSize
|
|
||||||
val density = holder.filenameView.resources.displayMetrics.density
|
val density = holder.filenameView.resources.displayMetrics.density
|
||||||
// On first call, holder.fileImageView.width is not initialized yet
|
// On first call, holder.fileImageView.width is not initialized yet
|
||||||
val imageWidth = holder.fileImageView.resources.getDimensionPixelSize(R.dimen.chat_avatar_size)
|
val imageWidth = holder.fileImageView.resources.getDimensionPixelSize(R.dimen.chat_avatar_size)
|
||||||
val minimumWidthWithText = round(paint.measureText(filename.toString())).toInt() + imageWidth + 32*density.toInt()
|
val minimumWidthWithText = ceil(BubbleThemeUtils.guessTextWidth(holder.filenameView, filename)).toInt() + imageWidth + 32*density.toInt()
|
||||||
val absoluteMinimumWidth = imageWidth*3
|
val absoluteMinimumWidth = imageWidth*3
|
||||||
return max(max(absoluteMinimumWidth, minimumWidthWithText), superVal)
|
return max(max(absoluteMinimumWidth, minimumWidthWithText), superVal)
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ data class MessageInformationData(
|
|||||||
val readReceipts: List<ReadReceiptData> = emptyList(),
|
val readReceipts: List<ReadReceiptData> = emptyList(),
|
||||||
val referencesInfoData: ReferencesInfoData? = null,
|
val referencesInfoData: ReferencesInfoData? = null,
|
||||||
val sentByMe : Boolean,
|
val sentByMe : Boolean,
|
||||||
|
val readReceiptAnonymous: AnonymousReadReceipt,
|
||||||
val isDirect: Boolean,
|
val isDirect: Boolean,
|
||||||
val e2eDecoration: E2EDecoration = E2EDecoration.NONE
|
val e2eDecoration: E2EDecoration = E2EDecoration.NONE
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
@ -85,4 +86,12 @@ enum class E2EDecoration {
|
|||||||
WARN_SENT_BY_UNKNOWN
|
WARN_SENT_BY_UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class AnonymousReadReceipt {
|
||||||
|
NONE,
|
||||||
|
PROCESSING,
|
||||||
|
// For future use?
|
||||||
|
//SENT,
|
||||||
|
//READ
|
||||||
|
}
|
||||||
|
|
||||||
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
fun ReadReceiptData.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
|
||||||
|
@ -19,12 +19,12 @@ package im.vector.app.features.home.room.detail.timeline.item
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.text.method.MovementMethod
|
import android.text.method.MovementMethod
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
|
||||||
import androidx.core.text.PrecomputedTextCompat
|
import androidx.core.text.PrecomputedTextCompat
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
import com.airbnb.epoxy.EpoxyAttribute
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
import com.airbnb.epoxy.EpoxyModelClass
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.ui.views.FooteredTextView
|
||||||
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
|
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base)
|
||||||
@ -76,7 +76,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
|||||||
override fun getViewType() = STUB_ID
|
override fun getViewType() = STUB_ID
|
||||||
|
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val messageView by bind<AppCompatTextView>(R.id.messageTextView)
|
val messageView by bind<FooteredTextView>(R.id.messageTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -86,4 +86,17 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
|||||||
override fun messageBubbleAllowed(context: Context): Boolean {
|
override fun messageBubbleAllowed(context: Context): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun allowFooterOverlay(holder: Holder): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun needsFooterReservation(holder: Holder): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reserveFooterSpace(holder: Holder, width: Int, height: Int) {
|
||||||
|
holder.messageView.footerWidth = width
|
||||||
|
holder.messageView.footerHeight = height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
|||||||
const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY"
|
const val SETTINGS_SHOW_URL_PREVIEW_KEY = "SETTINGS_SHOW_URL_PREVIEW_KEY"
|
||||||
private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY"
|
private const val SETTINGS_SEND_TYPING_NOTIF_KEY = "SETTINGS_SEND_TYPING_NOTIF_KEY"
|
||||||
private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY"
|
private const val SETTINGS_ENABLE_MARKDOWN_KEY = "SETTINGS_ENABLE_MARKDOWN_KEY"
|
||||||
private const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
|
const val SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY = "SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY"
|
||||||
private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY"
|
private const val SETTINGS_12_24_TIMESTAMPS_KEY = "SETTINGS_12_24_TIMESTAMPS_KEY"
|
||||||
private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY"
|
private const val SETTINGS_SHOW_READ_RECEIPTS_KEY = "SETTINGS_SHOW_READ_RECEIPTS_KEY"
|
||||||
private const val SETTINGS_SHOW_REDACTED_KEY = "SETTINGS_SHOW_REDACTED_KEY"
|
private const val SETTINGS_SHOW_REDACTED_KEY = "SETTINGS_SHOW_REDACTED_KEY"
|
||||||
|
@ -27,6 +27,7 @@ import im.vector.app.R
|
|||||||
import im.vector.app.core.extensions.restart
|
import im.vector.app.core.extensions.restart
|
||||||
import im.vector.app.core.preference.VectorListPreference
|
import im.vector.app.core.preference.VectorListPreference
|
||||||
import im.vector.app.core.preference.VectorPreference
|
import im.vector.app.core.preference.VectorPreference
|
||||||
|
import im.vector.app.core.preference.VectorSwitchPreference
|
||||||
import im.vector.app.features.configuration.VectorConfiguration
|
import im.vector.app.features.configuration.VectorConfiguration
|
||||||
import im.vector.app.features.themes.BubbleThemeUtils
|
import im.vector.app.features.themes.BubbleThemeUtils
|
||||||
import im.vector.app.features.themes.ThemeUtils
|
import im.vector.app.features.themes.ThemeUtils
|
||||||
@ -40,6 +41,9 @@ class VectorSettingsPreferencesFragment @Inject constructor(
|
|||||||
override var titleRes = R.string.settings_preferences
|
override var titleRes = R.string.settings_preferences
|
||||||
override val preferenceXmlRes = R.xml.vector_settings_preferences
|
override val preferenceXmlRes = R.xml.vector_settings_preferences
|
||||||
|
|
||||||
|
private var bubbleTimeLocationPref: VectorListPreference? = null
|
||||||
|
private var alwaysShowTimestampsPref: VectorSwitchPreference? = null
|
||||||
|
|
||||||
private val selectedLanguagePreference by lazy {
|
private val selectedLanguagePreference by lazy {
|
||||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY)!!
|
findPreference<VectorPreference>(VectorPreferences.SETTINGS_INTERFACE_LANGUAGE_PREFERENCE_KEY)!!
|
||||||
}
|
}
|
||||||
@ -85,12 +89,31 @@ class VectorSettingsPreferencesFragment @Inject constructor(
|
|||||||
darkThemePref.parent?.removePreference(darkThemePref)
|
darkThemePref.parent?.removePreference(darkThemePref)
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<VectorListPreference>(BubbleThemeUtils.BUBBLE_STYLE_KEY)!!
|
val bubbleStylePreference = findPreference<VectorListPreference>(BubbleThemeUtils.BUBBLE_STYLE_KEY)
|
||||||
.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ ->
|
bubbleStylePreference!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
BubbleThemeUtils.invalidateBubbleStyle()
|
BubbleThemeUtils.invalidateBubbleStyle()
|
||||||
|
updateBubbleDependencies(
|
||||||
|
bubbleStyle = newValue as String,
|
||||||
|
bubbleTimeLocation = bubbleTimeLocationPref!!.value
|
||||||
|
)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bubbleTimeLocationPref = findPreference<VectorListPreference>(BubbleThemeUtils.BUBBLE_TIME_LOCATION_KEY)
|
||||||
|
alwaysShowTimestampsPref = findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_ALWAYS_SHOW_TIMESTAMPS_KEY)
|
||||||
|
bubbleTimeLocationPref!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
|
BubbleThemeUtils.invalidateBubbleStyle()
|
||||||
|
updateBubbleDependencies(
|
||||||
|
bubbleStyle = bubbleStylePreference.value,
|
||||||
|
bubbleTimeLocation = newValue as String
|
||||||
|
)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
updateBubbleDependencies(
|
||||||
|
bubbleStyle = bubbleStylePreference.value,
|
||||||
|
bubbleTimeLocation = bubbleTimeLocationPref!!.value
|
||||||
|
)
|
||||||
|
|
||||||
// Url preview
|
// Url preview
|
||||||
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_SHOW_URL_PREVIEW_KEY)!!.let {
|
findPreference<SwitchPreference>(VectorPreferences.SETTINGS_SHOW_URL_PREVIEW_KEY)!!.let {
|
||||||
/*
|
/*
|
||||||
@ -202,4 +225,9 @@ class VectorSettingsPreferencesFragment @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateBubbleDependencies(bubbleStyle: String, bubbleTimeLocation: String) {
|
||||||
|
bubbleTimeLocationPref?.setEnabled(BubbleThemeUtils.isBubbleTimeLocationSettingAllowed(bubbleStyle))
|
||||||
|
alwaysShowTimestampsPref?.setEnabled(!BubbleThemeUtils.forceAlwaysShowTimestamps(bubbleStyle, bubbleTimeLocation))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
package im.vector.app.features.themes
|
package im.vector.app.features.themes
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.AnonymousReadReceipt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Util class for managing themes.
|
* Util class for managing themes.
|
||||||
*/
|
*/
|
||||||
object BubbleThemeUtils {
|
object BubbleThemeUtils {
|
||||||
const val BUBBLE_STYLE_KEY = "BUBBLE_STYLE_KEY"
|
const val BUBBLE_STYLE_KEY = "BUBBLE_STYLE_KEY"
|
||||||
|
const val BUBBLE_TIME_LOCATION_KEY = "BUBBLE_TIME_LOCATION_KEY"
|
||||||
|
|
||||||
const val BUBBLE_STYLE_NONE = "none"
|
const val BUBBLE_STYLE_NONE = "none"
|
||||||
const val BUBBLE_STYLE_START = "start"
|
const val BUBBLE_STYLE_START = "start"
|
||||||
const val BUBBLE_STYLE_BOTH = "both"
|
const val BUBBLE_STYLE_BOTH = "both"
|
||||||
|
const val BUBBLE_TIME_TOP = "top"
|
||||||
|
const val BUBBLE_TIME_BOTTOM = "bottom"
|
||||||
|
|
||||||
// Special case of BUBBLE_STYLE_BOTH, to allow non-bubble items align to the sender either way
|
// Special case of BUBBLE_STYLE_BOTH, to allow non-bubble items align to the sender either way
|
||||||
// (not meant for user setting, but internal use)
|
// (not meant for user setting, but internal use)
|
||||||
@ -20,6 +26,7 @@ object BubbleThemeUtils {
|
|||||||
const val BUBBLE_STYLE_START_HIDDEN = "start_hidden"
|
const val BUBBLE_STYLE_START_HIDDEN = "start_hidden"
|
||||||
|
|
||||||
private var mBubbleStyle: String = ""
|
private var mBubbleStyle: String = ""
|
||||||
|
private var mBubbleTimeLocation: String = ""
|
||||||
|
|
||||||
fun getBubbleStyle(context: Context): String {
|
fun getBubbleStyle(context: Context): String {
|
||||||
if (mBubbleStyle == "") {
|
if (mBubbleStyle == "") {
|
||||||
@ -28,6 +35,26 @@ object BubbleThemeUtils {
|
|||||||
return mBubbleStyle
|
return mBubbleStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getBubbleTimeLocation(context: Context): String {
|
||||||
|
if (mBubbleTimeLocation == "") {
|
||||||
|
mBubbleTimeLocation = PreferenceManager.getDefaultSharedPreferences(context).getString(BUBBLE_TIME_LOCATION_KEY, BUBBLE_TIME_BOTTOM)!!
|
||||||
|
}
|
||||||
|
if (!isBubbleTimeLocationSettingAllowed(context)) {
|
||||||
|
return BUBBLE_TIME_TOP;
|
||||||
|
}
|
||||||
|
return mBubbleTimeLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVisibleAnonymousReadReceipts(context: Context, readReceipt: AnonymousReadReceipt, sentByMe: Boolean): AnonymousReadReceipt {
|
||||||
|
// TODO
|
||||||
|
if (false) android.util.Log.e("SCSCSC", " " + context)
|
||||||
|
return if (sentByMe && (/*TODO setting*/ true || readReceipt == AnonymousReadReceipt.PROCESSING)) {
|
||||||
|
readReceipt
|
||||||
|
} else {
|
||||||
|
AnonymousReadReceipt.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun drawsActualBubbles(bubbleStyle: String): Boolean {
|
fun drawsActualBubbles(bubbleStyle: String): Boolean {
|
||||||
return bubbleStyle == BUBBLE_STYLE_START || bubbleStyle == BUBBLE_STYLE_BOTH
|
return bubbleStyle == BUBBLE_STYLE_START || bubbleStyle == BUBBLE_STYLE_BOTH
|
||||||
}
|
}
|
||||||
@ -38,5 +65,36 @@ object BubbleThemeUtils {
|
|||||||
|
|
||||||
fun invalidateBubbleStyle() {
|
fun invalidateBubbleStyle() {
|
||||||
mBubbleStyle = ""
|
mBubbleStyle = ""
|
||||||
|
mBubbleTimeLocation = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun guessTextWidth(view: TextView): Float {
|
||||||
|
return guessTextWidth(view, view.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun guessTextWidth(view: TextView, text: CharSequence): Float {
|
||||||
|
return guessTextWidth(view.textSize, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
fun guessTextWidth(textSize: Float, text: CharSequence): Float {
|
||||||
|
val paint = Paint()
|
||||||
|
paint.textSize = textSize
|
||||||
|
return paint.measureText(text.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceAlwaysShowTimestamps(bubbleStyle: String, bubbleTimeLocation: String): Boolean {
|
||||||
|
return isBubbleTimeLocationSettingAllowed(bubbleStyle) && bubbleTimeLocation == BUBBLE_TIME_BOTTOM
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isBubbleTimeLocationSettingAllowed(bubbleStyle: String): Boolean {
|
||||||
|
return bubbleStyle == BUBBLE_STYLE_BOTH || bubbleStyle == BUBBLE_STYLE_BOTH_HIDDEN
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceAlwaysShowTimestamps(context: Context): Boolean {
|
||||||
|
return forceAlwaysShowTimestamps(getBubbleStyle(context), getBubbleTimeLocation(context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isBubbleTimeLocationSettingAllowed(context: Context): Boolean {
|
||||||
|
return isBubbleTimeLocationSettingAllowed(getBubbleStyle(context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9
vector/src/main/res/drawable/ic_processing_msg.xml
Normal file
9
vector/src/main/res/drawable/ic_processing_msg.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="16dp"
|
||||||
|
android:width="16dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z" />
|
||||||
|
</vector>
|
@ -84,8 +84,7 @@
|
|||||||
android:layout_below="@id/messageMemberNameView"
|
android:layout_below="@id/messageMemberNameView"
|
||||||
android:layout_toEndOf="@id/messageStartGuideline"
|
android:layout_toEndOf="@id/messageStartGuideline"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="0dp"
|
android:layout_marginEnd="0dp">
|
||||||
>
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:id="@+id/bubbleView"
|
android:id="@+id/bubbleView"
|
||||||
@ -199,6 +198,41 @@
|
|||||||
tools:text="@tools:sample/date/hhmm" />
|
tools:text="@tools:sample/date/hhmm" />
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_alignEnd="@id/viewStubContainer"
|
||||||
|
tools:layout_alignBottom="@id/viewStubContainer"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:id="@+id/bubbleFootView">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bubbleFooterMessageTimeView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="4dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="?riotx_text_secondary"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
tools:text="@tools:sample/date/hhmm" />
|
||||||
|
<!-- We read maxWidth and maxHeight from code to guess footer size -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bubbleFooterReadReceipt"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxWidth="16dp"
|
||||||
|
android:maxHeight="16dp"
|
||||||
|
android:paddingStart="4dp"
|
||||||
|
android:paddingEnd="0dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
app:tint="?riotx_text_secondary"
|
||||||
|
tools:src="@drawable/ic_processing_msg" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<im.vector.app.core.ui.views.WrapWidthTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<im.vector.app.core.ui.views.FooteredTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/messageTextView"
|
android:id="@+id/messageTextView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
<string name="bubble_style_none">Keine</string>
|
<string name="bubble_style_none">Keine</string>
|
||||||
<string name="bubble_style_start">Selbe Seite</string>
|
<string name="bubble_style_start">Selbe Seite</string>
|
||||||
<string name="bubble_style_both">Beide Seiten</string>
|
<string name="bubble_style_both">Beide Seiten</string>
|
||||||
|
<string name="bubble_time_location">Zeitstempel-Platzierung</string>
|
||||||
|
<string name="bubble_time_location_top">Oben</string>
|
||||||
|
<string name="bubble_time_location_bottom">Unten</string>
|
||||||
|
|
||||||
<string name="settings_unimportant_counter_badge">Zähle unwichtige Chat-Ereignisse</string>
|
<string name="settings_unimportant_counter_badge">Zähle unwichtige Chat-Ereignisse</string>
|
||||||
<string name="settings_unimportant_counter_badge_summary">Betrachte auch Chats ohne Benachrichtigung beim Zählen der ungelesenen Nachrichten pro Kategorie</string>
|
<string name="settings_unimportant_counter_badge_summary">Betrachte auch Chats ohne Benachrichtigung beim Zählen der ungelesenen Nachrichten pro Kategorie</string>
|
||||||
|
@ -12,6 +12,15 @@
|
|||||||
<item>both</item>
|
<item>both</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="bubble_time_location_entries" translatable="false">
|
||||||
|
<item>@string/bubble_time_location_top</item>
|
||||||
|
<item>@string/bubble_time_location_bottom</item>
|
||||||
|
</string-array>
|
||||||
|
<string-array name="bubble_time_location_values" translatable="false">
|
||||||
|
<item>top</item>
|
||||||
|
<item>bottom</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
<string-array name="room_unread_kind_entries" translatable="false">
|
<string-array name="room_unread_kind_entries" translatable="false">
|
||||||
<item>@string/settings_room_unread_kind_default</item>
|
<item>@string/settings_room_unread_kind_default</item>
|
||||||
<item>@string/settings_room_unread_kind_content</item>
|
<item>@string/settings_room_unread_kind_content</item>
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
<string name="bubble_style_none">None</string>
|
<string name="bubble_style_none">None</string>
|
||||||
<string name="bubble_style_start">Same side</string>
|
<string name="bubble_style_start">Same side</string>
|
||||||
<string name="bubble_style_both">Both sides</string>
|
<string name="bubble_style_both">Both sides</string>
|
||||||
|
<string name="bubble_time_location">Timestamp location</string>
|
||||||
|
<string name="bubble_time_location_top">Top</string>
|
||||||
|
<string name="bubble_time_location_bottom">Bottom</string>
|
||||||
|
|
||||||
<string name="settings_unimportant_counter_badge">Count unimportant chat events</string>
|
<string name="settings_unimportant_counter_badge">Count unimportant chat events</string>
|
||||||
<string name="settings_unimportant_counter_badge_summary">Include chats without notifications in the category unread counter</string>
|
<string name="settings_unimportant_counter_badge_summary">Include chats without notifications in the category unread counter</string>
|
||||||
|
@ -39,6 +39,15 @@
|
|||||||
android:title="@string/bubble_style"
|
android:title="@string/bubble_style"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<im.vector.app.core.preference.VectorListPreference
|
||||||
|
android:key="BUBBLE_TIME_LOCATION_KEY"
|
||||||
|
android:defaultValue="bottom"
|
||||||
|
android:title="@string/bubble_time_location"
|
||||||
|
android:entries="@array/bubble_time_location_entries"
|
||||||
|
android:entryValues="@array/bubble_time_location_values"
|
||||||
|
android:summary="%s"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<im.vector.app.core.preference.VectorPreference
|
<im.vector.app.core.preference.VectorPreference
|
||||||
android:dialogTitle="@string/font_size"
|
android:dialogTitle="@string/font_size"
|
||||||
android:key="SETTINGS_INTERFACE_TEXT_SIZE_KEY"
|
android:key="SETTINGS_INTERFACE_TEXT_SIZE_KEY"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user