diff options
| author | 2023-06-26 14:55:57 +0200 | |
|---|---|---|
| committer | 2023-06-29 12:10:37 +0200 | |
| commit | 38928d30027f4563aadc5678b8c532ac1a6d7ddc (patch) | |
| tree | 010a48a461d0595b16129bd163f097faba969e26 | |
| parent | acf0dad9f7d996b28a3ab034cc7a38ff38929f24 (diff) | |
Add shapes to PIN bouncer
Bug: 282730134
Test: Manual verification. Please see video capture attached to b/282730134.
Test: Model unit tests
Change-Id: I59acd3332ea20dddf39e7b188764f6fe7b8ba0d0
6 files changed, 37 insertions, 23 deletions
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp index 8f8bb1bc2d5a..c6438c9ef955 100644 --- a/packages/SystemUI/compose/features/Android.bp +++ b/packages/SystemUI/compose/features/Android.bp @@ -34,6 +34,7 @@ android_library { "PlatformComposeCore", "androidx.compose.runtime_runtime", + "androidx.compose.animation_animation-graphics", "androidx.compose.material3_material3", "androidx.activity_activity-compose", ], diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 323fed0c11f3..85178bc26a62 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index b293ea604636..db6ca0bda0f6 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -61,6 +61,7 @@ constructor( private val pin: PinBouncerViewModel by lazy { PinBouncerViewModel( + applicationContext = applicationContext, applicationScope = applicationScope, interactor = interactor, isInputEnabled = isInputEnabled, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 014ebc321d46..641e863fa303 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 22ac1b678dcc..f811ce05e314 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -42,6 +42,7 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { ) private val underTest = PinBouncerViewModel( + applicationContext = context, applicationScope = testScope.backgroundScope, interactor = utils.bouncerInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 61432e2df274..608a187a1bc9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -67,6 +67,7 @@ class PinBouncerViewModelTest : SysuiTestCase() { ) private val underTest = PinBouncerViewModel( + applicationContext = context, applicationScope = testScope.backgroundScope, interactor = bouncerInteractor, isInputEnabled = MutableStateFlow(true).asStateFlow(), |