Merge changes from topic "shapes" into udc-qpr-dev
* changes: Add shapes to PIN bouncer Tweak AVDs to make them work with the compose androidx.compose.animation:animation-graphics library. Reformat AVDs to allow better understanding of the changes in the follow-up CL
This commit is contained in:
committed by
Android (Google) Code Review
commit
1672c54654
@@ -34,6 +34,7 @@ android_library {
|
||||
"PlatformComposeCore",
|
||||
|
||||
"androidx.compose.runtime_runtime",
|
||||
"androidx.compose.animation_animation-graphics",
|
||||
"androidx.compose.material3_material3",
|
||||
"androidx.activity_activity-compose",
|
||||
],
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalAnimationApi::class)
|
||||
@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class)
|
||||
|
||||
package com.android.systemui.bouncer.ui.composable
|
||||
|
||||
@@ -29,11 +29,14 @@ import androidx.compose.animation.core.Transition
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.keyframes
|
||||
import androidx.compose.animation.core.snap
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi
|
||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -61,8 +64,10 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
@@ -70,6 +75,7 @@ import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.android.compose.animation.Easings
|
||||
import com.android.compose.grid.VerticalGrid
|
||||
import com.android.internal.R.id.image
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
|
||||
import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
|
||||
@@ -139,7 +145,8 @@ private fun PinInputDisplay(viewModel: PinBouncerViewModel) {
|
||||
else -> EntryVisibility.Hidden
|
||||
}
|
||||
|
||||
ObscuredInputEntry(updateTransition(visibility, label = "Pin Entry $entry"))
|
||||
val shape = viewModel.pinShapes.getShape(entry.sequenceNumber)
|
||||
PinInputEntry(shape, updateTransition(visibility, label = "Pin Entry $entry"))
|
||||
|
||||
LaunchedEffect(entry) {
|
||||
// Remove entry from visiblePinEntries once the hide transition completed.
|
||||
@@ -171,15 +178,11 @@ private sealed class EntryVisibility {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
||||
private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) {
|
||||
// spec: http://shortn/_DEhE3Xl2bi
|
||||
val shapePadding = 6.dp
|
||||
val shapeOvershootSize = 22.dp
|
||||
val dismissStaggerDelayMs = 33
|
||||
val dismissDurationMs = 450
|
||||
val expansionDurationMs = 250
|
||||
val shapeExpandDurationMs = 83
|
||||
val shapeRetractDurationMs = 167
|
||||
val shapeCollapseDurationMs = 200
|
||||
|
||||
val animatedEntryWidth by
|
||||
@@ -194,19 +197,17 @@ private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
||||
},
|
||||
label = "entry space"
|
||||
) { state ->
|
||||
if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp
|
||||
if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
|
||||
}
|
||||
|
||||
val animatedShapeSize by
|
||||
transition.animateDp(
|
||||
transitionSpec = {
|
||||
when {
|
||||
EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown ->
|
||||
keyframes {
|
||||
durationMillis = shapeExpandDurationMs + shapeRetractDurationMs
|
||||
0.dp at 0 with Easings.Linear
|
||||
shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy
|
||||
}
|
||||
EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> {
|
||||
// The AVD contains the entry transition.
|
||||
snap()
|
||||
}
|
||||
targetState is EntryVisibility.BulkHidden -> {
|
||||
val target = targetState as EntryVisibility.BulkHidden
|
||||
tween(
|
||||
@@ -220,17 +221,21 @@ private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
||||
},
|
||||
label = "shape size"
|
||||
) { state ->
|
||||
when (state) {
|
||||
EntryVisibility.Shown -> entryShapeSize
|
||||
else -> 0.dp
|
||||
}
|
||||
if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
|
||||
}
|
||||
|
||||
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Layout(
|
||||
content = {
|
||||
// TODO(b/282730134): add support for dot shapes.
|
||||
Canvas(Modifier) { drawCircle(dotColor) }
|
||||
val image = AnimatedImageVector.animatedVectorResource(shapeResourceId)
|
||||
var atEnd by remember { mutableStateOf(false) }
|
||||
Image(
|
||||
painter = rememberAnimatedVectorPainter(image, atEnd),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
colorFilter = ColorFilter.tint(dotColor),
|
||||
)
|
||||
LaunchedEffect(Unit) { atEnd = true }
|
||||
}
|
||||
) { measurables, _ ->
|
||||
val shapeSizePx = animatedShapeSize.roundToPx()
|
||||
@@ -507,7 +512,7 @@ private suspend fun showFailureAnimation(
|
||||
}
|
||||
}
|
||||
|
||||
private val entryShapeSize = 16.dp
|
||||
private val entryShapeSize = 30.dp
|
||||
|
||||
private val pinButtonSize = 84.dp
|
||||
private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
|
||||
|
||||
@@ -1 +1,40 @@
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"><aapt:attr name="android:drawable"><vector android:height="30dp" android:width="30dp" android:viewportHeight="30" android:viewportWidth="30"><group android:name="_R_G"><group android:name="_R_G_L_0_G" android:translateX="28.237000000000002" android:translateY="23.112000000000002"><path android:name="_R_G_L_0_G_D_0_P_0" android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "/></group></group><group android:name="time_group"/></vector></aapt:attr><target android:name="time_group"><aapt:attr name="android:animation"><set android:ordering="together"><objectAnimator android:propertyName="translateX" android:duration="500" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/></set></aapt:attr></target></animated-vector>
|
||||
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt">
|
||||
<aapt:attr name="android:drawable">
|
||||
<vector
|
||||
android:height="30dp"
|
||||
android:width="30dp"
|
||||
android:viewportHeight="30"
|
||||
android:viewportWidth="30">
|
||||
<group android:name="_R_G">
|
||||
<group
|
||||
android:name="_R_G_L_0_G"
|
||||
android:translateX="28.237000000000002"
|
||||
android:translateY="23.112000000000002">
|
||||
<path
|
||||
android:name="_R_G_L_0_G_D_0_P_0"
|
||||
android:strokeColor="#ffffff"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeAlpha="1"
|
||||
android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c " />
|
||||
</group>
|
||||
</group>
|
||||
<group android:name="time_group" />
|
||||
</vector>
|
||||
</aapt:attr>
|
||||
<target android:name="time_group">
|
||||
<aapt:attr name="android:animation">
|
||||
<set android:ordering="together">
|
||||
<objectAnimator
|
||||
android:propertyName="translateX"
|
||||
android:duration="500"
|
||||
android:startOffset="0"
|
||||
android:valueFrom="0"
|
||||
android:valueTo="1"
|
||||
android:valueType="floatType" />
|
||||
</set>
|
||||
</aapt:attr>
|
||||
</target>
|
||||
</animated-vector>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -61,6 +61,7 @@ constructor(
|
||||
|
||||
private val pin: PinBouncerViewModel by lazy {
|
||||
PinBouncerViewModel(
|
||||
applicationContext = applicationContext,
|
||||
applicationScope = applicationScope,
|
||||
interactor = interactor,
|
||||
isInputEnabled = isInputEnabled,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.systemui.bouncer.ui.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import com.android.keyguard.PinShapeAdapter
|
||||
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
|
||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -29,6 +31,7 @@ import kotlinx.coroutines.launch
|
||||
|
||||
/** Holds UI state and handles user input for the PIN code bouncer UI. */
|
||||
class PinBouncerViewModel(
|
||||
applicationContext: Context,
|
||||
private val applicationScope: CoroutineScope,
|
||||
private val interactor: BouncerInteractor,
|
||||
isInputEnabled: StateFlow<Boolean>,
|
||||
@@ -37,6 +40,8 @@ class PinBouncerViewModel(
|
||||
isInputEnabled = isInputEnabled,
|
||||
) {
|
||||
|
||||
val pinShapes = PinShapeAdapter(applicationContext)
|
||||
|
||||
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
|
||||
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
|
||||
)
|
||||
private val underTest =
|
||||
PinBouncerViewModel(
|
||||
applicationContext = context,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
interactor =
|
||||
utils.bouncerInteractor(
|
||||
|
||||
@@ -67,6 +67,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
||||
)
|
||||
private val underTest =
|
||||
PinBouncerViewModel(
|
||||
applicationContext = context,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
interactor = bouncerInteractor,
|
||||
isInputEnabled = MutableStateFlow(true).asStateFlow(),
|
||||
|
||||
Reference in New Issue
Block a user