• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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