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 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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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!" }
|
||||||
|
|||||||
Reference in New Issue
Block a user