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

Commit eb5e6d55 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[bc25] Introduce QuickSettingsShadeOverlay." into main

parents c5a7a543 89b39377
Loading
Loading
Loading
Loading
+29 −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.scene

import com.android.systemui.qs.ui.composable.QuickSettingsShadeOverlay
import com.android.systemui.scene.ui.composable.Overlay
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet

@Module
interface QuickSettingsShadeOverlayModule {

    @Binds @IntoSet fun quickSettingsShade(overlay: QuickSettingsShadeOverlay): Overlay
}
+85 −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.qs.ui.composable

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.ContentScope
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayContentViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
import java.util.Optional
import javax.inject.Inject

@SysUISingleton
class QuickSettingsShadeOverlay
@Inject
constructor(
    private val actionsViewModelFactory: QuickSettingsShadeOverlayActionsViewModel.Factory,
    private val contentViewModelFactory: QuickSettingsShadeOverlayContentViewModel.Factory,
    private val tintedIconManagerFactory: TintedIconManager.Factory,
    private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
    private val statusBarIconController: StatusBarIconController,
) : Overlay {

    override val key = Overlays.QuickSettingsShade

    private val actionsViewModel: QuickSettingsShadeOverlayActionsViewModel by lazy {
        actionsViewModelFactory.create()
    }

    override suspend fun activate(): Nothing {
        actionsViewModel.activate()
    }

    @Composable
    override fun ContentScope.Content(
        modifier: Modifier,
    ) {
        val viewModel =
            rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
        OverlayShade(
            modifier = modifier,
            viewModelFactory = viewModel.overlayShadeViewModelFactory,
            lockscreenContent = { Optional.empty() },
        ) {
            Column {
                ExpandedShadeHeader(
                    viewModelFactory = viewModel.shadeHeaderViewModelFactory,
                    createTintedIconManager = tintedIconManagerFactory::create,
                    createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
                    statusBarIconController = statusBarIconController,
                    modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding),
                )

                ShadeBody(
                    viewModel = viewModel.quickSettingsContainerViewModel,
                )
            }
        }
    }
}
+83 −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.qs.ui.viewmodel

import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@EnableSceneContainer
class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {

    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository }

    private val underTest by lazy { kosmos.quickSettingsShadeOverlayActionsViewModel }

    @Test
    fun upTransitionSceneKey_topAligned_hidesShade() =
        testScope.runTest {
            val actions by collectLastValue(underTest.actions)
            fakeShadeRepository.setDualShadeAlignedToBottom(false)
            underTest.activateIn(this)

            assertThat((actions?.get(Swipe.Up) as? UserActionResult.HideOverlay)?.overlay)
                .isEqualTo(Overlays.QuickSettingsShade)
            assertThat(actions?.get(Swipe.Down)).isNull()
        }

    @Test
    fun upTransitionSceneKey_bottomAligned_doesNothing() =
        testScope.runTest {
            val actions by collectLastValue(underTest.actions)
            fakeShadeRepository.setDualShadeAlignedToBottom(true)
            underTest.activateIn(this)

            assertThat(actions?.get(Swipe.Up)).isNull()
            assertThat((actions?.get(Swipe.Down) as? UserActionResult.HideOverlay)?.overlay)
                .isEqualTo(Overlays.QuickSettingsShade)
        }

    @Test
    fun back_hidesShade() =
        testScope.runTest {
            val actions by collectLastValue(underTest.actions)
            underTest.activateIn(this)

            assertThat((actions?.get(Back) as? UserActionResult.HideOverlay)?.overlay)
                .isEqualTo(Overlays.QuickSettingsShade)
        }
}
+61 −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.qs.ui.viewmodel

import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.TransitionKeys
import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeAlignment
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

/** Models the UI state for the user actions for navigating to other scenes or overlays. */
class QuickSettingsShadeOverlayActionsViewModel
@AssistedInject
constructor(
    private val shadeInteractor: ShadeInteractor,
) : SceneActionsViewModel() {

    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
        setActions(
            buildMap {
                if (shadeInteractor.shadeAlignment == ShadeAlignment.Top) {
                    put(Swipe.Up, UserActionResult.HideOverlay(Overlays.QuickSettingsShade))
                } else {
                    put(
                        Swipe.Down,
                        UserActionResult.HideOverlay(
                            overlay = Overlays.QuickSettingsShade,
                            transitionKey = TransitionKeys.OpenBottomShade,
                        )
                    )
                }
                put(Back, UserActionResult.HideOverlay(Overlays.QuickSettingsShade))
            }
        )
    }

    @AssistedFactory
    interface Factory {
        fun create(): QuickSettingsShadeOverlayActionsViewModel
    }
}
+42 −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.qs.ui.viewmodel

import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject

/**
 * Models UI state used to render the content of the quick settings shade overlay.
 *
 * Different from [QuickSettingsShadeOverlayActionsViewModel], which only models user actions that
 * can be performed to navigate to other scenes.
 */
class QuickSettingsShadeOverlayContentViewModel
@AssistedInject
constructor(
    val overlayShadeViewModelFactory: OverlayShadeViewModel.Factory,
    val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
    val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
) {

    @AssistedFactory
    interface Factory {
        fun create(): QuickSettingsShadeOverlayContentViewModel
    }
}
Loading