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",
|
"PlatformComposeCore",
|
||||||
|
|
||||||
"androidx.compose.runtime_runtime",
|
"androidx.compose.runtime_runtime",
|
||||||
|
"androidx.compose.animation_animation-graphics",
|
||||||
"androidx.compose.material3_material3",
|
"androidx.compose.material3_material3",
|
||||||
"androidx.activity_activity-compose",
|
"androidx.activity_activity-compose",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:OptIn(ExperimentalAnimationApi::class)
|
@file:OptIn(ExperimentalAnimationApi::class, ExperimentalAnimationGraphicsApi::class)
|
||||||
|
|
||||||
package com.android.systemui.bouncer.ui.composable
|
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.animateDp
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.keyframes
|
|
||||||
import androidx.compose.animation.core.snap
|
import androidx.compose.animation.core.snap
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.core.updateTransition
|
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.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
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.draw.drawBehind
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.Layout
|
import androidx.compose.ui.layout.Layout
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.unit.Constraints
|
import androidx.compose.ui.unit.Constraints
|
||||||
@@ -70,6 +75,7 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.android.compose.animation.Easings
|
import com.android.compose.animation.Easings
|
||||||
import com.android.compose.grid.VerticalGrid
|
import com.android.compose.grid.VerticalGrid
|
||||||
|
import com.android.internal.R.id.image
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
|
import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance
|
||||||
import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
|
import com.android.systemui.bouncer.ui.viewmodel.EnteredKey
|
||||||
@@ -139,7 +145,8 @@ private fun PinInputDisplay(viewModel: PinBouncerViewModel) {
|
|||||||
else -> EntryVisibility.Hidden
|
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) {
|
LaunchedEffect(entry) {
|
||||||
// Remove entry from visiblePinEntries once the hide transition completed.
|
// Remove entry from visiblePinEntries once the hide transition completed.
|
||||||
@@ -171,15 +178,11 @@ private sealed class EntryVisibility {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
private fun PinInputEntry(shapeResourceId: Int, transition: Transition<EntryVisibility>) {
|
||||||
// spec: http://shortn/_DEhE3Xl2bi
|
// spec: http://shortn/_DEhE3Xl2bi
|
||||||
val shapePadding = 6.dp
|
|
||||||
val shapeOvershootSize = 22.dp
|
|
||||||
val dismissStaggerDelayMs = 33
|
val dismissStaggerDelayMs = 33
|
||||||
val dismissDurationMs = 450
|
val dismissDurationMs = 450
|
||||||
val expansionDurationMs = 250
|
val expansionDurationMs = 250
|
||||||
val shapeExpandDurationMs = 83
|
|
||||||
val shapeRetractDurationMs = 167
|
|
||||||
val shapeCollapseDurationMs = 200
|
val shapeCollapseDurationMs = 200
|
||||||
|
|
||||||
val animatedEntryWidth by
|
val animatedEntryWidth by
|
||||||
@@ -194,19 +197,17 @@ private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
|||||||
},
|
},
|
||||||
label = "entry space"
|
label = "entry space"
|
||||||
) { state ->
|
) { state ->
|
||||||
if (state == EntryVisibility.Shown) entryShapeSize + (shapePadding * 2) else 0.dp
|
if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
val animatedShapeSize by
|
val animatedShapeSize by
|
||||||
transition.animateDp(
|
transition.animateDp(
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
when {
|
when {
|
||||||
EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown ->
|
EntryVisibility.Hidden isTransitioningTo EntryVisibility.Shown -> {
|
||||||
keyframes {
|
// The AVD contains the entry transition.
|
||||||
durationMillis = shapeExpandDurationMs + shapeRetractDurationMs
|
snap()
|
||||||
0.dp at 0 with Easings.Linear
|
}
|
||||||
shapeOvershootSize at shapeExpandDurationMs with Easings.Legacy
|
|
||||||
}
|
|
||||||
targetState is EntryVisibility.BulkHidden -> {
|
targetState is EntryVisibility.BulkHidden -> {
|
||||||
val target = targetState as EntryVisibility.BulkHidden
|
val target = targetState as EntryVisibility.BulkHidden
|
||||||
tween(
|
tween(
|
||||||
@@ -220,17 +221,21 @@ private fun ObscuredInputEntry(transition: Transition<EntryVisibility>) {
|
|||||||
},
|
},
|
||||||
label = "shape size"
|
label = "shape size"
|
||||||
) { state ->
|
) { state ->
|
||||||
when (state) {
|
if (state == EntryVisibility.Shown) entryShapeSize else 0.dp
|
||||||
EntryVisibility.Shown -> entryShapeSize
|
|
||||||
else -> 0.dp
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
|
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
Layout(
|
Layout(
|
||||||
content = {
|
content = {
|
||||||
// TODO(b/282730134): add support for dot shapes.
|
val image = AnimatedImageVector.animatedVectorResource(shapeResourceId)
|
||||||
Canvas(Modifier) { drawCircle(dotColor) }
|
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, _ ->
|
) { measurables, _ ->
|
||||||
val shapeSizePx = animatedShapeSize.roundToPx()
|
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 pinButtonSize = 84.dp
|
||||||
private val pinButtonErrorShrinkFactor = 67.dp / pinButtonSize
|
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 {
|
private val pin: PinBouncerViewModel by lazy {
|
||||||
PinBouncerViewModel(
|
PinBouncerViewModel(
|
||||||
|
applicationContext = applicationContext,
|
||||||
applicationScope = applicationScope,
|
applicationScope = applicationScope,
|
||||||
interactor = interactor,
|
interactor = interactor,
|
||||||
isInputEnabled = isInputEnabled,
|
isInputEnabled = isInputEnabled,
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.systemui.bouncer.ui.viewmodel
|
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.authentication.shared.model.AuthenticationMethodModel
|
||||||
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
|
||||||
import kotlinx.coroutines.CoroutineScope
|
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. */
|
/** Holds UI state and handles user input for the PIN code bouncer UI. */
|
||||||
class PinBouncerViewModel(
|
class PinBouncerViewModel(
|
||||||
|
applicationContext: Context,
|
||||||
private val applicationScope: CoroutineScope,
|
private val applicationScope: CoroutineScope,
|
||||||
private val interactor: BouncerInteractor,
|
private val interactor: BouncerInteractor,
|
||||||
isInputEnabled: StateFlow<Boolean>,
|
isInputEnabled: StateFlow<Boolean>,
|
||||||
@@ -37,6 +40,8 @@ class PinBouncerViewModel(
|
|||||||
isInputEnabled = isInputEnabled,
|
isInputEnabled = isInputEnabled,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val pinShapes = PinShapeAdapter(applicationContext)
|
||||||
|
|
||||||
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
|
private val mutablePinEntries = MutableStateFlow<List<EnteredKey>>(emptyList())
|
||||||
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
|
val pinEntries: StateFlow<List<EnteredKey>> = mutablePinEntries
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
private val underTest =
|
private val underTest =
|
||||||
PinBouncerViewModel(
|
PinBouncerViewModel(
|
||||||
|
applicationContext = context,
|
||||||
applicationScope = testScope.backgroundScope,
|
applicationScope = testScope.backgroundScope,
|
||||||
interactor =
|
interactor =
|
||||||
utils.bouncerInteractor(
|
utils.bouncerInteractor(
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
private val underTest =
|
private val underTest =
|
||||||
PinBouncerViewModel(
|
PinBouncerViewModel(
|
||||||
|
applicationContext = context,
|
||||||
applicationScope = testScope.backgroundScope,
|
applicationScope = testScope.backgroundScope,
|
||||||
interactor = bouncerInteractor,
|
interactor = bouncerInteractor,
|
||||||
isInputEnabled = MutableStateFlow(true).asStateFlow(),
|
isInputEnabled = MutableStateFlow(true).asStateFlow(),
|
||||||
|
|||||||
Reference in New Issue
Block a user