Skip to content

Commit 513c2fd

Browse files
committed
Add user profile privacy settings screen
1 parent 2623af5 commit 513c2fd

File tree

3 files changed

+233
-49
lines changed

3 files changed

+233
-49
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright (c) 2014-2025 Stream.io Inc. All rights reserved.
3+
*
4+
* Licensed under the Stream 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+
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
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 io.getstream.chat.android.compose.sample.ui.profile
18+
19+
import androidx.compose.foundation.clickable
20+
import androidx.compose.foundation.layout.Arrangement
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.Row
23+
import androidx.compose.foundation.layout.Spacer
24+
import androidx.compose.foundation.layout.fillMaxWidth
25+
import androidx.compose.foundation.layout.padding
26+
import androidx.compose.foundation.layout.size
27+
import androidx.compose.foundation.shape.RoundedCornerShape
28+
import androidx.compose.material3.Button
29+
import androidx.compose.material3.ButtonDefaults
30+
import androidx.compose.material3.Icon
31+
import androidx.compose.material3.Switch
32+
import androidx.compose.material3.Text
33+
import androidx.compose.material3.ripple
34+
import androidx.compose.runtime.Composable
35+
import androidx.compose.runtime.getValue
36+
import androidx.compose.runtime.mutableStateOf
37+
import androidx.compose.runtime.remember
38+
import androidx.compose.runtime.setValue
39+
import androidx.compose.ui.Alignment
40+
import androidx.compose.ui.Modifier
41+
import androidx.compose.ui.graphics.Color
42+
import androidx.compose.ui.res.painterResource
43+
import androidx.compose.ui.unit.dp
44+
import androidx.compose.ui.unit.sp
45+
import io.getstream.chat.android.DeliveryReceipts
46+
import io.getstream.chat.android.PrivacySettings
47+
import io.getstream.chat.android.ReadReceipts
48+
import io.getstream.chat.android.TypingIndicators
49+
import io.getstream.chat.android.compose.R
50+
import io.getstream.chat.android.compose.ui.theme.ChatTheme
51+
52+
@Suppress("LongMethod")
53+
@Composable
54+
fun UserProfilePrivacySettingsScreen(
55+
privacySettings: PrivacySettings?,
56+
onSaveClick: (settings: PrivacySettings) -> Unit,
57+
) {
58+
var typingIndicators by remember(privacySettings) {
59+
mutableStateOf(privacySettings?.typingIndicators ?: TypingIndicators())
60+
}
61+
var deliveryReceipts by remember(privacySettings) {
62+
mutableStateOf(privacySettings?.deliveryReceipts ?: DeliveryReceipts())
63+
}
64+
var readReceipts by remember(privacySettings) {
65+
mutableStateOf(privacySettings?.readReceipts ?: ReadReceipts())
66+
}
67+
68+
Column {
69+
SwitchItem(
70+
label = "Typing Indicators",
71+
checked = typingIndicators.enabled,
72+
onCheckedChange = { checked ->
73+
typingIndicators = typingIndicators.copy(enabled = checked)
74+
},
75+
)
76+
SwitchItem(
77+
label = "Delivery Receipts",
78+
checked = deliveryReceipts.enabled,
79+
onCheckedChange = { checked ->
80+
deliveryReceipts = deliveryReceipts.copy(enabled = checked)
81+
},
82+
)
83+
SwitchItem(
84+
label = "Read Receipts",
85+
checked = readReceipts.enabled,
86+
onCheckedChange = { checked ->
87+
readReceipts = readReceipts.copy(enabled = checked)
88+
},
89+
)
90+
Button(
91+
onClick = {
92+
onSaveClick(
93+
PrivacySettings(
94+
typingIndicators = typingIndicators,
95+
deliveryReceipts = deliveryReceipts,
96+
readReceipts = readReceipts,
97+
),
98+
)
99+
},
100+
modifier = Modifier
101+
.fillMaxWidth()
102+
.padding(horizontal = 16.dp, vertical = 8.dp),
103+
colors = ButtonDefaults.buttonColors(
104+
containerColor = ChatTheme.colors.primaryAccent,
105+
),
106+
shape = RoundedCornerShape(12.dp),
107+
) {
108+
Row(
109+
verticalAlignment = Alignment.CenterVertically,
110+
horizontalArrangement = Arrangement.Center,
111+
) {
112+
Icon(
113+
painter = painterResource(id = R.drawable.stream_compose_ic_checkmark),
114+
contentDescription = null,
115+
tint = Color.White,
116+
modifier = Modifier.size(18.dp),
117+
)
118+
Spacer(modifier = Modifier.size(8.dp))
119+
Text(
120+
text = "Save Settings",
121+
style = ChatTheme.typography.bodyBold.copy(
122+
color = Color.White,
123+
fontSize = 16.sp,
124+
),
125+
)
126+
}
127+
}
128+
}
129+
}
130+
131+
@Composable
132+
private fun SwitchItem(
133+
label: String,
134+
checked: Boolean,
135+
onCheckedChange: (checked: Boolean) -> Unit,
136+
) {
137+
Row(
138+
modifier = Modifier
139+
.fillMaxWidth()
140+
.clickable(
141+
interactionSource = null,
142+
indication = ripple(),
143+
onClick = { onCheckedChange(!checked) },
144+
)
145+
.padding(horizontal = 16.dp, vertical = 6.dp),
146+
verticalAlignment = Alignment.CenterVertically,
147+
horizontalArrangement = Arrangement.SpaceBetween,
148+
) {
149+
Text(
150+
text = label,
151+
style = ChatTheme.typography.title3,
152+
color = ChatTheme.colors.textHighEmphasis,
153+
)
154+
Switch(
155+
checked = checked,
156+
onCheckedChange = null,
157+
)
158+
}
159+
}

stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/profile/UserProfileScreen.kt

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ fun UserProfileScreen(
152152
onUpdateProfilePictureClick = {
153153
modalSheet = ModalSheet.UpdateProfilePicture
154154
},
155+
onUpdatePrivacySettingsClick = {
156+
modalSheet = ModalSheet.UpdatePrivacySettings
157+
},
155158
)
156159
}
157160
when (modalSheet) {
@@ -202,6 +205,21 @@ fun UserProfileScreen(
202205
)
203206
}
204207

208+
ModalSheet.UpdatePrivacySettings -> ModalBottomSheet(
209+
onDismissRequest = { modalSheet = null },
210+
containerColor = ChatTheme.colors.appBackground,
211+
) {
212+
state.user?.let { user ->
213+
UserProfilePrivacySettingsScreen(
214+
privacySettings = user.privacySettings,
215+
onSaveClick = { settings ->
216+
modalSheet = null
217+
viewModel.updatePrivacySettings(settings)
218+
},
219+
)
220+
}
221+
}
222+
205223
null -> Unit
206224
}
207225

@@ -218,6 +236,7 @@ fun UserProfileScreen(
218236
is UserProfileViewEvent.RemoveProfilePictureError,
219237
->
220238
snackbarHostState.showSnackbar(message = event.error.message, actionLabel = "Dismiss")
239+
221240
is UserProfileViewEvent.UpdatePushPreferencesError -> {
222241
snackbarHostState.showSnackbar(message = event.error.message, actionLabel = "Dismiss")
223242
}
@@ -284,9 +303,9 @@ private enum class ModalSheet {
284303
UnreadCounts,
285304
PushPreferences,
286305
UpdateProfilePicture,
306+
UpdatePrivacySettings,
287307
}
288308

289-
@OptIn(ExperimentalMaterial3Api::class)
290309
@Suppress("LongMethod")
291310
@Composable
292311
private fun UserProfileScreenContent(
@@ -295,6 +314,7 @@ private fun UserProfileScreenContent(
295314
onUnreadCountsClick: () -> Unit = {},
296315
onPushPreferencesClick: () -> Unit = {},
297316
onUpdateProfilePictureClick: () -> Unit = {},
317+
onUpdatePrivacySettingsClick: () -> Unit = {},
298318
) {
299319
when (val user = state.user) {
300320
null -> {
@@ -359,60 +379,56 @@ private fun UserProfileScreenContent(
359379
}
360380
Divider()
361381
}
362-
Row(
363-
modifier = Modifier
364-
.fillMaxWidth()
365-
.clickable(
366-
interactionSource = null,
367-
indication = ripple(),
368-
onClick = onUnreadCountsClick,
369-
)
370-
.padding(start = 16.dp)
371-
.minimumInteractiveComponentSize(),
372-
horizontalArrangement = Arrangement.SpaceBetween,
373-
verticalAlignment = Alignment.CenterVertically,
374-
) {
375-
Text(
376-
text = "Unread Counts",
377-
style = ChatTheme.typography.title3,
378-
color = ChatTheme.colors.textHighEmphasis,
379-
)
380-
Icon(
381-
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
382-
contentDescription = null,
383-
tint = ChatTheme.colors.textLowEmphasis,
384-
)
385-
}
382+
NavigationItem(
383+
label = "Unread Counts",
384+
onClick = onUnreadCountsClick,
385+
)
386386
Divider()
387-
Row(
388-
modifier = Modifier
389-
.fillMaxWidth()
390-
.clickable(
391-
interactionSource = null,
392-
indication = ripple(),
393-
onClick = onPushPreferencesClick,
394-
)
395-
.padding(start = 16.dp)
396-
.minimumInteractiveComponentSize(),
397-
horizontalArrangement = Arrangement.SpaceBetween,
398-
verticalAlignment = Alignment.CenterVertically,
399-
) {
400-
Text(
401-
text = "Push Preferences",
402-
style = ChatTheme.typography.title3,
403-
color = ChatTheme.colors.textHighEmphasis,
404-
)
405-
Icon(
406-
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
407-
contentDescription = null,
408-
tint = ChatTheme.colors.textLowEmphasis,
409-
)
410-
}
387+
NavigationItem(
388+
label = "Push Preferences",
389+
onClick = onPushPreferencesClick,
390+
)
391+
Divider()
392+
NavigationItem(
393+
label = "Privacy Settings",
394+
onClick = onUpdatePrivacySettingsClick,
395+
)
411396
}
412397
}
413398
}
414399
}
415400

