mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-25 20:23:19 +00:00
Ensure proper text size is used when displaying and editing text stories.
This commit is contained in:
committed by
Cody Henthorne
parent
4abb169568
commit
40020728de
@@ -1,156 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mediasend.v2.text
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.EditTextUtil
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class AutoSizeEmojiEditText @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : EmojiEditText(context, attrs) {
|
||||
|
||||
private val maxTextSize = DimensionUnit.DP.toPixels(32f)
|
||||
private val minTextSize = DimensionUnit.DP.toPixels(6f)
|
||||
private var lowerBounds = minTextSize
|
||||
private var upperBounds = maxTextSize
|
||||
|
||||
private val sizeSet: MutableSet<Float> = mutableSetOf()
|
||||
|
||||
private var beforeText: String? = null
|
||||
private var beforeCursorPosition = 0
|
||||
|
||||
private val watcher: TextWatcher = object : TextWatcher {
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
beforeText = s.toString()
|
||||
beforeCursorPosition = start
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
if (lineCount == 0) {
|
||||
doOnNextLayout {
|
||||
checkCountAndAddListener()
|
||||
}
|
||||
} else {
|
||||
checkCountAndAddListener()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(this, 700)
|
||||
|
||||
if (attrs != null) {
|
||||
context.obtainStyledAttributes(attrs, R.styleable.AutoSizeEmojiEditText).use { typedArray ->
|
||||
if (typedArray.getBoolean(R.styleable.AutoSizeEmojiEditText_aseet_EnforceLineCount, true)) {
|
||||
addTextChangedListener(watcher)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addTextChangedListener(watcher)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
if (isInEditMode) return
|
||||
|
||||
try {
|
||||
val operation = getNextAutoSizeOperation()
|
||||
val newSize = when (operation) {
|
||||
AutoSizeOperation.INCREASE -> {
|
||||
lowerBounds = textSize
|
||||
val midpoint = abs(lowerBounds - upperBounds) / 2f + lowerBounds
|
||||
min(maxTextSize, midpoint)
|
||||
}
|
||||
AutoSizeOperation.DECREASE -> {
|
||||
upperBounds = textSize
|
||||
val midpoint = abs(lowerBounds - upperBounds) / 2f + lowerBounds
|
||||
max(minTextSize, midpoint)
|
||||
}
|
||||
AutoSizeOperation.NONE -> return
|
||||
}
|
||||
|
||||
if (abs(upperBounds - lowerBounds) < 1f) {
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerBounds)
|
||||
return
|
||||
} else if (sizeSet.add(newSize) || operation == AutoSizeOperation.INCREASE) {
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize)
|
||||
measure(widthMeasureSpec, heightMeasureSpec)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} finally {
|
||||
upperBounds = maxTextSize
|
||||
lowerBounds = minTextSize
|
||||
sizeSet.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNextAutoSizeOperation(): AutoSizeOperation {
|
||||
if (lineCount == 0) {
|
||||
return AutoSizeOperation.NONE
|
||||
}
|
||||
|
||||
val availableHeight = measuredHeight - paddingTop - paddingBottom
|
||||
if (availableHeight <= 0) {
|
||||
return AutoSizeOperation.NONE
|
||||
}
|
||||
|
||||
val pixelsRequired = lineHeight * lineCount
|
||||
|
||||
return if (pixelsRequired > availableHeight) {
|
||||
if (textSize > minTextSize) {
|
||||
AutoSizeOperation.DECREASE
|
||||
} else {
|
||||
AutoSizeOperation.NONE
|
||||
}
|
||||
} else if (pixelsRequired < availableHeight) {
|
||||
if (textSize < maxTextSize) {
|
||||
AutoSizeOperation.INCREASE
|
||||
} else {
|
||||
AutoSizeOperation.NONE
|
||||
}
|
||||
} else {
|
||||
AutoSizeOperation.NONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCountAndAddListener(): Boolean {
|
||||
removeTextChangedListener(watcher)
|
||||
|
||||
if (lineCount > 12) {
|
||||
setText(beforeText)
|
||||
setSelection(beforeCursorPosition)
|
||||
addTextChangedListener(watcher)
|
||||
return true
|
||||
}
|
||||
|
||||
if (getNextAutoSizeOperation() != AutoSizeOperation.NONE) {
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
addTextChangedListener(watcher)
|
||||
return false
|
||||
}
|
||||
|
||||
private enum class AutoSizeOperation {
|
||||
INCREASE,
|
||||
DECREASE,
|
||||
NONE
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,8 @@ class TextStoryPostTextEntryFragment : KeyboardEntryDialogFragment(
|
||||
}
|
||||
|
||||
private fun initializeInput() {
|
||||
TextStoryTextWatcher.install(input)
|
||||
|
||||
input.filters = input.filters + bufferFilter
|
||||
input.doOnTextChanged { _, _, _, _ ->
|
||||
presentHint()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.thoughtcrime.securesms.mediasend.v2.text
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.util.TypedValue
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import org.signal.core.util.BreakIteratorCompat
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.EditTextUtil
|
||||
|
||||
class TextStoryTextWatcher private constructor(private val textView: TextView) : TextWatcher {
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
ensureProperTextSize(textView)
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||
|
||||
override fun afterTextChanged(s: Editable) = Unit
|
||||
|
||||
companion object {
|
||||
fun ensureProperTextSize(textView: TextView) {
|
||||
val breakIteratorCompat = BreakIteratorCompat.getInstance()
|
||||
breakIteratorCompat.setText(textView.text)
|
||||
val length = breakIteratorCompat.countBreaks()
|
||||
val expectedTextSize = when {
|
||||
length < 50 -> 36f
|
||||
length < 200 -> 24f
|
||||
else -> 18f
|
||||
}
|
||||
|
||||
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, DimensionUnit.DP.toPixels(expectedTextSize))
|
||||
}
|
||||
|
||||
fun install(textView: TextView) {
|
||||
val watcher = TextStoryTextWatcher(textView)
|
||||
|
||||
if (textView is EditText) {
|
||||
EditTextUtil.addGraphemeClusterLimitFilter(textView, 700)
|
||||
}
|
||||
|
||||
textView.addTextChangedListener(watcher)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextAlignment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationState
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryScale
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryTextWatcher
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
@@ -59,6 +60,7 @@ class StoryTextPostView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
TextStoryTextWatcher.install(textView)
|
||||
textView.doAfterTextChanged {
|
||||
textAlignment?.apply {
|
||||
adjustTextTranslationX(this)
|
||||
|
||||
Reference in New Issue
Block a user