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:
@@ -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