401+
@Composable
402+
private fun NavigationItem(
403+
label: String,
404+
onClick: () -> Unit,
405+
) {
406+
Row(
407+
modifier = Modifier
408+
.fillMaxWidth()
409+
.clickable(
410+
interactionSource = null,
411+
indication = ripple(),
412+
onClick = onClick,
413+
)
414+
.padding(start = 16.dp)
415+
.minimumInteractiveComponentSize(),
416+
horizontalArrangement = Arrangement.SpaceBetween,
417+
verticalAlignment = Alignment.CenterVertically,
418+
) {
419+
Text(
420+
text = label,
421+
style = ChatTheme.typography.title3,
422+
color = ChatTheme.colors.textHighEmphasis,
423+
)
424+
Icon(
425+
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
426+
contentDescription = null,
427+
tint = ChatTheme.colors.textLowEmphasis,
428+
)
429+
}
430+
}
431+
416432
@Composable
417433
private fun UserProfilePicture(
418434
modifier: Modifier,

stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/profile/UserProfileViewModel.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package io.getstream.chat.android.compose.sample.ui.profile
1818

1919
import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.viewModelScope
21+
import io.getstream.chat.android.PrivacySettings
2122
import io.getstream.chat.android.client.ChatClient
2223
import io.getstream.chat.android.client.utils.ProgressCallback
2324
import io.getstream.chat.android.models.PushPreferenceLevel
@@ -100,6 +101,14 @@ class UserProfileViewModel(
100101
}
101102
}
102103

104+
fun updatePrivacySettings(settings: PrivacySettings) {
105+
viewModelScope.launch {
106+
val user = state.value.user!!
107+
chatClient.updateUser(user = user.copy(privacySettings = settings))
108+
.await()
109+
}
110+
}
111+
103112
fun loadUnreadCounts() {
104113
_state.update { currentState -> currentState.copy(unreadCounts = null) }
105114
viewModelScope.launch {

0 commit comments

Comments
 (0)