From a16f3629dc82e24fd9dc8490f3f04475e4b95342 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 18 Jul 2025 18:11:05 +0400 Subject: [PATCH] kotlin --- .../chat/simplex/common/model/ChatModel.kt | 4 + .../simplex/common/views/chat/ChatView.kt | 193 +++++++++++++++--- .../common/views/chat/item/ChatItemView.kt | 1 + .../commonMain/resources/MR/base/strings.xml | 10 + 4 files changed, 176 insertions(+), 32 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 588ab17cd2..779e6ec88b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2813,6 +2813,7 @@ data class ChatItem ( is CIContent.RcvDirectE2EEInfo -> false is CIContent.SndGroupE2EEInfo -> false is CIContent.RcvGroupE2EEInfo -> false + is CIContent.ChatBanner -> false else -> true } @@ -2879,6 +2880,7 @@ data class ChatItem ( is CIContent.RcvDirectE2EEInfo -> false is CIContent.SndGroupE2EEInfo -> false is CIContent.RcvGroupE2EEInfo -> false + is CIContent.ChatBanner -> false is CIContent.InvalidJSON -> false } @@ -3549,6 +3551,7 @@ sealed class CIContent: ItemContent { @Serializable @SerialName("rcvDirectE2EEInfo") class RcvDirectE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("sndGroupE2EEInfo") class SndGroupE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("rcvGroupE2EEInfo") class RcvGroupE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } + @Serializable @SerialName("chatBanner") object ChatBanner: CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("invalidJSON") data class InvalidJSON(val json: String): CIContent() { override val msgContent: MsgContent? get() = null } override val text: String get() = when (this) { @@ -3582,6 +3585,7 @@ sealed class CIContent: ItemContent { is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) is SndGroupE2EEInfo -> e2eeInfoNoPQStr is RcvGroupE2EEInfo -> e2eeInfoNoPQStr + is ChatBanner -> "" is InvalidJSON -> "invalid data" } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 5f00a592b8..0d04bb5101 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1754,6 +1754,130 @@ fun BoxScope.ChatItemsList( ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap) } } + + @Composable + fun ChatBannerView() { + fun chatContext(): String? { + return when (chatInfo) { + is ChatInfo.Direct -> { + val contact = chatInfo.contact + val preparedLinkType = contact.preparedContact?.uiConnLinkType + if (contact.nextConnectPrepared && preparedLinkType != null) { + when (preparedLinkType) { + ConnectionMode.Inv -> generalGetString(MR.strings.chat_banner_1_time_invitation) + ConnectionMode.Con -> generalGetString(MR.strings.chat_banner_contact_address) + } + } else if (contact.nextAcceptContactRequest) { + generalGetString(MR.strings.chat_banner_contact_request) + } else { + generalGetString(MR.strings.chat_banner_contact) + } + } + + is ChatInfo.Group -> { + val groupInfo = chatInfo.groupInfo + when (groupInfo.businessChat?.chatType) { + null -> { + if (groupInfo.membership.memberStatus == GroupMemberStatus.MemCreator) { + generalGetString(MR.strings.chat_banner_your_group) + } else { + generalGetString(MR.strings.chat_banner_group) + } + } + + BusinessChatType.Business -> generalGetString(MR.strings.chat_banner_business) + BusinessChatType.Customer -> generalGetString(MR.strings.chat_banner_customer) + } + } + + else -> null + } + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(horizontal = DEFAULT_PADDING) + ) { + Spacer(modifier = Modifier.height(60.dp)) + + Surface( + shape = RoundedCornerShape(18.dp), + color = MaterialTheme.colors.background + ) { + val bannerModifier = if (appPlatform.isDesktop) Modifier.width(400.dp) else Modifier.fillMaxWidth() + Column( + verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = bannerModifier + .padding(DEFAULT_PADDING) + ) { + ChatInfoImage(chatInfo, size = 96.dp) + + Text( + chatInfo.displayName, + style = MaterialTheme.typography.h3, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.widthIn(max = 240.dp) + ) + + val fullName = chatInfo.fullName.trim() + if (fullName.isNotEmpty() && fullName != chatInfo.displayName && fullName != chatInfo.displayName.trim()) { + Text( + fullName, + style = MaterialTheme.typography.h4, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.widthIn(max = 260.dp) + ) + } + + val descr = chatInfo.shortDescr?.trim() + if (descr != null && descr != "") { + Text( + descr, + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onBackground, + textAlign = TextAlign.Center, + maxLines = 4, + overflow = TextOverflow.Ellipsis, + lineHeight = 21.sp + ) + } + + val contextStr = chatContext() + if (contextStr != null) { + Spacer(modifier = Modifier.height(0.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF, Alignment.CenterHorizontally), + ) { + Icon( + painterResource(MR.images.ic_info), + contentDescription = null, + tint = MaterialTheme.colors.secondary + ) + Text( + contextStr, + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.secondary + ) + } + } + } + } + + Spacer(modifier = Modifier.height(60.dp)) + } + } + LazyColumnWithScrollBar( Modifier.align(Alignment.BottomCenter), state = listState.value, @@ -1768,42 +1892,47 @@ fun BoxScope.ChatItemsList( ) { val mergedItemsValue = mergedItems.value itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged -> - val isLastItem = index == mergedItemsValue.items.lastIndex - val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null val listItem = merged.newest() val item = listItem.item - val range = if (merged is MergedItem.Grouped) { - merged.rangeInReversed.value - } else { - null - } - val showAvatar = shouldShowAvatar(item, merged.oldest().nextItem) - val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } - val itemSeparation: ItemSeparation - val prevItemSeparationLargeGap: Boolean - if (merged is MergedItem.Single || isRevealed.value) { - val prev = listItem.prevItem - itemSeparation = getItemSeparation(item, prev) - val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem - prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item) - } else { - itemSeparation = getItemSeparation(item, null) - prevItemSeparationLargeGap = false - } - ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) { - if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems) - } - if (last != null) { - // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items - DateSeparator(last.meta.itemTs) - } - if (item.isRcvNew) { - val itemIds = when (merged) { - is MergedItem.Single -> listOf(merged.item.item.id) - is MergedItem.Grouped -> merged.items.map { it.item.id } + if (item.content is CIContent.ChatBanner) { + ChatBannerView() + } else { + val isLastItem = index == mergedItemsValue.items.lastIndex + val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null + val range = if (merged is MergedItem.Grouped) { + merged.rangeInReversed.value + } else { + null + } + val showAvatar = shouldShowAvatar(item, merged.oldest().nextItem) + val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } } + val itemSeparation: ItemSeparation + val prevItemSeparationLargeGap: Boolean + if (merged is MergedItem.Single || isRevealed.value) { + val prev = listItem.prevItem + itemSeparation = getItemSeparation(item, prev) + val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem + prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item) + } else { + itemSeparation = getItemSeparation(item, null) + prevItemSeparationLargeGap = false + } + ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) { + if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems) + } + + if (last != null) { + // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items + DateSeparator(last.meta.itemTs) + } + if (item.isRcvNew) { + val itemIds = when (merged) { + is MergedItem.Single -> listOf(merged.item.item.id) + is MergedItem.Grouped -> merged.items.map { it.item.id } + } + MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead) } - MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 740fc95871..0f9b3151fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -787,6 +787,7 @@ fun ChatItemView( is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText() is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText() + is CIContent.ChatBanner -> Spacer(modifier = Modifier.size(0.dp)) is CIContent.InvalidJSON -> { CIInvalidJSONView(c.json) DeleteItemMenu() diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 0a6b2b539d..92012bd12f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -485,6 +485,16 @@ 1 chat with a member %d chat(s) + + 1-time invitation + Contact address + Contact request + Contact + Your group + Group + Business + Customer + Share message… Share media…