From f63852185dff6840383468d335eca16060710e4b Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Tue, 24 Jan 2023 10:43:50 -0800 Subject: [PATCH] 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 --- ...KeyguardQuickAffordancePreviewConstants.kt | 1 + .../ui/binder/KeyguardBottomAreaViewBinder.kt | 8 +++ .../ui/preview/KeyguardPreviewRenderer.kt | 20 ++++-- .../viewmodel/KeyguardBottomAreaViewModel.kt | 43 +++++++++--- .../KeyguardQuickAffordanceViewModel.kt | 1 + .../KeyguardBottomAreaViewModelTest.kt | 69 ++++++++++++++++++- 6 files changed, 126 insertions(+), 16 deletions(-) diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt index 18e8a962dc70e..bf922bc98bcb8 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordancePreviewConstants.kt @@ -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" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index d020529d2baec..e9d7a5b8ed6f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -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) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index a5ae8ba58d459..88085749ece08 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -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 } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt index 5d85680efcf46..1e3b60c27d841 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt @@ -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 = - 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 { - 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() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt index cf3a6daa40bb6..cb68a82118e2a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 022afdd61fc25..4b04b7b54dcd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -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!" }