From c2790838dfbcb3c9b20853ce8e4e26df758e7e52 Mon Sep 17 00:00:00 2001 From: AbronStudio Date: Wed, 20 Aug 2025 12:19:08 +0330 Subject: [PATCH] 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. --- .../screens/chat/drawer/ChannelSideDrawer.kt | 1 - .../screens/settings/UserButtons.kt | 230 ++++++++++++------ .../peptide/screens/chat/ChatRouterScreen.kt | 5 +- .../peptide/sheets/ChannelContextSheet.kt | 38 +-- .../chat/peptide/sheets/MemberListSheet.kt | 5 +- .../java/chat/peptide/sheets/UserInfoSheet.kt | 111 ++++++--- .../res/drawable/icn_notif_all_messages.xml | 9 + .../res/drawable/icn_notif_mention_only.xml | 9 + app/src/main/res/drawable/icn_notif_mute.xml | 9 + app/src/main/res/drawable/icn_notif_none.xml | 9 + .../res/drawable/icn_notif_use_default.xml | 9 + 11 files changed, 292 insertions(+), 143 deletions(-) create mode 100644 app/src/main/res/drawable/icn_notif_all_messages.xml create mode 100644 app/src/main/res/drawable/icn_notif_mention_only.xml create mode 100644 app/src/main/res/drawable/icn_notif_mute.xml create mode 100644 app/src/main/res/drawable/icn_notif_none.xml create mode 100644 app/src/main/res/drawable/icn_notif_use_default.xml diff --git a/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt b/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt index 905151aa..e925f41a 100644 --- a/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt +++ b/app/src/main/java/chat/peptide/composables/screens/chat/drawer/ChannelSideDrawer.kt @@ -203,7 +203,6 @@ fun ChannelSideDrawer( ) { UserInfoSheet( userId = userContextSheetTarget!!, - serverId = null, dismissSheet = { userContextSheetState.hide() userContextSheetTarget = null diff --git a/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt b/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt index 41394410..99f84223 100644 --- a/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt +++ b/app/src/main/java/chat/peptide/composables/screens/settings/UserButtons.kt @@ -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)) } } } \ No newline at end of file diff --git a/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt index 96168f83..b5653dea 100644 --- a/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/peptide/screens/chat/ChatRouterScreen.kt @@ -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 diff --git a/app/src/main/java/chat/peptide/sheets/ChannelContextSheet.kt b/app/src/main/java/chat/peptide/sheets/ChannelContextSheet.kt index f707c31f..e7020fc9 100644 --- a/app/src/main/java/chat/peptide/sheets/ChannelContextSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/ChannelContextSheet.kt @@ -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) } } diff --git a/app/src/main/java/chat/peptide/sheets/MemberListSheet.kt b/app/src/main/java/chat/peptide/sheets/MemberListSheet.kt index 1cc1e805..9a422e6d 100644 --- a/app/src/main/java/chat/peptide/sheets/MemberListSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/MemberListSheet.kt @@ -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 diff --git a/app/src/main/java/chat/peptide/sheets/UserInfoSheet.kt b/app/src/main/java/chat/peptide/sheets/UserInfoSheet.kt index 4bca62d3..628d2d07 100644 --- a/app/src/main/java/chat/peptide/sheets/UserInfoSheet.kt +++ b/app/src/main/java/chat/peptide/sheets/UserInfoSheet.kt @@ -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) { diff --git a/app/src/main/res/drawable/icn_notif_all_messages.xml b/app/src/main/res/drawable/icn_notif_all_messages.xml new file mode 100644 index 00000000..8ea11146 --- /dev/null +++ b/app/src/main/res/drawable/icn_notif_all_messages.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icn_notif_mention_only.xml b/app/src/main/res/drawable/icn_notif_mention_only.xml new file mode 100644 index 00000000..9c02575c --- /dev/null +++ b/app/src/main/res/drawable/icn_notif_mention_only.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icn_notif_mute.xml b/app/src/main/res/drawable/icn_notif_mute.xml new file mode 100644 index 00000000..6ca87c16 --- /dev/null +++ b/app/src/main/res/drawable/icn_notif_mute.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icn_notif_none.xml b/app/src/main/res/drawable/icn_notif_none.xml new file mode 100644 index 00000000..24b069b4 --- /dev/null +++ b/app/src/main/res/drawable/icn_notif_none.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icn_notif_use_default.xml b/app/src/main/res/drawable/icn_notif_use_default.xml new file mode 100644 index 00000000..3d28c65a --- /dev/null +++ b/app/src/main/res/drawable/icn_notif_use_default.xml @@ -0,0 +1,9 @@ + + +