summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt170
1 files changed, 69 insertions, 101 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 383c748f5dd8..3afd33f4c90c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.ui.composable
+import android.view.HapticFeedbackConstants
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
@@ -32,14 +33,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.integerResource
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import com.android.internal.R
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
import kotlin.math.min
@@ -82,17 +84,26 @@ internal fun PatternBouncer(
val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState()
// Map of animatables for the scale of each dot, keyed by dot.
- val scales = remember(dots) { dots.associateWith { Animatable(1f) } }
+ val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } }
// Map of animatables for the lines that connect between selected dots, keyed by the destination
// dot of the line.
- val lines = remember(dots) { dots.associateWith { Animatable(1f) } }
+ val lineFadeOutAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } }
+ val lineFadeOutAnimationDurationMs =
+ integerResource(R.integer.lock_pattern_line_fade_out_duration)
+ val lineFadeOutAnimationDelayMs = integerResource(R.integer.lock_pattern_line_fade_out_delay)
val scope = rememberCoroutineScope()
+ val view = LocalView.current
// When the current dot is changed, we need to update our animations.
LaunchedEffect(currentDot) {
+ view.performHapticFeedback(
+ HapticFeedbackConstants.VIRTUAL_KEY,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
+ )
+
// Make sure that the current dot is scaled up while the other dots are scaled back down.
- scales.entries.forEach { (dot, animatable) ->
+ dotScalingAnimatables.entries.forEach { (dot, animatable) ->
val isSelected = dot == currentDot
launch {
animatable.animateTo(if (isSelected) 2f else 1f)
@@ -102,15 +113,26 @@ internal fun PatternBouncer(
}
}
- // Make sure that all dot-connecting lines are decaying, if they're not already animating.
- selectedDots.forEach {
- lines[it]?.let { line ->
+ selectedDots.forEach { dot ->
+ lineFadeOutAnimatables[dot]?.let { line ->
if (!line.isRunning) {
scope.launch {
- line.animateTo(
- targetValue = 0f,
- animationSpec = tween(durationMillis = 500),
- )
+ if (dot == currentDot) {
+ // Reset the fade-out animation for the current dot. When the current
+ // dot is switched, this entire code block runs again for the newly
+ // selected dot.
+ line.snapTo(1f)
+ } else {
+ // For all non-current dots, make sure that the lines are fading out.
+ line.animateTo(
+ targetValue = 0f,
+ animationSpec =
+ tween(
+ durationMillis = lineFadeOutAnimationDurationMs,
+ delayMillis = lineFadeOutAnimationDelayMs,
+ ),
+ )
+ }
}
}
}
@@ -134,7 +156,7 @@ internal fun PatternBouncer(
},
onDragEnd = {
inputPosition = null
- lines.values.forEach { animatable ->
+ lineFadeOutAnimatables.values.forEach { animatable ->
scope.launch { animatable.animateTo(1f) }
}
viewModel.onDragEnd()
@@ -154,14 +176,22 @@ internal fun PatternBouncer(
selectedDots.forEachIndexed { index, dot ->
if (index > 0) {
val previousDot = selectedDots[index - 1]
+ val lineFadeOutAnimationProgress = lineFadeOutAnimatables[previousDot]!!.value
+ val startLerp = 1 - lineFadeOutAnimationProgress
+ val from = pixelOffset(previousDot, spacing, verticalOffset)
+ val to = pixelOffset(dot, spacing, verticalOffset)
+ val lerpedFrom =
+ Offset(
+ x = from.x + (to.x - from.x) * startLerp,
+ y = from.y + (to.y - from.y) * startLerp,
+ )
drawLine(
- from = previousDot,
- to = dot,
- alpha = { distance -> lineAlpha(spacing, distance) },
- spacing = spacing,
- verticalOffset = verticalOffset,
- lineColor = lineColor,
- lineStrokeWidth = lineStrokeWidth,
+ start = lerpedFrom,
+ end = to,
+ cap = StrokeCap.Round,
+ alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+ color = lineColor,
+ strokeWidth = lineStrokeWidth,
)
}
}
@@ -169,93 +199,31 @@ internal fun PatternBouncer(
// Draw the line between the most recently-selected dot and the input pointer position.
inputPosition?.let { lineEnd ->
currentDot?.let { dot ->
+ val from = pixelOffset(dot, spacing, verticalOffset)
+ val lineLength = sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
drawLine(
- from = dot,
- to = lineEnd,
- alpha = { distance -> lineAlpha(spacing, distance) },
- spacing = spacing,
- verticalOffset = verticalOffset,
- lineColor = lineColor,
- lineStrokeWidth = lineStrokeWidth,
+ start = from,
+ end = lineEnd,
+ cap = StrokeCap.Round,
+ alpha = lineAlpha(spacing, lineLength),
+ color = lineColor,
+ strokeWidth = lineStrokeWidth,
)
}
}
// Draw each dot on the grid.
dots.forEach { dot ->
- drawDot(
- dot = dot,
- scaleFactor = { scales[dot]?.value ?: 1f },
- spacing = spacing,
- verticalOffset = verticalOffset,
- dotColor = dotColor,
- dotRadius = dotRadius,
+ drawCircle(
+ center = pixelOffset(dot, spacing, verticalOffset),
+ color = dotColor,
+ radius = dotRadius * (dotScalingAnimatables[dot]?.value ?: 1f),
)
}
}
}
-/** Draws the given [dot]. */
-private fun DrawScope.drawDot(
- dot: PatternDotViewModel,
- scaleFactor: () -> Float,
- spacing: Float,
- verticalOffset: Float,
- dotColor: Color,
- dotRadius: Float,
-) {
- drawCircle(
- color = dotColor,
- radius = dotRadius * scaleFactor.invoke(),
- center = pixelOffset(dot, spacing, verticalOffset),
- )
-}
-
-/** Draws a line from the [from] origin dot to the [to] destination dot. */
-private fun DrawScope.drawLine(
- from: PatternDotViewModel,
- to: PatternDotViewModel,
- alpha: (distance: Float) -> Float,
- spacing: Float,
- verticalOffset: Float,
- lineColor: Color,
- lineStrokeWidth: Float,
-) {
- drawLine(
- from = from,
- to = pixelOffset(to, spacing, verticalOffset),
- alpha = alpha,
- spacing = spacing,
- verticalOffset = verticalOffset,
- lineColor = lineColor,
- lineStrokeWidth = lineStrokeWidth,
- )
-}
-
-/** Draws a line from the [from] origin dot to the [to] destination. */
-private fun DrawScope.drawLine(
- from: PatternDotViewModel,
- to: Offset,
- alpha: (distance: Float) -> Float,
- spacing: Float,
- verticalOffset: Float,
- lineColor: Color,
- lineStrokeWidth: Float,
-) {
- val fromAsOffset = pixelOffset(from, spacing, verticalOffset)
- val distance = sqrt((to.y - fromAsOffset.y).pow(2) + (to.x - fromAsOffset.x).pow(2))
-
- drawLine(
- color = lineColor,
- start = fromAsOffset,
- end = to,
- strokeWidth = lineStrokeWidth,
- cap = StrokeCap.Round,
- alpha = alpha.invoke(distance),
- )
-}
-
-/** Returns an [Offset] representation of the given [dot] in pixel coordinates. */
+/** Returns an [Offset] representation of the given [dot], in pixel coordinates. */
private fun pixelOffset(
dot: PatternDotViewModel,
spacing: Float,
@@ -268,14 +236,14 @@ private fun pixelOffset(
}
/**
- * Returns the alpha for a line between dots where dots are [spacing] apart from each other on the
- * dot grid and the line ends [distance] away from the origin dot.
+ * Returns the alpha for a line between dots where dots are normally [gridSpacing] apart from each
+ * other on the dot grid and the line ends [lineLength] away from the origin dot.
*
- * The reason [distance] can be different from [spacing] is that all lines originate in dots but one
- * line might end where the user input pointer is, which isn't always a dot position.
+ * The reason [lineLength] can be different from [gridSpacing] is that all lines originate in dots
+ * but one line might end where the user input pointer is, which isn't always a dot position.
*/
-private fun lineAlpha(spacing: Float, distance: Float): Float {
+private fun lineAlpha(gridSpacing: Float, lineLength: Float = gridSpacing): Float {
// Custom curve for the alpha of a line as a function of its distance from its source dot. The
// farther the user input pointer goes from the line, the more opaque the line gets.
- return ((distance / spacing - 0.3f) * 4f).coerceIn(0f, 1f)
+ return ((lineLength / gridSpacing - 0.3f) * 4f).coerceIn(0f, 1f)
}