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

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

Merge "Add app selector menu" into main

parents e98a5dc9 a634cb2c
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2025 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="16dp"
    android:height="16dp"
    android:viewportWidth="16"
    android:viewportHeight="16">
  <path
      android:pathData="M2,16C1.45,16 0.975,15.808 0.575,15.425C0.192,15.025 0,14.55 0,14C0,13.45 0.192,12.983 0.575,12.6C0.975,12.2 1.45,12 2,12C2.55,12 3.017,12.2 3.4,12.6C3.8,12.983 4,13.45 4,14C4,14.55 3.8,15.025 3.4,15.425C3.017,15.808 2.55,16 2,16ZM8,16C7.45,16 6.975,15.808 6.575,15.425C6.192,15.025 6,14.55 6,14C6,13.45 6.192,12.983 6.575,12.6C6.975,12.2 7.45,12 8,12C8.55,12 9.017,12.2 9.4,12.6C9.8,12.983 10,13.45 10,14C10,14.55 9.8,15.025 9.4,15.425C9.017,15.808 8.55,16 8,16ZM14,16C13.45,16 12.975,15.808 12.575,15.425C12.192,15.025 12,14.55 12,14C12,13.45 12.192,12.983 12.575,12.6C12.975,12.2 13.45,12 14,12C14.55,12 15.017,12.2 15.4,12.6C15.8,12.983 16,13.45 16,14C16,14.55 15.8,15.025 15.4,15.425C15.017,15.808 14.55,16 14,16ZM2,10C1.45,10 0.975,9.808 0.575,9.425C0.192,9.025 0,8.55 0,8C0,7.45 0.192,6.983 0.575,6.6C0.975,6.2 1.45,6 2,6C2.55,6 3.017,6.2 3.4,6.6C3.8,6.983 4,7.45 4,8C4,8.55 3.8,9.025 3.4,9.425C3.017,9.808 2.55,10 2,10ZM8,10C7.45,10 6.975,9.808 6.575,9.425C6.192,9.025 6,8.55 6,8C6,7.45 6.192,6.983 6.575,6.6C6.975,6.2 7.45,6 8,6C8.55,6 9.017,6.2 9.4,6.6C9.8,6.983 10,7.45 10,8C10,8.55 9.8,9.025 9.4,9.425C9.017,9.808 8.55,10 8,10ZM14,10C13.45,10 12.975,9.808 12.575,9.425C12.192,9.025 12,8.55 12,8C12,7.45 12.192,6.983 12.575,6.6C12.975,6.2 13.45,6 14,6C14.55,6 15.017,6.2 15.4,6.6C15.8,6.983 16,7.45 16,8C16,8.55 15.8,9.025 15.4,9.425C15.017,9.808 14.55,10 14,10ZM2,4C1.45,4 0.975,3.808 0.575,3.425C0.192,3.025 0,2.55 0,2C0,1.45 0.192,0.983 0.575,0.6C0.975,0.2 1.45,-0 2,-0C2.55,-0 3.017,0.2 3.4,0.6C3.8,0.983 4,1.45 4,2C4,2.55 3.8,3.025 3.4,3.425C3.017,3.808 2.55,4 2,4ZM8,4C7.45,4 6.975,3.808 6.575,3.425C6.192,3.025 6,2.55 6,2C6,1.45 6.192,0.983 6.575,0.6C6.975,0.2 7.45,-0 8,-0C8.55,-0 9.017,0.2 9.4,0.6C9.8,0.983 10,1.45 10,2C10,2.55 9.8,3.025 9.4,3.425C9.017,3.808 8.55,4 8,4ZM14,4C13.45,4 12.975,3.808 12.575,3.425C12.192,3.025 12,2.55 12,2C12,1.45 12.192,0.983 12.575,0.6C12.975,0.2 13.45,-0 14,-0C14.55,-0 15.017,0.2 15.4,0.6C15.8,0.983 16,1.45 16,2C16,2.55 15.8,3.025 15.4,3.425C15.017,3.808 14.55,4 14,4Z"
      android:fillColor="#ffffff"/>
