kotlin, labels

This commit is contained in:
Evgeny @ SimpleX Chat
2026-05-31 19:25:23 +00:00
parent 000475e3ce
commit 3f6ee72dc9
4 changed files with 210 additions and 1 deletions
@@ -659,7 +659,7 @@ struct GroupChatInfoView: View {
}
private func channelWebAccessButton() -> some View {
let title: LocalizedStringKey = groupInfo.useRelays ? "Channel page & name" : "Group page & name"
let title: LocalizedStringKey = groupInfo.useRelays ? "Channel webpage" : "Group webpage"
return NavigationLink {
ChannelWebAccessView(groupInfo: $groupInfo)
.navigationBarTitle(title)
@@ -0,0 +1,171 @@
package chat.simplex.common.views.chat.group
import SectionBottomSpacer
import SectionItemView
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
@Composable
fun ChannelWebPageView(
rhId: Long?,
groupInfo: GroupInfo,
chatModel: ChatModel,
close: () -> Unit
) {
val isChannel = groupInfo.useRelays
val access = groupInfo.groupProfile.publicGroup?.publicGroupAccess
val webPage = rememberSaveable { mutableStateOf(access?.groupWebPage ?: "") }
val allowEmbedding = rememberSaveable { mutableStateOf(access?.allowEmbedding ?: false) }
val groupRelays = remember { mutableStateListOf<GroupRelay>() }
val dataUnchanged = webPage.value.trim() == (access?.groupWebPage ?: "") &&
allowEmbedding.value == (access?.allowEmbedding ?: false)
val save: () -> Unit = {
withBGApi {
val trimmedPage = webPage.value.trim()
val newAccess = PublicGroupAccess(
groupWebPage = trimmedPage.ifEmpty { null },
groupDomain = access?.groupDomain,
domainWebPage = access?.domainWebPage ?: false,
allowEmbedding = allowEmbedding.value
)
val gp = groupInfo.groupProfile.copy(
publicGroup = groupInfo.groupProfile.publicGroup?.copy(publicGroupAccess = newAccess)
)
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, gp, isChannel)
if (gInfo != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, gInfo)
}
close()
}
}
}
val closeWithAlert = {
if (dataUnchanged) {
close()
} else {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.save_preferences_question),
confirmText = generalGetString(if (isChannel) MR.strings.save_and_notify_channel_subscribers else MR.strings.save_and_notify_group_members),
dismissText = generalGetString(MR.strings.exit_without_saving),
onConfirm = save,
onDismiss = close,
)
}
}
LaunchedEffect(Unit) {
val relays = chatModel.controller.apiGetGroupRelays(groupInfo.groupId)
groupRelays.clear()
groupRelays.addAll(relays)
}
BackHandler(onBack = closeWithAlert)
ModalView(close = closeWithAlert) {
ChannelWebPageLayout(
isChannel = isChannel,
webPage = webPage,
allowEmbedding = allowEmbedding,
groupRelays = groupRelays,
groupInfo = groupInfo,
dataUnchanged = dataUnchanged,
save = save
)
}
}
@Composable
private fun ChannelWebPageLayout(
isChannel: Boolean,
webPage: MutableState<String>,
allowEmbedding: MutableState<Boolean>,
groupRelays: List<GroupRelay>,
groupInfo: GroupInfo,
dataUnchanged: Boolean,
save: () -> Unit
) {
val clipboard = LocalClipboardManager.current
ColumnWithScrollBar {
AppBarTitle(stringResource(if (isChannel) MR.strings.channel_webpage else MR.strings.group_webpage))
SectionView(stringResource(MR.strings.web_page).uppercase()) {
SectionItemView {
ProfileNameField(webPage, stringResource(MR.strings.web_page_url_placeholder))
}
PreferenceToggle(stringResource(MR.strings.allow_embedding), checked = allowEmbedding.value) {
allowEmbedding.value = it
}
}
SectionTextFooter(stringResource(MR.strings.web_page_footer))
val embedCode = embedCode(groupRelays, groupInfo)
if (embedCode != null) {
SectionView(stringResource(MR.strings.embed_code).uppercase()) {
SectionItemView {
Text(
embedCode,
style = MaterialTheme.typography.body2.copy(fontFamily = FontFamily.Monospace, fontSize = 12.sp),
maxLines = 6,
overflow = TextOverflow.Ellipsis
)
}
SectionItemView({
clipboard.setText(AnnotatedString(embedCode))
showToast(generalGetString(MR.strings.copied))
}) {
Icon(painterResource(MR.images.ic_content_copy), null, tint = MaterialTheme.colors.primary)
Spacer(Modifier.width(8.dp))
Text(stringResource(MR.strings.copy_embed_code), color = MaterialTheme.colors.primary)
}
}
}
SectionView {
SectionItemView(save, disabled = dataUnchanged) {
Text(
stringResource(MR.strings.save_verb),
color = if (dataUnchanged) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
}
SectionBottomSpacer()
}
}
private fun embedCode(groupRelays: List<GroupRelay>, groupInfo: GroupInfo): String? {
val pg = groupInfo.groupProfile.publicGroup ?: return null
val relayUrls = groupRelays.mapNotNull { it.relayCap.baseWebUrl }
if (relayUrls.isEmpty()) return null
val urls = relayUrls.joinToString(",")
return """<div data-simplex-group-preview
data-relay-urls="$urls"
data-public-group-id="${pg.publicGroupId}"
data-group-link="${pg.groupLink}"></div>
<script src="https://simplex.chat/js/channel-preview.js"></script>"""
}
@@ -13,6 +13,7 @@ import androidx.compose.animation.*
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
@@ -27,6 +28,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@@ -173,6 +175,9 @@ fun ModalData.GroupChatInfoView(
manageGroupLink = {
ModalManager.end.showModal(cardScreen = true) { GroupLinkView(chatModel, rhId, groupInfo, groupLink, onGroupLinkUpdated, isChannel = groupInfo.useRelays, shareGroupInfo = groupInfo) }
},
manageWebPage = {
ModalManager.end.showCustomModal { close -> ChannelWebPageView(rhId, groupInfo, chatModel, close) }
},
onSearchClicked = onSearchClicked,
deletingItems = deletingItems
)
@@ -502,6 +507,7 @@ fun ModalData.GroupChatInfoLayout(
clearChat: () -> Unit,
leaveGroup: () -> Unit,
manageGroupLink: () -> Unit,
manageWebPage: () -> Unit,
close: () -> Unit = { ModalManager.closeAllModalsEverywhere()},
onSearchClicked: () -> Unit,
deletingItems: State<Boolean>
@@ -608,6 +614,7 @@ fun ModalData.GroupChatInfoLayout(
if (groupInfo.isOwner && groupLink != null) {
anyTopSectionRowShow = true
ChannelLinkButton(manageGroupLink)
ChannelWebPageButton(groupInfo, manageWebPage)
} else if (channelLink != null) {
anyTopSectionRowShow = true
ChannelLinkQRCodeSection(channelLink)
@@ -930,6 +937,18 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
modifier = Modifier.combinedClickable(onClick = copyDisplayName, onLongClick = copyDisplayName).onRightClick(copyDisplayName)
)
ChatInfoDescription(cInfo, displayName, copyNameToClipboard)
val webPage = groupInfo.groupProfile.publicGroup?.publicGroupAccess?.groupWebPage
if (webPage != null) {
val uriHandler = LocalUriHandler.current
Text(
webPage,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.primary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.clickable { uriHandler.openUriCatching(webPage) }
)
}
if (groupInfo.useRelays) {
val count = groupInfo.groupSummary.publicMemberCount
if (count != null && count > 0) {
@@ -1191,6 +1210,16 @@ private fun ChannelLinkButton(onClick: () -> Unit) {
)
}
@Composable
private fun ChannelWebPageButton(groupInfo: GroupInfo, onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_travel_explore),
stringResource(if (groupInfo.useRelays) MR.strings.channel_webpage else MR.strings.group_webpage),
onClick,
iconColor = MaterialTheme.colors.secondary
)
}
@Composable
private fun ChannelLinkQRCodeSection(groupLink: String) {
val clipboard = LocalClipboardManager.current
@@ -1395,6 +1424,7 @@ fun PreviewGroupChatInfoLayout() {
clearChat = {},
leaveGroup = {},
manageGroupLink = {},
manageWebPage = {},
onSearchClicked = {},
deletingItems = remember { mutableStateOf(true) }
)
@@ -1918,6 +1918,14 @@
<string name="button_welcome_message">Welcome message</string>
<string name="group_link">Group link</string>
<string name="channel_link">Channel link</string>
<string name="channel_webpage">Channel webpage</string>
<string name="group_webpage">Group webpage</string>
<string name="web_page">Web page</string>
<string name="web_page_url_placeholder">Web page URL</string>
<string name="allow_embedding">Allow embedding</string>
<string name="web_page_footer">Set a web page URL where your channel preview is hosted. Allow embedding to let any website embed the preview.</string>
<string name="embed_code">Embed code</string>
<string name="copy_embed_code">Copy embed code</string>
<string name="create_group_link">Create group link</string>
<string name="button_create_group_link">Create link</string>
<string name="delete_link_question">Delete link?</string>