mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 04:08:44 +03:00
Message footer: handle small width/height pictures
Change-Id: Ib8554c5057f09277cb9ef2a29a57d3a85cf35417
This commit is contained in:
parent
1481a3edd3
commit
e9e522603d
4 changed files with 153 additions and 38 deletions
|
@ -343,10 +343,20 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show the footer in front of the viewStub
|
||||
*/
|
||||
open fun allowFooterOverlay(holder: H): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to show the footer aligned below the viewStub - requires enough width!
|
||||
*/
|
||||
open fun allowFooterBelow(holder: H): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
open fun needsFooterReservation(holder: H): Boolean {
|
||||
return false
|
||||
}
|
||||
|
@ -363,6 +373,45 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
(attributes.informationData.isDirect && attributes.informationData.senderId == attributes.informationData.dmChatPartnerId)
|
||||
}
|
||||
|
||||
protected fun getFooterMeasures(holder: H): Array<Int> {
|
||||
val anonymousReadReceipt = BubbleThemeUtils.getVisibleAnonymousReadReceipts(holder.bubbleFootView.context,
|
||||
attributes.informationData.readReceiptAnonymous, attributes.informationData.sentByMe)
|
||||
return getFooterMeasures(holder, anonymousReadReceipt)
|
||||
}
|
||||
|
||||
private fun getFooterMeasures(holder: H, anonymousReadReceipt: AnonymousReadReceipt): Array<Int> {
|
||||
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
|
||||
}
|
||||
return arrayOf(footerWidth, footerHeight)
|
||||
}
|
||||
|
||||
override fun setBubbleLayout(holder: H, bubbleStyle: String, bubbleStyleSetting: String, reverseBubble: Boolean) {
|
||||
super.setBubbleLayout(holder, bubbleStyle, bubbleStyleSetting, reverseBubble)
|
||||
|
||||
|
@ -442,53 +491,60 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
|||
}
|
||||
}
|
||||
|
||||
val footerLayoutParams = holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams
|
||||
var footerMarginStartDp = 4
|
||||
var footerMarginEndDp = 1
|
||||
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)
|
||||
footerLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
|
||||
footerLayoutParams.addRule(RelativeLayout.ALIGN_END, R.id.viewStubContainer)
|
||||
footerLayoutParams.removeRule(RelativeLayout.BELOW)
|
||||
footerLayoutParams.removeRule(RelativeLayout.END_OF)
|
||||
if (needsFooterReservation(holder)) {
|
||||
// Remove style used when not having reserved space
|
||||
removeFooterOverlayStyle(holder, density)
|
||||
|
||||
// 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
|
||||
}
|
||||
val footerMeasures = getFooterMeasures(holder, anonymousReadReceipt)
|
||||
val footerWidth = footerMeasures[0]
|
||||
val footerHeight = footerMeasures[1]
|
||||
|
||||
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 {
|
||||
// We have no reserved space -> style it to ensure readability on arbitrary backgrounds
|
||||
styleFooterOverlay(holder, density)
|
||||
}
|
||||
} else {
|
||||
(holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.BELOW, R.id.viewStubContainer)
|
||||
(holder.bubbleFootView.layoutParams as RelativeLayout.LayoutParams).removeRule(RelativeLayout.ALIGN_BOTTOM)
|
||||
when {
|
||||
allowFooterBelow(holder) -> {
|
||||
footerLayoutParams.addRule(RelativeLayout.BELOW, R.id.viewStubContainer)
|
||||
footerLayoutParams.addRule(RelativeLayout.ALIGN_END, R.id.viewStubContainer)
|
||||
footerLayoutParams.removeRule(RelativeLayout.ALIGN_BOTTOM)
|
||||
footerLayoutParams.removeRule(RelativeLayout.END_OF)
|
||||
footerLayoutParams.removeRule(RelativeLayout.START_OF)
|
||||
}
|
||||
reverseBubble -> /* force footer on the left / at the start */ {
|
||||
footerLayoutParams.addRule(RelativeLayout.START_OF, R.id.viewStubContainer)
|
||||
footerLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
|
||||
footerLayoutParams.removeRule(RelativeLayout.ALIGN_END)
|
||||
footerLayoutParams.removeRule(RelativeLayout.END_OF)
|
||||
footerLayoutParams.removeRule(RelativeLayout.BELOW)
|
||||
// Reverse margins
|
||||
footerMarginStartDp = 1
|
||||
// 4 as previously the start margin, +4 to compensate the missing inner padding for the textView which we have on the other side
|
||||
footerMarginEndDp = 8
|
||||
}
|
||||
else -> /* footer on the right / at the end */ {
|
||||
footerLayoutParams.addRule(RelativeLayout.END_OF, R.id.viewStubContainer)
|
||||
footerLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.viewStubContainer)
|
||||
footerLayoutParams.removeRule(RelativeLayout.ALIGN_END)
|
||||
footerLayoutParams.removeRule(RelativeLayout.BELOW)
|
||||
footerLayoutParams.removeRule(RelativeLayout.START_OF)
|
||||
}
|
||||
}
|
||||
removeFooterOverlayStyle(holder, density)
|
||||
}
|
||||
footerLayoutParams.marginStart = round(footerMarginStartDp*density).toInt()
|
||||
footerLayoutParams.marginEnd = round(footerMarginEndDp*density).toInt()
|
||||
}
|
||||
if (bubbleStyle == BubbleThemeUtils.BUBBLE_STYLE_BOTH_HIDDEN) {
|
||||
// We need to align the non-bubble member name view to pseudo bubbles
|
||||
|
|
|
@ -54,9 +54,28 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
|||
@EpoxyAttribute
|
||||
lateinit var contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder
|
||||
|
||||
var lastAllowedFooterOverlay: Boolean = true
|
||||
var lastShowFooterBellow: Boolean = true
|
||||
var forceAllowFooterOverlay: Boolean? = null
|
||||
var showFooterBellow: Boolean = true
|
||||
|
||||
override fun bind(holder: Holder) {
|
||||
forceAllowFooterOverlay = null
|
||||
super.bind(holder)
|
||||
imageContentRenderer.render(mediaData, mode, holder.imageView)
|
||||
|
||||
val onImageSizeListener = object: ImageContentRenderer.OnImageSizeListener {
|
||||
override fun onImageSizeUpdated(width: Int, height: Int) {
|
||||
// Image size change -> different footer space situation possible
|
||||
val footerMeasures = getFooterMeasures(holder)
|
||||
forceAllowFooterOverlay = shouldAllowFooterOverlay(footerMeasures, width, height)
|
||||
val newShowFooterBellow = shouldShowFooterBellow(footerMeasures, width)
|
||||
if (lastAllowedFooterOverlay != forceAllowFooterOverlay || newShowFooterBellow != lastShowFooterBellow) {
|
||||
showFooterBellow = newShowFooterBellow
|
||||
updateMessageBubble(holder.imageView.context, holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
imageContentRenderer.render(mediaData, mode, holder.imageView, onImageSizeListener)
|
||||
if (!attributes.informationData.sendState.hasFailed()) {
|
||||
contentUploadStateTrackerBinder.bind(
|
||||
attributes.informationData.eventId,
|
||||
|
@ -122,8 +141,42 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
|||
}
|
||||
}
|
||||
|
||||
private fun shouldAllowFooterOverlay(footerMeasures: Array<Int>, imageWidth: Int, imageHeight: Int): Boolean {
|
||||
val footerWidth = footerMeasures[0]
|
||||
val footerHeight = footerMeasures[1]
|
||||
return imageWidth > 1.5*footerWidth && imageHeight > 1.5*footerHeight
|
||||
}
|
||||
|
||||
private fun shouldShowFooterBellow(footerMeasures: Array<Int>, imageWidth: Int): Boolean {
|
||||
// Only show footer bellow if the width is not the limiting factor (or it will get cut).
|
||||
// Otherwise, we can not be sure in this place that we'll have enough space on the side
|
||||
val footerWidth = footerMeasures[0]
|
||||
return imageWidth > 1.5*footerWidth
|
||||
}
|
||||
|
||||
override fun allowFooterOverlay(holder: Holder): Boolean {
|
||||
return true
|
||||
val rememberedAllowFooterOverlay = forceAllowFooterOverlay
|
||||
if (rememberedAllowFooterOverlay != null) {
|
||||
lastAllowedFooterOverlay = rememberedAllowFooterOverlay
|
||||
return rememberedAllowFooterOverlay
|
||||
}
|
||||
val imageWidth = holder.imageView.width
|
||||
val imageHeight = holder.imageView.height
|
||||
if (imageWidth == 0 && imageHeight == 0) {
|
||||
// Not initialised yet, assume true
|
||||
lastAllowedFooterOverlay = true
|
||||
return true
|
||||
}
|
||||
// If the footer covers most of the image, or is even larger than the image, move it outside
|
||||
val footerMeasures = getFooterMeasures(holder)
|
||||
lastAllowedFooterOverlay = shouldAllowFooterOverlay(footerMeasures, imageWidth, imageHeight)
|
||||
return lastAllowedFooterOverlay
|
||||
}
|
||||
|
||||
override fun allowFooterBelow(holder: Holder): Boolean {
|
||||
val showBellow = showFooterBellow
|
||||
lastShowFooterBellow = showBellow
|
||||
return showBellow
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -110,12 +110,13 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
|||
.into(imageView)
|
||||
}
|
||||
|
||||
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
||||
fun render(data: Data, mode: Mode, imageView: ImageView, onImageSizeListener: OnImageSizeListener? = null) {
|
||||
val size = processSize(data, mode)
|
||||
imageView.updateLayoutParams {
|
||||
width = size.width
|
||||
height = size.height
|
||||
}
|
||||
onImageSizeListener?.onImageSizeUpdated(size.width, size.height)
|
||||
// a11y
|
||||
imageView.contentDescription = data.filename
|
||||
|
||||
|
@ -134,6 +135,7 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
|||
width = newSize.width
|
||||
height = newSize.height
|
||||
}
|
||||
onImageSizeListener?.onImageSizeUpdated(newSize.width, newSize.height)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
@ -350,4 +352,8 @@ class ImageContentRenderer @Inject constructor(private val localFilesHelper: Loc
|
|||
}
|
||||
return Size(finalWidth, finalHeight)
|
||||
}
|
||||
|
||||
interface OnImageSizeListener {
|
||||
fun onImageSizeUpdated(width: Int, height: Int)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,11 +208,11 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_alignEnd="@id/viewStubContainer"
|
||||
android:layout_marginEnd="1dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
tools:layout_marginStart="4dp"
|
||||
tools:layout_marginEnd="1dp"
|
||||
tools:layout_alignBottom="@id/viewStubContainer"
|
||||
tools:layout_alignEnd="@id/viewStubContainer"
|
||||
tools:paddingTop="4dp"
|
||||
android:id="@+id/bubbleFootView">
|
||||
<TextView
|
||||
|
|
Loading…
Add table
Reference in a new issue