diff options
6 files changed, 201 insertions, 85 deletions
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 694357d534fb..b8544a64d9da 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -31,10 +31,10 @@ app:layout_constraintBottom_toBottomOf="@id/volume_dialog_settings" app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container" app:layout_constraintStart_toStartOf="@id/volume_dialog_main_slider_container" - app:layout_constraintTop_toTopOf="@id/volume_ringer_and_drawer_container" /> + app:layout_constraintTop_toTopOf="@id/volume_ringer_drawer" /> <include - android:id="@id/volume_ringer_and_drawer_container" + android:id="@id/volume_ringer_drawer" layout="@layout/volume_ringer_drawer" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml index dc6780aeae60..38bb783c2920 100644 --- a/packages/SystemUI/res/layout/volume_ringer_button.xml +++ b/packages/SystemUI/res/layout/volume_ringer_button.xml @@ -14,6 +14,7 @@ ~ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" > @@ -25,6 +26,7 @@ android:layout_marginBottom="@dimen/volume_dialog_components_spacing" android:contentDescription="@string/volume_ringer_mode" android:gravity="center" + android:tint="?androidprv:attr/materialColorOnSurface" android:src="@drawable/volume_ringer_item_bg" android:background="@drawable/volume_ringer_item_bg"/> diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index b71c4700c0fa..d850bbe63afd 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -14,55 +14,18 @@ ~ limitations under the License. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/volume_ringer_and_drawer_container" - android:layout_width="wrap_content" +<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/volume_ringer_drawer" + android:layout_width="match_parent" android:layout_height="wrap_content" android:clipChildren="false" android:clipToPadding="false" android:gravity="center" - android:layoutDirection="ltr"> + android:layoutDirection="ltr" + android:orientation="vertical" + app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene"> - <!-- Drawer view, invisible by default. --> - <FrameLayout - android:id="@+id/volume_drawer_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> + <!-- add ringer buttons here --> - <!-- View that is animated to a tapped ringer selection, so it appears selected. --> - <FrameLayout - android:id="@+id/volume_drawer_selection_background" - android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size" - android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size" - android:layout_gravity="bottom|right" - android:alpha="0.0" - android:background="@drawable/volume_drawer_selection_bg" /> - - <LinearLayout - android:id="@+id/volume_drawer_options" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <!-- add ringer buttons here --> - - </LinearLayout> - - </FrameLayout> - - <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding - position in the drawer. When the drawer is closed, it animates back. --> - <ImageButton - android:id="@+id/volume_new_ringer_active_button" - android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size" - android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size" - android:layout_marginBottom="@dimen/volume_dialog_components_spacing" - android:background="@drawable/volume_drawer_selection_bg" - android:contentDescription="@string/volume_ringer_change" - android:gravity="center" - android:src="@drawable/ic_volume_media" - android:tint="?androidprv:attr/materialColorOnPrimary" /> - -</FrameLayout>
\ No newline at end of file +</androidx.constraintlayout.motion.widget.MotionLayout> diff --git a/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml new file mode 100644 index 000000000000..877637e0b0d8 --- /dev/null +++ b/packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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. + --> + +<MotionScene xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <Transition + android:id="@+id/transition" + app:constraintSetEnd="@+id/volume_dialog_ringer_drawer_open" + app:constraintSetStart="@+id/volume_dialog_ringer_drawer_close" + app:transitionEasing="path(0.05f, 0.7f, 0.1f, 1f)" + app:duration="400"> + </Transition> + + <ConstraintSet android:id="@+id/volume_dialog_ringer_drawer_close"> + <Constraint + android:id="@+id/volume_ringer_drawer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + </ConstraintSet> + + <ConstraintSet android:id="@+id/volume_dialog_ringer_drawer_open"> + <Constraint + android:id="@+id/volume_ringer_drawer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + </ConstraintSet> + +</MotionScene>
\ No newline at end of file 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 c4b028d7d98b..1963ba22d444 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 @@ -18,14 +18,18 @@ package com.android.systemui.volume.dialog.ringer.ui.binder import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup 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 com.android.internal.R as internalR +import com.android.settingslib.Utils import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.lifecycle.viewModel 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.viewmodel.RingerButtonViewModel import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState @@ -33,7 +37,6 @@ import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel import javax.inject.Inject -import kotlin.math.abs import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -44,12 +47,8 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact fun bind(view: View) { with(view) { - val drawerAndRingerContainer = - requireViewById<View>(R.id.volume_ringer_and_drawer_container) - val drawerContainer = requireViewById<View>(R.id.volume_drawer_container) - val selectedButtonView = - requireViewById<ImageButton>(R.id.volume_new_ringer_active_button) val volumeDialogBackgroundView = requireViewById<View>(R.id.volume_dialog_background) + val drawerContainer = requireViewById<MotionLayout>(R.id.volume_ringer_drawer) repeatWhenAttached { viewModel( traceName = "VolumeDialogRingerViewBinder", @@ -62,29 +61,26 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact is RingerViewModelState.Available -> { val uiModel = ringerState.uiModel - bindSelectedButton(viewModel, uiModel, selectedButtonView) - bindDrawerButtons(viewModel, uiModel.availableButtons) + bindDrawerButtons(viewModel, uiModel) - // Set up views background and visibility - drawerAndRingerContainer.visibility = View.VISIBLE + // Set up view background and visibility + drawerContainer.visibility = View.VISIBLE when (uiModel.drawerState) { is RingerDrawerState.Initial -> { - drawerContainer.visibility = View.GONE - selectedButtonView.visibility = View.VISIBLE + drawerContainer.closeDrawer(uiModel.currentButtonIndex) volumeDialogBackgroundView.setBackgroundResource( R.drawable.volume_dialog_background ) } is RingerDrawerState.Closed -> { - drawerContainer.visibility = View.GONE - selectedButtonView.visibility = View.VISIBLE + drawerContainer.closeDrawer(uiModel.currentButtonIndex) volumeDialogBackgroundView.setBackgroundResource( R.drawable.volume_dialog_background ) } is RingerDrawerState.Open -> { - drawerContainer.visibility = View.VISIBLE - selectedButtonView.visibility = View.GONE + // Open drawer + drawerContainer.transitionToEnd() if ( uiModel.currentButtonIndex != uiModel.availableButtons.size - 1 @@ -97,7 +93,7 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact } } is RingerViewModelState.Unavailable -> { - drawerAndRingerContainer.visibility = View.GONE + drawerContainer.visibility = View.GONE volumeDialogBackgroundView.setBackgroundResource( R.drawable.volume_dialog_background ) @@ -112,15 +108,21 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact private fun View.bindDrawerButtons( viewModel: VolumeDialogRingerDrawerViewModel, - availableButtons: List<RingerButtonViewModel?>, + uiModel: RingerViewModel, ) { - val drawerOptions = requireViewById<ViewGroup>(R.id.volume_drawer_options) - val count = availableButtons.size - drawerOptions.ensureChildCount(R.layout.volume_ringer_button, count) + val drawerContainer = requireViewById<MotionLayout>(R.id.volume_ringer_drawer) + val count = uiModel.availableButtons.size + drawerContainer.ensureChildCount(R.layout.volume_ringer_button, count) - availableButtons.fastForEachIndexed { index, ringerButton -> + uiModel.availableButtons.fastForEachIndexed { index, ringerButton -> ringerButton?.let { - drawerOptions.getChildAt(count - index - 1).bindDrawerButton(it, viewModel) + val view = drawerContainer.getChildAt(count - index - 1) + // TODO (b/369995871): object animator for button switch ( active <-> inactive ) + if (index == uiModel.currentButtonIndex) { + view.bindDrawerButton(uiModel.selectedButton, viewModel, isSelected = true) + } else { + view.bindDrawerButton(it, viewModel) + } } } } @@ -128,15 +130,29 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact private fun View.bindDrawerButton( buttonViewModel: RingerButtonViewModel, viewModel: VolumeDialogRingerDrawerViewModel, + isSelected: Boolean = false, ) { with(requireViewById<ImageButton>(R.id.volume_drawer_button)) { setImageResource(buttonViewModel.imageResId) contentDescription = context.getString(buttonViewModel.contentDescriptionResId) - setOnClickListener { viewModel.onRingerButtonClicked(buttonViewModel.ringerMode) } + if (isSelected) { + setBackgroundResource(R.drawable.volume_drawer_selection_bg) + setColorFilter( + Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnPrimary) + ) + } else { + setBackgroundResource(R.drawable.volume_ringer_item_bg) + setColorFilter( + Utils.getColorAttrDefaultColor(context, internalR.attr.materialColorOnSurface) + ) + } + setOnClickListener { + viewModel.onRingerButtonClicked(buttonViewModel.ringerMode, isSelected) + } } } - private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) { + private fun MotionLayout.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) { val childCountDelta = childCount - count when { childCountDelta > 0 -> { @@ -144,21 +160,107 @@ constructor(private val viewModelFactory: VolumeDialogRingerDrawerViewModel.Fact } childCountDelta < 0 -> { val inflater = LayoutInflater.from(context) - repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) } + repeat(-childCountDelta) { + inflater.inflate(viewLayoutId, this, true) + getChildAt(childCount - 1).id = View.generateViewId() + } + cloneConstraintSet(R.id.volume_dialog_ringer_drawer_open) + .adjustOpenConstraintsForDrawer(this) } } } - private fun bindSelectedButton( - viewModel: VolumeDialogRingerDrawerViewModel, - uiModel: RingerViewModel, - selectedButtonView: ImageButton, + private fun MotionLayout.closeDrawer(selectedIndex: Int) { + cloneConstraintSet(R.id.volume_dialog_ringer_drawer_close) + .adjustClosedConstraintsForDrawer(selectedIndex, this) + transitionToStart() + } + + 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, ) { - with(uiModel) { - selectedButtonView.setImageResource(selectedButton.imageResId) - selectedButtonView.setOnClickListener { - viewModel.onRingerButtonClicked(selectedButton.ringerMode) + 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) } } 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 e040638324ac..624dcc71e2a9 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 @@ -84,8 +84,8 @@ constructor( .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .build() - fun onRingerButtonClicked(ringerMode: RingerMode) { - if (drawerState.value is RingerDrawerState.Open) { + fun onRingerButtonClicked(ringerMode: RingerMode, isSelectedButton: Boolean = false) { + if (drawerState.value is RingerDrawerState.Open && !isSelectedButton) { Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value) provideTouchFeedback(ringerMode) maybeShowToast(ringerMode) |