UX polish for lock screen preview (1/3).
If shown in the context of the quick affordance picker, the following apply: 1. The wallpaper is dim 2. The clock is dim 3. The unselected quick affordance is dim 4. The selected quick affordance is not dim and is a bit bigger If shown in the context of the normal lock screen or the preview of the lock screen on the main page of the wallpaper picker, none of the above applies. Test: Manually verified this matches the mocks in quick affordance picker, wallpaper picker, and the lock screen. Fix: 266013381 Bug: 266116562 Change-Id: I1a6c5efb32a43cdb36dce79c74db6bc914ce265f
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!" }
|
||||
|
||||
Reference in New Issue
Block a user