diff options
6 files changed, 272 insertions, 121 deletions
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index d850bbe63afd..cd8f18f7dcdb 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -23,7 +23,6 @@ android:clipToPadding="false" android:gravity="center" android:layoutDirection="ltr" - android:orientation="vertical" app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene"> <!-- add ringer buttons here --> diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt index f04fb2c3b8d5..1ae56824ca25 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt @@ -24,16 +24,16 @@ import android.widget.ImageButton import androidx.annotation.LayoutRes import androidx.compose.ui.util.fastForEachIndexed import androidx.constraintlayout.motion.widget.MotionLayout -import androidx.constraintlayout.widget.ConstraintSet import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatValueHolder import androidx.dynamicanimation.animation.SpringAnimation import androidx.dynamicanimation.animation.SpringForce import com.android.internal.R as internalR import com.android.systemui.res.R -import com.android.systemui.util.children import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope import com.android.systemui.volume.dialog.ringer.ui.util.VolumeDialogRingerDrawerTransitionListener +import com.android.systemui.volume.dialog.ringer.ui.util.updateCloseState +import com.android.systemui.volume.dialog.ringer.ui.util.updateOpenState import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonUiModel import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState @@ -93,6 +93,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { } drawerContainer.setTransitionListener(ringerDrawerTransitionListener) volumeDialogBackgroundView.background = volumeDialogBackgroundView.background.mutate() + viewModel.ringerViewModel .onEach { ringerState -> when (ringerState) { @@ -110,7 +111,10 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { unselectedButtonUiModel, ) ringerDrawerTransitionListener.setProgressChangeEnabled(true) - drawerContainer.closeDrawer(uiModel.currentButtonIndex) + drawerContainer.closeDrawer( + uiModel.currentButtonIndex, + ringerState.orientation, + ) } is RingerDrawerState.Closed -> { @@ -125,8 +129,10 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { unselectedButtonUiModel, onProgressChanged = { progress, isReverse -> // Let's make button progress when switching matches - // motionLayout transition progress. When full radius, - // progress is 0.0. When small radius, progress is 1.0. + // motionLayout transition progress. When full + // radius, + // progress is 0.0. When small radius, progress is + // 1.0. backgroundAnimationProgress = if (isReverse) { 1F - progress @@ -147,7 +153,10 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { true ) } - drawerContainer.closeDrawer(uiModel.currentButtonIndex) + drawerContainer.closeDrawer( + uiModel.currentButtonIndex, + ringerState.orientation, + ) } } } @@ -167,6 +176,7 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { } else { ringerDrawerTransitionListener.setProgressChangeEnabled(true) } + updateOpenState(drawerContainer, ringerState.orientation) drawerContainer.transitionToState( R.id.volume_dialog_ringer_drawer_open ) @@ -223,23 +233,30 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { // We only need to execute on roundness animation end and volume dialog background // progress update once because these changes should be applied once on volume dialog // background and ringer drawer views. - selectedButton.animateTo( - selectedButtonUiModel, - if (uiModel.currentButtonIndex == count - 1) { - onProgressChanged - } else { - { _, _ -> } - }, - roundnessAnimationEndListener, - ) - unselectedButton.animateTo( - unselectedButtonUiModel, - if (previousIndex == count - 1) { - onProgressChanged - } else { - { _, _ -> } - }, - ) + val selectedCornerRadius = (selectedButton.background as GradientDrawable).cornerRadius + if (selectedCornerRadius.toInt() != selectedButtonUiModel.cornerRadius) { + selectedButton.animateTo( + selectedButtonUiModel, + if (uiModel.currentButtonIndex == count - 1) { + onProgressChanged + } else { + { _, _ -> } + }, + roundnessAnimationEndListener, + ) + } + val unselectedCornerRadius = + (unselectedButton.background as GradientDrawable).cornerRadius + if (unselectedCornerRadius.toInt() != unselectedButtonUiModel.cornerRadius) { + unselectedButton.animateTo( + unselectedButtonUiModel, + if (previousIndex == count - 1) { + onProgressChanged + } else { + { _, _ -> } + }, + ) + } } else { bindButtons(viewModel, uiModel, onAnimationEnd) } @@ -318,107 +335,16 @@ constructor(private val viewModel: VolumeDialogRingerDrawerViewModel) { inflater.inflate(viewLayoutId, this, true) getChildAt(childCount - 1).id = View.generateViewId() } - cloneConstraintSet(R.id.volume_dialog_ringer_drawer_open) - .adjustOpenConstraintsForDrawer(this) } } } - private fun MotionLayout.closeDrawer(selectedIndex: Int) { + private fun MotionLayout.closeDrawer(selectedIndex: Int, orientation: Int) { setTransition(R.id.close_to_open_transition) - cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close) - .adjustClosedConstraintsForDrawer(selectedIndex, this) + updateCloseState(this, selectedIndex, orientation) transitionToState(R.id.volume_dialog_ringer_drawer_close) } - private fun ConstraintSet.adjustOpenConstraintsForDrawer(motionLayout: MotionLayout) { - motionLayout.children.forEachIndexed { index, button -> - setButtonPositionConstraints(motionLayout, index, button) - setAlpha(button.id, 1.0F) - constrainWidth( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - constrainHeight( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - if (index != motionLayout.childCount - 1) { - setMargin( - button.id, - ConstraintSet.BOTTOM, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_components_spacing - ), - ) - } - } - motionLayout.updateState(R.id.volume_dialog_ringer_drawer_open, this) - } - - private fun ConstraintSet.adjustClosedConstraintsForDrawer( - selectedIndex: Int, - motionLayout: MotionLayout, - ) { - motionLayout.children.forEachIndexed { index, button -> - setButtonPositionConstraints(motionLayout, index, button) - constrainWidth( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - if (selectedIndex != motionLayout.childCount - index - 1) { - setAlpha(button.id, 0.0F) - constrainHeight(button.id, 0) - setMargin(button.id, ConstraintSet.BOTTOM, 0) - } else { - setAlpha(button.id, 1.0F) - constrainHeight( - button.id, - motionLayout.context.resources.getDimensionPixelSize( - R.dimen.volume_dialog_ringer_drawer_button_size - ), - ) - } - } - motionLayout.updateState(R.id.volume_dialog_ringer_drawer_close, this) - } - - private fun ConstraintSet.setButtonPositionConstraints( - motionLayout: MotionLayout, - index: Int, - button: View, - ) { - if (motionLayout.getChildAt(index - 1) == null) { - connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP) - } else { - connect( - button.id, - ConstraintSet.TOP, - motionLayout.getChildAt(index - 1).id, - ConstraintSet.BOTTOM, - ) - } - - if (motionLayout.getChildAt(index + 1) == null) { - connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) - } else { - connect( - button.id, - ConstraintSet.BOTTOM, - motionLayout.getChildAt(index + 1).id, - ConstraintSet.TOP, - ) - } - connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START) - connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) - } - private suspend fun ImageButton.animateTo( ringerButtonUiModel: RingerButtonUiModel, onProgressChanged: (Float, Boolean) -> Unit = { _, _ -> }, diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt new file mode 100644 index 000000000000..25ba1bd3a1d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.dialog.ringer.ui.util + +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.view.View +import androidx.constraintlayout.motion.widget.MotionLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.res.R +import com.android.systemui.util.children + +fun updateOpenState(ringerDrawer: MotionLayout, orientation: Int) { + val openSet = ringerDrawer.cloneConstraintSet(R.id.volume_dialog_ringer_drawer_open) + openSet.adjustOpenConstraintsForDrawer(ringerDrawer, orientation) + ringerDrawer.updateState(R.id.volume_dialog_ringer_drawer_open, openSet) +} + +fun updateCloseState(ringerDrawer: MotionLayout, selectedIndex: Int, orientation: Int) { + val closeSet = ringerDrawer.cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close) + closeSet.adjustClosedConstraintsForDrawer(ringerDrawer, selectedIndex, orientation) + ringerDrawer.updateState(R.id.volume_dialog_ringer_drawer_close, closeSet) +} + +private fun ConstraintSet.setButtonPositionPortraitConstraints( + motionLayout: MotionLayout, + index: Int, + button: View, +) { + if (motionLayout.getChildAt(index - 1) == null) { + connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP) + } else { + connect( + button.id, + ConstraintSet.TOP, + motionLayout.getChildAt(index - 1).id, + ConstraintSet.BOTTOM, + ) + } + + if (motionLayout.getChildAt(index + 1) == null) { + connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) + } else { + connect( + button.id, + ConstraintSet.BOTTOM, + motionLayout.getChildAt(index + 1).id, + ConstraintSet.TOP, + ) + } + connect(button.id, ConstraintSet.START, motionLayout.id, ConstraintSet.START) + connect(button.id, ConstraintSet.END, motionLayout.id, ConstraintSet.END) + clear(button.id, ConstraintSet.LEFT) + clear(button.id, ConstraintSet.RIGHT) +} + +private fun ConstraintSet.setButtonPositionLandscapeConstraints( + motionLayout: MotionLayout, + index: Int, + button: View, +) { + if (motionLayout.getChildAt(index - 1) == null) { + connect(button.id, ConstraintSet.LEFT, motionLayout.id, ConstraintSet.LEFT) + } else { + connect( + button.id, + ConstraintSet.LEFT, + motionLayout.getChildAt(index - 1).id, + ConstraintSet.RIGHT, + ) + } + if (motionLayout.getChildAt(index + 1) == null) { + connect(button.id, ConstraintSet.RIGHT, motionLayout.id, ConstraintSet.RIGHT) + } else { + connect( + button.id, + ConstraintSet.RIGHT, + motionLayout.getChildAt(index + 1).id, + ConstraintSet.LEFT, + ) + } + connect(button.id, ConstraintSet.TOP, motionLayout.id, ConstraintSet.TOP) + connect(button.id, ConstraintSet.BOTTOM, motionLayout.id, ConstraintSet.BOTTOM) + clear(button.id, ConstraintSet.START) + clear(button.id, ConstraintSet.END) +} + +private fun ConstraintSet.adjustOpenConstraintsForDrawer( + motionLayout: MotionLayout, + lastOrientation: Int, +) { + motionLayout.children.forEachIndexed { index, button -> + setAlpha(button.id, 1.0F) + constrainWidth( + button.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + constrainHeight( + button.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + when (lastOrientation) { + ORIENTATION_LANDSCAPE -> { + setButtonPositionLandscapeConstraints(motionLayout, index, button) + if (index != motionLayout.childCount - 1) { + setMargin( + button.id, + ConstraintSet.RIGHT, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_components_spacing + ), + ) + } else { + setMargin(button.id, ConstraintSet.RIGHT, 0) + } + setMargin(button.id, ConstraintSet.BOTTOM, 0) + } + ORIENTATION_PORTRAIT -> { + setButtonPositionPortraitConstraints(motionLayout, index, button) + if (index != motionLayout.childCount - 1) { + setMargin( + button.id, + ConstraintSet.BOTTOM, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_components_spacing + ), + ) + } else { + setMargin(button.id, ConstraintSet.BOTTOM, 0) + } + setMargin(button.id, ConstraintSet.RIGHT, 0) + } + } + } +} + +private fun ConstraintSet.adjustClosedConstraintsForDrawer( + motionLayout: MotionLayout, + selectedIndex: Int, + lastOrientation: Int, +) { + motionLayout.children.forEachIndexed { index, button -> + setMargin(button.id, ConstraintSet.RIGHT, 0) + setMargin(button.id, ConstraintSet.BOTTOM, 0) + when (lastOrientation) { + ORIENTATION_LANDSCAPE -> { + setButtonPositionLandscapeConstraints(motionLayout, index, button) + if (selectedIndex != motionLayout.childCount - index - 1) { + setAlpha(button.id, 0.0F) + constrainWidth(button.id, 0) + } else { + setAlpha(button.id, 1.0F) + constrainWidth( + button.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + } + constrainHeight( + button.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + } + ORIENTATION_PORTRAIT -> { + setButtonPositionPortraitConstraints(motionLayout, index, button) + if (selectedIndex != motionLayout.childCount - index - 1) { + setAlpha(button.id, 0.0F) + constrainHeight(button.id, 0) + } else { + setAlpha(button.id, 1.0F) + constrainHeight( + button.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + } + constrainWidth( + button.id, + motionLayout.context.resources.getDimensionPixelSize( + R.dimen.volume_dialog_ringer_drawer_button_size + ), + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt index 78b00afa9510..50898b670938 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModelState.kt @@ -19,7 +19,8 @@ package com.android.systemui.volume.dialog.ringer.ui.viewmodel /** Models ringer view model state. */ sealed class RingerViewModelState { - data class Available(val uiModel: RingerViewModel) : RingerViewModelState() + data class Available(val uiModel: RingerViewModel, val orientation: Int) : + RingerViewModelState() data object Unavailable : RingerViewModelState() } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt index 627d75ee108d..eec64d9a2f86 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt @@ -33,6 +33,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.onConfigChanged import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope @@ -48,6 +50,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -65,19 +68,29 @@ constructor( private val vibrator: VibratorHelper, private val volumeDialogLogger: VolumeDialogLogger, private val visibilityInteractor: VolumeDialogVisibilityInteractor, + configurationController: ConfigurationController, ) { private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial) + private val orientation: StateFlow<Int> = + configurationController.onConfigChanged + .map { it.orientation } + .stateIn( + coroutineScope, + SharingStarted.Eagerly, + applicationContext.resources.configuration.orientation, + ) val ringerViewModel: StateFlow<RingerViewModelState> = combine( soundPolicyInteractor.isZenMuted(AudioStream(STREAM_RING)), ringerInteractor.ringerModel, drawerState, - ) { isZenMuted, ringerModel, state -> + orientation, + ) { isZenMuted, ringerModel, state, orientation -> level = ringerModel.level levelMax = ringerModel.levelMax - ringerModel.toViewModel(state, isZenMuted) + ringerModel.toViewModel(state, isZenMuted, orientation) } .flowOn(backgroundDispatcher) .stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable) @@ -133,6 +146,7 @@ constructor( private fun VolumeDialogRingerModel.toViewModel( drawerState: RingerDrawerState, isZenMuted: Boolean, + orientation: Int, ): RingerViewModelState { val currentIndex = availableModes.indexOf(currentRingerMode) if (currentIndex == -1) { @@ -149,7 +163,8 @@ constructor( currentButtonIndex = currentIndex, selectedButton = it, drawerState = drawerState, - ) + ), + orientation, ) } ?: RingerViewModelState.Unavailable } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt index 34661ced71b2..4fda95bab2ec 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor +import com.android.systemui.statusbar.policy.configurationController import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor import com.android.systemui.volume.dialog.shared.volumeDialogLogger @@ -37,5 +38,6 @@ val Kosmos.volumeDialogRingerDrawerViewModel by vibrator = vibratorHelper, volumeDialogLogger = volumeDialogLogger, visibilityInteractor = volumeDialogVisibilityInteractor, + configurationController = configurationController, ) } |