Loading packages/SystemUI/res/layout/volume_dialog.xml +2 −2 Original line number Diff line number Diff line Loading @@ -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" Loading packages/SystemUI/res/layout/volume_ringer_button.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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" > Loading @@ -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"/> Loading packages/SystemUI/res/layout/volume_ringer_drawer.xml +9 −46 Original line number Diff line number Diff line Loading @@ -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"> <!-- 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"> <!-- 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"> android:layoutDirection="ltr" android:orientation="vertical" app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene"> <!-- 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> packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml 0 → 100644 +49 −0 Original line number Diff line number Diff line <?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 packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt +137 −35 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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", Loading @@ -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 Loading @@ -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 ) Loading @@ -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) } } } } Loading @@ -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 -> { Loading @@ -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, ) { 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, ) { with(uiModel) { selectedButtonView.setImageResource(selectedButton.imageResId) selectedButtonView.setOnClickListener { viewModel.onRingerButtonClicked(selectedButton.ringerMode) 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) } } Loading
packages/SystemUI/res/layout/volume_dialog.xml +2 −2 Original line number Diff line number Diff line Loading @@ -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" Loading
packages/SystemUI/res/layout/volume_ringer_button.xml +2 −0 Original line number Diff line number Diff line Loading @@ -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" > Loading @@ -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"/> Loading
packages/SystemUI/res/layout/volume_ringer_drawer.xml +9 −46 Original line number Diff line number Diff line Loading @@ -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"> <!-- 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"> <!-- 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"> android:layoutDirection="ltr" android:orientation="vertical" app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene"> <!-- 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>
packages/SystemUI/res/xml/volume_dialog_ringer_drawer_motion_scene.xml 0 → 100644 +49 −0 Original line number Diff line number Diff line <?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
packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt +137 −35 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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", Loading @@ -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 Loading @@ -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 ) Loading @@ -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) } } } } Loading @@ -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 -> { Loading @@ -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, ) { 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, ) { with(uiModel) { selectedButtonView.setImageResource(selectedButton.imageResId) selectedButtonView.setOnClickListener { viewModel.onRingerButtonClicked(selectedButton.ringerMode) 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) } }