From 9225f437e935507a768415d0c489d797ba966f33 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 25 Nov 2022 09:55:51 +0000 Subject: [PATCH] android: simplex link mode setting, ios: untrusted simplex links (#1412) * android: simplex link mode setting, ios: untrusted simplex links * correction Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> --- .../java/chat/simplex/app/model/ChatModel.kt | 12 +++--- .../chat/simplex/app/views/chat/ChatView.kt | 13 +++++-- .../simplex/app/views/chat/ContextItemView.kt | 4 +- .../simplex/app/views/chat/item/CIFileView.kt | 2 +- .../app/views/chat/item/ChatItemView.kt | 5 ++- .../app/views/chat/item/FramedItemView.kt | 27 ++++++++++---- .../app/views/chat/item/TextItemView.kt | 12 ++++-- .../app/views/chatlist/ChatListNavLinkView.kt | 11 ++++-- .../app/views/chatlist/ChatPreviewView.kt | 5 ++- .../app/views/usersettings/PrivacySettings.kt | 37 +++++++++++++++++-- .../app/src/main/res/values-de/strings.xml | 5 +++ .../app/src/main/res/values-ru/strings.xml | 5 +++ .../app/src/main/res/values/strings.xml | 7 +++- .../Views/Chat/ChatItem/MsgContentView.swift | 14 +++---- .../Views/UserSettings/PrivacySettings.swift | 2 +- apps/ios/SimpleXChat/ChatTypes.swift | 3 +- 16 files changed, 119 insertions(+), 45 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt index 4d37ab2eb2..77a1c743ca 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt @@ -79,6 +79,7 @@ class ChatModel(val controller: ChatController) { val sharedContent = mutableStateOf(null as SharedContent?) val filesToDelete = mutableSetOf() + val simplexLinkMode = mutableStateOf(controller.appPrefs.simplexLinkMode.get()) fun updateUserProfile(profile: LocalProfile) { val user = currentUser.value @@ -1496,17 +1497,17 @@ object MsgContentSerializer : KSerializer { @Serializable class FormattedText(val text: String, val format: Format? = null) { // TODO make it dependent on simplexLinkMode preference - val link: String? = when (format) { + fun link(mode: SimplexLinkMode): String? = when (format) { is Format.Uri -> text - is Format.SimplexLink -> format.simplexUri + is Format.SimplexLink -> if (mode == SimplexLinkMode.BROWSER) text else format.simplexUri is Format.Email -> "mailto:$text" is Format.Phone -> "tel:$text" else -> null } // TODO make it dependent on simplexLinkMode preference - val viewText: String = - if (format is Format.SimplexLink) simplexLinkText(format.linkType, format.smpHosts) else text + fun viewText(mode: SimplexLinkMode): String = + if (format is Format.SimplexLink && mode == SimplexLinkMode.DESCRIPTION) simplexLinkText(format.linkType, format.smpHosts) else text fun simplexLinkText(linkType: SimplexLinkType, smpHosts: List): String = "${linkType.description} (${String.format(generalGetString(R.string.simplex_link_connection), smpHosts.firstOrNull() ?: "?")})" @@ -1521,8 +1522,7 @@ sealed class Format { @Serializable @SerialName("secret") class Secret: Format() @Serializable @SerialName("colored") class Colored(val color: FormatColor): Format() @Serializable @SerialName("uri") class Uri: Format() - // TODO trustedUri: Boolean - @Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val smpHosts: List): Format() + @Serializable @SerialName("simplexLink") class SimplexLink(val linkType: SimplexLinkType, val simplexUri: String, val trustedUri: Boolean, val smpHosts: List): Format() @Serializable @SerialName("email") class Email: Format() @Serializable @SerialName("phone") class Phone: Format() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt index 7f96c4076d..cf83008efc 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt @@ -119,6 +119,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { chatModel.chatItems, searchText, useLinkPreviews = useLinkPreviews, + linkMode = chatModel.simplexLinkMode.value, chatModelIncognito = chatModel.incognito.value, back = { hideKeyboard(view) @@ -242,6 +243,7 @@ fun ChatLayout( chatItems: List, searchValue: State, useLinkPreviews: Boolean, + linkMode: SimplexLinkMode, chatModelIncognito: Boolean, back: () -> Unit, info: () -> Unit, @@ -291,7 +293,7 @@ fun ChatLayout( BoxWithConstraints(Modifier.fillMaxHeight().padding(contentPadding)) { ChatItemsList( chat, unreadCount, composeState, chatItems, searchValue, - useLinkPreviews, chatModelIncognito, showMemberInfo, loadPrevMessages, deleteMessage, + useLinkPreviews, linkMode, chatModelIncognito, showMemberInfo, loadPrevMessages, deleteMessage, receiveFile, joinGroup, acceptCall, markRead, setFloatingButton, onComposed, ) } @@ -449,6 +451,7 @@ fun BoxWithConstraintsScope.ChatItemsList( chatItems: List, searchValue: State, useLinkPreviews: Boolean, + linkMode: SimplexLinkMode, chatModelIncognito: Boolean, showMemberInfo: (GroupInfo, GroupMember) -> Unit, loadPrevMessages: (ChatInfo) -> Unit, @@ -561,11 +564,11 @@ fun BoxWithConstraintsScope.ChatItemsList( } else { Spacer(Modifier.size(42.dp)) } - ChatItemView(chat.chatInfo, cItem, composeState, provider, showMember = showMember, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem) + ChatItemView(chat.chatInfo, cItem, composeState, provider, showMember = showMember, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem) } } else { Box(Modifier.padding(start = 104.dp, end = 12.dp).then(swipeableModifier)) { - ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem) + ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = {}, acceptCall = acceptCall, scrollToItem = scrollToItem) } } } else { // direct message @@ -576,7 +579,7 @@ fun BoxWithConstraintsScope.ChatItemsList( end = if (sent) 12.dp else 76.dp, ).then(swipeableModifier) ) { - ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = joinGroup, acceptCall = acceptCall, scrollToItem = scrollToItem) + ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, joinGroup = joinGroup, acceptCall = acceptCall, scrollToItem = scrollToItem) } } @@ -950,6 +953,7 @@ fun PreviewChatLayout() { chatItems = chatItems, searchValue, useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, chatModelIncognito = false, back = {}, info = {}, @@ -1007,6 +1011,7 @@ fun PreviewGroupChatLayout() { chatItems = chatItems, searchValue, useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, chatModelIncognito = false, back = {}, info = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt index 3f0b647932..c07ff70aa1 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContextItemView.kt @@ -14,8 +14,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import chat.simplex.app.R -import chat.simplex.app.model.CIDirection -import chat.simplex.app.model.ChatItem +import chat.simplex.app.model.* import chat.simplex.app.ui.theme.HighOrLowlight import chat.simplex.app.ui.theme.SimpleXTheme import chat.simplex.app.views.chat.item.* @@ -53,6 +52,7 @@ fun ContextItemView( MarkdownText( contextItem.text, contextItem.formattedText, sender = contextItem.memberDisplayName, senderBold = true, maxLines = 3, + linkMode = SimplexLinkMode.DESCRIPTION, modifier = Modifier.fillMaxWidth(), ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt index f12bbcfc6d..055a8d5fc5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIFileView.kt @@ -205,6 +205,6 @@ class ChatItemProvider: PreviewParameterProvider { fun PreviewCIFileFramedItemView(@PreviewParameter(ChatItemProvider::class) chatItem: ChatItem) { val showMenu = remember { mutableStateOf(false) } SimpleXTheme { - FramedItemView(ChatInfo.Direct.sampleData, chatItem, showMenu = showMenu, receiveFile = {}) + FramedItemView(ChatInfo.Direct.sampleData, chatItem, linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {}) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt index 2e491cdbc4..23210e6ba5 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt @@ -34,6 +34,7 @@ fun ChatItemView( imageProvider: (() -> ImageGalleryProvider)? = null, showMember: Boolean = false, useLinkPreviews: Boolean, + linkMode: SimplexLinkMode, deleteMessage: (Long, CIDeleteMode) -> Unit, receiveFile: (Long) -> Unit, joinGroup: (Long) -> Unit, @@ -73,7 +74,7 @@ fun ChatItemView( EmojiItemView(cItem) } else { val onLinkLongClick = { _: String -> showMenu.value = true } - FramedItemView(cInfo, cItem, uriHandler, imageProvider, showMember = showMember, showMenu, receiveFile, onLinkLongClick, scrollToItem) + FramedItemView(cInfo, cItem, uriHandler, imageProvider, showMember = showMember, linkMode = linkMode, showMenu, receiveFile, onLinkLongClick, scrollToItem) } DropdownMenu( expanded = showMenu.value, @@ -235,6 +236,7 @@ fun PreviewChatItemView() { 1, CIDirection.DirectSnd(), Clock.System.now(), "hello" ), useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, deleteMessage = { _, _ -> }, receiveFile = {}, @@ -253,6 +255,7 @@ fun PreviewChatItemViewDeletedContent() { ChatInfo.Direct.sampleData, ChatItem.getDeletedContentSampleData(), useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, deleteMessage = { _, _ -> }, receiveFile = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index 28a0b5322a..ac6063c4d2 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -40,6 +40,7 @@ fun FramedItemView( uriHandler: UriHandler? = null, imageProvider: (() -> ImageGalleryProvider)? = null, showMember: Boolean = false, + linkMode: SimplexLinkMode, showMenu: MutableState, receiveFile: (Long) -> Unit, onLinkLongClick: (link: String) -> Unit = {}, @@ -63,7 +64,8 @@ fun FramedItemView( qi.text MarkdownText( text, qi.formattedText, sender = qi.sender(membership()), senderBold = true, maxLines = 3, - style = TextStyle(fontSize = 15.sp, color = MaterialTheme.colors.onSurface) + style = TextStyle(fontSize = 15.sp, color = MaterialTheme.colors.onSurface), + linkMode = linkMode ) } } @@ -114,7 +116,7 @@ fun FramedItemView( fun ciFileView(ci: ChatItem, text: String) { CIFileView(ci.file, ci.meta.itemEdited, receiveFile) if (text != "") { - CIMarkdownText(ci, showMember, uriHandler) + CIMarkdownText(ci, showMember, linkMode = linkMode, uriHandler) } } @@ -153,27 +155,27 @@ fun FramedItemView( if (mc.text == "") { metaColor = Color.White } else { - CIMarkdownText(ci, showMember, uriHandler) + CIMarkdownText(ci, showMember, linkMode, uriHandler) } } is MsgContent.MCVoice -> { CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, mc.text != "" || ci.quotedItem != null, ci, metaColor) if (mc.text != "") { - CIMarkdownText(ci, showMember, uriHandler) + CIMarkdownText(ci, showMember, linkMode, uriHandler) } } is MsgContent.MCFile -> ciFileView(ci, mc.text) is MsgContent.MCUnknown -> if (ci.file == null) { - CIMarkdownText(ci, showMember, uriHandler, onLinkLongClick) + CIMarkdownText(ci, showMember, linkMode, uriHandler, onLinkLongClick) } else { ciFileView(ci, mc.text) } is MsgContent.MCLink -> { ChatItemLinkView(mc.preview) - CIMarkdownText(ci, showMember, uriHandler, onLinkLongClick) + CIMarkdownText(ci, showMember, linkMode, uriHandler, onLinkLongClick) } - else -> CIMarkdownText(ci, showMember, uriHandler, onLinkLongClick) + else -> CIMarkdownText(ci, showMember, linkMode, uriHandler, onLinkLongClick) } } } @@ -191,13 +193,14 @@ fun FramedItemView( fun CIMarkdownText( ci: ChatItem, showMember: Boolean, + linkMode: SimplexLinkMode, uriHandler: UriHandler?, onLinkLongClick: (link: String) -> Unit = {} ) { Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { MarkdownText( ci.content.text, ci.formattedText, if (showMember) ci.memberDisplayName else null, - metaText = ci.timestampText, edited = ci.meta.itemEdited, + metaText = ci.timestampText, edited = ci.meta.itemEdited, linkMode = linkMode, uriHandler = uriHandler, senderBold = true, onLinkLongClick = onLinkLongClick ) } @@ -248,6 +251,7 @@ fun PreviewTextItemViewSnd(@PreviewParameter(EditedProvider::class) edited: Bool ChatItem.getSampleData( 1, CIDirection.DirectSnd(), Clock.System.now(), "hello", itemEdited = edited, ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -264,6 +268,7 @@ fun PreviewTextItemViewRcv(@PreviewParameter(EditedProvider::class) edited: Bool ChatItem.getSampleData( 1, CIDirection.DirectRcv(), Clock.System.now(), "hello", itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -284,6 +289,7 @@ fun PreviewTextItemViewLong(@PreviewParameter(EditedProvider::class) edited: Boo "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -305,6 +311,7 @@ fun PreviewTextItemViewQuote(@PreviewParameter(EditedProvider::class) edited: Bo quotedItem = CIQuote.getSample(1, Clock.System.now(), "hi", chatDir = CIDirection.DirectRcv()), itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -326,6 +333,7 @@ fun PreviewTextItemViewEmoji(@PreviewParameter(EditedProvider::class) edited: Bo quotedItem = CIQuote.getSample(1, Clock.System.now(), "Lorem ipsum dolor sit amet", chatDir = CIDirection.DirectRcv()), itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -354,6 +362,7 @@ fun PreviewQuoteWithTextAndImage(@PreviewParameter(EditedProvider::class) edited quotedItem = ciQuote, itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -382,6 +391,7 @@ fun PreviewQuoteWithLongTextAndImage(@PreviewParameter(EditedProvider::class) ed quotedItem = ciQuote, itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) @@ -409,6 +419,7 @@ fun PreviewQuoteWithLongTextAndFile(@PreviewParameter(EditedProvider::class) edi quotedItem = ciQuote, itemEdited = edited ), + linkMode = SimplexLinkMode.DESCRIPTION, showMenu = showMenu, receiveFile = {} ) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt index 7566adcf1d..26c0102c06 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.* import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import androidx.core.text.BidiFormatter @@ -49,6 +50,7 @@ fun MarkdownText ( uriHandler: UriHandler? = null, senderBold: Boolean = false, modifier: Modifier = Modifier, + linkMode: SimplexLinkMode, onLinkLongClick: (link: String) -> Unit = {} ) { val textLayoutDirection = remember (text) { @@ -79,12 +81,16 @@ fun MarkdownText ( for (ft in formattedText) { if (ft.format == null) append(ft.text) else { - val link = ft.link + val link = ft.link(linkMode) if (link != null) { hasLinks = true - val ftStyle = ft.format.style + val ftStyle = if (ft.format is Format.SimplexLink && !ft.format.trustedUri && linkMode == SimplexLinkMode.BROWSER) { + SpanStyle(color = Color.Red, textDecoration = TextDecoration.Underline) + } else { + ft.format.style + } withAnnotation(tag = "URL", annotation = link) { - withStyle(ftStyle) { append(ft.viewText) } + withStyle(ftStyle) { append(ft.viewText(linkMode)) } } } else { withStyle(ft.format.style) { append(ft.text) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt index e5ad61e1e4..8f6fc34c89 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListNavLinkView.kt @@ -33,6 +33,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat } val stopped = chatModel.chatRunning.value == false + val linkMode = chatModel.controller.appPrefs.simplexLinkMode.get() LaunchedEffect(chat.id) { showMenu.value = false delay(500L) @@ -40,7 +41,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { when (chat.chatInfo) { is ChatInfo.Direct -> ChatListNavLinkLayout( - chatLinkPreview = { ChatPreviewView(chat, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, stopped) }, + chatLinkPreview = { ChatPreviewView(chat, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, stopped, linkMode) }, click = { directChatAction(chat.chatInfo, chatModel) }, dropdownMenuItems = { ContactMenuItems(chat, chatModel, showMenu, showMarkRead) }, showMenu, @@ -48,7 +49,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) { ) is ChatInfo.Group -> ChatListNavLinkLayout( - chatLinkPreview = { ChatPreviewView(chat, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, stopped) }, + chatLinkPreview = { ChatPreviewView(chat, chatModel.incognito.value, chatModel.currentUser.value?.profile?.displayName, stopped, linkMode) }, click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) }, dropdownMenuItems = { GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, showMarkRead) }, showMenu, @@ -586,7 +587,8 @@ fun PreviewChatListNavLinkDirect() { ), false, null, - stopped = false + stopped = false, + linkMode = SimplexLinkMode.DESCRIPTION ) }, click = {}, @@ -623,7 +625,8 @@ fun PreviewChatListNavLinkGroup() { ), false, null, - stopped = false + stopped = false, + linkMode = SimplexLinkMode.DESCRIPTION ) }, click = {}, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt index 66ae008ada..860861a5fd 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt @@ -26,7 +26,7 @@ import chat.simplex.app.views.chat.item.MarkdownText import chat.simplex.app.views.helpers.* @Composable -fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileDisplayName: String?, stopped: Boolean) { +fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileDisplayName: String?, stopped: Boolean, linkMode: SimplexLinkMode) { val cInfo = chat.chatInfo @Composable @@ -86,6 +86,7 @@ fun ChatPreviewView(chat: Chat, chatModelIncognito: Boolean, currentUserProfileD ci.text, ci.formattedText, sender = if (cInfo is ChatInfo.Group && !ci.chatDir.sent) ci.memberDisplayName else null, + linkMode = linkMode, senderBold = true, metaText = null, maxLines = 2, @@ -232,6 +233,6 @@ fun ChatStatusImage(chat: Chat) { @Composable fun PreviewChatPreviewView() { SimpleXTheme { - ChatPreviewView(Chat.sampleData, false, "", stopped = false) + ChatPreviewView(Chat.sampleData, false, "", stopped = false, linkMode = SimplexLinkMode.DESCRIPTION) } } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt index acb4d5a029..1fa77a0db6 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/PrivacySettings.kt @@ -1,18 +1,20 @@ package chat.simplex.app.views.usersettings import SectionDivider +import SectionItemView import SectionSpacer +import SectionTextFooter import SectionView import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import chat.simplex.app.R -import chat.simplex.app.model.ChatModel -import chat.simplex.app.views.helpers.AppBarTitle +import chat.simplex.app.model.* +import chat.simplex.app.views.helpers.* @Composable fun PrivacySettingsView( @@ -23,6 +25,7 @@ fun PrivacySettingsView( Modifier.fillMaxWidth(), horizontalAlignment = Alignment.Start ) { + val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode AppBarTitle(stringResource(R.string.your_privacy)) SectionView(stringResource(R.string.settings_section_title_device)) { ChatLockItem(chatModel.performLA, setPerformLA) @@ -38,6 +41,34 @@ fun PrivacySettingsView( } SettingsPreferenceItem(Icons.Outlined.TravelExplore, stringResource(R.string.send_link_previews), chatModel.controller.appPrefs.privacyLinkPreviews) SectionDivider() + SectionItemView { SimpleXLinkOptions(chatModel.simplexLinkMode, onSelected = { + simplexLinkMode.set(it) + chatModel.simplexLinkMode.value = it + }) } + } + if (chatModel.simplexLinkMode.value == SimplexLinkMode.BROWSER) { + SectionTextFooter(stringResource(R.string.simplex_link_mode_browser_warning)) } } } + +@Composable +private fun SimpleXLinkOptions(simplexLinkModeState: State, onSelected: (SimplexLinkMode) -> Unit) { + val values = remember { + SimplexLinkMode.values().map { + when (it) { + SimplexLinkMode.DESCRIPTION -> it to generalGetString(R.string.simplex_link_mode_description) + SimplexLinkMode.FULL -> it to generalGetString(R.string.simplex_link_mode_full) + SimplexLinkMode.BROWSER -> it to generalGetString(R.string.simplex_link_mode_browser) + } + } + } + ExposedDropDownSettingRow( + generalGetString(R.string.simplex_link_mode), + values, + simplexLinkModeState, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = onSelected + ) +} diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index 3f1c12d998..7bc9785fbe 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -46,6 +46,11 @@ SimpleX Einmal-Link SimpleX Gruppen-Link über %1$s + ***SimpleX links + ***Description + ***Full link + ***Via browser + ***Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red. Fehler beim Speichern der SMP-Server diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index 0314b4c641..67fd38b343 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -46,6 +46,11 @@ SimpleX одноразовая ссылка SimpleX ссылка группы через %1$s + SimpleX ссылки + Описание + Полная ссылка + В браузере + Использование ссылки в браузере может уменьшить конфиденциальность и безопасность соединения. Ссылки на неизвестные сайты будут красными. Ошибка при сохранении SMP серверов diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 372983c2dc..39daffbf13 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -43,9 +43,14 @@ SimpleX contact address - SimpleX 1-time invitation + SimpleX one-time invitation SimpleX group link via %1$s + SimpleX links + Description + Full link + Via browser + Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red. Error saving SMP servers diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift index 50ff527dff..000c3da752 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/MsgContentView.swift @@ -10,7 +10,6 @@ import SwiftUI import SimpleXChat private let uiLinkColor = UIColor(red: 0, green: 0.533, blue: 1, alpha: 1) -private let linkColor = Color(uiColor: uiLinkColor) struct MsgContentView: View { var text: String @@ -70,11 +69,13 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text { case .secret: return Text(t).foregroundColor(.clear).underline(color: .primary) case let .colored(color): return Text(t).foregroundColor(color.uiColor) case .uri: return linkText(t, t, preview, prefix: "") - case let .simplexLink(linkType, simplexUri, smpHosts): + case let .simplexLink(linkType, simplexUri, trustedUri, smpHosts): switch privacySimplexLinkModeDefault.get() { case .description: return linkText(simplexLinkText(linkType, smpHosts), simplexUri, preview, prefix: "") case .full: return linkText(t, simplexUri, preview, prefix: "") - case .browser: return linkText(t, t, preview, prefix: "") + case .browser: return trustedUri + ? linkText(t, t, preview, prefix: "") + : linkText(t, t, preview, prefix: "", color: .red, uiColor: .red) } case .email: return linkText(t, t, preview, prefix: "mailto:") case .phone: return linkText(t, t.replacingOccurrences(of: " ", with: ""), preview, prefix: "tel:") @@ -84,13 +85,12 @@ private func formatText(_ ft: FormattedText, _ preview: Bool) -> Text { } } -private func linkText(_ s: String, _ link: String, - _ preview: Bool, prefix: String) -> Text { +private func linkText(_ s: String, _ link: String, _ preview: Bool, prefix: String, color: Color = Color(uiColor: uiLinkColor), uiColor: UIColor = uiLinkColor) -> Text { preview - ? Text(s).foregroundColor(linkColor).underline(color: linkColor) + ? Text(s).foregroundColor(color).underline(color: color) : Text(AttributedString(s, attributes: AttributeContainer([ .link: NSURL(string: prefix + link) as Any, - .foregroundColor: uiLinkColor as Any + .foregroundColor: uiColor as Any ]))).underline() } diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 65cb397117..0f5ec43d6b 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -53,7 +53,7 @@ struct PrivacySettings: View { Text("Chats") } footer: { if case .browser = simplexLinkMode { - Text("Opening the link in the browser may reduce connection privacy and security.") + Text("Opening the link in the browser may reduce connection privacy and security. Untrusted SimpleX links will be red.") } } } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index d7533ce2c9..1d5bd0e15a 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1733,8 +1733,7 @@ public enum Format: Decodable, Equatable { case secret case colored(color: FormatColor) case uri - // TODO trustedUri: Bool - case simplexLink(linkType: SimplexLinkType, simplexUri: String, smpHosts: [String]) + case simplexLink(linkType: SimplexLinkType, simplexUri: String, trustedUri: Bool, smpHosts: [String]) case email case phone }