Ensure proper text size is used when displaying and editing text stories.
This commit is contained in:
parent
4abb169568
commit
40020728de
7 changed files with 58 additions and 173 deletions
|
@ -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)
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<org.thoughtcrime.securesms.mediasend.v2.text.AutoSizeEmojiEditText
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiEditText
|
||||
android:id="@+id/input"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -29,17 +29,14 @@
|
|||
android:gravity="center"
|
||||
android:hint="@string/TextStoryPostTextEntryFragment__add_text"
|
||||
android:inputType="textMultiLine"
|
||||
android:maxLength="700"
|
||||
android:maxLines="12"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingBottom="13dp"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
|
||||
android:textColor="@color/core_white"
|
||||
android:textSize="32dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="36dp"
|
||||
tools:ignore="SpUsage"
|
||||
tools:text="Miles" />
|
||||
tools:text="THIS IS SOME TEXT THAT I AM ENTERING" />
|
||||
</FrameLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.mediasend.v2.text.TextAlignmentButton
|
||||
|
|
|
@ -12,30 +12,28 @@
|
|||
android:layout_height="match_parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Story.Text" />
|
||||
|
||||
<org.thoughtcrime.securesms.mediasend.v2.text.AutoSizeEmojiEditText
|
||||
android:focusable="false"
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/text_story_post_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:background="@drawable/rounded_rectangle_secondary_18"
|
||||
android:focusable="false"
|
||||
android:gravity="center"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingTop="15dp"
|
||||
android:paddingBottom="13dp"
|
||||
android:text="@string/TextStoryPostCreationFragment__tap_to_add_text"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
|
||||
android:textColor="@color/core_white"
|
||||
android:textStyle="bold"
|
||||
android:textSize="36dp"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:aseet_EnforceLineCount="false"
|
||||
tools:ignore="SpUsage"
|
||||
tools:text="THIS IS SOME TEXT THAT I AM ENTERING" />
|
||||
tools:text="TEST" />
|
||||
|
||||
<org.thoughtcrime.securesms.stories.StoryLinkPreviewView
|
||||
android:id="@+id/text_story_post_link_preview"
|
||||
|
|
|
@ -23,10 +23,6 @@
|
|||
|
||||
<attr name="contact_filter_toolbar_icon_tint" format="color" />
|
||||
|
||||
<declare-styleable name="AutoSizeEmojiEditText">
|
||||
<attr name="aseet_EnforceLineCount" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="SegmentedProgressBar">
|
||||
<attr name="totalSegments" format="integer" />
|
||||
<attr name="timePerSegment" format="integer" />
|
||||
|
|
Loading…
Add table
Reference in a new issue