1 /*
<lambda>null2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.android.systemui.people.ui.compose
18
19 import android.annotation.StringRes
20 import androidx.compose.foundation.Image
21 import androidx.compose.foundation.clickable
22 import androidx.compose.foundation.layout.Arrangement
23 import androidx.compose.foundation.layout.Column
24 import androidx.compose.foundation.layout.Row
25 import androidx.compose.foundation.layout.Spacer
26 import androidx.compose.foundation.layout.fillMaxSize
27 import androidx.compose.foundation.layout.fillMaxWidth
28 import androidx.compose.foundation.layout.height
29 import androidx.compose.foundation.layout.padding
30 import androidx.compose.foundation.layout.safeDrawingPadding
31 import androidx.compose.foundation.layout.size
32 import androidx.compose.foundation.rememberScrollState
33 import androidx.compose.foundation.shape.RoundedCornerShape
34 import androidx.compose.foundation.verticalScroll
35 import androidx.compose.material3.MaterialTheme
36 import androidx.compose.material3.Surface
37 import androidx.compose.material3.Text
38 import androidx.compose.runtime.Composable
39 import androidx.compose.runtime.LaunchedEffect
40 import androidx.compose.runtime.getValue
41 import androidx.compose.runtime.key
42 import androidx.compose.ui.Alignment
43 import androidx.compose.ui.Modifier
44 import androidx.compose.ui.graphics.asImageBitmap
45 import androidx.compose.ui.res.dimensionResource
46 import androidx.compose.ui.res.stringResource
47 import androidx.compose.ui.text.style.TextAlign
48 import androidx.compose.ui.unit.Dp
49 import androidx.compose.ui.unit.dp
50 import androidx.lifecycle.compose.collectAsStateWithLifecycle
51 import com.android.systemui.compose.modifiers.sysuiResTag
52 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
53 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
54 import com.android.systemui.res.R
55
56 /**
57 * Compose the screen associated to a [PeopleViewModel].
58 *
59 * @param viewModel the [PeopleViewModel] that should be composed.
60 * @param onResult the callback called with the result of this screen. Callers should usually finish
61 * the Activity/Fragment/View hosting this Composable once a result is available.
62 */
63 @Composable
64 fun PeopleScreen(
65 viewModel: PeopleViewModel,
66 onResult: (PeopleViewModel.Result) -> Unit,
67 modifier: Modifier = Modifier,
68 ) {
69 val priorityTiles by viewModel.priorityTiles.collectAsStateWithLifecycle()
70 val recentTiles by viewModel.recentTiles.collectAsStateWithLifecycle()
71
72 // Call [onResult] this activity when the ViewModel tells us so.
73 LaunchedEffect(viewModel.result) {
74 viewModel.result.collect { result ->
75 if (result != null) {
76 viewModel.clearResult()
77 onResult(result)
78 }
79 }
80 }
81
82 Surface(color = MaterialTheme.colorScheme.background, modifier = modifier.fillMaxSize()) {
83 if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
84 PeopleScreenWithConversations(priorityTiles, recentTiles, viewModel.onTileClicked)
85 } else {
86 PeopleScreenEmpty(viewModel.onUserJourneyCancelled)
87 }
88 }
89 }
90
91 @Composable
PeopleScreenWithConversationsnull92 private fun PeopleScreenWithConversations(
93 priorityTiles: List<PeopleTileViewModel>,
94 recentTiles: List<PeopleTileViewModel>,
95 onTileClicked: (PeopleTileViewModel) -> Unit,
96 modifier: Modifier = Modifier,
97 ) {
98 Column(
99 modifier.fillMaxSize().safeDrawingPadding().sysuiResTag("top_level_with_conversations")
100 ) {
101 Column(
102 Modifier.fillMaxWidth().padding(PeopleSpacePadding),
103 horizontalAlignment = Alignment.CenterHorizontally,
104 ) {
105 Text(
106 stringResource(R.string.select_conversation_title),
107 style = MaterialTheme.typography.headlineSmall,
108 textAlign = TextAlign.Center,
109 )
110
111 Spacer(Modifier.height(24.dp))
112
113 Text(
114 stringResource(R.string.select_conversation_text),
115 Modifier.padding(horizontal = 24.dp),
116 style = MaterialTheme.typography.bodyLarge,
117 textAlign = TextAlign.Center,
118 )
119 }
120
121 Column(
122 Modifier.fillMaxWidth()
123 .sysuiResTag("scroll_view")
124 .verticalScroll(rememberScrollState())
125 .padding(top = 16.dp, bottom = PeopleSpacePadding, start = 8.dp, end = 8.dp)
126 ) {
127 val hasPriorityConversations = priorityTiles.isNotEmpty()
128 if (hasPriorityConversations) {
129 ConversationList(R.string.priority_conversations, priorityTiles, onTileClicked)
130 }
131
132 if (recentTiles.isNotEmpty()) {
133 if (hasPriorityConversations) {
134 Spacer(Modifier.height(35.dp))
135 }
136
137 ConversationList(R.string.recent_conversations, recentTiles, onTileClicked)
138 }
139 }
140 }
141 }
142
143 @Composable
ConversationListnull144 private fun ConversationList(
145 @StringRes headerTextResource: Int,
146 tiles: List<PeopleTileViewModel>,
147 onTileClicked: (PeopleTileViewModel) -> Unit,
148 modifier: Modifier = Modifier,
149 ) {
150 val largeCornerRadius = dimensionResource(R.dimen.people_space_widget_radius)
151 val smallCornerRadius = 4.dp
152
153 fun topRadius(i: Int): Dp = if (i == 0) largeCornerRadius else smallCornerRadius
154 fun bottomRadius(i: Int): Dp =
155 if (i == tiles.lastIndex) largeCornerRadius else smallCornerRadius
156
157 Column(modifier, verticalArrangement = Arrangement.spacedBy(2.dp)) {
158 Text(
159 stringResource(headerTextResource),
160 Modifier.padding(start = 16.dp, bottom = 8.dp),
161 style = MaterialTheme.typography.labelLarge,
162 color = MaterialTheme.colorScheme.primary,
163 )
164
165 tiles.forEachIndexed { index, tile ->
166 key(tile.key.toString()) {
167 Tile(
168 tile,
169 onTileClicked,
170 topCornerRadius = topRadius(index),
171 bottomCornerRadius = bottomRadius(index),
172 )
173 }
174 }
175 }
176 }
177
178 @Composable
Tilenull179 private fun Tile(
180 tile: PeopleTileViewModel,
181 onTileClicked: (PeopleTileViewModel) -> Unit,
182 topCornerRadius: Dp,
183 bottomCornerRadius: Dp,
184 modifier: Modifier = Modifier,
185 ) {
186 Surface(
187 modifier,
188 color = MaterialTheme.colorScheme.secondaryContainer,
189 shape =
190 RoundedCornerShape(
191 topStart = topCornerRadius,
192 topEnd = topCornerRadius,
193 bottomStart = bottomCornerRadius,
194 bottomEnd = bottomCornerRadius,
195 ),
196 ) {
197 Row(
198 Modifier.fillMaxWidth().clickable { onTileClicked(tile) }.padding(12.dp),
199 verticalAlignment = Alignment.CenterVertically,
200 ) {
201 Image(
202 tile.icon.asImageBitmap(),
203 // TODO(b/238993727): Add a content description.
204 contentDescription = null,
205 Modifier.size(dimensionResource(R.dimen.avatar_size_for_medium)),
206 )
207
208 Text(
209 tile.username ?: "",
210 Modifier.padding(horizontal = 16.dp),
211 style = MaterialTheme.typography.titleLarge,
212 )
213 }
214 }
215 }
216
217 /** The padding applied to the PeopleSpace screen. */
218 internal val PeopleSpacePadding = 24.dp
219