mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-10 00:08:55 +03:00
[merge,WIP] interface'd FooteredTextView
Change-Id: I62f09fff7d094ebb3bf6690b17c951e4e48e80c7
This commit is contained in:
parent
51274af2fe
commit
8c2b9ec6f4
9 changed files with 202 additions and 145 deletions
vector/src/main
java/im/vector/app
core/ui/views
features/home/room/detail/timeline/item
res/layout
|
@ -0,0 +1,149 @@
|
||||||
|
package im.vector.app.core.ui.views
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.text.Layout
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import androidx.core.text.getSpans
|
||||||
|
import androidx.core.text.toSpanned
|
||||||
|
import androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.features.html.HtmlCodeSpan
|
||||||
|
import io.noties.markwon.core.spans.EmphasisSpan
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
interface AbstractFooteredTextView {
|
||||||
|
|
||||||
|
fun getAppCompatTextView(): AppCompatTextView
|
||||||
|
fun setMeasuredDimensionExposed(measuredWidth: Int, measuredHeight: Int)
|
||||||
|
|
||||||
|
val footerState: FooterState
|
||||||
|
|
||||||
|
class FooterState {
|
||||||
|
var footerHeight: Int = 0
|
||||||
|
var footerWidth: Int = 0
|
||||||
|
//var widthLimit: Float = 0f
|
||||||
|
|
||||||
|
// Some Rect to use during draw, since we should not alloc it during draw
|
||||||
|
val testBounds = Rect()
|
||||||
|
|
||||||
|
// Workaround to RTL languages with non-RTL content messages aligning left instead of start
|
||||||
|
var requiredHorizontalCanvasMove = 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateDimensionsWithFooter(widthMeasureSpec: Int, heightMeasureSpec: Int) = with(getAppCompatTextView()) {
|
||||||
|
// Default case
|
||||||
|
footerState.requiredHorizontalCanvasMove = 0f
|
||||||
|
|
||||||
|
// Get max available width
|
||||||
|
//val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
||||||
|
val widthSize = View.MeasureSpec.getSize(widthMeasureSpec)
|
||||||
|
//val widthLimit = if (widthMode == MeasureSpec.AT_MOST) { widthSize.toFloat() } else { Float.MAX_VALUE }
|
||||||
|
val widthLimit = widthSize.toFloat()
|
||||||
|
/*
|
||||||
|
// Sometimes, widthLimit is not the actual limit, so remember it... ?
|
||||||
|
if (this.widthLimit > widthLimit) {
|
||||||
|
widthLimit = this.widthLimit
|
||||||
|
} else {
|
||||||
|
this.widthLimit = widthLimit
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
val lastLine = layout.lineCount - 1
|
||||||
|
|
||||||
|
// Let's check if the last line's text has the same RTL behaviour as the layout direction.
|
||||||
|
val viewIsRtl = layoutDirection == LAYOUT_DIRECTION_RTL
|
||||||
|
val looksLikeRtl = layout.getParagraphDirection(lastLine) == Layout.DIR_RIGHT_TO_LEFT
|
||||||
|
/*
|
||||||
|
val lastVisibleCharacter = layout.getLineVisibleEnd(lastLine) - 1
|
||||||
|
val looksLikeRtl = layout.isRtlCharAt(lastVisibleCharacter)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get required width for all lines
|
||||||
|
var maxLineWidth = 0f
|
||||||
|
for (i in 0 until layout.lineCount) {
|
||||||
|
// For some reasons, the getLineWidth is not working too well with RTL lines when rendering replies.
|
||||||
|
// -> https://github.com/SchildiChat/SchildiChat-android/issues/74
|
||||||
|
// However, the bounds method is a little generous sometimes (reserving too much space),
|
||||||
|
// so we don't want to use it over getLineWidth() unless required.
|
||||||
|
maxLineWidth = if (layout.getParagraphDirection(i) == Layout.DIR_RIGHT_TO_LEFT) {
|
||||||
|
layout.getLineBounds(i, footerState.testBounds)
|
||||||
|
max((footerState.testBounds.right - footerState.testBounds.left).toFloat(), maxLineWidth)
|
||||||
|
} else {
|
||||||
|
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(lastLine)
|
||||||
|
|
||||||
|
// Required width if putting footer in the same line as the last line
|
||||||
|
val widthWithHorizontalFooter = (
|
||||||
|
if (looksLikeRtl == viewIsRtl)
|
||||||
|
widthLastLine
|
||||||
|
else
|
||||||
|
(maxLineWidth + resources.getDimensionPixelSize(R.dimen.sc_footer_rtl_mismatch_extra_padding))
|
||||||
|
) + footerState.footerWidth
|
||||||
|
|
||||||
|
// If the last line is a multi-line code block, we have never space in the last line (as the black background always uses full width)
|
||||||
|
val forceNewlineFooter: Boolean
|
||||||
|
// For italic text, we need some extra space due to a wrap_content bug: https://stackoverflow.com/q/4353836
|
||||||
|
val addItalicPadding: Boolean
|
||||||
|
|
||||||
|
if (text is Spannable || text is Spanned) {
|
||||||
|
val span = text.toSpanned()
|
||||||
|
// If not found, -1+1 = 0
|
||||||
|
val lastLineStart = span.lastIndexOf("\n") + 1
|
||||||
|
val lastLineCodeSpans = span.getSpans<HtmlCodeSpan>(lastLineStart)
|
||||||
|
forceNewlineFooter = lastLineCodeSpans.any { it.isBlock }
|
||||||
|
addItalicPadding = span.getSpans<EmphasisSpan>().isNotEmpty()
|
||||||
|
} else {
|
||||||
|
forceNewlineFooter = false
|
||||||
|
addItalicPadding = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is there space for a horizontal footer?
|
||||||
|
if (widthWithHorizontalFooter <= widthLimit && !forceNewlineFooter) {
|
||||||
|
// Reserve extra horizontal footer space if necessary
|
||||||
|
if (widthWithHorizontalFooter > newWidth) {
|
||||||
|
newWidth = ceil(widthWithHorizontalFooter).toInt()
|
||||||
|
|
||||||
|
if (viewIsRtl) {
|
||||||
|
footerState.requiredHorizontalCanvasMove = widthWithHorizontalFooter - measuredWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Reserve vertical footer space
|
||||||
|
newHeight += footerState.footerHeight
|
||||||
|
// Ensure enough width for footer bellow
|
||||||
|
newWidth = max(newWidth, footerState.footerWidth +
|
||||||
|
resources.getDimensionPixelSize(R.dimen.sc_footer_padding_compensation) +
|
||||||
|
2 * resources.getDimensionPixelSize(R.dimen.sc_footer_overlay_padding))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addItalicPadding) {
|
||||||
|
newWidth += resources.getDimensionPixelSize(R.dimen.italic_text_view_extra_padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
//setMeasuredDimension(newWidth, newHeight)
|
||||||
|
Pair(newWidth, newHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateFooterOnPreDraw(canvas: Canvas?) {
|
||||||
|
// Workaround to RTL languages with non-RTL content messages aligning left instead of start
|
||||||
|
if (footerState.requiredHorizontalCanvasMove > 0f) {
|
||||||
|
canvas?.translate(footerState.requiredHorizontalCanvasMove, 0f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package im.vector.app.core.ui.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
|
import io.element.android.wysiwyg.EditorStyledTextView
|
||||||
|
|
||||||
|
class FooteredEditorStyledTextView @JvmOverloads constructor(
|
||||||
|
context: Context,
|
||||||
|
attrs: AttributeSet? = null,
|
||||||
|
defStyleAttr: Int = 0
|
||||||
|
): EditorStyledTextView(context, attrs, defStyleAttr), AbstractFooteredTextView {
|
||||||
|
|
||||||
|
override val footerState: AbstractFooteredTextView.FooterState = AbstractFooteredTextView.FooterState()
|
||||||
|
override fun getAppCompatTextView(): AppCompatTextView = this
|
||||||
|
override fun setMeasuredDimensionExposed(measuredWidth: Int, measuredHeight: Int) = setMeasuredDimension(measuredWidth, measuredHeight)
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
// First, let super measure the content for our normal TextView use
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
|
val updatedMeasures = updateDimensionsWithFooter(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
setMeasuredDimension(updatedMeasures.first, updatedMeasures.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas?) {
|
||||||
|
updateFooterOnPreDraw(canvas)
|
||||||
|
|
||||||
|
super.onDraw(canvas)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,148 +2,29 @@ package im.vector.app.core.ui.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Rect
|
|
||||||
import android.text.Layout
|
|
||||||
import android.text.Spannable
|
|
||||||
import android.text.Spanned
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
import androidx.core.text.getSpans
|
|
||||||
import androidx.core.text.toSpanned
|
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.features.html.HtmlCodeSpan
|
|
||||||
import io.noties.markwon.core.spans.EmphasisSpan
|
|
||||||
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(
|
class FooteredTextView @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0
|
defStyleAttr: Int = 0
|
||||||
): AppCompatTextView(context, attrs, defStyleAttr) {
|
): AppCompatTextView(context, attrs, defStyleAttr), AbstractFooteredTextView {
|
||||||
|
|
||||||
var footerHeight: Int = 0
|
override val footerState: AbstractFooteredTextView.FooterState = AbstractFooteredTextView.FooterState()
|
||||||
var footerWidth: Int = 0
|
override fun getAppCompatTextView(): AppCompatTextView = this
|
||||||
//var widthLimit: Float = 0f
|
override fun setMeasuredDimensionExposed(measuredWidth: Int, measuredHeight: Int) = setMeasuredDimension(measuredWidth, measuredHeight)
|
||||||
|
|
||||||
// Some Rect to use during draw, since we should not alloc it during draw
|
|
||||||
private val testBounds = Rect()
|
|
||||||
|
|
||||||
// Workaround to RTL languages with non-RTL content messages aligning left instead of start
|
|
||||||
private var requiredHorizontalCanvasMove = 0f
|
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
// First, let super measure the content for our normal TextView use
|
// First, let super measure the content for our normal TextView use
|
||||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||||
|
|
||||||
// Default case
|
val updatedMeasures = updateDimensionsWithFooter(widthMeasureSpec, heightMeasureSpec)
|
||||||
requiredHorizontalCanvasMove = 0f
|
setMeasuredDimension(updatedMeasures.first, updatedMeasures.second)
|
||||||
|
|
||||||
// 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 }
|
|
||||||
val widthLimit = widthSize.toFloat()
|
|
||||||
/*
|
|
||||||
// Sometimes, widthLimit is not the actual limit, so remember it... ?
|
|
||||||
if (this.widthLimit > widthLimit) {
|
|
||||||
widthLimit = this.widthLimit
|
|
||||||
} else {
|
|
||||||
this.widthLimit = widthLimit
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
val lastLine = layout.lineCount - 1
|
|
||||||
|
|
||||||
// Let's check if the last line's text has the same RTL behaviour as the layout direction.
|
|
||||||
val viewIsRtl = layoutDirection == LAYOUT_DIRECTION_RTL
|
|
||||||
val looksLikeRtl = layout.getParagraphDirection(lastLine) == Layout.DIR_RIGHT_TO_LEFT
|
|
||||||
/*
|
|
||||||
val lastVisibleCharacter = layout.getLineVisibleEnd(lastLine) - 1
|
|
||||||
val looksLikeRtl = layout.isRtlCharAt(lastVisibleCharacter)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get required width for all lines
|
|
||||||
var maxLineWidth = 0f
|
|
||||||
for (i in 0 until layout.lineCount) {
|
|
||||||
// For some reasons, the getLineWidth is not working too well with RTL lines when rendering replies.
|
|
||||||
// -> https://github.com/SchildiChat/SchildiChat-android/issues/74
|
|
||||||
// However, the bounds method is a little generous sometimes (reserving too much space),
|
|
||||||
// so we don't want to use it over getLineWidth() unless required.
|
|
||||||
maxLineWidth = if (layout.getParagraphDirection(i) == Layout.DIR_RIGHT_TO_LEFT) {
|
|
||||||
layout.getLineBounds(i, testBounds)
|
|
||||||
max((testBounds.right - testBounds.left).toFloat(), maxLineWidth)
|
|
||||||
} else {
|
|
||||||
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(lastLine)
|
|
||||||
|
|
||||||
// Required width if putting footer in the same line as the last line
|
|
||||||
val widthWithHorizontalFooter = (
|
|
||||||
if (looksLikeRtl == viewIsRtl)
|
|
||||||
widthLastLine
|
|
||||||
else
|
|
||||||
(maxLineWidth + resources.getDimensionPixelSize(R.dimen.sc_footer_rtl_mismatch_extra_padding))
|
|
||||||
) + footerWidth
|
|
||||||
|
|
||||||
// If the last line is a multi-line code block, we have never space in the last line (as the black background always uses full width)
|
|
||||||
val forceNewlineFooter: Boolean
|
|
||||||
// For italic text, we need some extra space due to a wrap_content bug: https://stackoverflow.com/q/4353836
|
|
||||||
val addItalicPadding: Boolean
|
|
||||||
|
|
||||||
if (text is Spannable || text is Spanned) {
|
|
||||||
val span = text.toSpanned()
|
|
||||||
// If not found, -1+1 = 0
|
|
||||||
val lastLineStart = span.lastIndexOf("\n") + 1
|
|
||||||
val lastLineCodeSpans = span.getSpans<HtmlCodeSpan>(lastLineStart)
|
|
||||||
forceNewlineFooter = lastLineCodeSpans.any { it.isBlock }
|
|
||||||
addItalicPadding = span.getSpans<EmphasisSpan>().isNotEmpty()
|
|
||||||
} else {
|
|
||||||
forceNewlineFooter = false
|
|
||||||
addItalicPadding = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is there space for a horizontal footer?
|
|
||||||
if (widthWithHorizontalFooter <= widthLimit && !forceNewlineFooter) {
|
|
||||||
// Reserve extra horizontal footer space if necessary
|
|
||||||
if (widthWithHorizontalFooter > newWidth) {
|
|
||||||
newWidth = ceil(widthWithHorizontalFooter).toInt()
|
|
||||||
|
|
||||||
if (viewIsRtl) {
|
|
||||||
requiredHorizontalCanvasMove = widthWithHorizontalFooter - measuredWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Reserve vertical footer space
|
|
||||||
newHeight += footerHeight
|
|
||||||
// Ensure enough width for footer bellow
|
|
||||||
newWidth = max(newWidth, footerWidth +
|
|
||||||
resources.getDimensionPixelSize(R.dimen.sc_footer_padding_compensation) +
|
|
||||||
2 * resources.getDimensionPixelSize(R.dimen.sc_footer_overlay_padding))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addItalicPadding) {
|
|
||||||
newWidth += resources.getDimensionPixelSize(R.dimen.italic_text_view_extra_padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
setMeasuredDimension(newWidth, newHeight)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas?) {
|
override fun onDraw(canvas: Canvas?) {
|
||||||
// Workaround to RTL languages with non-RTL content messages aligning left instead of start
|
updateFooterOnPreDraw(canvas)
|
||||||
if (requiredHorizontalCanvasMove > 0f) {
|
|
||||||
canvas?.translate(requiredHorizontalCanvasMove, 0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onDraw(canvas)
|
super.onDraw(canvas)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.ClickListener
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.ui.views.FooteredTextView
|
import im.vector.app.core.ui.views.AbstractFooteredTextView
|
||||||
import im.vector.app.core.utils.TextUtils
|
import im.vector.app.core.utils.TextUtils
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
import im.vector.app.features.home.room.detail.timeline.helper.AudioMessagePlaybackTracker
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
|
||||||
|
@ -224,7 +224,7 @@ abstract class MessageAudioItem : AbsMessageItem<MessageAudioItem.Holder>() {
|
||||||
val audioPlaybackTime by bind<TextView>(R.id.audioPlaybackTime)
|
val audioPlaybackTime by bind<TextView>(R.id.audioPlaybackTime)
|
||||||
val progressLayout by bind<ViewGroup>(R.id.messageFileUploadProgressLayout)
|
val progressLayout by bind<ViewGroup>(R.id.messageFileUploadProgressLayout)
|
||||||
val fileSize by bind<TextView>(R.id.fileSize)
|
val fileSize by bind<TextView>(R.id.fileSize)
|
||||||
val captionView by bind<FooteredTextView>(R.id.messageCaptionView)
|
val captionView by bind<AbstractFooteredTextView>(R.id.messageCaptionView)
|
||||||
val audioPlaybackDuration by bind<TextView>(R.id.audioPlaybackDuration)
|
val audioPlaybackDuration by bind<TextView>(R.id.audioPlaybackDuration)
|
||||||
val audioSeekBar by bind<SeekBar>(R.id.audioSeekBar)
|
val audioSeekBar by bind<SeekBar>(R.id.audioSeekBar)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ import com.airbnb.epoxy.EpoxyModelClass
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.ui.views.FooteredTextView
|
import im.vector.app.core.ui.views.AbstractFooteredTextView
|
||||||
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.home.room.detail.timeline.style.TimelineMessageLayout
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
|
@ -154,7 +154,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
|
||||||
val fileImageWrapper by bind<ViewGroup>(R.id.messageFileImageView)
|
val fileImageWrapper by bind<ViewGroup>(R.id.messageFileImageView)
|
||||||
val fileDownloadProgress by bind<ProgressBar>(R.id.messageFileProgressbar)
|
val fileDownloadProgress by bind<ProgressBar>(R.id.messageFileProgressbar)
|
||||||
val filenameView by bind<TextView>(R.id.messageFilenameView)
|
val filenameView by bind<TextView>(R.id.messageFilenameView)
|
||||||
val captionView by bind<FooteredTextView>(R.id.messageCaptionView)
|
val captionView by bind<AbstractFooteredTextView>(R.id.messageCaptionView)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -34,17 +34,15 @@ import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.core.extensions.setTextOrHide
|
import im.vector.app.core.extensions.setTextOrHide
|
||||||
import im.vector.app.core.files.LocalFilesHelper
|
import im.vector.app.core.files.LocalFilesHelper
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
import im.vector.app.core.ui.views.FooteredTextView
|
import im.vector.app.core.ui.views.AbstractFooteredTextView
|
||||||
import im.vector.app.core.utils.DimensionConverter
|
import im.vector.app.core.utils.DimensionConverter
|
||||||
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.home.room.detail.timeline.style.TimelineMessageLayout
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
|
import im.vector.app.features.home.room.detail.timeline.style.granularRoundedCorners
|
||||||
import im.vector.app.features.home.room.detail.timeline.view.ScMessageBubbleWrapView
|
import im.vector.app.features.home.room.detail.timeline.view.ScMessageBubbleWrapView
|
||||||
import im.vector.app.features.media.ImageContentRenderer
|
import im.vector.app.features.media.ImageContentRenderer
|
||||||
import im.vector.app.features.themes.defaultScBubbleAppearance
|
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.api.util.MimeTypes
|
|
||||||
|
|
||||||
@EpoxyModelClass
|
@EpoxyModelClass
|
||||||
abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Holder>() {
|
abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Holder>() {
|
||||||
|
@ -255,7 +253,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
|
||||||
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
class Holder : AbsMessageItem.Holder(STUB_ID) {
|
||||||
val progressLayout by bind<ViewGroup>(R.id.messageMediaUploadProgressLayout)
|
val progressLayout by bind<ViewGroup>(R.id.messageMediaUploadProgressLayout)
|
||||||
val imageView by bind<ImageView>(R.id.messageThumbnailView)
|
val imageView by bind<ImageView>(R.id.messageThumbnailView)
|
||||||
val captionView by bind<FooteredTextView>(R.id.messageCaptionView)
|
val captionView by bind<AbstractFooteredTextView>(R.id.messageCaptionView)
|
||||||
val playContentView by bind<ImageView>(R.id.messageMediaPlayView)
|
val playContentView by bind<ImageView>(R.id.messageMediaPlayView)
|
||||||
val mediaContentView by bind<ViewGroup>(R.id.messageContentMedia)
|
val mediaContentView by bind<ViewGroup>(R.id.messageContentMedia)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,18 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.item
|
package im.vector.app.features.home.room.detail.timeline.item
|
||||||
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.method.MovementMethod
|
|
||||||
import android.view.ViewStub
|
import android.view.ViewStub
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
import androidx.core.text.PrecomputedTextCompat
|
import androidx.core.text.PrecomputedTextCompat
|
||||||
import androidx.core.view.isVisible
|
|
||||||
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.epoxy.onClick
|
import im.vector.app.core.epoxy.onClick
|
||||||
import im.vector.app.core.epoxy.onLongClickIgnoringLinks
|
import im.vector.app.core.epoxy.onLongClickIgnoringLinks
|
||||||
import im.vector.app.core.ui.views.FooteredTextView
|
import im.vector.app.core.ui.views.AbstractFooteredTextView
|
||||||
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.reply.InReplyToView
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
|
||||||
import im.vector.app.features.home.room.detail.timeline.reply.PreviewReplyUiState
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.reply.ReplyPreviewRetriever
|
|
||||||
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
|
import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.AbstractPreviewUrlView
|
import im.vector.app.features.home.room.detail.timeline.url.AbstractPreviewUrlView
|
||||||
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
|
||||||
|
@ -105,7 +100,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
holder.previewUrlView.delegate = previewUrlCallback
|
holder.previewUrlView.delegate = previewUrlCallback
|
||||||
holder.previewUrlView.renderMessageLayout(attributes.informationData.messageLayout)
|
holder.previewUrlView.renderMessageLayout(attributes.informationData.messageLayout)
|
||||||
|
|
||||||
val messageView: FooteredTextView = holder.messageView(useRichTextEditorStyle) //if (useRichTextEditorStyle) holder.richMessageView else holder.plainMessageView
|
val messageView: AbstractFooteredTextView = holder.messageView(useRichTextEditorStyle) //if (useRichTextEditorStyle) holder.richMessageView else holder.plainMessageView
|
||||||
if (useBigFont) {
|
if (useBigFont) {
|
||||||
messageView.textSize = 44F
|
messageView.textSize = 44F
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,10 +150,10 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
||||||
lateinit var previewUrlView: AbstractPreviewUrlView // set to either previewUrlViewElement or previewUrlViewSc by layout
|
lateinit var previewUrlView: AbstractPreviewUrlView // set to either previewUrlViewElement or previewUrlViewSc by layout
|
||||||
private val richMessageStub by bind<ViewStub>(R.id.richMessageTextViewStub)
|
private val richMessageStub by bind<ViewStub>(R.id.richMessageTextViewStub)
|
||||||
private val plainMessageStub by bind<ViewStub>(R.id.plainMessageTextViewStub)
|
private val plainMessageStub by bind<ViewStub>(R.id.plainMessageTextViewStub)
|
||||||
val richMessageView: FooteredTextView by lazy {
|
val richMessageView: AbstractFooteredTextView by lazy {
|
||||||
richMessageStub.inflate().findViewById(R.id.messageTextView)
|
richMessageStub.inflate().findViewById(R.id.messageTextView)
|
||||||
}
|
}
|
||||||
val plainMessageView: FooteredTextView by lazy {
|
val plainMessageView: AbstractFooteredTextView by lazy {
|
||||||
plainMessageStub.inflate().findViewById(R.id.messageTextView)
|
plainMessageStub.inflate().findViewById(R.id.messageTextView)
|
||||||
}
|
}
|
||||||
fun messageView(useRichTextEditorStyle: Boolean) = if (useRichTextEditorStyle) richMessageView else plainMessageView
|
fun messageView(useRichTextEditorStyle: Boolean) = if (useRichTextEditorStyle) richMessageView else plainMessageView
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
but it also makes it align better for RTL texts in RTL locales - at least
|
but it also makes it align better for RTL texts in RTL locales - at least
|
||||||
until the user pill is touched...
|
until the user pill is touched...
|
||||||
-->
|
-->
|
||||||
<im.vector.app.core.ui.views.FooteredTextView android:id="@+id/messageTextView"
|
<im.vector.app.core.ui.views.FooteredTextView
|
||||||
|
android:id="@+id/messageTextView"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
style="@style/Widget.Vector.TextView.Body"
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
but it also makes it align better for RTL texts in RTL locales - at least
|
but it also makes it align better for RTL texts in RTL locales - at least
|
||||||
until the user pill is touched...
|
until the user pill is touched...
|
||||||
-->
|
-->
|
||||||
<io.element.android.wysiwyg.EditorStyledTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<io.element.android.wysiwyg.EditorStyledTextView
|
||||||
|
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"
|
||||||
style="@style/Widget.Vector.TextView.Body"
|
style="@style/Widget.Vector.TextView.Body"
|
||||||
|
|
Loading…
Add table
Reference in a new issue