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

Commit 08c01a33 authored by Michael Mikhail's avatar Michael Mikhail
Browse files

Add expansion animation for ringer drawer

Flag: com.android.systemui.volume_redesign
Bug: 369995871
Test: Checked UI
Change-Id: I7722f7eef4763c3511120697d6da5fd0487f1f42
parent 108818e1
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