mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-11 20:27:39 +00:00
desktop: fix rail badges, profile switching and lists
This commit is contained in:
1 parent
b267060f53
commit
f5d4bc1cdc
6 files changed
+69
-29
No files matched your search
+4
-4
@@ -613,12 +613,12 @@ private fun ChatListToolbar(userPickerState: MutableStateFlow<AnimatedViewState>
|
||||
DefaultAppBar(
|
||||
navigationButton = {
|
||||
if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) {
|
||||
NavigationButtonMenu {
|
||||
if (!LocalFlatLayout.current) NavigationButtonMenu {
|
||||
ModalManager.start.showModalCloseable(cardScreen = true) { close ->
|
||||
SettingsView(chatModel, setPerformLA, close)
|
||||
}
|
||||
}
|
||||
} else if (!LocalFlatLayout.current) {
|
||||
} else if (!LocalFlatLayout.current || chatModel.desktopNoUserNoRemote) {
|
||||
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||
val allRead = users
|
||||
.filter { u -> !u.user.activeUser && !u.user.hidden }
|
||||
@@ -1021,7 +1021,7 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
|
||||
// bottom toolbar: search bar below, so on desktop the connect row goes below the tags
|
||||
TagsOrConnectByName(searchText, connectNameCandidate) { candidate ->
|
||||
TagsView(searchText)
|
||||
Divider()
|
||||
if (!LocalFlatLayout.current) Divider()
|
||||
ConnectByNameRow(candidate, searchText, connectNameCandidate, close = null)
|
||||
}
|
||||
ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink, connectNameCandidate)
|
||||
@@ -1032,7 +1032,7 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
|
||||
// top toolbar: search bar above, so on desktop the connect row goes above the tags
|
||||
TagsOrConnectByName(searchText, connectNameCandidate) { candidate ->
|
||||
ConnectByNameRow(candidate, searchText, connectNameCandidate, close = null)
|
||||
Divider()
|
||||
if (!LocalFlatLayout.current) Divider()
|
||||
TagsView(searchText)
|
||||
}
|
||||
Divider()
|
||||
|
||||
+22
-5
@@ -84,12 +84,13 @@ fun ChatPreviewView(
|
||||
|
||||
@Composable
|
||||
fun chatPreviewTitleText(color: Color = Color.Unspecified) {
|
||||
val flat = LocalFlatLayout.current
|
||||
Text(
|
||||
cInfo.chatViewName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = if (flat) MaterialTheme.typography.body1 else MaterialTheme.typography.h3,
|
||||
fontWeight = if (!flat) FontWeight.Bold else if (chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat) FontWeight.SemiBold else FontWeight.Normal,
|
||||
color = color
|
||||
)
|
||||
}
|
||||
@@ -466,14 +467,28 @@ fun ChatPreviewView(
|
||||
}
|
||||
|
||||
if (LocalFlatLayout.current) {
|
||||
LaunchedEffect(chatModel.deletedChats.value) {
|
||||
val ci = chat.chatItems.lastOrNull()
|
||||
val playing = AudioPlayer.currentlyPlaying.value
|
||||
// stops a voice message of a deleted chat, as the voice preview effect does in the full row
|
||||
if (ci != null && playing != null && playing.fileSource.filePath == ci.file?.fileSource?.filePath && chatModel.deletedChats.value.contains(chatModel.remoteHostId() to chat.id)) {
|
||||
AudioPlayer.stop(ci)
|
||||
}
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
ChatInfoImage(cInfo, size = COMPACT_CHAT_IMAGE_SIZE * fontSizeSqrtMultiplier)
|
||||
Box(contentAlignment = Alignment.BottomEnd) {
|
||||
ChatInfoImage(cInfo, size = COMPACT_CHAT_IMAGE_SIZE * fontSizeSqrtMultiplier)
|
||||
chatPreviewImageOverlayIcon()
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Box(Modifier.weight(1f)) {
|
||||
chatPreviewTitle()
|
||||
}
|
||||
Spacer(Modifier.width(8.sp.toDp()))
|
||||
chatPreviewUnreadIndicator()
|
||||
Box(Modifier.offset(y = -COMPACT_BADGE_Y_CORRECTION)) {
|
||||
chatPreviewUnreadIndicator()
|
||||
}
|
||||
Spacer(Modifier.width(4.sp.toDp()))
|
||||
chatStatusImage()
|
||||
}
|
||||
return
|
||||
@@ -552,7 +567,9 @@ fun ChatPreviewView(
|
||||
}
|
||||
}
|
||||
|
||||
private val COMPACT_CHAT_IMAGE_SIZE = 32.dp
|
||||
internal val COMPACT_CHAT_IMAGE_SIZE = 32.dp
|
||||
// cancels the 3dp yOffset that UnreadBadge applies for the two-line row
|
||||
private val COMPACT_BADGE_Y_CORRECTION = 3.dp
|
||||
|
||||
@Composable
|
||||
private fun SmallContentPreview(borderColor: Color = MaterialTheme.colors.onSurface.copy(alpha = 0.12f), content: @Composable BoxScope.() -> Unit) {
|
||||
|
||||
+4
-2
@@ -13,6 +13,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.LocalFlatLayout
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.model.PendingContactConnection
|
||||
import chat.simplex.common.model.getTimestampText
|
||||
@@ -22,8 +23,9 @@ import chat.simplex.res.*
|
||||
@Composable
|
||||
fun ContactConnectionView(contactConnection: PendingContactConnection) {
|
||||
Row {
|
||||
Box(Modifier.size(72.dp * fontSizeSqrtMultiplier), contentAlignment = Alignment.Center) {
|
||||
ProfileImage(size = 54.dp * fontSizeSqrtMultiplier, null, if (contactConnection.initiated) MR.images.ic_add_link else MR.images.ic_link)
|
||||
val flat = LocalFlatLayout.current
|
||||
Box(Modifier.size((if (flat) COMPACT_CHAT_IMAGE_SIZE else 72.dp) * fontSizeSqrtMultiplier), contentAlignment = Alignment.Center) {
|
||||
ProfileImage(size = (if (flat) COMPACT_CHAT_IMAGE_SIZE else 54.dp) * fontSizeSqrtMultiplier, null, if (contactConnection.initiated) MR.images.ic_add_link else MR.images.ic_link)
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
||||
+2
-1
@@ -12,6 +12,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.LocalFlatLayout
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.model.ChatInfo
|
||||
import chat.simplex.common.model.getTimestampText
|
||||
@@ -21,7 +22,7 @@ import chat.simplex.res.*
|
||||
@Composable
|
||||
fun ContactRequestView(contactRequest: ChatInfo.ContactRequest) {
|
||||
Row {
|
||||
ChatInfoImage(contactRequest, size = 72.dp * fontSizeSqrtMultiplier)
|
||||
ChatInfoImage(contactRequest, size = (if (LocalFlatLayout.current) COMPACT_CHAT_IMAGE_SIZE else 72.dp) * fontSizeSqrtMultiplier)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, end = 8.sp.toDp())
|
||||
|
||||
+33
-16
@@ -10,6 +10,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -32,9 +33,14 @@ val DESKTOP_RAIL_WIDTH = 64.dp
|
||||
private val RAIL_ITEM_SIZE = 40.dp
|
||||
private val RAIL_ICON_SIZE = 22.dp
|
||||
private val RAIL_ACTIVE_MARK_WIDTH = 4.dp
|
||||
private val RAIL_ACTIVE_BACKGROUND_WIDTH = 40.dp
|
||||
private val RAIL_ACTIVE_BACKGROUND_HEIGHT = 32.dp
|
||||
private val RAIL_UNREAD_DOT_SIZE = 6.dp
|
||||
private const val RAIL_ACTIVE_BACKGROUND_ALPHA = 0.12f
|
||||
private const val RAIL_BACKGROUND_ALPHA = 0.04f
|
||||
|
||||
@Composable
|
||||
// Spec: spec/client/chat-list.md#DesktopRail
|
||||
fun DesktopRail(userPickerState: MutableStateFlow<AnimatedViewState>, setPerformLA: (Boolean) -> Unit) {
|
||||
Row(
|
||||
Modifier
|
||||
@@ -82,7 +88,8 @@ private fun RailUsers(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||
.sortedByDescending { it.user.activeOrder }
|
||||
}
|
||||
}
|
||||
val enabled = remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote
|
||||
val switchAllowed = profileSwitchAllowed(remember { chatModel.sharedContent }.value) && !remember { chatModel.switchingUsersAndHosts }.value
|
||||
val switchEnabled = switchAllowed && (remember { chatModel.chatRunning }.value == true || chatModel.connectedToRemote)
|
||||
users.forEach { u ->
|
||||
val user = u.user
|
||||
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
|
||||
@@ -94,19 +101,22 @@ private fun RailUsers(userPickerState: MutableStateFlow<AnimatedViewState>) {
|
||||
.background(MaterialTheme.colors.primary, RoundedCornerShape(topEnd = RAIL_ACTIVE_MARK_WIDTH, bottomEnd = RAIL_ACTIVE_MARK_WIDTH))
|
||||
)
|
||||
}
|
||||
Box(
|
||||
Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable(enabled = enabled) {
|
||||
if (user.activeUser) {
|
||||
ModalManager.start.closeModals()
|
||||
userPickerState.value = AnimatedViewState.VISIBLE
|
||||
} else {
|
||||
switchToUser(user)
|
||||
Box {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable(enabled = if (user.activeUser) switchAllowed else switchEnabled) {
|
||||
if (user.activeUser) {
|
||||
ModalManager.start.closeModals()
|
||||
userPickerState.value = AnimatedViewState.VISIBLE
|
||||
} else {
|
||||
if (userPickerState.value.isVisible()) userPickerState.value = AnimatedViewState.HIDING
|
||||
switchToUser(user)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
ProfileImage(RAIL_ITEM_SIZE * fontSizeSqrtMultiplier, user.profile.image)
|
||||
) {
|
||||
ProfileImage(RAIL_ITEM_SIZE * fontSizeSqrtMultiplier, user.profile.image)
|
||||
}
|
||||
if (!user.activeUser && u.unreadCount > 0) {
|
||||
userUnreadBadge(u.unreadCount, user.showNtfs, hasPadding = false)
|
||||
}
|
||||
@@ -124,6 +134,7 @@ private fun RailTags() {
|
||||
val rhId = chatModel.remoteHostId()
|
||||
|
||||
RailItem(MR.images.ic_menu, stringResource(MR.strings.chat_list_all), active = activeFilter.value == null) {
|
||||
ModalManager.start.closeModals()
|
||||
activeFilter.value = null
|
||||
}
|
||||
PresetTagKind.entries.filter { (presetTags[it] ?: 0) > 0 }.forEach { tag ->
|
||||
@@ -159,6 +170,7 @@ private fun RailTags() {
|
||||
}
|
||||
|
||||
private fun toggleFilter(filter: ActiveFilter) {
|
||||
ModalManager.start.closeModals()
|
||||
chatModel.activeChatTagFilter.value = if (chatModel.activeChatTagFilter.value == filter) null else filter
|
||||
}
|
||||
|
||||
@@ -182,14 +194,19 @@ private fun RailItem(
|
||||
.padding(vertical = 4.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Box(
|
||||
Modifier
|
||||
.size(RAIL_ACTIVE_BACKGROUND_WIDTH * fontSizeSqrtMultiplier, RAIL_ACTIVE_BACKGROUND_HEIGHT * fontSizeSqrtMultiplier)
|
||||
.background(if (active) MaterialTheme.colors.primary.copy(alpha = RAIL_ACTIVE_BACKGROUND_ALPHA) else Color.Transparent, RoundedCornerShape(DEFAULT_PADDING_HALF)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (emoji != null) {
|
||||
ReactionIcon(emoji, fontSize = 18.sp)
|
||||
} else {
|
||||
Icon(painterResource(icon), label, Modifier.size(RAIL_ICON_SIZE), tint = color)
|
||||
Icon(painterResource(icon), label, Modifier.size(RAIL_ICON_SIZE * fontSizeSqrtMultiplier), tint = color)
|
||||
}
|
||||
if (unread) {
|
||||
Box(Modifier.align(Alignment.TopEnd).size(6.dp).background(MaterialTheme.colors.primary, CircleShape))
|
||||
Box(Modifier.align(Alignment.TopEnd).padding(DEFAULT_PADDING_HALF / 2).size(RAIL_UNREAD_DOT_SIZE).background(MaterialTheme.colors.primary, CircleShape))
|
||||
}
|
||||
}
|
||||
Text(
|
||||
|
||||
+4
-1
@@ -102,7 +102,7 @@ private fun ShareListToolbar(chatModel: ChatModel, stopped: Boolean, onSearchVal
|
||||
val sharedContent = remember { chatModel.sharedContent }.value
|
||||
when {
|
||||
showSearch -> NavigationButtonBack(hideSearchOnBack)
|
||||
(users.size > 1 || chatModel.remoteHosts.isNotEmpty()) && sharedContent !is SharedContent.Forward && sharedContent !is SharedContent.ChatLink && sharedContent !is SharedContent.MyAddress -> {
|
||||
(users.size > 1 || chatModel.remoteHosts.isNotEmpty()) && profileSwitchAllowed(sharedContent) -> {
|
||||
val allRead = users
|
||||
.filter { u -> !u.user.activeUser && !u.user.hidden }
|
||||
.all { u -> u.unreadCount == 0 }
|
||||
@@ -221,3 +221,6 @@ private fun ShareList(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun profileSwitchAllowed(sharedContent: SharedContent?): Boolean =
|
||||
sharedContent !is SharedContent.Forward && sharedContent !is SharedContent.ChatLink && sharedContent !is SharedContent.MyAddress
|
||||
Reference in new issue
Block a user