mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-08 06:46:11 +00:00
feat: unify people lists with conversation-row avatar design
- Extract shared PeerAvatar (initial circle + lower-right transport badge) from the conversation row and use it in the mesh peer list and the geohash/Nostr people list - Remove the favorite toggle button from the peer list; favorite state is now a small star badge on the avatar (filled = we favorited, outline = they favorited us), so favoriting only happens from the private chat - Show the unread-count badge on peer rows, matching conversation rows
This commit is contained in:
parent
bf5bd8c417
commit
4fdc9babbe
@ -281,6 +281,7 @@ private fun GeohashPersonItem(
|
||||
onTap: () -> Unit
|
||||
) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
val statusIconRes =
|
||||
if (isTeleported) R.drawable.ic_spec_teleport
|
||||
@ -298,25 +299,14 @@ private fun GeohashPersonItem(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
// Exact height, not padding: a row that sizes to its content makes the card change
|
||||
// height whenever the list reorders.
|
||||
.height(SheetRowHeight)
|
||||
.clickable(onClick = onTap)
|
||||
.padding(horizontal = SheetRowHorizontal),
|
||||
.padding(horizontal = SheetRowHorizontal, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(SheetRowLeadingSlot),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (hasUnreadDM) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = stringResource(R.string.cd_unread_message),
|
||||
modifier = Modifier.size(22.dp),
|
||||
tint = palette.accentOrange
|
||||
)
|
||||
} else {
|
||||
PeerAvatar(
|
||||
name = baseNameRaw,
|
||||
color = baseColor,
|
||||
badge = {
|
||||
Icon(
|
||||
painter = painterResource(statusIconRes),
|
||||
contentDescription = if (isTeleported) {
|
||||
@ -324,13 +314,13 @@ private fun GeohashPersonItem(
|
||||
} else {
|
||||
stringResource(R.string.section_on_location)
|
||||
},
|
||||
modifier = Modifier.size(22.dp),
|
||||
tint = baseColor
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = if (isTeleported) palette.accentPurple else colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(SheetRowLeadingGutter))
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
@ -365,5 +355,16 @@ private fun GeohashPersonItem(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUnreadDM) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Email,
|
||||
contentDescription = stringResource(R.string.cd_unread_message),
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(18.dp),
|
||||
tint = palette.accentOrange
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -831,18 +831,14 @@ fun PeopleSection(
|
||||
displayName = displayName,
|
||||
isDirect = isDirectLive,
|
||||
isWifiAware = peerID in wifiAwarePeerIDs,
|
||||
isConnected = true,
|
||||
isSelected = conversationID == selectedPrivatePeer || peerID == selectedPrivatePeer,
|
||||
isFavorite = isFavorite,
|
||||
theyFavoritedUs = theyFavoritedUs,
|
||||
isVerified = isVerified,
|
||||
hasUnreadDM = combinedHasUnread,
|
||||
colorScheme = colorScheme,
|
||||
viewModel = viewModel,
|
||||
onItemClick = { onPrivateChatStart(peerID) },
|
||||
onToggleFavorite = {
|
||||
Log.d("SidebarComponents", "Sidebar toggle favorite: peerID=$peerID, currentFavorite=$isFavorite")
|
||||
viewModel.toggleFavorite(peerID)
|
||||
},
|
||||
unreadCount = if (combinedUnreadCount > 0) combinedUnreadCount else if (combinedHasUnread) 1 else 0,
|
||||
showNostrGlobe = false,
|
||||
showHashSuffix = showHash
|
||||
@ -881,18 +877,14 @@ fun PeopleSection(
|
||||
peerID = favPeerID,
|
||||
displayName = dn,
|
||||
isDirect = false,
|
||||
isConnected = false,
|
||||
isSelected = conversationID == selectedPrivatePeer || (mappedConnectedPeerID ?: favPeerID) == selectedPrivatePeer,
|
||||
isFavorite = true,
|
||||
theyFavoritedUs = fav.theyFavoritedUs,
|
||||
isVerified = isVerified,
|
||||
hasUnreadDM = hasUnread,
|
||||
colorScheme = colorScheme,
|
||||
viewModel = viewModel,
|
||||
onItemClick = { onPrivateChatStart(mappedConnectedPeerID ?: favPeerID) },
|
||||
onToggleFavorite = {
|
||||
Log.d("SidebarComponents", "Sidebar toggle favorite (offline): peerID=$favPeerID")
|
||||
viewModel.toggleFavorite(favPeerID)
|
||||
},
|
||||
unreadCount = if (unreadCount > 0) unreadCount else if (hasUnread) 1 else 0,
|
||||
showNostrGlobe = (fav.isMutual && fav.peerNostrPublicKey != null),
|
||||
showHashSuffix = showHash
|
||||
@ -1263,72 +1255,42 @@ private fun ConversationRow(
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(42.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(38.dp)
|
||||
.background(assignedColor.copy(alpha = 0.16f), CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = baseNameRaw
|
||||
.trim()
|
||||
.firstOrNull()
|
||||
?.uppercase()
|
||||
?: "#",
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
),
|
||||
color = assignedColor
|
||||
)
|
||||
}
|
||||
PeerAvatar(
|
||||
name = baseNameRaw,
|
||||
color = assignedColor,
|
||||
badge = {
|
||||
when {
|
||||
conversation.isConnected -> Icon(
|
||||
painter = painterResource(
|
||||
conversationTransportIcon(
|
||||
isReachedOverInternet = false,
|
||||
isWifiAware = isWifiAware,
|
||||
isDirect = isDirect
|
||||
)
|
||||
),
|
||||
contentDescription = connectionDescription,
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.BottomEnd),
|
||||
shape = CircleShape,
|
||||
color = colorScheme.surface,
|
||||
tonalElevation = 1.dp
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
when {
|
||||
conversation.isConnected -> Icon(
|
||||
painter = painterResource(
|
||||
conversationTransportIcon(
|
||||
isReachedOverInternet = false,
|
||||
isWifiAware = isWifiAware,
|
||||
isDirect = isDirect
|
||||
)
|
||||
),
|
||||
contentDescription = connectionDescription,
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
conversation.transport == DirectMessageTransport.NOSTR -> Icon(
|
||||
painter = painterResource(R.drawable.ic_spec_globe),
|
||||
contentDescription = stringResource(
|
||||
R.string.offline_reachable_via_nostr
|
||||
),
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = palette.accentPurple
|
||||
)
|
||||
|
||||
conversation.transport == DirectMessageTransport.NOSTR -> Icon(
|
||||
painter = painterResource(R.drawable.ic_spec_globe),
|
||||
contentDescription = stringResource(
|
||||
R.string.offline_reachable_via_nostr
|
||||
),
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = palette.accentPurple
|
||||
)
|
||||
|
||||
else -> Icon(
|
||||
imageVector = Icons.Outlined.Circle,
|
||||
contentDescription = stringResource(R.string.offline_not_in_mesh),
|
||||
modifier = Modifier.size(11.dp),
|
||||
tint = palette.textTertiary
|
||||
)
|
||||
}
|
||||
else -> Icon(
|
||||
imageVector = Icons.Outlined.Circle,
|
||||
contentDescription = stringResource(R.string.offline_not_in_mesh),
|
||||
modifier = Modifier.size(11.dp),
|
||||
tint = palette.textTertiary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
@ -1559,15 +1521,14 @@ private fun PeerItem(
|
||||
displayName: String,
|
||||
isDirect: Boolean,
|
||||
isWifiAware: Boolean = false,
|
||||
isConnected: Boolean = true,
|
||||
isSelected: Boolean,
|
||||
isFavorite: Boolean,
|
||||
theyFavoritedUs: Boolean = false,
|
||||
isVerified: Boolean,
|
||||
hasUnreadDM: Boolean,
|
||||
colorScheme: ColorScheme,
|
||||
viewModel: ChatViewModel,
|
||||
onItemClick: () -> Unit,
|
||||
onToggleFavorite: () -> Unit,
|
||||
unreadCount: Int = 0,
|
||||
showNostrGlobe: Boolean = false,
|
||||
showHashSuffix: Boolean = true
|
||||
@ -1595,57 +1556,47 @@ private fun PeerItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onItemClick)
|
||||
.padding(horizontal = SheetRowHorizontal, vertical = SheetRowVertical),
|
||||
.padding(horizontal = SheetRowHorizontal, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.size(SheetRowLeadingSlot),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (isSelected) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(SheetRowSelectedDot)
|
||||
.background(colorScheme.primary, CircleShape)
|
||||
)
|
||||
} else if (hasUnreadDM) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_spec_envelope),
|
||||
contentDescription = stringResource(R.string.cd_unread_message),
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.accentOrange
|
||||
)
|
||||
} else if (showNostrGlobe) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_spec_globe),
|
||||
contentDescription = stringResource(R.string.cd_reachable_via_nostr),
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.accentPurple
|
||||
)
|
||||
} else if (!isDirect && isFavorite) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Circle,
|
||||
contentDescription = stringResource(R.string.cd_offline_favorite),
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = palette.textTertiary
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
conversationTransportIcon(
|
||||
isReachedOverInternet = false,
|
||||
isWifiAware = isWifiAware,
|
||||
isDirect = isDirect
|
||||
)
|
||||
),
|
||||
contentDescription = connectionDescription,
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
PeerAvatar(
|
||||
name = baseNameRaw,
|
||||
color = baseColor,
|
||||
isFavorite = isFavorite,
|
||||
theyFavoritedUs = theyFavoritedUs,
|
||||
badge = {
|
||||
when {
|
||||
isConnected -> Icon(
|
||||
painter = painterResource(
|
||||
conversationTransportIcon(
|
||||
isReachedOverInternet = false,
|
||||
isWifiAware = isWifiAware,
|
||||
isDirect = isDirect
|
||||
)
|
||||
),
|
||||
contentDescription = connectionDescription,
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = colorScheme.primary
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(SheetRowLeadingGutter))
|
||||
showNostrGlobe -> Icon(
|
||||
painter = painterResource(R.drawable.ic_spec_globe),
|
||||
contentDescription = stringResource(R.string.cd_reachable_via_nostr),
|
||||
modifier = Modifier.size(13.dp),
|
||||
tint = palette.accentPurple
|
||||
)
|
||||
|
||||
else -> Icon(
|
||||
imageVector = Icons.Outlined.Circle,
|
||||
contentDescription = stringResource(R.string.cd_offline_favorite),
|
||||
modifier = Modifier.size(11.dp),
|
||||
tint = palette.textTertiary
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
@ -1682,21 +1633,18 @@ private fun PeerItem(
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(36.dp)
|
||||
.clickable(onClick = onToggleFavorite),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// Three-state star (matches private-chat header): grey outline (no relation),
|
||||
// orange outline (they favorited us), filled orange (we favorited them).
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (isFavorite) R.drawable.ic_spec_star_filled else R.drawable.ic_spec_star
|
||||
),
|
||||
contentDescription = if (isFavorite) "Remove from favorites" else "Add to favorites",
|
||||
modifier = Modifier.size(PeerRowIconSize),
|
||||
tint = if (isFavorite || theyFavoritedUs) palette.accentOrange else palette.textTertiary
|
||||
UnreadBadge(
|
||||
count = unreadCount,
|
||||
colorScheme = colorScheme,
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
)
|
||||
|
||||
if (isSelected) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp)
|
||||
.size(SheetRowSelectedDot)
|
||||
.background(colorScheme.primary, CircleShape)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
105
app/src/main/java/com/bitchat/android/ui/PeerAvatar.kt
Normal file
105
app/src/main/java/com/bitchat/android/ui/PeerAvatar.kt
Normal file
@ -0,0 +1,105 @@
|
||||
package com.bitchat.android.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.ui.theme.BitchatFontFamily
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
|
||||
internal val PeerAvatarBadgeSize = 18.dp
|
||||
private val PeerAvatarStarSize = 16.dp
|
||||
|
||||
@Composable
|
||||
internal fun PeerAvatar(
|
||||
name: String,
|
||||
color: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
isFavorite: Boolean = false,
|
||||
theyFavoritedUs: Boolean = false,
|
||||
badge: (@Composable () -> Unit)? = null
|
||||
) {
|
||||
val palette = LocalBitchatPalette.current
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
Box(
|
||||
modifier = modifier.size(42.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(38.dp)
|
||||
.background(color.copy(alpha = 0.16f), CircleShape),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = name.trim().firstOrNull()?.uppercase() ?: "#",
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
fontFamily = BitchatFontFamily,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
),
|
||||
color = color
|
||||
)
|
||||
}
|
||||
|
||||
if (badge != null) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(PeerAvatarBadgeSize)
|
||||
.align(Alignment.BottomEnd),
|
||||
shape = CircleShape,
|
||||
color = colorScheme.surface,
|
||||
tonalElevation = 1.dp
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
badge()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFavorite || theyFavoritedUs) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(PeerAvatarStarSize)
|
||||
.align(Alignment.TopEnd),
|
||||
shape = CircleShape,
|
||||
color = colorScheme.surface,
|
||||
tonalElevation = 1.dp
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Icon(
|
||||
painter = painterResource(
|
||||
if (isFavorite) {
|
||||
R.drawable.ic_spec_star_filled
|
||||
} else {
|
||||
R.drawable.ic_spec_star
|
||||
}
|
||||
),
|
||||
contentDescription = stringResource(
|
||||
if (isFavorite) {
|
||||
R.string.cd_favorite
|
||||
} else {
|
||||
R.string.cd_favorited_you
|
||||
}
|
||||
),
|
||||
modifier = Modifier.size(10.dp),
|
||||
tint = palette.accentOrange
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -69,6 +69,8 @@
|
||||
<!-- Favorites accessibility -->
|
||||
<string name="cd_add_favorite">Add to favorites</string>
|
||||
<string name="cd_remove_favorite">Remove from favorites</string>
|
||||
<string name="cd_favorite">Favorite</string>
|
||||
<string name="cd_favorited_you">Favorited you</string>
|
||||
<string name="cd_add_bookmark">Add bookmark</string>
|
||||
|
||||
<!-- Chat header & accessibility -->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user