Refactor out animateFloatMergePredictiveBackAsState (#221)

* Refactor out animateFloatMergePredictiveBackAsState

* Revert EditTextHeight to 80dp

* Fix

* Fix animation when content too long

* Fix double navbar padding

* Fix lazycolumn jumpping

* Fix padding

* Fix padding again

* Refine

---------

Co-authored-by: Lokesh Gidra <lokeshgidra@google.com>
This commit is contained in:
nullArrayListandLokesh Gidra authored and GitHub committed 2023-11-09 05:24:42 +00:00
1 parent 9ca7be081a
commit 77ec7e76e9
2 files changed
+83 -43

No files matched your search

@@ -26,9 +26,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -62,7 +60,6 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -80,6 +77,7 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.max
import androidx.compose.ui.util.lerp
import androidx.core.text.inSpans
import androidx.core.text.parseAsHtml
@@ -101,19 +99,16 @@ import com.hippo.ehviewer.ui.main.GalleryCommentCard
import com.hippo.ehviewer.ui.openBrowser
import com.hippo.ehviewer.ui.scene.GalleryListScene.Companion.toStartArgs
import com.hippo.ehviewer.ui.tools.LocalDialogState
import com.hippo.ehviewer.ui.tools.animateFloatMergePredictiveBackAsState
import com.hippo.ehviewer.util.ExceptionUtils
import com.hippo.ehviewer.util.ReadableTime
import com.hippo.ehviewer.util.TextUrl
import com.hippo.ehviewer.util.addTextToClipboard
import com.hippo.ehviewer.util.findActivity
import com.hippo.ehviewer.util.getParcelableCompat
import com.hippo.ehviewer.util.isAtLeastU
import com.ramcosta.composedestinations.annotation.Destination
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import moe.tarsin.coroutines.runSuspendCatching
import rikka.core.res.resolveColor
@@ -151,6 +146,8 @@ private fun Context.generateComment(
return TextUrl.handleTextUrl(ssb)
}
private val MiniumContentPaddingEditText = 88.dp
@Destination
@Composable
fun GalleryCommentsScreen(galleryDetail: GalleryDetail, navigator: NavController) {
@@ -158,30 +155,9 @@ fun GalleryCommentsScreen(galleryDetail: GalleryDetail, navigator: NavController
val dialogState = LocalDialogState.current
val coroutineScope = rememberCoroutineScope()
// Predictive animation pattern, extract it out if needed
// Principal Value
var commenting by rememberSaveable { mutableStateOf(false) }
// Natural animator state correspond with principal value
val coState by animateFloatAsState(
targetValue = if (commenting) 1f else 0f,
label = "animationProgress",
)
// User predictive back animation progress holder && value correspond with UI State
var animationProgress by remember { mutableFloatStateOf(0F) }
// Update UI animation state
animationProgress = if (commenting || !isAtLeastU) coState else min(animationProgress, coState)
PredictiveBackHandler(commenting) { progress ->
try {
progress.collect {
animationProgress = 1F - it.progress
}
commenting = false
} catch (e: CancellationException) {
commenting = true
animationProgress = 1F
}
}
val commentingBackField = rememberSaveable { mutableStateOf(false) }
var commenting by commentingBackField
val animationProgress by animateFloatMergePredictiveBackAsState(enable = commentingBackField)
var userComment by rememberSaveable { mutableStateOf("") }
var comments by rememberSaveable { mutableStateOf(galleryDetail.comments) }
@@ -285,15 +261,15 @@ fun GalleryCommentsScreen(galleryDetail: GalleryDetail, navigator: NavController
},
) { paddingValues ->
val keylineMargin = dimensionResource(id = R.dimen.keyline_margin)
var editTextMeasured by remember { mutableStateOf(0.dp) }
var editTextMeasured by remember { mutableStateOf(MiniumContentPaddingEditText) }
Box(modifier = Modifier.fillMaxSize().imePadding()) {
val additionalPadding = if (commenting) {
editTextMeasured
} else {
if (!comments.hasMore) {
16.dp + 56.dp // Fab space + Fab size
MiniumContentPaddingEditText
} else {
16.dp // Fab space
0.dp
}
}
LazyColumn(
@@ -403,21 +379,22 @@ fun GalleryCommentsScreen(galleryDetail: GalleryDetail, navigator: NavController
}
}
Surface(
modifier = Modifier.align(Alignment.BottomCenter).onGloballyPositioned { coordinates ->
editTextMeasured = with(density) { coordinates.size.height.toDp() }
}.layout { measurable, constraints ->
val startHeight = max(constraints.minHeight, 80.dp.roundToPx()).coerceAtMost(constraints.maxHeight)
val endWidth = constraints.maxWidth
val width = lerp(0, endWidth, animationProgress)
val height = lerp(0, startHeight, animationProgress)
modifier = Modifier.align(Alignment.BottomCenter).layout { measurable, constraints ->
val origin = measurable.measure(constraints)
val width = lerp(0, origin.width, 1 - animationProgress)
val height = lerp(0, origin.height, 1 - animationProgress)
val placeable = measurable.measure(Constraints.fixed(width, height))
layout(width, height) {
placeable.placeRelative(0, 0)
}
}.clip(RoundedCornerShape(((1 - animationProgress) * 100).roundToInt())),
}.clip(RoundedCornerShape((animationProgress * 100).roundToInt())),
color = MaterialTheme.colorScheme.primaryContainer,
) {
Row(modifier = Modifier.fillMaxWidth().navigationBarsPadding()) {
Row(
modifier = Modifier.fillMaxWidth().navigationBarsPadding().onGloballyPositioned { coordinates ->
editTextMeasured = max(with(density) { coordinates.size.height.toDp() }, MiniumContentPaddingEditText)
},
) {
BasicTextField2(
value = userComment,
onValueChange = { userComment = it },
@@ -0,0 +1,63 @@
package com.hippo.ehviewer.ui.tools
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.hippo.ehviewer.util.isAtLeastU
import kotlin.math.max
import kotlinx.coroutines.CancellationException
/* enable 0 1
* animationProgress 1F 0F
*
* BackHandler enabling:
* enable 0 -> 1 immediately
* animationProgress 1F -> 0F by animationSpec
*
* BackGesture handling:
* enable 1 -> 0 when finished
* animationProgress 0F -> 1F
* SDK >= 34 by userGesture progress
* else by animationSpec when finished
*/
@Composable
fun animateFloatMergePredictiveBackAsState(
enable: MutableState<Boolean>,
animationSpec: AnimationSpec<Float> = spring(),
): State<Float> {
// Principal Value
var principal by enable
// Natural animator state correspond with principal value
val coState by animateFloatAsState(
targetValue = if (principal) 0f else 1f,
animationSpec = animationSpec,
label = "animationProgress",
)
// User predictive back animation progress holder && value correspond with UI State
val ret = remember { mutableFloatStateOf(1F) }
var animationProgress by ret
// Update UI animation state
animationProgress = if (principal || !isAtLeastU) coState else max(animationProgress, coState)
PredictiveBackHandler(principal) { progress ->
try {
progress.collect {
animationProgress = it.progress
}
principal = false
} catch (e: CancellationException) {
principal = true
animationProgress = 0F
}
}
return ret
}