refactor: Improve user context handling in UserInfoSheet and UserButtons

This commit enhances the user context management in UserInfoSheet and UserButtons components.

- Removed unused parameters and improved the handling of user relationships in UserButtons.
- Updated UserInfoSheet to fetch user data more efficiently and reflect relationship changes.
- Introduced loading indicators for friend request actions in UserButtons to enhance user experience.
- Adjusted ModalBottomSheet states in MemberListSheet and ChatRouterScreen to skip partially expanded states for better usability.
- Cleaned up code and improved overall readability in the affected files.
This commit is contained in:
AbronStudio 2025-08-20 12:19:08 +03:30
parent d639048b5c
commit c2790838df
11 changed files with 292 additions and 143 deletions

View File

@ -203,7 +203,6 @@ fun ChannelSideDrawer(
) {
UserInfoSheet(
userId = userContextSheetTarget!!,
serverId = null,
dismissSheet = {
userContextSheetState.hide()
userContextSheetTarget = null

View File

@ -7,12 +7,12 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -24,41 +24,52 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import chat.peptide.R
import chat.peptide.api.PeptideAPI
import chat.peptide.api.routes.user.acceptFriendRequest
import chat.peptide.api.routes.user.blockUser
import chat.peptide.api.routes.user.friendUser
import chat.peptide.api.routes.user.openDM
import chat.peptide.api.routes.user.unblockUser
import chat.peptide.api.routes.user.unfriendUser
import chat.peptide.api.schemas.User
import chat.peptide.callbacks.Action
import chat.peptide.callbacks.ActionChannel
import chat.peptide.composables.generic.SquareButton
import chat.peptide.internals.Platform
import kotlinx.coroutines.launch
import logcat.LogPriority
import logcat.asLog
import logcat.logcat
private const val REL_NONE: String = "None"
private const val REL_USER: String = "User"
private const val REL_FRIEND: String = "Friend"
private const val REL_OUTGOING: String = "Outgoing"
private const val REL_INCOMING: String = "Incoming"
private const val REL_BLOCKED: String = "Blocked"
private const val REL_BLOCKED_OTHER: String = "BlockedOther"
@Composable
fun UserButtons(
user: User,
dismissSheet: suspend () -> Unit
dismissSheet: suspend () -> Unit,
onRelationshipChanged: (String?) -> Unit = {}
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val clipboard = LocalClipboardManager.current
var botEasterEgg by remember { mutableStateOf(false) }
var menuOpen by remember { mutableStateOf(false) }
var isAddFriendLoading by remember { mutableStateOf(false) }
var isOpenDMLoading by remember { mutableStateOf(false) }
var isCancelRequestLoading by remember { mutableStateOf(false) }
var isAcceptRequestLoading by remember { mutableStateOf(false) }
var isDeclineRequestLoading by remember { mutableStateOf(false) }
// Always derive latest user from cache to reflect relationship changes
val latestUser: User = user.id?.let { id -> PeptideAPI.userCache[id] } ?: user
val relationship: String = latestUser.relationship ?: REL_NONE
if (user.id == null) return Row {
SquareButton(
@ -85,23 +96,39 @@ fun UserButtons(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
when (user.relationship) {
"None" -> {
if (user.bot == null) {
when (relationship) {
REL_NONE -> {
if (latestUser.bot == null) {
SquareButton(
onClick = {
scope.launch {
try {
friendUser("${user.username}#${user.discriminator}")
isAddFriendLoading = true
friendUser("${latestUser.username}#${latestUser.discriminator}")
latestUser.id?.let { id ->
val updated = latestUser.copy(relationship = REL_OUTGOING)
PeptideAPI.userCache[id] =
(PeptideAPI.userCache[id]?.copy(relationship = REL_OUTGOING))
?: updated
onRelationshipChanged(REL_OUTGOING)
}
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
} finally {
isAddFriendLoading = false
}
}
},
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
enabled = !isAddFriendLoading
) {
Text(stringResource(R.string.user_info_sheet_add_friend))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.user_info_sheet_add_friend))
if (isAddFriendLoading) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = LocalContentColor.current)
}
}
}
} else {
Row(
@ -133,7 +160,7 @@ fun UserButtons(
}
}
"User" -> {
REL_USER -> {
SquareButton(
onClick = {
scope.launch {
@ -149,106 +176,151 @@ fun UserButtons(
}
}
"Friend" -> {
REL_FRIEND -> {
FilledTonalButton(
onClick = {
scope.launch {
val dm = openDM(user.id)
if (dm.id != null) {
if (PeptideAPI.channelCache[dm.id] == null)
PeptideAPI.channelCache[dm.id] = dm
ActionChannel.send(Action.SwitchChannel(dm.id))
dismissSheet()
} else {
Toast.makeText(
context,
context.getString(R.string.user_info_sheet_failed_to_open_dm),
Toast.LENGTH_SHORT
).show()
latestUser.id?.let {
scope.launch {
try {
isOpenDMLoading = true
val dm = openDM(latestUser.id)
if (dm.id != null) {
if (PeptideAPI.channelCache[dm.id] == null)
PeptideAPI.channelCache[dm.id] = dm
ActionChannel.send(Action.SwitchChannel(dm.id))
dismissSheet()
} else {
Toast.makeText(
context,
context.getString(R.string.user_info_sheet_failed_to_open_dm),
Toast.LENGTH_SHORT
).show()
}
} finally {
isOpenDMLoading = false
}
}
}
},
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
enabled = !isOpenDMLoading
) {
Text(stringResource(R.string.user_info_sheet_send_message))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.user_info_sheet_send_message))
if (isOpenDMLoading) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = LocalContentColor.current)
}
}
}
// Remove friend (in overflow menu)
}
"Outgoing" -> {
REL_OUTGOING -> {
SquareButton(
onClick = {
scope.launch {
try {
unfriendUser(user.id)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
latestUser.id?.let {
scope.launch {
try {
isCancelRequestLoading = true
unfriendUser(latestUser.id)
val updated = latestUser.copy(relationship = REL_NONE)
PeptideAPI.userCache[latestUser.id] =
(PeptideAPI.userCache[latestUser.id]?.copy(relationship = REL_NONE))
?: updated
onRelationshipChanged(REL_NONE)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
} finally {
isCancelRequestLoading = false
}
}
}
},
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
enabled = !isCancelRequestLoading
) {
Text(stringResource(R.string.user_info_sheet_cancel_request))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.user_info_sheet_cancel_request))
if (isCancelRequestLoading) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = LocalContentColor.current)
}
}
}
}
"Incoming" -> {
REL_INCOMING -> {
SquareButton(
onClick = {
scope.launch {
try {
acceptFriendRequest(user.id)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
latestUser.id?.let {
scope.launch {
try {
isAcceptRequestLoading = true
acceptFriendRequest(latestUser.id)
val updated = latestUser.copy(relationship = REL_FRIEND)
PeptideAPI.userCache[latestUser.id] =
(PeptideAPI.userCache[latestUser.id]?.copy(relationship = REL_FRIEND))
?: updated
onRelationshipChanged(REL_FRIEND)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
} finally {
isAcceptRequestLoading = false
}
}
}
},
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
enabled = !isAcceptRequestLoading
) {
Text(stringResource(R.string.user_info_sheet_accept_request))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.user_info_sheet_accept_request))
if (isAcceptRequestLoading) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = LocalContentColor.current)
}
}
}
SquareButton(
onClick = {
scope.launch {
try {
unfriendUser(user.id)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
latestUser.id?.let {
scope.launch {
try {
isDeclineRequestLoading = true
unfriendUser( latestUser.id)
val updated = latestUser.copy(relationship = REL_NONE)
PeptideAPI.userCache[latestUser.id] =
(PeptideAPI.userCache[latestUser.id]?.copy(relationship = REL_NONE))
?: updated
onRelationshipChanged(REL_NONE)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
} finally {
isDeclineRequestLoading = false
}
}
}
},
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error,
contentColor = MaterialTheme.colorScheme.onError,
),
modifier = Modifier.weight(1f)
modifier = Modifier.weight(1f),
enabled = !isDeclineRequestLoading
) {
Text(stringResource(R.string.user_info_sheet_decline_request))
}
}
"Blocked" -> {
SquareButton(
onClick = {
scope.launch {
try {
unblockUser(user.id)
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
}
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(stringResource(R.string.user_info_sheet_decline_request))
if (isDeclineRequestLoading) {
CircularProgressIndicator(modifier = Modifier.size(16.dp), strokeWidth = 2.dp, color = LocalContentColor.current)
}
},
modifier = Modifier.weight(1f)
) {
Text(stringResource(R.string.user_info_sheet_unblock))
}
}
}
"BlockedOther" -> Box(Modifier.weight(1f))
REL_BLOCKED, REL_BLOCKED_OTHER -> Box(Modifier.weight(1f))
else -> Box(Modifier.weight(1f))
}
}
}

View File

@ -631,7 +631,9 @@ fun ChatRouterScreen(
}
if (showUserContextSheet) {
val userContextSheetState = rememberModalBottomSheetState()
val userContextSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
)
ModalBottomSheet(
sheetState = userContextSheetState,
@ -641,7 +643,6 @@ fun ChatRouterScreen(
) {
UserInfoSheet(
userId = userContextSheetTarget,
serverId = userContextSheetServer,
dismissSheet = {
userContextSheetState.hide()
showUserContextSheet = false

View File

@ -12,7 +12,6 @@ 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@ -53,6 +52,7 @@ import kotlinx.coroutines.launch
import chat.peptide.api.settings.NotificationType
import chat.peptide.api.settings.SyncedSettings
import chat.peptide.api.schemas.NotificationSettings
import kotlinx.coroutines.GlobalScope
@Composable
fun ChannelContextSheet(channelId: String, onHideSheet: suspend () -> Unit) {
@ -344,30 +344,6 @@ fun ChannelContextSheet(channelId: String, onHideSheet: suspend () -> Unit) {
}
}
@Composable
private fun NotificationOptionButton(
title: String,
isSelected: Boolean,
onSelect: () -> Unit
) {
SheetButton(
headlineContent = { Text(text = title) },
leadingContent = {
Icon(
painter = painterResource(id = R.drawable.icn_notification_settings_24dp),
contentDescription = null
)
},
trailingContent = {
androidx.compose.material3.RadioButton(
selected = isSelected,
onClick = onSelect
)
},
onClick = onSelect
)
}
@Composable
fun NotificationRadioRow(
title: String,
@ -381,7 +357,13 @@ fun NotificationRadioRow(
headlineContent = { Text(text = title) },
leadingContent = {
Icon(
painter = painterResource(id = R.drawable.icn_notification_settings_24dp),
painter = painterResource(id = when(type){
NotificationType.DEFAULT -> R.drawable.icn_notif_use_default
NotificationType.MUTED -> R.drawable.icn_notif_mute
NotificationType.ALL -> R.drawable.icn_notif_all_messages
NotificationType.MENTIONS_ONLY -> R.drawable.icn_notif_mention_only
NotificationType.NONE -> R.drawable.icn_notif_none
}),
contentDescription = null
)
},
@ -401,7 +383,7 @@ fun NotificationRadioRow(
}
}
)
kotlinx.coroutines.GlobalScope.launch {
GlobalScope.launch {
SyncedSettings.updateNotifications(updated)
}
}
@ -420,7 +402,7 @@ fun NotificationRadioRow(
}
}
)
kotlinx.coroutines.GlobalScope.launch {
GlobalScope.launch {
SyncedSettings.updateNotifications(updated)
}
}

View File

@ -226,7 +226,9 @@ fun MemberListSheet(
}
if (showUserInfoSheet) {
val userContextSheetState = rememberModalBottomSheetState()
val userContextSheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true,
)
ModalBottomSheet(
sheetState = userContextSheetState,
@ -236,7 +238,6 @@ fun MemberListSheet(
) {
UserInfoSheet(
userId = userInfoSheetTarget,
serverId = serverId,
dismissSheet = {
userContextSheetState.hide()
showUserInfoSheet = false

View File

@ -24,14 +24,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.SmallFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -49,7 +52,8 @@ import androidx.compose.ui.unit.dp
import chat.peptide.R
import chat.peptide.api.PeptideAPI
import chat.peptide.api.routes.user.blockUser
import chat.peptide.api.routes.user.getOrFetchUser
import chat.peptide.api.routes.user.fetchUser
import chat.peptide.api.routes.user.unblockUser
import chat.peptide.api.routes.user.unfriendUser
import chat.peptide.api.schemas.ChannelType
import chat.peptide.api.schemas.NotificationSettings
@ -63,7 +67,6 @@ import chat.peptide.composables.generic.NonIdealState
import chat.peptide.composables.generic.SheetButton
import chat.peptide.composables.generic.UserAvatar
import chat.peptide.composables.screens.settings.UserButtons
import chat.peptide.composables.sheets.SheetTile
import chat.peptide.internals.Platform
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -75,25 +78,22 @@ import logcat.logcat
@Composable
fun UserInfoSheet(
userId: String,
serverId: String? = null,
dismissSheet: suspend () -> Unit
) {
var resolvedUser by remember { mutableStateOf(PeptideAPI.userCache[userId]) }
LaunchedEffect(userId) {
try {
if (resolvedUser == null) {
resolvedUser = getOrFetchUser(userId)
}
// Show cached value immediately if present
resolvedUser = PeptideAPI.userCache[userId]
// Fetch fresh to ensure latest relationship state
val fresh = fetchUser(userId)
resolvedUser = fresh
} catch (e: Exception) {
e.printStackTrace()
}
}
val member = serverId?.let { PeptideAPI.members.getMember(it, userId) }
val server = PeptideAPI.serverCache[serverId]
if (resolvedUser == null) {
NonIdealState(
icon = {
@ -143,7 +143,7 @@ fun UserInfoSheet(
}
// Page state: 0 = main, 1 = notifications
var page by remember { mutableStateOf(0) }
var page by remember { mutableIntStateOf(0) }
AnimatedContent(
targetState = page,
@ -195,7 +195,7 @@ fun UserInfoSheet(
)
resolvedUser!!.username?.let { uname ->
Text(
text = "@" + uname,
text = "@$uname",
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
@ -245,17 +245,6 @@ fun UserInfoSheet(
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.surfaceBright)
) {
SheetButton(
headlineContent = { Text(text = stringResource(id = R.string.overview_screen_settings)) },
leadingContent = {
Icon(
painter = painterResource(R.drawable.icn_settings_24dp),
contentDescription = null
)
},
onClick = { /* open profile */ }
)
HorizontalDivider()
SheetButton(
headlineContent = { Text(text = stringResource(id = R.string.message_friends)) },
leadingContent = {
@ -303,8 +292,10 @@ fun UserInfoSheet(
}
}
// Danger block (Close DM, Report User)
item(key = "danger-actions", span = StaggeredGridItemSpan.FullLine) {
// Danger block (Block, Remove Friend, Report User)
item(key = "danger-actions_${resolvedUser!!.relationship}", span = StaggeredGridItemSpan.FullLine) {
var isBlockActionLoading by remember { mutableStateOf(false) }
var isRemoveFriendLoading by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.padding(top = 16.dp)
@ -312,29 +303,67 @@ fun UserInfoSheet(
.background(MaterialTheme.colorScheme.surfaceBright)
) {
SheetButton(
headlineContent = { Text(text = stringResource(id = R.string.user_info_sheet_block)) },
headlineContent = {
Text(
text = stringResource(
id = if (resolvedUser?.relationship == "Blocked") {
R.string.user_info_sheet_unblock
} else R.string.user_info_sheet_block
)
)
},
leadingContent = {
Icon(
painter = painterResource(R.drawable.icn_block_24dp),
contentDescription = null,
)
},
trailingContent = {
if (isBlockActionLoading) {
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
strokeWidth = 2.dp,
color = LocalContentColor.current
)
}
},
onClick = {
scope.launch {
try {
isBlockActionLoading = true
resolvedUser?.id?.let { userId ->
blockUser(userId)
if (resolvedUser?.relationship == "Blocked") {
unblockUser(userId)
// Update local cache/state to reflect new relationship
val updated = resolvedUser!!.copy(relationship = null)
resolvedUser = updated
PeptideAPI.userCache[userId] =
(PeptideAPI.userCache[userId]?.copy(relationship = null))
?: updated
try { fetchUser(userId) } catch (_: Exception) {}
} else {
blockUser(userId)
// Update local cache/state to reflect new relationship
val updated = resolvedUser!!.copy(relationship = "Blocked")
resolvedUser = updated
PeptideAPI.userCache[userId] =
(PeptideAPI.userCache[userId]?.copy(relationship = "Blocked"))
?: updated
try { fetchUser(userId) } catch (_: Exception) {}
}
}
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
} finally {
isBlockActionLoading = false
}
}
},
modifier = Modifier,
dangerous = true
)
if(resolvedUser?.relationship == "Friend"){
if (resolvedUser?.relationship == "Friend") {
HorizontalDivider()
SheetButton(
headlineContent = { Text(stringResource(R.string.user_info_sheet_remove_friend)) },
@ -344,14 +373,32 @@ fun UserInfoSheet(
contentDescription = null
)
},
trailingContent = {
if (isRemoveFriendLoading) {
CircularProgressIndicator(
modifier = Modifier.size(18.dp),
strokeWidth = 2.dp,
color = LocalContentColor.current
)
}
},
onClick = {
resolvedUser?.id?.let {userId ->
resolvedUser?.id?.let { userId ->
scope.launch {
try {
isRemoveFriendLoading = true
unfriendUser(userId)
val updated = resolvedUser!!.copy(relationship = "None")
resolvedUser = updated
PeptideAPI.userCache[userId] =
(PeptideAPI.userCache[userId]?.copy(relationship = "None"))
?: updated
try { fetchUser(userId) } catch (_: Exception) {}
} catch (e: Exception) {
if (e.message == "NoEffect") return@launch
logcat(LogPriority.ERROR) { e.asLog() }
} finally {
isRemoveFriendLoading = false
}
}
}
@ -390,8 +437,10 @@ fun UserInfoSheet(
// Removed joined, badges, and bio sections per request
item(key = "actions", span = StaggeredGridItemSpan.FullLine) {
UserButtons(resolvedUser!!, dismissSheet)
item(key = "actions_${resolvedUser!!.id}_${resolvedUser!!.relationship}", span = StaggeredGridItemSpan.FullLine) {
UserButtons(resolvedUser!!, dismissSheet, onRelationshipChanged = { newRel ->
resolvedUser = resolvedUser!!.copy(relationship = newRel)
})
}
} else {
item(key = "notifications", span = StaggeredGridItemSpan.FullLine) {

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M29.003,14.665C28.827,14.757 28.621,14.774 28.432,14.714C28.242,14.654 28.084,14.521 27.993,14.345C27.277,12.931 26.193,11.735 24.856,10.885C24.773,10.832 24.7,10.764 24.643,10.683C24.587,10.603 24.546,10.512 24.524,10.416C24.503,10.319 24.5,10.22 24.517,10.123C24.533,10.026 24.569,9.933 24.622,9.85C24.674,9.766 24.743,9.694 24.823,9.637C24.903,9.58 24.994,9.54 25.09,9.518C25.187,9.496 25.286,9.494 25.383,9.51C25.48,9.527 25.573,9.563 25.657,9.615C27.215,10.613 28.481,12.007 29.324,13.655C29.416,13.831 29.433,14.037 29.373,14.227C29.313,14.416 29.18,14.574 29.003,14.665ZM11.351,14.75C11.489,14.75 11.623,14.712 11.741,14.641C11.858,14.569 11.954,14.467 12.017,14.345C12.733,12.931 13.816,11.735 15.154,10.885C15.322,10.778 15.441,10.61 15.485,10.416C15.529,10.222 15.494,10.018 15.388,9.85C15.282,9.681 15.113,9.562 14.919,9.518C14.725,9.474 14.521,9.509 14.353,9.615C12.794,10.613 11.529,12.007 10.686,13.655C10.626,13.769 10.598,13.897 10.602,14.026C10.606,14.154 10.644,14.28 10.711,14.389C10.778,14.5 10.872,14.59 10.984,14.653C11.096,14.717 11.222,14.75 11.351,14.75ZM28.798,24.494C28.931,24.722 29.001,24.981 29.002,25.245C29.003,25.508 28.935,25.768 28.803,25.997C28.672,26.225 28.483,26.416 28.254,26.548C28.026,26.68 27.767,26.75 27.503,26.75H23.678C23.506,27.598 23.046,28.36 22.377,28.907C21.707,29.455 20.868,29.754 20.003,29.754C19.138,29.754 18.3,29.455 17.63,28.907C16.961,28.36 16.501,27.598 16.328,26.75H12.503C12.24,26.749 11.981,26.679 11.753,26.547C11.525,26.415 11.336,26.225 11.205,25.996C11.074,25.767 11.005,25.508 11.006,25.244C11.007,24.981 11.078,24.722 11.211,24.494C12.055,23.037 12.503,20.964 12.503,18.5C12.503,16.511 13.294,14.603 14.7,13.197C16.107,11.79 18.014,11 20.003,11C21.993,11 23.9,11.79 25.307,13.197C26.713,14.603 27.503,16.511 27.503,18.5C27.503,20.963 27.952,23.035 28.798,24.494ZM22.124,26.75H17.883C18.038,27.188 18.326,27.567 18.705,27.836C19.085,28.104 19.538,28.248 20.003,28.248C20.468,28.248 20.922,28.104 21.302,27.836C21.681,27.567 21.969,27.188 22.124,26.75Z"
android:fillColor="#FFFFFD"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M20,10.25C17.414,10.25 14.934,11.277 13.106,13.106C11.277,14.934 10.25,17.414 10.25,20C10.25,22.586 11.277,25.066 13.106,26.894C14.934,28.723 17.414,29.75 20,29.75C22.017,29.75 24.134,29.142 25.665,28.125C25.747,28.071 25.818,28.001 25.873,27.919C25.928,27.837 25.966,27.745 25.986,27.649C26.005,27.552 26.005,27.453 25.986,27.356C25.967,27.259 25.929,27.167 25.875,27.085C25.82,27.003 25.75,26.933 25.668,26.878C25.587,26.823 25.495,26.784 25.398,26.765C25.302,26.746 25.202,26.745 25.105,26.764C25.009,26.783 24.917,26.821 24.835,26.876C23.563,27.723 21.707,28.25 20,28.25C18.368,28.25 16.773,27.766 15.417,26.86C14.06,25.953 13.002,24.665 12.378,23.157C11.754,21.65 11.59,19.991 11.908,18.39C12.227,16.79 13.013,15.32 14.166,14.166C15.32,13.013 16.79,12.227 18.39,11.908C19.991,11.59 21.65,11.754 23.157,12.378C24.665,13.002 25.953,14.06 26.86,15.417C27.766,16.773 28.25,18.368 28.25,20C28.25,22.48 27.23,23 26.375,23C25.52,23 24.5,22.48 24.5,20V16.25C24.5,16.051 24.421,15.86 24.28,15.72C24.14,15.579 23.949,15.5 23.75,15.5C23.551,15.5 23.36,15.579 23.22,15.72C23.079,15.86 23,16.051 23,16.25V16.649C22.327,16.046 21.488,15.659 20.592,15.54C19.696,15.42 18.785,15.573 17.977,15.978C17.169,16.384 16.502,17.024 16.063,17.814C15.624,18.604 15.433,19.508 15.515,20.408C15.597,21.308 15.948,22.163 16.522,22.861C17.097,23.559 17.869,24.067 18.736,24.32C19.604,24.573 20.528,24.559 21.388,24.279C22.247,24 23.003,23.468 23.556,22.753C24.118,23.878 25.089,24.5 26.375,24.5C28.488,24.5 29.75,22.818 29.75,20C29.747,17.415 28.719,14.937 26.891,13.109C25.063,11.281 22.585,10.253 20,10.25ZM20,23C19.407,23 18.827,22.824 18.333,22.494C17.84,22.165 17.455,21.696 17.228,21.148C17.001,20.6 16.942,19.997 17.058,19.415C17.173,18.833 17.459,18.298 17.879,17.879C18.298,17.459 18.833,17.173 19.415,17.058C19.997,16.942 20.6,17.001 21.148,17.228C21.696,17.455 22.165,17.84 22.494,18.333C22.824,18.827 23,19.407 23,20C23,20.796 22.684,21.559 22.121,22.121C21.559,22.684 20.796,23 20,23Z"
android:fillColor="#FFFFFD"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M28.798,26C28.784,26.024 28.764,26.044 28.741,26.059C28.718,26.074 28.691,26.083 28.664,26.086C28.636,26.088 28.608,26.084 28.583,26.074C28.557,26.063 28.534,26.047 28.516,26.026L15.807,12.049C15.771,12.009 15.744,11.961 15.729,11.91C15.713,11.858 15.708,11.804 15.715,11.751C15.723,11.697 15.741,11.646 15.77,11.6C15.799,11.555 15.837,11.516 15.882,11.486C17.134,10.658 18.608,10.227 20.11,10.25C24.219,10.312 27.481,13.728 27.5,17.836C27.513,21.106 28.279,23.605 28.796,24.492C28.93,24.721 29.001,24.981 29.001,25.246C29.002,25.511 28.931,25.771 28.798,26ZM23,28.25H17.021C16.828,28.248 16.641,28.32 16.499,28.449C16.356,28.579 16.267,28.758 16.25,28.95C16.243,29.053 16.258,29.156 16.292,29.253C16.327,29.349 16.381,29.438 16.451,29.513C16.522,29.588 16.607,29.648 16.701,29.688C16.795,29.729 16.897,29.75 17,29.75H22.976C23.169,29.752 23.357,29.681 23.5,29.552C23.644,29.422 23.733,29.242 23.75,29.05C23.757,28.947 23.743,28.844 23.708,28.747C23.673,28.65 23.619,28.562 23.549,28.487C23.479,28.412 23.393,28.352 23.299,28.311C23.205,28.271 23.103,28.25 23,28.25ZM13.048,11.245C12.982,11.171 12.902,11.111 12.812,11.068C12.723,11.025 12.626,11 12.527,10.994C12.428,10.989 12.329,11.003 12.235,11.037C12.142,11.07 12.056,11.122 11.982,11.188C11.909,11.255 11.849,11.336 11.807,11.425C11.765,11.515 11.741,11.613 11.737,11.712C11.733,11.811 11.749,11.91 11.783,12.003C11.817,12.096 11.87,12.182 11.938,12.254L13.512,13.986C12.844,15.128 12.493,16.427 12.493,17.75C12.493,21.04 11.729,23.562 11.209,24.475C11.071,24.712 10.998,24.983 11,25.258C11.002,25.533 11.077,25.802 11.218,26.038C11.35,26.257 11.538,26.437 11.761,26.562C11.984,26.687 12.236,26.751 12.492,26.75H25.121L26.944,28.754C27.078,28.902 27.265,28.99 27.463,28.999C27.662,29.009 27.857,28.939 28.004,28.805C28.151,28.672 28.24,28.485 28.249,28.286C28.259,28.087 28.189,27.893 28.055,27.746L13.048,11.245Z"
android:fillColor="#FFFFFD"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M20,10.25C18.072,10.25 16.187,10.822 14.583,11.893C12.98,12.965 11.73,14.487 10.992,16.269C10.254,18.05 10.061,20.011 10.437,21.902C10.814,23.793 11.742,25.531 13.106,26.894C14.469,28.258 16.207,29.187 18.098,29.563C19.989,29.939 21.95,29.746 23.731,29.008C25.513,28.27 27.035,27.02 28.107,25.417C29.178,23.813 29.75,21.928 29.75,20C29.747,17.415 28.719,14.937 26.891,13.109C25.063,11.281 22.585,10.253 20,10.25ZM28.25,20C28.252,21.929 27.574,23.797 26.337,25.276L14.724,13.663C15.928,12.66 17.394,12.022 18.948,11.822C20.502,11.622 22.081,11.869 23.5,12.534C24.919,13.199 26.12,14.254 26.961,15.576C27.802,16.899 28.249,18.433 28.25,20ZM11.75,20C11.748,18.071 12.425,16.203 13.663,14.724L25.276,26.337C24.072,27.34 22.606,27.978 21.052,28.178C19.498,28.378 17.919,28.131 16.5,27.466C15.081,26.801 13.88,25.746 13.039,24.424C12.198,23.101 11.751,21.567 11.75,20Z"
android:fillColor="#FFFFFD"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:pathData="M30.309,18.051C30.288,17.945 30.244,17.845 30.181,17.758C30.118,17.671 30.037,17.598 29.944,17.545L27.147,15.951L27.136,12.799C27.136,12.691 27.112,12.583 27.066,12.485C27.02,12.387 26.954,12.299 26.871,12.229C25.856,11.371 24.688,10.713 23.428,10.291C23.329,10.258 23.224,10.245 23.119,10.255C23.015,10.264 22.914,10.295 22.823,10.347L20.002,11.923L17.178,10.344C17.086,10.292 16.985,10.261 16.881,10.251C16.776,10.242 16.671,10.254 16.571,10.288C15.312,10.712 14.145,11.373 13.133,12.233C13.05,12.303 12.983,12.39 12.938,12.488C12.892,12.587 12.868,12.693 12.867,12.802L12.853,15.957L10.057,17.55C9.963,17.604 9.882,17.676 9.819,17.764C9.756,17.851 9.713,17.951 9.692,18.056C9.436,19.343 9.436,20.667 9.692,21.953C9.713,22.058 9.756,22.158 9.819,22.246C9.882,22.333 9.963,22.406 10.057,22.459L12.853,24.053L12.865,27.206C12.865,27.314 12.889,27.421 12.934,27.52C12.98,27.618 13.047,27.705 13.13,27.776C14.144,28.634 15.312,29.291 16.572,29.713C16.671,29.747 16.777,29.759 16.881,29.75C16.985,29.74 17.086,29.709 17.178,29.658L20.002,28.076L22.825,29.656C22.937,29.719 23.063,29.751 23.191,29.75C23.273,29.75 23.354,29.737 23.432,29.711C24.691,29.286 25.858,28.626 26.871,27.767C26.953,27.697 27.02,27.61 27.066,27.512C27.112,27.413 27.135,27.306 27.136,27.198L27.15,24.043L29.947,22.45C30.04,22.396 30.121,22.324 30.184,22.236C30.247,22.149 30.29,22.049 30.311,21.943C30.566,20.658 30.565,19.336 30.309,18.051ZM20.002,23.75C19.26,23.75 18.535,23.53 17.918,23.118C17.302,22.706 16.821,22.12 16.537,21.435C16.253,20.75 16.179,19.996 16.324,19.268C16.468,18.541 16.826,17.873 17.35,17.348C17.874,16.824 18.543,16.467 19.27,16.322C19.997,16.177 20.751,16.252 21.437,16.535C22.122,16.819 22.708,17.3 23.12,17.917C23.532,18.533 23.752,19.258 23.752,20C23.752,20.994 23.357,21.948 22.653,22.652C21.95,23.355 20.996,23.75 20.002,23.75Z"
android:fillColor="#FFFFFD"/>
</vector>