Loading packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/LargeScreenCaptureShareScreenContent.kt +21 −5 Original line number Diff line number Diff line Loading @@ -19,18 +19,34 @@ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import androidx.compose.runtime.Composable import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.screencapture.common.ui.compose.ScreenCaptureContent import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareViewModel import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareToolbarViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel import javax.inject.Inject class LargeScreenCaptureShareScreenContent @Inject constructor(private val viewModelFactory: PreShareViewModel.Factory) : ScreenCaptureContent { constructor( private val preShareToolbarViewModelFactory: PreShareToolbarViewModel.Factory, private val shareContentListViewModelFactory: ShareContentListViewModel.Factory, private val recentTaskViewModelFactory: RecentTaskViewModel.Factory, ) : ScreenCaptureContent { @Composable override fun Content() { val viewModel: PreShareViewModel = rememberViewModel("PreShareViewModel") { viewModelFactory.create() } val preShareToolbarViewModel: PreShareToolbarViewModel = rememberViewModel("PreShareToolbarViewModel") { preShareToolbarViewModelFactory.create() } val shareContentListViewModel: ShareContentListViewModel = rememberViewModel("ShareContentListViewModel") { shareContentListViewModelFactory.create() } PreShareUI(viewModel = viewModel) PreShareUI( preShareToolbarViewModel = preShareToolbarViewModel, shareContentListViewModel = shareContentListViewModel, recentTaskViewModelFactory = recentTaskViewModelFactory, ) } } packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/PreShareToolbar.kt +14 −9 Original line number Diff line number Diff line Loading @@ -32,26 +32,26 @@ import com.android.systemui.screencapture.common.ui.compose.RadioButtonGroup import com.android.systemui.screencapture.common.ui.compose.RadioButtonGroupItem import com.android.systemui.screencapture.common.ui.compose.Toolbar import com.android.systemui.screencapture.common.ui.compose.loadIcon import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareToolbarViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ScreenShareTarget @Composable @OptIn(ExperimentalMaterial3ExpressiveApi::class) fun PreShareToolbar( viewModel: PreShareViewModel, preShareToolbarViewModel: PreShareToolbarViewModel, expanded: Boolean, onCloseClick: () -> Unit, modifier: Modifier = Modifier, ) { val tabIcon by loadIcon( viewModel = viewModel, viewModel = preShareToolbarViewModel, resId = R.drawable.ic_screen_capture_tab, contentDescription = null, ) val windowIcon by loadIcon( viewModel = viewModel, viewModel = preShareToolbarViewModel, resId = R.drawable.ic_screen_capture_window, contentDescription = null, ) Loading @@ -59,13 +59,18 @@ fun PreShareToolbar( listOf( RadioButtonGroupItem( icon = tabIcon, isSelected = viewModel.selectedScreenShareTarget == ScreenShareTarget.TAB, onClick = { viewModel.onTargetSelected(ScreenShareTarget.TAB) }, isSelected = preShareToolbarViewModel.selectedScreenShareTarget == ScreenShareTarget.TAB, onClick = { preShareToolbarViewModel.onTargetSelected(ScreenShareTarget.TAB) }, ), RadioButtonGroupItem( icon = windowIcon, isSelected = viewModel.selectedScreenShareTarget == ScreenShareTarget.APP_WINDOW, onClick = { viewModel.onTargetSelected(ScreenShareTarget.APP_WINDOW) }, isSelected = preShareToolbarViewModel.selectedScreenShareTarget == ScreenShareTarget.APP_WINDOW, onClick = { preShareToolbarViewModel.onTargetSelected(ScreenShareTarget.APP_WINDOW) }, ), ) Loading @@ -77,7 +82,7 @@ fun PreShareToolbar( val shareIcon by loadIcon( viewModel = viewModel, viewModel = preShareToolbarViewModel, resId = R.drawable.ic_present_to_all, ContentDescription.Resource(R.string.screen_share_toolbar_share_button), ) Loading packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/PreShareUI.kt +29 −5 Original line number Diff line number Diff line Loading @@ -16,13 +16,37 @@ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareViewModel import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareToolbarViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel /** Main component for the screen share UI. */ @Composable fun PreShareUI(viewModel: PreShareViewModel) { PreShareToolbar(viewModel = viewModel, expanded = true, onCloseClick = {}) // TODO: Add PreShareSelector here. fun PreShareUI( preShareToolbarViewModel: PreShareToolbarViewModel, shareContentListViewModel: ShareContentListViewModel, recentTaskViewModelFactory: RecentTaskViewModel.Factory, ) { Column( verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.size(800.dp, 600.dp), ) { PreShareToolbar( preShareToolbarViewModel = preShareToolbarViewModel, expanded = true, onCloseClick = {}, ) ShareContentSelector( shareContentListViewModel = shareContentListViewModel, recentTaskViewModelFactory = recentTaskViewModelFactory, ) } } packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/ShareContentList.kt +56 −54 Original line number Diff line number Diff line Loading @@ -16,10 +16,10 @@ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import android.graphics.Bitmap import android.graphics.Color import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height Loading @@ -27,71 +27,83 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.unit.dp import androidx.core.graphics.createBitmap /** * A temporary data class representing a single item in the list. This will be replaced by a * ViewModel in the next step. */ private data class ContentItem(val icon: Bitmap?, val label: CharSequence?) import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel /** * A composable that displays a scrollable list of shareable content (e.g., recent apps). * * @param modifier The modifier to be applied to the composable. * @param viewModel The ViewModel that provides the list of tasks and manages selection state. * @param recentTaskViewModelFactory A factory to create a [RecentTaskViewModel] for each item. * @param selectedRecentTaskViewModel The selected RecentTaskViewModel. */ @Composable fun ShareContentList(modifier: Modifier = Modifier) { // TODO(b/436886242): Remove dummy data and inject view model. val contentList = listOf( ContentItem(icon = null, label = "App 1"), ContentItem(icon = null, label = "App 2"), ContentItem(icon = null, label = "App 3"), ContentItem(icon = null, label = "App 4"), ) fun ShareContentList( modifier: Modifier = Modifier, viewModel: ShareContentListViewModel, recentTaskViewModelFactory: RecentTaskViewModel.Factory, selectedRecentTaskViewModel: RecentTaskViewModel?, ) { val recentTasks by viewModel.recentTasks.collectAsStateWithLifecycle(initialValue = null) LazyColumn(modifier = modifier.height(120.dp).width(148.dp)) { itemsIndexed(contentList) { index, contentItem -> // Use the real list of recent tasks, handling the nullable case. recentTasks?.let { tasks -> items(items = tasks) { task -> val currentRecentTaskViewModel: RecentTaskViewModel = rememberViewModel( traceName = "ShareContentListItemViewModel#${task.taskId}", key = task, ) { recentTaskViewModelFactory.create(task) } SelectorItem( icon = contentItem.icon, label = contentItem.label, isSelected = index == 0, onItemSelected = {}, currentRecentTaskViewModel = currentRecentTaskViewModel, isSelected = currentRecentTaskViewModel.task == selectedRecentTaskViewModel?.task, onItemSelected = { viewModel.selectedRecentTaskViewModel = currentRecentTaskViewModel }, ) } } } } /** * A composable that displays a single item in the share content list. It shows an icon and a label, * and its appearance changes based on whether it is selected. * A composable that displays a single item in the share content list. * * @param icon The icon to display for the item. A placeholder is used if null. * @param label The text label for the item. A placeholder is used if null. * @param isSelected Whether this item is currently selected. * @param currentRecentTaskViewModel The [RecentTaskViewModel] that holds the state for this * specific item. * @param isSelected The boolean if the currentRecentTaskViewModel is selected. * @param onItemSelected The callback to be invoked when this item is clicked. */ @Composable private fun SelectorItem( icon: Bitmap?, label: CharSequence?, currentRecentTaskViewModel: RecentTaskViewModel, isSelected: Boolean, onItemSelected: () -> Unit, ) { // Get the icon and label from the item's ViewModel. val icon = currentRecentTaskViewModel.icon?.getOrNull() val label = currentRecentTaskViewModel.label?.getOrNull() Surface( shape = RoundedCornerShape(20.dp), color = Loading @@ -103,12 +115,16 @@ private fun SelectorItem( modifier = Modifier.padding(12.dp).clickable(onClick = onItemSelected), verticalAlignment = Alignment.CenterVertically, ) { Image( // TODO: Address the hardcoded placeholder color. bitmap = icon?.asImageBitmap() ?: createDefaultColorImageBitmap(20, 20, Color.BLUE), contentDescription = null, modifier = Modifier.size(16.dp).clip(CircleShape), ) Box( modifier = Modifier.size(16.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.surfaceVariant) ) { if (icon != null) { Image(bitmap = icon.asImageBitmap(), contentDescription = label?.toString()) } } Spacer(modifier = Modifier.width(8.dp)) Text( text = label?.toString() ?: "Title", Loading @@ -118,17 +134,3 @@ private fun SelectorItem( } } } /** * Creates an [ImageBitmap] of a given size, filled with a solid color. Used as a placeholder for * when an app icon is not available. * * @param width The width of the bitmap. * @param height The height of the bitmap. * @param color The color to fill the bitmap with. */ private fun createDefaultColorImageBitmap(width: Int, height: Int, color: Int): ImageBitmap { val bitmap = createBitmap(width, height) bitmap.eraseColor(color) return bitmap.asImageBitmap() } packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/ShareContentSelector.kt 0 → 100644 +118 −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. */ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text 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.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel @Composable fun ShareContentSelector( shareContentListViewModel: ShareContentListViewModel, recentTaskViewModelFactory: RecentTaskViewModel.Factory, ) { val selectedRecentTaskViewModel = shareContentListViewModel.selectedRecentTaskViewModel Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = RoundedCornerShape(28.dp)) { Column(modifier = Modifier.padding(8.dp)) { Text(text = "Share an app", modifier = Modifier.padding(12.dp, 8.dp).fillMaxWidth()) Spacer(modifier = Modifier.height(16.dp)) Row(modifier = Modifier.padding(8.dp)) { // The sharing content item list. ShareContentList( viewModel = shareContentListViewModel, recentTaskViewModelFactory = recentTaskViewModelFactory, selectedRecentTaskViewModel = selectedRecentTaskViewModel, ) Spacer(modifier = Modifier.width(10.dp)) ItemPreview( preview = selectedRecentTaskViewModel?.thumbnail?.getOrNull()?.asImageBitmap(), modifier = Modifier.weight(1f).height(120.dp), ) } DisclaimerText() AudioSwitch() } } } @Composable private fun ItemPreview(preview: ImageBitmap?, modifier: Modifier = Modifier) { Box( modifier = modifier .clip(RoundedCornerShape(16.dp)) .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center, ) { if (preview != null) { Image( bitmap = preview, contentDescription = "preview", modifier = Modifier.matchParentSize(), contentScale = ContentScale.Fit, ) } } } @Composable private fun DisclaimerText() { Text( text = "Disclaimer When you’re sharing your entire screen, anything shown on your screen" + " is recorded. So be careful with things like passwords, payment details," + " messages, photos, and audio & video.", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(12.dp, 8.dp).fillMaxWidth(), ) } @Composable private fun AudioSwitch() { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.padding(12.dp, 0.dp).fillMaxWidth(), ) { Text(text = "Share audio", style = MaterialTheme.typography.labelMedium) Switch(checked = true, onCheckedChange = {}, modifier = Modifier.height(24.dp)) } } Loading
packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/LargeScreenCaptureShareScreenContent.kt +21 −5 Original line number Diff line number Diff line Loading @@ -19,18 +19,34 @@ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import androidx.compose.runtime.Composable import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.screencapture.common.ui.compose.ScreenCaptureContent import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareViewModel import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareToolbarViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel import javax.inject.Inject class LargeScreenCaptureShareScreenContent @Inject constructor(private val viewModelFactory: PreShareViewModel.Factory) : ScreenCaptureContent { constructor( private val preShareToolbarViewModelFactory: PreShareToolbarViewModel.Factory, private val shareContentListViewModelFactory: ShareContentListViewModel.Factory, private val recentTaskViewModelFactory: RecentTaskViewModel.Factory, ) : ScreenCaptureContent { @Composable override fun Content() { val viewModel: PreShareViewModel = rememberViewModel("PreShareViewModel") { viewModelFactory.create() } val preShareToolbarViewModel: PreShareToolbarViewModel = rememberViewModel("PreShareToolbarViewModel") { preShareToolbarViewModelFactory.create() } val shareContentListViewModel: ShareContentListViewModel = rememberViewModel("ShareContentListViewModel") { shareContentListViewModelFactory.create() } PreShareUI(viewModel = viewModel) PreShareUI( preShareToolbarViewModel = preShareToolbarViewModel, shareContentListViewModel = shareContentListViewModel, recentTaskViewModelFactory = recentTaskViewModelFactory, ) } }
packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/PreShareToolbar.kt +14 −9 Original line number Diff line number Diff line Loading @@ -32,26 +32,26 @@ import com.android.systemui.screencapture.common.ui.compose.RadioButtonGroup import com.android.systemui.screencapture.common.ui.compose.RadioButtonGroupItem import com.android.systemui.screencapture.common.ui.compose.Toolbar import com.android.systemui.screencapture.common.ui.compose.loadIcon import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareToolbarViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ScreenShareTarget @Composable @OptIn(ExperimentalMaterial3ExpressiveApi::class) fun PreShareToolbar( viewModel: PreShareViewModel, preShareToolbarViewModel: PreShareToolbarViewModel, expanded: Boolean, onCloseClick: () -> Unit, modifier: Modifier = Modifier, ) { val tabIcon by loadIcon( viewModel = viewModel, viewModel = preShareToolbarViewModel, resId = R.drawable.ic_screen_capture_tab, contentDescription = null, ) val windowIcon by loadIcon( viewModel = viewModel, viewModel = preShareToolbarViewModel, resId = R.drawable.ic_screen_capture_window, contentDescription = null, ) Loading @@ -59,13 +59,18 @@ fun PreShareToolbar( listOf( RadioButtonGroupItem( icon = tabIcon, isSelected = viewModel.selectedScreenShareTarget == ScreenShareTarget.TAB, onClick = { viewModel.onTargetSelected(ScreenShareTarget.TAB) }, isSelected = preShareToolbarViewModel.selectedScreenShareTarget == ScreenShareTarget.TAB, onClick = { preShareToolbarViewModel.onTargetSelected(ScreenShareTarget.TAB) }, ), RadioButtonGroupItem( icon = windowIcon, isSelected = viewModel.selectedScreenShareTarget == ScreenShareTarget.APP_WINDOW, onClick = { viewModel.onTargetSelected(ScreenShareTarget.APP_WINDOW) }, isSelected = preShareToolbarViewModel.selectedScreenShareTarget == ScreenShareTarget.APP_WINDOW, onClick = { preShareToolbarViewModel.onTargetSelected(ScreenShareTarget.APP_WINDOW) }, ), ) Loading @@ -77,7 +82,7 @@ fun PreShareToolbar( val shareIcon by loadIcon( viewModel = viewModel, viewModel = preShareToolbarViewModel, resId = R.drawable.ic_present_to_all, ContentDescription.Resource(R.string.screen_share_toolbar_share_button), ) Loading
packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/PreShareUI.kt +29 −5 Original line number Diff line number Diff line Loading @@ -16,13 +16,37 @@ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareViewModel import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.PreShareToolbarViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel /** Main component for the screen share UI. */ @Composable fun PreShareUI(viewModel: PreShareViewModel) { PreShareToolbar(viewModel = viewModel, expanded = true, onCloseClick = {}) // TODO: Add PreShareSelector here. fun PreShareUI( preShareToolbarViewModel: PreShareToolbarViewModel, shareContentListViewModel: ShareContentListViewModel, recentTaskViewModelFactory: RecentTaskViewModel.Factory, ) { Column( verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.size(800.dp, 600.dp), ) { PreShareToolbar( preShareToolbarViewModel = preShareToolbarViewModel, expanded = true, onCloseClick = {}, ) ShareContentSelector( shareContentListViewModel = shareContentListViewModel, recentTaskViewModelFactory = recentTaskViewModelFactory, ) } }
packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/ShareContentList.kt +56 −54 Original line number Diff line number Diff line Loading @@ -16,10 +16,10 @@ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import android.graphics.Bitmap import android.graphics.Color import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height Loading @@ -27,71 +27,83 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.unit.dp import androidx.core.graphics.createBitmap /** * A temporary data class representing a single item in the list. This will be replaced by a * ViewModel in the next step. */ private data class ContentItem(val icon: Bitmap?, val label: CharSequence?) import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel /** * A composable that displays a scrollable list of shareable content (e.g., recent apps). * * @param modifier The modifier to be applied to the composable. * @param viewModel The ViewModel that provides the list of tasks and manages selection state. * @param recentTaskViewModelFactory A factory to create a [RecentTaskViewModel] for each item. * @param selectedRecentTaskViewModel The selected RecentTaskViewModel. */ @Composable fun ShareContentList(modifier: Modifier = Modifier) { // TODO(b/436886242): Remove dummy data and inject view model. val contentList = listOf( ContentItem(icon = null, label = "App 1"), ContentItem(icon = null, label = "App 2"), ContentItem(icon = null, label = "App 3"), ContentItem(icon = null, label = "App 4"), ) fun ShareContentList( modifier: Modifier = Modifier, viewModel: ShareContentListViewModel, recentTaskViewModelFactory: RecentTaskViewModel.Factory, selectedRecentTaskViewModel: RecentTaskViewModel?, ) { val recentTasks by viewModel.recentTasks.collectAsStateWithLifecycle(initialValue = null) LazyColumn(modifier = modifier.height(120.dp).width(148.dp)) { itemsIndexed(contentList) { index, contentItem -> // Use the real list of recent tasks, handling the nullable case. recentTasks?.let { tasks -> items(items = tasks) { task -> val currentRecentTaskViewModel: RecentTaskViewModel = rememberViewModel( traceName = "ShareContentListItemViewModel#${task.taskId}", key = task, ) { recentTaskViewModelFactory.create(task) } SelectorItem( icon = contentItem.icon, label = contentItem.label, isSelected = index == 0, onItemSelected = {}, currentRecentTaskViewModel = currentRecentTaskViewModel, isSelected = currentRecentTaskViewModel.task == selectedRecentTaskViewModel?.task, onItemSelected = { viewModel.selectedRecentTaskViewModel = currentRecentTaskViewModel }, ) } } } } /** * A composable that displays a single item in the share content list. It shows an icon and a label, * and its appearance changes based on whether it is selected. * A composable that displays a single item in the share content list. * * @param icon The icon to display for the item. A placeholder is used if null. * @param label The text label for the item. A placeholder is used if null. * @param isSelected Whether this item is currently selected. * @param currentRecentTaskViewModel The [RecentTaskViewModel] that holds the state for this * specific item. * @param isSelected The boolean if the currentRecentTaskViewModel is selected. * @param onItemSelected The callback to be invoked when this item is clicked. */ @Composable private fun SelectorItem( icon: Bitmap?, label: CharSequence?, currentRecentTaskViewModel: RecentTaskViewModel, isSelected: Boolean, onItemSelected: () -> Unit, ) { // Get the icon and label from the item's ViewModel. val icon = currentRecentTaskViewModel.icon?.getOrNull() val label = currentRecentTaskViewModel.label?.getOrNull() Surface( shape = RoundedCornerShape(20.dp), color = Loading @@ -103,12 +115,16 @@ private fun SelectorItem( modifier = Modifier.padding(12.dp).clickable(onClick = onItemSelected), verticalAlignment = Alignment.CenterVertically, ) { Image( // TODO: Address the hardcoded placeholder color. bitmap = icon?.asImageBitmap() ?: createDefaultColorImageBitmap(20, 20, Color.BLUE), contentDescription = null, modifier = Modifier.size(16.dp).clip(CircleShape), ) Box( modifier = Modifier.size(16.dp) .clip(CircleShape) .background(MaterialTheme.colorScheme.surfaceVariant) ) { if (icon != null) { Image(bitmap = icon.asImageBitmap(), contentDescription = label?.toString()) } } Spacer(modifier = Modifier.width(8.dp)) Text( text = label?.toString() ?: "Title", Loading @@ -118,17 +134,3 @@ private fun SelectorItem( } } } /** * Creates an [ImageBitmap] of a given size, filled with a solid color. Used as a placeholder for * when an app icon is not available. * * @param width The width of the bitmap. * @param height The height of the bitmap. * @param color The color to fill the bitmap with. */ private fun createDefaultColorImageBitmap(width: Int, height: Int, color: Int): ImageBitmap { val bitmap = createBitmap(width, height) bitmap.eraseColor(color) return bitmap.asImageBitmap() }
packages/SystemUI/src/com/android/systemui/screencapture/sharescreen/largescreen/ui/compose/ShareContentSelector.kt 0 → 100644 +118 −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. */ package com.android.systemui.screencapture.sharescreen.largescreen.ui.compose import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text 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.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import com.android.systemui.screencapture.common.ui.viewmodel.RecentTaskViewModel import com.android.systemui.screencapture.sharescreen.largescreen.ui.viewmodel.ShareContentListViewModel @Composable fun ShareContentSelector( shareContentListViewModel: ShareContentListViewModel, recentTaskViewModelFactory: RecentTaskViewModel.Factory, ) { val selectedRecentTaskViewModel = shareContentListViewModel.selectedRecentTaskViewModel Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = RoundedCornerShape(28.dp)) { Column(modifier = Modifier.padding(8.dp)) { Text(text = "Share an app", modifier = Modifier.padding(12.dp, 8.dp).fillMaxWidth()) Spacer(modifier = Modifier.height(16.dp)) Row(modifier = Modifier.padding(8.dp)) { // The sharing content item list. ShareContentList( viewModel = shareContentListViewModel, recentTaskViewModelFactory = recentTaskViewModelFactory, selectedRecentTaskViewModel = selectedRecentTaskViewModel, ) Spacer(modifier = Modifier.width(10.dp)) ItemPreview( preview = selectedRecentTaskViewModel?.thumbnail?.getOrNull()?.asImageBitmap(), modifier = Modifier.weight(1f).height(120.dp), ) } DisclaimerText() AudioSwitch() } } } @Composable private fun ItemPreview(preview: ImageBitmap?, modifier: Modifier = Modifier) { Box( modifier = modifier .clip(RoundedCornerShape(16.dp)) .background(MaterialTheme.colorScheme.surfaceVariant), contentAlignment = Alignment.Center, ) { if (preview != null) { Image( bitmap = preview, contentDescription = "preview", modifier = Modifier.matchParentSize(), contentScale = ContentScale.Fit, ) } } } @Composable private fun DisclaimerText() { Text( text = "Disclaimer When you’re sharing your entire screen, anything shown on your screen" + " is recorded. So be careful with things like passwords, payment details," + " messages, photos, and audio & video.", style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(12.dp, 8.dp).fillMaxWidth(), ) } @Composable private fun AudioSwitch() { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.padding(12.dp, 0.dp).fillMaxWidth(), ) { Text(text = "Share audio", style = MaterialTheme.typography.labelMedium) Switch(checked = true, onCheckedChange = {}, modifier = Modifier.height(24.dp)) } }