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 @@
+
+
+