Merge "UX polish for lock screen preview (1/3)." into tm-qpr-dev am: 9ddeac9025 am: b3fea560f2

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21077389

Change-Id: Ie0dc8b8233815bdc3a875224c0622ae7c55f5955
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Ale Nijamkin
2023-01-25 05:18:11 +00:00
committed by Automerger Merge Worker
6 changed files with 126 additions and 16 deletions

View File

@@ -21,4 +21,5 @@ object KeyguardQuickAffordancePreviewConstants {
const val MESSAGE_ID_SLOT_SELECTED = 1337
const val KEY_SLOT_ID = "slot_id"
const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
}

View File

@@ -66,6 +66,8 @@ import kotlinx.coroutines.launch
object KeyguardBottomAreaViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L
private const val SCALE_SELECTED_BUTTON = 1.23f
private const val DIM_ALPHA = 0.3f
/**
* Defines interface for an object that acts as the binding between the view and its view-model.
@@ -315,6 +317,12 @@ object KeyguardBottomAreaViewBinder {
} else {
null
}
view
.animate()
.scaleX(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
.scaleY(if (viewModel.isSelected) SCALE_SELECTED_BUTTON else 1f)
.alpha(if (viewModel.isDimmed) DIM_ALPHA else 1f)
.start()
view.isClickable = viewModel.isClickable
if (viewModel.isClickable) {

View File

@@ -24,7 +24,6 @@ import android.content.IntentFilter
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.os.IBinder
import android.view.Gravity
import android.view.LayoutInflater
import android.view.SurfaceControlViewHost
import android.view.View
@@ -65,6 +64,11 @@ constructor(
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
private val shouldHighlightSelectedAffordance: Boolean =
bundle.getBoolean(
KeyguardQuickAffordancePreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
false,
)
private var host: SurfaceControlViewHost
@@ -82,6 +86,7 @@ constructor(
bundle.getString(
KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
),
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
runBlocking(mainDispatcher) {
host =
@@ -154,8 +159,7 @@ constructor(
bottomAreaView,
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM,
FrameLayout.LayoutParams.MATCH_PARENT,
),
)
}
@@ -195,7 +199,13 @@ constructor(
?.events
?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
clockView?.let { parentView.removeView(it) }
clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) }
clockView =
clockController.clock?.largeClock?.view?.apply {
if (shouldHighlightSelectedAffordance) {
alpha = DIM_ALPHA
}
parentView.addView(this)
}
}
companion object {
@@ -203,5 +213,7 @@ constructor(
private const val KEY_VIEW_WIDTH = "width"
private const val KEY_VIEW_HEIGHT = "height"
private const val KEY_DISPLAY_ID = "display_id"
private const val DIM_ALPHA = 0.3f
}
}

View File

@@ -45,12 +45,17 @@ constructor(
private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper,
) {
data class PreviewMode(
val isInPreviewMode: Boolean = false,
val shouldHighlightSelectedAffordance: Boolean = false,
)
/**
* Whether this view-model instance is powering the preview experience that renders exclusively
* in the wallpaper picker application. This should _always_ be `false` for the real lock screen
* experience.
*/
private val isInPreviewMode = MutableStateFlow(false)
private val previewMode = MutableStateFlow(PreviewMode())
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
@@ -87,8 +92,8 @@ constructor(
keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */
val alpha: Flow<Float> =
isInPreviewMode.flatMapLatest { isInPreviewMode ->
if (isInPreviewMode) {
previewMode.flatMapLatest {
if (it.isInPreviewMode) {
flowOf(1f)
} else {
bottomAreaInteractor.alpha.distinctUntilChanged()
@@ -129,9 +134,18 @@ constructor(
* lock screen.
*
* @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
* @param shouldHighlightSelectedAffordance Whether the selected quick affordance should be
* highlighted (while all others are dimmed to make the selected one stand out).
*/
fun enablePreviewMode(initiallySelectedSlotId: String?) {
isInPreviewMode.value = true
fun enablePreviewMode(
initiallySelectedSlotId: String?,
shouldHighlightSelectedAffordance: Boolean,
) {
previewMode.value =
PreviewMode(
isInPreviewMode = true,
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
onPreviewSlotSelected(
initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
)
@@ -150,9 +164,9 @@ constructor(
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
return isInPreviewMode.flatMapLatest { isInPreviewMode ->
return previewMode.flatMapLatest { previewMode ->
combine(
if (isInPreviewMode) {
if (previewMode.isInPreviewMode) {
quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
} else {
quickAffordanceInteractor.quickAffordance(position = position)
@@ -161,11 +175,18 @@ constructor(
areQuickAffordancesFullyOpaque,
selectedPreviewSlotId,
) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
val isSelected = selectedPreviewSlotId == position.toSlotId()
model.toViewModel(
animateReveal = !isInPreviewMode && animateReveal,
isClickable = isFullyOpaque && !isInPreviewMode,
animateReveal = !previewMode.isInPreviewMode && animateReveal,
isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
isSelected =
(isInPreviewMode && selectedPreviewSlotId == position.toSlotId()),
previewMode.isInPreviewMode &&
previewMode.shouldHighlightSelectedAffordance &&
isSelected,
isDimmed =
previewMode.isInPreviewMode &&
previewMode.shouldHighlightSelectedAffordance &&
!isSelected,
)
}
.distinctUntilChanged()
@@ -176,6 +197,7 @@ constructor(
animateReveal: Boolean,
isClickable: Boolean,
isSelected: Boolean,
isDimmed: Boolean,
): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
@@ -194,6 +216,7 @@ constructor(
isActivated = activationState is ActivationState.Active,
isSelected = isSelected,
useLongPress = quickAffordanceInteractor.useLongPress,
isDimmed = isDimmed,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}

View File

@@ -31,6 +31,7 @@ data class KeyguardQuickAffordanceViewModel(
val isActivated: Boolean = false,
val isSelected: Boolean = false,
val useLongPress: Boolean = false,
val isDimmed: Boolean = false,
) {
data class OnClickedParameters(
val configKey: String,

View File

@@ -234,7 +234,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Test
fun `startButton - in preview mode - visible even when keyguard not showing`() =
testScope.runTest {
underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
underTest.enablePreviewMode(
initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
shouldHighlightSelectedAffordance = true,
)
repository.setKeyguardShowing(false)
val latest = collectLastValue(underTest.startButton)
@@ -263,12 +266,67 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
isSelected = true,
),
configKey = configKey,
)
assertThat(latest()?.isSelected).isTrue()
}
@Test
fun `endButton - in higlighted preview mode - dimmed when other is selected`() =
testScope.runTest {
underTest.enablePreviewMode(
initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
shouldHighlightSelectedAffordance = true,
)
repository.setKeyguardShowing(false)
val startButton = collectLastValue(underTest.startButton)
val endButton = collectLastValue(underTest.endButton)
val icon: Icon = mock()
setUpQuickAffordanceModel(
position = KeyguardQuickAffordancePosition.BOTTOM_START,
testConfig =
TestConfig(
isVisible = true,
isClickable = true,
isActivated = true,
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
),
)
val configKey =
setUpQuickAffordanceModel(
position = KeyguardQuickAffordancePosition.BOTTOM_END,
testConfig =
TestConfig(
isVisible = true,
isClickable = true,
isActivated = true,
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
),
)
assertQuickAffordanceViewModel(
viewModel = endButton(),
testConfig =
TestConfig(
isVisible = true,
isClickable = false,
isActivated = true,
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
isDimmed = true,
),
configKey = configKey,
)
}
@Test
fun `endButton - present - visible model - do nothing on click`() =
testScope.runTest {
@@ -377,7 +435,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Test
fun `alpha - in preview mode - does not change`() =
testScope.runTest {
underTest.enablePreviewMode(null)
underTest.enablePreviewMode(
initiallySelectedSlotId = null,
shouldHighlightSelectedAffordance = false,
)
val value = collectLastValue(underTest.alpha)
assertThat(value()).isEqualTo(1f)
@@ -639,6 +700,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
if (testConfig.isVisible) {
assertThat(viewModel.icon).isEqualTo(testConfig.icon)
viewModel.onClicked.invoke(
@@ -664,6 +727,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
val icon: Icon? = null,
val canShowWhileLocked: Boolean = false,
val intent: Intent? = null,
val isSelected: Boolean = false,
val isDimmed: Boolean = false,
) {
init {
check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }