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…