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

Commit 7e725e06 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Rework Spatial Audio component to work as a Toggle when head tracking...

Merge "Rework Spatial Audio component to work as a Toggle when head tracking is unavailable" into main
parents 94ce0fba 941354f0
Loading
Loading
Loading
Loading
+5 −14
Original line number Diff line number Diff line
@@ -16,16 +16,13 @@

package com.android.systemui.volume.panel.component.spatialaudio

import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioComponent
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey

@@ -40,14 +37,8 @@ interface SpatialAudioModule {
        criteria: SpatialAudioAvailabilityCriteria
    ): ComponentAvailabilityCriteria

    companion object {

        @Provides
    @Binds
    @IntoMap
    @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
        fun provideVolumePanelUiComponent(
            viewModel: SpatialAudioViewModel,
            popup: SpatialAudioPopup,
        ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
    }
    fun bindVolumePanelUiComponent(component: SpatialAudioComponent): VolumePanelUiComponent
}
+60 −0
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.panel.component.spatialaudio.ui.composable

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import javax.inject.Inject

/** [ComposeVolumePanelUiComponent] that represents spatial audio button in the Volume Panel. */
class SpatialAudioComponent
@Inject
constructor(
    private val viewModel: SpatialAudioViewModel,
    private val popup: SpatialAudioPopup,
) : ComposeVolumePanelUiComponent {

    @Composable
    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
        val shouldUsePopup by viewModel.shouldUsePopup.collectAsStateWithLifecycle()

        val buttonComponent: ComposeVolumePanelUiComponent =
            remember(shouldUsePopup) {
                if (shouldUsePopup) {
                    ButtonComponent(viewModel.spatialAudioButton, popup::show)
                } else {
                    ToggleButtonComponent(viewModel.spatialAudioButton) {
                        if (it) {
                            viewModel.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
                        } else {
                            viewModel.setEnabled(SpatialAudioEnabledModel.Disabled)
                        }
                    }
                }
            }
        with(buttonComponent) { Content(modifier) }
    }
}
+25 −0
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.
  -->

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="960"
    android:viewportWidth="960">
    <path
        android:fillColor="@android:color/white"
        android:pathData="M360,880q-134,0 -227,-93T40,560h80q0,100 70,170t170,70v80ZM360,740q-75,0 -127.5,-52.5T180,560h80q0,42 29,71t71,29v80ZM400,600q-33,0 -56.5,-23.5T320,520v-320q0,-33 23.5,-56.5T400,120h160q33,0 56.5,23.5T640,200v320q0,33 -23.5,56.5T560,600L400,600ZM400,520h160v-320L400,200v320ZM600,740v-80q42,0 71,-29t29,-71h80q0,75 -52.5,127.5T600,740ZM600,880v-80q100,0 170,-70t70,-170h80q0,134 -93,227T600,880ZM400,520h160,-160Z" />
</vector>
+50 −10
Original line number Diff line number Diff line
@@ -48,15 +48,31 @@ constructor(
    private val uiEventLogger: UiEventLogger,
) {

    private val spatialSpeakerIcon =
        Icon.Resource(R.drawable.ic_spatial_speaker, contentDescription = null)

    val spatialAudioButton: StateFlow<ButtonViewModel?> =
        interactor.isEnabled
            .map {
                val isChecked = it is SpatialAudioEnabledModel.SpatialAudioEnabled
                it.toViewModel(isChecked)
        combine(interactor.isEnabled, interactor.isAvailable) { isEnabled, isAvailable ->
                isEnabled
                    .toViewModel(
                        isChecked = isEnabled is SpatialAudioEnabledModel.SpatialAudioEnabled,
                        isHeadTrackingAvailable =
                            isAvailable is SpatialAudioAvailabilityModel.SpatialAudio,
                    )
                    .copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
            }
            .stateIn(scope, SharingStarted.Eagerly, null)

    val shouldUsePopup: StateFlow<Boolean> =
        interactor.isAvailable
            .map {
                // head tracking availability means there are three possible states for the spatial
                // audio: disabled, enabled regular, enabled with head tracking.
                // Show popup in this case instead of a togglealbe button.
                it is SpatialAudioAvailabilityModel.SpatialAudio
            }
            .stateIn(scope, SharingStarted.Eagerly, false)

    val isAvailable: StateFlow<Boolean> =
        availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)

@@ -73,8 +89,12 @@ constructor(
                        }
                    }
                    .map { isEnabled ->
                        val isChecked = isEnabled == currentIsEnabled
                        val buttonViewModel: ButtonViewModel = isEnabled.toViewModel(isChecked)
                        val buttonViewModel: ButtonViewModel =
                            isEnabled.toViewModel(
                                isChecked = isEnabled == currentIsEnabled,
                                isHeadTrackingAvailable =
                                    isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
                            )
                        SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
                    }
            }
@@ -97,11 +117,21 @@ constructor(
        scope.launch { interactor.setEnabled(model) }
    }

    private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ButtonViewModel {
    private fun SpatialAudioEnabledModel.toViewModel(
        isChecked: Boolean,
        isHeadTrackingAvailable: Boolean,
    ): ButtonViewModel {
        // This method deliberately uses the same icon for the case when head tracking is disabled
        // to show a toggle button with a non-changing icon
        if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
            return ButtonViewModel(
                isActive = isChecked,
                icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
                icon =
                    if (isHeadTrackingAvailable) {
                        Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null)
                    } else {
                        spatialSpeakerIcon
                    },
                label = context.getString(R.string.volume_panel_spatial_audio_tracking)
            )
        }
@@ -109,7 +139,12 @@ constructor(
        if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
            return ButtonViewModel(
                isActive = isChecked,
                icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
                icon =
                    if (isHeadTrackingAvailable) {
                        Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null)
                    } else {
                        spatialSpeakerIcon
                    },
                label = context.getString(R.string.volume_panel_spatial_audio_fixed)
            )
        }
@@ -117,7 +152,12 @@ constructor(
        if (this is SpatialAudioEnabledModel.Disabled) {
            return ButtonViewModel(
                isActive = isChecked,
                icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
                icon =
                    if (isHeadTrackingAvailable) {
                        Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null)
                    } else {
                        spatialSpeakerIcon
                    },
                label = context.getString(R.string.volume_panel_spatial_audio_off)
            )
        }