Merge "[flexiglass] Pattern bouncer UX polish." into udc-dev

This commit is contained in:
Ale Nijamkin
2023-05-17 21:39:19 +00:00
committed by Android (Google) Code Review

View File

@@ -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)
}