refactor: Replace LoadingButtonAndroid with compose

REF: https://gist.github.com/mmolosay/584ce5c47567cb66228b76ef98c3c4e4
This commit is contained in:
Ahmad Ansori Palembani 2024-08-24 09:03:50 +07:00
parent 61e43e047f
commit 6f94dd091b
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
9 changed files with 228 additions and 26 deletions

View file

@ -235,7 +235,6 @@ dependencies {
implementation(libs.aboutlibraries)
// UI
implementation(libs.loading.button)
implementation(libs.fastadapter)
implementation(libs.fastadapter.extensions.binding)
implementation(libs.flexible.adapter)

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.widget.preference
import android.app.Dialog
import android.os.Bundle
import android.view.View
import androidx.annotation.StringRes
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import dev.icerock.moko.resources.StringResource
@ -53,7 +52,7 @@ abstract class LoginDialogPreference(
binding.usernameInput.hint = view.context.getString(usernameLabelRes)
}
binding.login.setOnClickListener {
binding.login.setOnClick {
checkLogin()
}

View file

@ -2,13 +2,7 @@ package eu.kanade.tachiyomi.widget.preference
import android.os.Bundle
import android.view.View
import androidx.annotation.StringRes
import br.com.simplepass.loadingbutton.animatedDrawables.ProgressType
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import yokai.i18n.MR
import yokai.util.lang.getString
import dev.icerock.moko.resources.compose.stringResource
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.util.system.toast
@ -16,6 +10,8 @@ import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.i18n.MR
import yokai.util.lang.getString
class TrackLoginDialog(usernameLabelRes: StringResource? = null, bundle: Bundle? = null) :
LoginDialogPreference(usernameLabelRes, bundle) {
@ -36,10 +32,7 @@ class TrackLoginDialog(usernameLabelRes: StringResource? = null, bundle: Bundle?
override fun checkLogin() {
v?.apply {
binding.login.apply {
progressType = ProgressType.INDETERMINATE
startAnimation()
}
binding.login.startAnimation()
if (binding.username.text.isNullOrBlank() || binding.password.text.isNullOrBlank()) {
errorResult()
context.toast(MR.strings.username_must_not_be_blank)

View file

@ -0,0 +1,154 @@
package yokai.presentation.component
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.VisibilityThreshold
import androidx.compose.animation.core.animateDp
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.updateTransition
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkHorizontally
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
// REF: https://gist.github.com/mmolosay/584ce5c47567cb66228b76ef98c3c4e4
private val SpringStiffness = Spring.StiffnessMediumLow
@Composable
fun LoadingButton(
text: () -> String,
loading: () -> Boolean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val transition = updateTransition(
targetState = loading(),
label = "master transition",
)
val horizontalContentPadding by transition.animateDp(
transitionSpec = {
spring(
stiffness = SpringStiffness,
)
},
targetValueByState = { toLoading -> if (toLoading) 12.dp else 24.dp },
label = "button's content padding",
)
Button(
onClick = onClick,
modifier = modifier.defaultMinSize(minWidth = 1.dp),
contentPadding = PaddingValues(
horizontal = horizontalContentPadding,
vertical = 8.dp,
),
) {
Box(contentAlignment = Alignment.Center) {
LoadingContent(
loadingStateTransition = transition,
)
PrimaryContent(
text = text(),
loadingStateTransition = transition,
)
}
}
}
@Composable
private fun LoadingContent(
loadingStateTransition: Transition<Boolean>,
) {
loadingStateTransition.AnimatedVisibility(
visible = { loading -> loading },
enter = fadeIn(),
exit = fadeOut(
animationSpec = spring(
stiffness = SpringStiffness,
visibilityThreshold = 0.10f,
),
),
) {
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
color = LocalContentColor.current,
strokeWidth = 1.5f.dp,
strokeCap = StrokeCap.Round,
)
}
}
@Composable
private fun PrimaryContent(
text: String,
loadingStateTransition: Transition<Boolean>,
) {
loadingStateTransition.AnimatedVisibility(
visible = { loading -> !loading },
enter = fadeIn() + expandHorizontally(
animationSpec = spring(
stiffness = SpringStiffness,
dampingRatio = Spring.DampingRatioMediumBouncy,
visibilityThreshold = IntSize.VisibilityThreshold,
),
expandFrom = Alignment.CenterHorizontally,
),
exit = fadeOut(
animationSpec = spring(
stiffness = SpringStiffness,
visibilityThreshold = 0.10f,
),
) + shrinkHorizontally(
animationSpec = spring(
stiffness = SpringStiffness,
// dampingRatio is not applicable here, size cannot become negative
visibilityThreshold = IntSize.VisibilityThreshold,
),
shrinkTowards = Alignment.CenterHorizontally,
),
) {
Text(
text = text,
modifier = Modifier
// so that bouncing button's width doesn't cut first and last letters
.padding(horizontal = 4.dp),
fontSize = 16.sp,
)
}
}
@Preview
@Composable
private fun LoadingButtonPreview() {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
LoadingButton(
modifier = Modifier.fillMaxWidth(),
text = { "Test" },
loading = { false },
onClick = {},
)
}
}

View file

@ -0,0 +1,61 @@
package yokai.presentation.component
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.widget.FrameLayout
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.AbstractComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import eu.kanade.tachiyomi.R
import yokai.presentation.theme.YokaiTheme
class LoadingButtonComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AbstractComposeView(context, attrs, defStyleAttr) {
var text by mutableStateOf("placeholder")
private var onClick: () -> Unit = {}
private var isLoading by mutableStateOf(false)
init {
layoutParams = FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT, Gravity.CENTER)
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindowOrReleasedFromPool)
attrs?.let {
val arr = context.obtainStyledAttributes(it, R.styleable.LoadingButtonComposeView)
text = context.getString(arr.getResourceId(R.styleable.LoadingButtonComposeView_android_text, R.string.log_in))
}
}
fun setOnClick(onClick: () -> Unit) {
this.onClick = onClick
}
fun startAnimation() {
isLoading = true
}
fun revertAnimation(after: () -> Unit = {}) {
isLoading = false
after()
}
@Composable
override fun Content() {
YokaiTheme {
LoadingButton(
modifier = Modifier.fillMaxWidth(),
text = { text },
loading = { isLoading },
onClick = onClick,
)
}
}
}

View file

@ -49,21 +49,12 @@
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<br.com.simplepass.loadingbutton.customViews.CircularProgressButton
<yokai.presentation.component.LoadingButtonComposeView
android:id="@+id/login"
style="@style/Widget.Tachiyomi.Button.Primary"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:scaleType="fitCenter"
android:text="@string/log_in"
android:textSize="16sp"
app:finalCornerAngle="50dp"
app:initialCornerAngle="2dp"
app:spinning_bar_color="@color/md_white_1000"
app:spinning_bar_padding="6dp"
app:spinning_bar_width="3dp" />
android:text="@string/log_in" />
</LinearLayout>
</LinearLayout>

View file

@ -49,6 +49,10 @@
<attr name="endIcon" format="reference" />
</declare-styleable>
<declare-styleable name="LoadingButtonComposeView">
<attr name="android:text" />
</declare-styleable>
<bool name="isLightMode">true</bool>
<fraction name="chartRatio">1</fraction>

View file

@ -100,6 +100,8 @@
<color name="pale_green">#99CC99</color>
<color name="strong_green">#106010</color>
<color name="black">#000</color>
<!-- Navigation overlay colors -->
<color name="navigation_menu">#CC95818D</color>
<color name="navigation_next">#CCA6CFD5</color>

View file

@ -57,7 +57,6 @@ jsoup = { module = "org.jsoup:jsoup", version = "1.17.1" }
junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit" }
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-android = { module = "androidx.test.ext:junit", version = "1.1.5" }
loading-button = { module = "com.github.leandroBorgesFerreira:LoadingButtonAndroid", version = "2.2.0" } # FIXME: Don't depends on this
mockk = { module = "io.mockk:mockk", version = "1.13.11" }
moko-resources = { module = "dev.icerock.moko:resources", version.ref = "moko" }