</vector>
+25 −0
Original line number Diff line number Diff line
<!--
  ~ Copyright (C) 2025 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="6dp"
    android:height="8dp"
    android:viewportHeight="8"
    android:viewportWidth="6">
    <path
        android:fillColor="#ffffff"
        android:pathData="M3.7,4L0.9667,1.2667C0.8444,1.1444 0.7833,1.0056 0.7833,0.85C0.7833,0.6833 0.8444,0.5389 0.9667,0.4167C1.0889,0.2944 1.2278,0.2333 1.3833,0.2333C1.55,0.2333 1.6944,0.2944 1.8167,0.4167L4.9833,3.5833C5.0389,3.6389 5.0778,3.7055 5.1,3.7833C5.1333,3.85 5.15,3.9222 5.15,4C5.15,4.0778 5.1333,4.1556 5.1,4.2333C5.0778,4.3 5.0389,4.3611 4.9833,4.4167L1.8167,7.5833C1.6944,7.7056 1.5556,7.7667 1.4,7.7667C1.2444,7.7556 1.1056,7.6889 0.9833,7.5667C0.8611,7.4444 0.8,7.3056 0.8,7.15C0.8,6.9833 0.8611,6.8389 0.9833,6.7167L3.7,4Z" />
</vector>
+2 −0
Original line number Diff line number Diff line
@@ -352,6 +352,8 @@
    <string name="screen_record_entire_screen">Entire screen</string>
    <!-- Screen recording should record a single app [CHAR LIMIT=35] -->
    <string name="screen_record_single_app">Single app</string>
    <!-- Screen recording hint shown on a button leading to choosing an app to record [CHAR LIMIT=35] -->
    <string name="screen_record_single_app_hint">App to record</string>
    <!-- Screen recording should record a single app but there are no apps open [CHAR LIMIT=35] -->
    <string name="screen_record_single_app_no_recents">Single app (Open an app to choose)</string>
    <!-- Screen recording should record device audio setting [CHAR LIMIT=35] -->
+4 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.screencapture.common

