Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 09ba9519 authored by Michael Mikhail's avatar Michael Mikhail Committed by Android (Google) Code Review
Browse files

Merge "Add expansion animation for ringer drawer" into main

parents 6a359f58 08c01a33
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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"
+2 −0
Original line number Diff line number Diff line
@@ -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"/>

+9 −46
Original line number Diff line number Diff line
@@ -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>
+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
+137 −35
Original line number Diff line number Diff line
@@ -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,
    ) {
        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