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

Commit a4fda58f authored by Anton Potapov's avatar Anton Potapov
Browse files

Add Volume Dialog window title based on the currently active slider.

Flag: com.android.systemui.volume_redesign
Bug: 369994090
Test: manual on the foldable
Change-Id: If80334326b0ec37b8ff850196dd710dcb97aa02d
parent 70e721f9
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -25,20 +25,20 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.volume.Events
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.ui.binder.VolumeDialogBinder
import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
import javax.inject.Inject

class VolumeDialog
@Inject
constructor(
    @Application context: Context,
    private val dialogBinder: VolumeDialogBinder,
    private val viewBinder: VolumeDialogViewBinder,
    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        dialogBinder.bind(this)
        viewBinder.bind(this)
    }

    /**
+10 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.volume.dialog.shared.model

import android.content.Context
import androidx.annotation.StringRes
import com.android.systemui.plugins.VolumeDialogController

@@ -29,7 +30,9 @@ data class VolumeDialogStreamModel(
    val levelMax: Int = 0,
    val muted: Boolean = false,
    val muteSupported: Boolean = false,
    /** You likely need to use [streamLabel] instead. */
    @StringRes val name: Int = 0,
    /** You likely need to use [streamLabel] instead. */
    val remoteLabel: String? = null,
    val routedToBluetooth: Boolean = false,
) {
@@ -51,3 +54,10 @@ data class VolumeDialogStreamModel(
        routedToBluetooth = legacyState.routedToBluetooth,
    )
}

fun VolumeDialogStreamModel.streamLabel(context: Context): String {
    if (remoteLabel != null) {
        return remoteLabel
    }
    return context.resources.getString(name)
}
+0 −93
Original line number Diff line number Diff line
/*
 * 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.ui.binder

import android.app.Dialog
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

/** Binds the Volume Dialog itself. */
@VolumeDialogScope
class VolumeDialogBinder
@Inject
constructor(
    @VolumeDialog private val coroutineScope: CoroutineScope,
    private val volumeDialogViewBinder: VolumeDialogViewBinder,
    private val slidersViewBinder: VolumeDialogSlidersViewBinder,
    private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
    private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
    private val gravityViewModel: VolumeDialogGravityViewModel,
) {

    fun bind(dialog: Dialog) {
        with(dialog) {
            setupWindow(window!!)
            dialog.setContentView(R.layout.volume_dialog)
            dialog.setCanceledOnTouchOutside(true)

            with(dialog.requireViewById<View>(R.id.volume_dialog_container)) {
                volumeDialogRingerViewBinder.bind(this)
                slidersViewBinder.bind(this)
                settingsButtonViewBinder.bind(this)
                volumeDialogViewBinder.bind(dialog, this)
            }
        }
    }

    /** Configures [Window] for the [Dialog]. */
    private fun setupWindow(window: Window) =
        with(window) {
            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
            addFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
            )
            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)

            requestFeature(Window.FEATURE_NO_TITLE)
            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
            setWindowAnimations(-1)
            setFormat(PixelFormat.TRANSLUCENT)

            attributes =
                attributes.apply {
                    title = "VolumeDialog" // Not the same as Window#setTitle
                }
            setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)

            gravityViewModel.dialogGravity.onEach { window.setGravity(it) }.launchIn(coroutineScope)
        }
}
+54 −1
Original line number Diff line number Diff line
@@ -17,15 +17,26 @@
package com.android.systemui.volume.dialog.ui.binder

import android.app.Dialog
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import com.android.internal.view.RotationPolicy
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.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
@@ -40,6 +51,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach

/** Binds the root view of the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -52,9 +64,15 @@ constructor(
    private val viewModelFactory: VolumeDialogViewModel.Factory,
    private val jankListenerFactory: JankListenerFactory,
    private val tracer: VolumeTracer,
    @VolumeDialog private val coroutineScope: CoroutineScope,
    private val volumeDialogRingerViewBinder: VolumeDialogRingerViewBinder,
    private val slidersViewBinder: VolumeDialogSlidersViewBinder,
    private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
) {

    fun bind(dialog: Dialog, view: View) {
    fun bind(dialog: Dialog) {
        setupDialog(dialog)
        val view: View = dialog.requireViewById(R.id.volume_dialog_container)
        view.alpha = 0f
        view.repeatWhenAttached {
            view.viewModel(
@@ -62,11 +80,46 @@ constructor(
                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                factory = { viewModelFactory.create() },
            ) { viewModel ->
                viewModel.dialogTitle.onEach { dialog.window?.setTitle(it) }.launchIn(this)

                animateVisibility(view, dialog, viewModel.dialogVisibilityModel)

                awaitCancellation()
            }
        }
        volumeDialogRingerViewBinder.bind(view)
        slidersViewBinder.bind(view)
        settingsButtonViewBinder.bind(view)
    }

    /** Configures [Window] for the [Dialog]. */
    private fun setupDialog(dialog: Dialog) {
        with(dialog.window!!) {
            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
            addFlags(
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or
                    WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
            )
            addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)

            requestFeature(Window.FEATURE_NO_TITLE)
            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
            setWindowAnimations(-1)
            setFormat(PixelFormat.TRANSLUCENT)

            attributes =
                attributes.apply {
                    title = "VolumeDialog" // Not the same as Window#setTitle
                }
            setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)

            gravityViewModel.dialogGravity.onEach { setGravity(it) }.launchIn(coroutineScope)
        }
        dialog.setContentView(R.layout.volume_dialog)
        dialog.setCanceledOnTouchOutside(true)
    }

    private fun CoroutineScope.animateVisibility(
+22 −1
Original line number Diff line number Diff line
@@ -16,21 +16,42 @@

package com.android.systemui.volume.dialog.ui.viewmodel

import android.content.Context
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.shared.model.streamLabel
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map

/** Provides a state for the Volume Dialog. */
@OptIn(ExperimentalCoroutinesApi::class)
class VolumeDialogViewModel
@AssistedInject
constructor(dialogVisibilityInteractor: VolumeDialogVisibilityInteractor) : ExclusiveActivatable() {
constructor(
    private val context: Context,
    dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
    volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
    private val volumeDialogSliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
) : ExclusiveActivatable() {

    val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
        dialogVisibilityInteractor.dialogVisibility
    val dialogTitle: Flow<String> =
        volumeDialogSlidersInteractor.sliders.flatMapLatest { slidersModel ->
            val interactor = volumeDialogSliderInteractorFactory.create(slidersModel.slider)
            interactor.slider.map { sliderModel ->
                context.getString(R.string.volume_dialog_title, sliderModel.streamLabel(context))
            }
        }

    override suspend fun onActivated(): Nothing {
        awaitCancellation()