diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 495bd1a141..56c9cca24f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -25,8 +25,8 @@ configurations.configureEach { exclude(module = "commons-logging") } -val canonicalVersionCode = 416 -val canonicalVersionName = "1.27.0" +val canonicalVersionCode = 419 +val canonicalVersionName = "1.27.1" val postFixSize = 10 val abiPostFix = mapOf( diff --git a/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt b/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt index 196e9fa181..6f2379cd99 100644 --- a/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt +++ b/app/src/main/java/org/session/libsession/utilities/ViewUtils.kt @@ -5,6 +5,7 @@ import android.os.Build import android.text.Layout import android.text.StaticLayout import android.text.TextDirectionHeuristics +import android.text.TextUtils import android.util.TypedValue import android.view.View import android.view.View.TEXT_ALIGNMENT_CENTER @@ -14,8 +15,6 @@ import android.view.ViewGroup import android.widget.TextView import androidx.annotation.AttrRes import androidx.annotation.ColorInt -import androidx.core.text.TextDirectionHeuristicsCompat -import androidx.core.widget.TextViewCompat @ColorInt fun Context.getColorFromAttr( @@ -27,7 +26,7 @@ fun Context.getColorFromAttr( return typedValue.data } -inline fun View.modifyLayoutParams(function: LP.() -> Unit) { +inline fun View.modifyLayoutParams(function: LP.() -> Unit) { layoutParams = (layoutParams as LP).apply { function() } } @@ -35,43 +34,51 @@ fun TextView.needsCollapsing( availableWidthPx: Int, maxLines: Int ): Boolean { - if (availableWidthPx <= 0 || text.isNullOrEmpty()) return false + // Pick the width the TextView will actually respect before draw + val rawWidth = when { + measuredWidth > 0 -> measuredWidth + // if maxWidth is set, we check it + maxWidth in 1 until Int.MAX_VALUE -> minOf(availableWidthPx, maxWidth) + else -> availableWidthPx + } + val contentWidth = (rawWidth - paddingLeft - paddingRight).coerceAtLeast(0) + if (contentWidth <= 0 || text.isNullOrEmpty()) return false - // The exact text that will be drawn (all-caps, password dots …) val textForLayout = transformationMethod?.getTransformation(text, this) ?: text - // Build a StaticLayout that mirrors this TextView’s wrap rules + val alignment = when (textAlignment) { + TEXT_ALIGNMENT_CENTER -> Layout.Alignment.ALIGN_CENTER + TEXT_ALIGNMENT_VIEW_END, TEXT_ALIGNMENT_TEXT_END -> Layout.Alignment.ALIGN_OPPOSITE + else -> Layout.Alignment.ALIGN_NORMAL + } + val direction = when (textDirection) { + View.TEXT_DIRECTION_FIRST_STRONG_RTL -> TextDirectionHeuristics.FIRSTSTRONG_RTL + View.TEXT_DIRECTION_RTL -> TextDirectionHeuristics.RTL + View.TEXT_DIRECTION_LTR -> TextDirectionHeuristics.LTR + else -> TextDirectionHeuristics.FIRSTSTRONG_LTR + } + val builder = StaticLayout.Builder - .obtain(textForLayout, 0, textForLayout.length, paint, availableWidthPx) + .obtain(textForLayout, 0, textForLayout.length, paint, contentWidth) .setIncludePad(includeFontPadding) .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier) - .setBreakStrategy(breakStrategy) // API 23+ + .setBreakStrategy(breakStrategy) .setHyphenationFrequency(hyphenationFrequency) - .setMaxLines(Int.MAX_VALUE) - - // Alignment (honours RTL if textAlignment is END/VIEW_END) - builder.setAlignment( - when (textAlignment) { - TEXT_ALIGNMENT_CENTER -> Layout.Alignment.ALIGN_CENTER - TEXT_ALIGNMENT_VIEW_END, - TEXT_ALIGNMENT_TEXT_END -> Layout.Alignment.ALIGN_OPPOSITE - else -> Layout.Alignment.ALIGN_NORMAL - } - ) - - // Direction heuristic - val dir = when (textDirection) { - View.TEXT_DIRECTION_FIRST_STRONG_RTL -> TextDirectionHeuristics.FIRSTSTRONG_RTL - View.TEXT_DIRECTION_RTL -> TextDirectionHeuristics.RTL - View.TEXT_DIRECTION_LTR -> TextDirectionHeuristics.LTR - else -> TextDirectionHeuristics.FIRSTSTRONG_LTR - } - builder.setTextDirection(dir) - - builder.setEllipsize(ellipsize) + .setAlignment(alignment) + .setTextDirection(direction) + .setMaxLines(maxLines) // cap at maxLines + .setEllipsize(ellipsize ?: TextUtils.TruncateAt.END) // compute ellipsis builder.setJustificationMode(justificationMode) val layout = builder.build() - return layout.lineCount > maxLines + + // Fewer than maxLines: definitely no truncation + if (layout.lineCount < maxLines) return false + // (Defensive) more than maxLines: truncated + if (layout.lineCount > maxLines) return true + + // Exactly maxLines: truncated if last line is ellipsized or characters were cut + val last = (maxLines - 1).coerceAtMost(layout.lineCount - 1) + return layout.getEllipsisCount(last) > 0 || layout.getLineEnd(last) < textForLayout.length } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index fb3fb851f6..f26fcb8eef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -355,9 +355,13 @@ class VisibleMessageContentView : ConstraintLayout { } } + val widthCap = binding.bodyTextView.maxWidth + .takeIf { it > 0 && it < Int.MAX_VALUE } + ?: resources.getDimensionPixelSize(R.dimen.max_bubble_width) + // if the text was already manually expanded, we can skip this logic if(!isTextExpanded && binding.bodyTextView.needsCollapsing( - availableWidthPx = context.resources.getDimensionPixelSize(R.dimen.max_bubble_width), + availableWidthPx = widthCap, maxLines = MAX_COLLAPSED_LINE_COUNT) ){ // show the "Read mode" button