import android.view.Display
import com.android.systemui.screencapture.common.shared.model.ScreenCaptureUiParameters
import com.android.systemui.screencapture.common.ui.compose.ScreenCaptureContent
import dagger.BindsInstance
@@ -50,6 +51,9 @@ interface ScreenCaptureUiComponent {
        @BindsInstance
        fun setParameters(@ScreenCaptureUi parameters: ScreenCaptureUiParameters): Builder

        /** [Display] that hosts the Screen Capture UI. */
        @BindsInstance fun setDisplay(@ScreenCaptureUi display: Display): Builder

        /**
         * Builds this [ScreenCaptureUiComponent]. Actual Subcomponent Builders should override this
         * method with their own version that returns the actual subcomponent type.
+76 −48
Original line number Diff line number Diff line
@@ -14,17 +14,15 @@
 * limitations under the License.
 */

@file:OptIn(ExperimentalMaterial3Api::class)

package com.android.systemui.screencapture.record.smallscreen.ui.compose

import android.graphics.Bitmap
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -36,33 +34,33 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.carousel.CarouselDefaults
import androidx.compose.material3.carousel.CarouselItemScope
import androidx.compose.material3.carousel.HorizontalUncontainedCarousel
import androidx.compose.material3.carousel.rememberCarouselState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformIconButton
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.res.R
import com.android.systemui.screencapture.common.domain.model.ScreenCaptureRecentTask
import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel
import com.android.systemui.screencapture.record.smallscreen.ui.viewmodel.RecordDetailsAppSelectorViewModel
import com.android.systemui.screencapture.record.smallscreen.ui.viewmodel.RecordDetailsAppViewModel

@Composable
fun RecordDetailsAppSelector(
    viewModel: RecordDetailsAppSelectorViewModel,
    onBackPressed: () -> Unit,
    onTaskSelected: (ScreenCaptureRecentTask) -> Unit,
    modifier: Modifier = Modifier,
) {
    Column(
@@ -89,53 +87,72 @@ fun RecordDetailsAppSelector(
                color = MaterialTheme.colorScheme.onSurface,
            )
        }

        val carouselState = rememberCarouselState { viewModel.apps.size }
        HorizontalUncontainedCarousel(
            state = carouselState,
            itemWidth = 168.dp,
            itemSpacing = 24.dp,
            contentPadding = PaddingValues(horizontal = 32.dp),
            flingBehavior = CarouselDefaults.singleAdvanceFlingBehavior(carouselState),
        val tasks = viewModel.recentTasks
        val pagerState = rememberPagerState { tasks?.size ?: 1 }
        HorizontalPager(
            state = pagerState,
            contentPadding = PaddingValues(horizontal = 68.dp),
            pageSpacing = 22.dp,
            modifier = Modifier,
        ) { index ->
            val appViewModel = viewModel.apps[index]
            AppPreview(viewModel = appViewModel, modifier = Modifier)
            val task = tasks?.getOrNull(index)
            val taskViewModel =
                task?.let {
                    rememberViewModel("RecordDetailsAppSelector#taskViewModel_$index") {
                        viewModel.createTaskViewModel(task)
                    }
                }
            AppPreview(
                viewModel = taskViewModel,
                onClick = { if (task != null) onTaskSelected(task) },
            )
        }
    }
}

@Composable
private fun CarouselItemScope.AppPreview(
    viewModel: RecordDetailsAppViewModel,
private fun AppPreview(
    viewModel: RecentTaskViewModel?,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    cornersRadius: Dp = 16.dp,
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(12.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier =
            modifier
                .maskClip(RoundedCornerShape(cornersRadius))
                .clickable(onClick = viewModel.onSelect),
        modifier = modifier,
    ) {
        Icon(
            bitmap = viewModel.icon.asImageBitmap(),
        val icon = viewModel?.icon?.getOrNull()
        if (icon == null) {
            Spacer(Modifier.size(18.dp))
        } else {
            Image(
                bitmap = icon.asImageBitmap(),
                contentDescription = null,
                modifier = Modifier.size(18.dp),
            )
        }

        Card(
            onClick = onClick,
            shape = RoundedCornerShape(20.dp),
            colors =
                CardDefaults.cardColors(
                    containerColor = MaterialTheme.colorScheme.surfaceContainer
                ),
            modifier = Modifier.aspectRatio(viewModel?.thumbnail?.getOrNull().aspectRatio),
        ) {
            AnimatedContent(
            targetState = viewModel.thumbnail,
                targetState = viewModel?.thumbnail?.getOrNull(),
                contentAlignment = Alignment.Center,
                transitionSpec = { fadeIn() togetherWith fadeOut() },
            modifier =
                Modifier.clip(RoundedCornerShape(cornersRadius)).aspectRatio(9 / 16f).fillMaxSize(),
                modifier = Modifier.fillMaxSize(),
            ) { thumbnail ->
                if (thumbnail == null) {
                    Spacer(
                        modifier =
                        Modifier.background(color = MaterialTheme.colorScheme.surfaceContainerHigh)
                            Modifier.background(
                                color = MaterialTheme.colorScheme.surfaceContainerHigh
                            )
                    )
                } else {
                    Image(bitmap = thumbnail.asImageBitmap(), contentDescription = null)
@@ -143,3 +160,14 @@ private fun CarouselItemScope.AppPreview(
            }
        }
    }
}

private val Bitmap?.aspectRatio: Float
    @Composable
    get() {
        return if (this == null) {
            with(LocalResources.current.displayMetrics) { widthPixels / heightPixels.toFloat() }
        } else {
            width / height.toFloat()
        }
    }
Loading