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:
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
|
||||
}
|
||||
Reference in new issue
Block a user