Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix text glitches to new line in TypeAnimationTextView #4874

Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@
package com.duckduckgo.common.ui.view

import android.content.Context
import android.graphics.Color
import android.text.Spannable
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.duckduckgo.common.utils.extensions.html
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.text.BreakIterator
import java.text.StringCharacterIterator
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.*

@Suppress("NoHardcodedCoroutineDispatcher")
class TypeAnimationTextView @JvmOverloads constructor(
Expand All @@ -38,7 +46,6 @@ class TypeAnimationTextView @JvmOverloads constructor(

private var typingAnimationJob: Job? = null
private var delayAfterAnimationInMs: Long = 300
private val breakIterator = BreakIterator.getCharacterInstance()

var typingDelayInMs: Long = 20
var textInDialog: Spanned? = null
Expand All @@ -48,7 +55,6 @@ class TypeAnimationTextView @JvmOverloads constructor(
isCancellable: Boolean = true,
afterAnimation: () -> Unit = {},
) {
textInDialog = textDialog.html(context)
if (isCancellable) {
setOnClickListener {
if (hasAnimationStarted()) {
Expand All @@ -57,24 +63,29 @@ class TypeAnimationTextView @JvmOverloads constructor(
}
}
}
if (typingAnimationJob?.isActive == true) typingAnimationJob?.cancel()
typingAnimationJob = launch {
textInDialog?.let { spanned ->

breakIterator.text = StringCharacterIterator(spanned.toString())
typingAnimationJob?.cancel()

var nextIndex = breakIterator.next()
while (nextIndex != BreakIterator.DONE) {
text = spanned.subSequence(0, nextIndex)
nextIndex = breakIterator.next()
textInDialog = textDialog.html(context).let(::SpannableString).also { textInDialog ->
typingAnimationJob = launch {
val transparentSpan = ForegroundColorSpan(Color.TRANSPARENT)
breakSequence(textInDialog).forEach { index ->
text = textInDialog.apply { setSpan(transparentSpan, index, length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) }
delay(typingDelayInMs)
}

delay(delayAfterAnimationInMs)
afterAnimation()
}
}
}

private fun breakSequence(charSequence: CharSequence) =
BreakIterator.getCharacterInstance()
.apply { text = StringCharacterIterator(charSequence.toString()) }
.let { generateSequence { it.next() } }
.takeWhile { it != BreakIterator.DONE }

fun hasAnimationStarted() = typingAnimationJob?.isActive == true

fun hasAnimationFinished() = typingAnimationJob?.isCompleted == true
Expand Down