Merge changes from topic "shapes" into udc-qpr-dev am: 1672c54654
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23811467 Change-Id: I9d3f7bcdff55cf3467ea6fde6f9dad0d57933c3e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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