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

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

Change-Id: I28bd30d431578ef05cf3a8b8151c0bf571944c7e
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:11:32 +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 MESSAGE_ID_SLOT_SELECTED = 1337
const val KEY_SLOT_ID = "slot_id" const val KEY_SLOT_ID = "slot_id"
const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_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 { object KeyguardBottomAreaViewBinder {
private const val EXIT_DOZE_BUTTON_REVEAL_ANIMATION_DURATION_MS = 250L 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. * Defines interface for an object that acts as the binding between the view and its view-model.
@@ -315,6 +317,12 @@ object KeyguardBottomAreaViewBinder {
} else { } else {
null 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 view.isClickable = viewModel.isClickable
if (viewModel.isClickable) { if (viewModel.isClickable) {

View File

@@ -24,7 +24,6 @@ import android.content.IntentFilter
import android.hardware.display.DisplayManager import android.hardware.display.DisplayManager
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.SurfaceControlViewHost import android.view.SurfaceControlViewHost
import android.view.View import android.view.View
@@ -65,6 +64,11 @@ constructor(
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN) val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH) private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT) 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 private var host: SurfaceControlViewHost
@@ -82,6 +86,7 @@ constructor(
bundle.getString( bundle.getString(
KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID, KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
), ),
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
) )
runBlocking(mainDispatcher) { runBlocking(mainDispatcher) {
host = host =
@@ -154,8 +159,7 @@ constructor(
bottomAreaView, bottomAreaView,
FrameLayout.LayoutParams( FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.MATCH_PARENT,
Gravity.BOTTOM,
), ),
) )
} }
@@ -195,7 +199,13 @@ constructor(
?.events ?.events
?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView)) ?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
clockView?.let { parentView.removeView(it) } 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 { companion object {
@@ -203,5 +213,7 @@ constructor(
private const val KEY_VIEW_WIDTH = "width" private const val KEY_VIEW_WIDTH = "width"
private const val KEY_VIEW_HEIGHT = "height" private const val KEY_VIEW_HEIGHT = "height"
private const val KEY_DISPLAY_ID = "display_id" 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 bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper, 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 * 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 * in the wallpaper picker application. This should _always_ be `false` for the real lock screen
* experience. * 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 * 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() keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */ /** An observable for the alpha level for the entire bottom area. */
val alpha: Flow<Float> = val alpha: Flow<Float> =
isInPreviewMode.flatMapLatest { isInPreviewMode -> previewMode.flatMapLatest {
if (isInPreviewMode) { if (it.isInPreviewMode) {
flowOf(1f) flowOf(1f)
} else { } else {
bottomAreaInteractor.alpha.distinctUntilChanged() bottomAreaInteractor.alpha.distinctUntilChanged()
@@ -129,9 +134,18 @@ constructor(
* lock screen. * lock screen.
* *
* @param initiallySelectedSlotId The ID of the initial slot to render as the selected one. * @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?) { fun enablePreviewMode(
isInPreviewMode.value = true initiallySelectedSlotId: String?,
shouldHighlightSelectedAffordance: Boolean,
) {
previewMode.value =
PreviewMode(
isInPreviewMode = true,
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
onPreviewSlotSelected( onPreviewSlotSelected(
initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
) )
@@ -150,9 +164,9 @@ constructor(
private fun button( private fun button(
position: KeyguardQuickAffordancePosition position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> { ): Flow<KeyguardQuickAffordanceViewModel> {
return isInPreviewMode.flatMapLatest { isInPreviewMode -> return previewMode.flatMapLatest { previewMode ->
combine( combine(
if (isInPreviewMode) { if (previewMode.isInPreviewMode) {
quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position) quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
} else { } else {
quickAffordanceInteractor.quickAffordance(position = position) quickAffordanceInteractor.quickAffordance(position = position)
@@ -161,11 +175,18 @@ constructor(
areQuickAffordancesFullyOpaque, areQuickAffordancesFullyOpaque,
selectedPreviewSlotId, selectedPreviewSlotId,
) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId -> ) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
val isSelected = selectedPreviewSlotId == position.toSlotId()
model.toViewModel( model.toViewModel(
animateReveal = !isInPreviewMode && animateReveal, animateReveal = !previewMode.isInPreviewMode && animateReveal,
isClickable = isFullyOpaque && !isInPreviewMode, isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
isSelected = isSelected =
(isInPreviewMode && selectedPreviewSlotId == position.toSlotId()), previewMode.isInPreviewMode &&
previewMode.shouldHighlightSelectedAffordance &&
isSelected,
isDimmed =
previewMode.isInPreviewMode &&
previewMode.shouldHighlightSelectedAffordance &&
!isSelected,
) )
} }
.distinctUntilChanged() .distinctUntilChanged()
@@ -176,6 +197,7 @@ constructor(
animateReveal: Boolean, animateReveal: Boolean,
isClickable: Boolean, isClickable: Boolean,
isSelected: Boolean, isSelected: Boolean,
isDimmed: Boolean,
): KeyguardQuickAffordanceViewModel { ): KeyguardQuickAffordanceViewModel {
return when (this) { return when (this) {
is KeyguardQuickAffordanceModel.Visible -> is KeyguardQuickAffordanceModel.Visible ->
@@ -194,6 +216,7 @@ constructor(
isActivated = activationState is ActivationState.Active, isActivated = activationState is ActivationState.Active,
isSelected = isSelected, isSelected = isSelected,
useLongPress = quickAffordanceInteractor.useLongPress, useLongPress = quickAffordanceInteractor.useLongPress,
isDimmed = isDimmed,
) )
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel() is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
} }

View File

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

View File

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