diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 52d671e323..ea569a6e2b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -187,8 +187,8 @@ 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; }; 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; }; - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a */; }; - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a */; }; + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi-ghc9.6.3.a */; }; + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi.a */; }; 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; 64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; }; @@ -569,8 +569,8 @@ 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a"; sourceTree = ""; }; - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a"; sourceTree = ""; }; + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi-ghc9.6.3.a"; sourceTree = ""; }; + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi.a"; sourceTree = ""; }; 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; }; @@ -739,8 +739,8 @@ 64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */, 64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */, 64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */, - 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a in Frameworks */, - 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a in Frameworks */, + 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi-ghc9.6.3.a in Frameworks */, + 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -826,8 +826,8 @@ 64C829992D54AEEE006B9E89 /* libffi.a */, 64C829982D54AEED006B9E89 /* libgmp.a */, 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */, - 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu-ghc9.6.3.a */, - 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.17-3blIIOCVZmm6YZ1Ew74Hcu.a */, + 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi-ghc9.6.3.a */, + 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.18-BcXIpdD8LSsInPsEEY8eHi.a */, ); path = Libraries; sourceTree = ""; @@ -2079,7 +2079,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2129,7 +2129,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -2171,7 +2171,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2191,7 +2191,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 5NN7GUYB6T; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; @@ -2216,7 +2216,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -2253,7 +2253,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -2290,7 +2290,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2341,7 +2341,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2395,7 +2395,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; @@ -2429,7 +2429,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 328; + CURRENT_PROJECT_VERSION = 329; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 480320e33e..51128646e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -419,9 +419,9 @@ fun ComposeView( withLongRunningApi(slow = 60_000) { if (wait != null) delay(wait) if (pendingLinkUrl.value != url) return@withLongRunningApi - if (chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.get() - && !chatModel.controller.appPrefs.networkUseSocksProxy.get()) { - showLinkPreviewsConfirmAlert { enable -> + if (chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.get()) { + val socksEnabled = chatModel.controller.appPrefs.networkUseSocksProxy.get() + showLinkPreviewsConfirmAlert(socksEnabled) { enable -> if (enable != null) { chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.set(false) chatModel.controller.appPrefs.privacyLinkPreviews.set(enable) @@ -1703,10 +1703,15 @@ fun ComposeView( } } -private fun showLinkPreviewsConfirmAlert(onChoice: (Boolean?) -> Unit) { +private fun showLinkPreviewsConfirmAlert(socksEnabled: Boolean, onChoice: (Boolean?) -> Unit) { AlertManager.shared.showAlertDialogButtonsColumn( title = generalGetString(MR.strings.link_previews_alert_title), - text = AnnotatedString(generalGetString(MR.strings.link_previews_alert_desc)), + text = AnnotatedString( + if (socksEnabled) + generalGetString(MR.strings.link_previews_alert_desc) + "\n\n" + generalGetString(MR.strings.link_previews_alert_desc_socks) + else + generalGetString(MR.strings.link_previews_alert_desc) + ), onDismissRequest = { onChoice(null) }, buttons = { Column { @@ -1714,13 +1719,13 @@ private fun showLinkPreviewsConfirmAlert(onChoice: (Boolean?) -> Unit) { AlertManager.shared.hideAlert() onChoice(false) }) { - Text(stringResource(MR.strings.link_previews_alert_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + Text(stringResource(MR.strings.link_previews_alert_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) } SectionItemView({ AlertManager.shared.hideAlert() onChoice(true) }) { - Text(stringResource(MR.strings.link_previews_alert_enable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + Text(stringResource(MR.strings.link_previews_alert_enable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = if (socksEnabled) MaterialTheme.colors.primary else Color.Red) } // SectionItemView({ // AlertManager.shared.hideAlert() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 93513f9edb..01dcd021f7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -351,7 +351,7 @@ private fun ConnectBannerCard() { painterResource(if (isDark) MR.images.banner_create_link_light else MR.images.banner_create_link), contentDescription = null, contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().aspectRatio(BANNER_IMAGE_RATIO) ) } else { BannerGradientBox(isDark) { @@ -382,7 +382,7 @@ private fun ConnectBannerCard() { painterResource(if (isDark) MR.images.banner_paste_link_light else MR.images.banner_paste_link), contentDescription = null, contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth().aspectRatio(BANNER_IMAGE_RATIO) ) } else { BannerGradientBox(isDark) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt index 9c529e547a..d2a98ae101 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.LinkPreview +import chat.simplex.common.model.NetworkProxyAuth import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.chatViewScrollState @@ -24,67 +25,124 @@ import chat.simplex.common.views.chat.item.CHAT_IMAGE_LAYOUT_ID import chat.simplex.common.views.chat.item.imageViewFullWidth import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.jsoup.Jsoup +import java.net.Authenticator +import java.net.InetSocketAddress +import java.net.PasswordAuthentication +import java.net.Proxy import java.net.URL +import java.util.UUID private const val OG_SELECT_QUERY = "meta[property^=og:]" private const val ICON_SELECT_QUERY = "link[rel^=icon],link[rel^=apple-touch-icon],link[rel^=shortcut icon]" private val IMAGE_SUFFIXES = listOf(".jpg", ".png", ".ico", ".webp", ".gif") +// Authenticator.setDefault is process-global. The mutex serializes preview fetches +// so concurrent calls cannot clobber each other's authenticator, and so the +// snapshot/restore in getLinkPreview is race-free. +private val previewMutex = Mutex() + suspend fun getLinkPreview(url: String): LinkPreview? { return withContext(Dispatchers.IO) { - try { - val title: String? - val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withContext null - var imageUri = when { - IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> { - title = u.path.substringAfterLast("/") - url - } - else -> { - val connection = Jsoup.connect(url) - .ignoreContentType(true) - .timeout(10000) - .followRedirects(true) - - val response = if (url.lowercase().startsWith("https://x.com/")) { - // Apple sends request with special user-agent which handled differently by X.com. - // Different response that includes video poster from post - connection - .userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0") - .execute() - } else { - connection - .execute() - } - val doc = response.parse() - val ogTags = doc.select(OG_SELECT_QUERY) - title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title() - ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content") - ?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href") - } - } - if (imageUri != null) { - imageUri = normalizeImageUri(u, imageUri) + previewMutex.withLock { + try { try { - val stream = URL(imageUri).openStream() - val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000) - // TODO add once supported in iOS - // val description = ogTags.firstOrNull { - // it.attr("property") == "og:description" - // }?.attr("content") ?: "" - if (title != null) { - return@withContext LinkPreview(url, title, description = "", image) + val title: String? + val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withLock null + val useSocksProxy = appPrefs.networkUseSocksProxy.get() + val proxy: Proxy? + if (useSocksProxy) { + val networkProxy = appPrefs.networkProxy.get() + proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress(networkProxy.host, networkProxy.port)) + val (authUser, authPass) = when (networkProxy.auth) { + NetworkProxyAuth.USERNAME -> + if (networkProxy.username.isNotEmpty() && networkProxy.password.isNotEmpty()) + networkProxy.username to networkProxy.password + else + null to null + // Per-call random credentials drive Tor-style stream isolation: each + // preview gets its own circuit, and previews don't share a circuit + // with other unauthenticated traffic on the proxy. + NetworkProxyAuth.ISOLATE -> + UUID.randomUUID().toString() to UUID.randomUUID().toString() + } + if (authUser != null && authPass != null) { + Authenticator.setDefault(object : Authenticator() { + override fun getPasswordAuthentication(): PasswordAuthentication? = + // Only respond when the SOCKS proxy itself challenges. A destination + // server returning 401 also triggers RequestorType.SERVER; without + // this gate, the JDK's auto-retry would post our SOCKS credentials + // in an Authorization header to the destination. + if (requestingHost == networkProxy.host && requestingPort == networkProxy.port) + PasswordAuthentication(authUser, authPass.toCharArray()) + else null + }) + } else { + Authenticator.setDefault(null) + } + } else { + proxy = null + Authenticator.setDefault(null) + } + var imageUri = when { + IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> { + title = u.path.substringAfterLast("/") + url + } + else -> { + val connection = Jsoup.connect(url) + .ignoreContentType(true) + .timeout(10000) + .followRedirects(true) + .proxy(proxy) + + val response = if (url.lowercase().startsWith("https://x.com/")) { + // Apple sends request with special user-agent which handled differently by X.com. + // Different response that includes video poster from post + connection + .userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0") + .execute() + } else { + connection + .execute() + } + val doc = response.parse() + val ogTags = doc.select(OG_SELECT_QUERY) + title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title() + ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content") + ?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href") + } + } + if (imageUri != null) { + imageUri = normalizeImageUri(u, imageUri) + try { + val conn = URL(imageUri).openConnection(proxy ?: Proxy.NO_PROXY) + conn.connectTimeout = 20_000 + conn.readTimeout = 20_000 + val stream = conn.getInputStream() + val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000) + // TODO add once supported in iOS + // val description = ogTags.firstOrNull { + // it.attr("property") == "og:description" + // }?.attr("content") ?: "" + if (title != null) { + return@withLock LinkPreview(url, title, description = "", image) + } + } catch (e: Exception) { + e.printStackTrace() + } } } catch (e: Exception) { e.printStackTrace() } + return@withLock null + } finally { + Authenticator.setDefault(null) } - } catch (e: Exception) { - e.printStackTrace() } - return@withContext null } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 5b186875fa..74dadcd671 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -105,7 +105,9 @@ fun SimpleXInfoLayout( user: User?, onboardingStage: SharedPreference? ) { - Column(Modifier.fillMaxSize().systemBarsPadding().padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally) { + val topBar = onboardingStage == null && !appPrefs.oneHandUI.state.value + val modifier = Modifier.fillMaxSize().systemBarsPadding().padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING) + Column(if (topBar) modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier) else modifier, horizontalAlignment = Alignment.CenterHorizontally) { Box(Modifier.padding(top = DEFAULT_PADDING * 2).widthIn(max = if (appPlatform.isAndroid) 185.dp else 160.dp), contentAlignment = Alignment.Center) { SimpleXLogo() } @@ -156,6 +158,8 @@ fun SimpleXInfoLayout( ModalManager.fullscreen.showModal { HowItWorks(user, onboardingStage) } }) } + } else { + Spacer(Modifier) } } ) 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 b6494accde..c9ed86af11 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1067,7 +1067,7 @@ for each contact and group member.\nPlease note: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail.]]> Update transport isolation mode? Use .onion hosts to No if SOCKS proxy does not support them.]]> - Please note: message and file relays are connected via SOCKS proxy. Calls and sending link previews use direct connection.]]> + Please note: message and file relays are connected via SOCKS proxy. Calls use direct connection.]]> Private routing Always Unknown servers @@ -2646,7 +2646,7 @@ Easier to invite your friends 👋 We made connecting simpler for new users. Safe web links - - opt-in to send link previews.\n- prevent hyperlink phishing.\n- remove link tracking. + - opt-in to send link previews.\n- use SOCKS proxy if enabled.\n- prevent hyperlink phishing.\n- remove link tracking. Non-profit governance To make SimpleX Network last. View updated conditions @@ -3061,6 +3061,7 @@ Enable link previews? Sending a link preview may reveal your IP address to the website. You can change this in Privacy settings later. + Link preview will be requested via SOCKS proxy. DNS lookup may still happen locally via your DNS resolver. Enable Disable \ No newline at end of file diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 64a43a7a80..16d50eef7e 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -24,13 +24,13 @@ android.nonTransitiveRClass=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.5-beta.11 -android.version_code=343 +android.version_name=6.5-beta.12 +android.version_code=344 android.bundle=false -desktop.version_name=6.5-beta.11 -desktop.version_code=138 +desktop.version_name=6.5-beta.12 +desktop.version_code=139 kotlin.version=2.1.20 gradle.plugin.version=8.7.0 diff --git a/cabal.project b/cabal.project index 557dee3951..6a432babca 100644 --- a/cabal.project +++ b/cabal.project @@ -21,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 858fac7f4f821a2df6fbea03a1bfbb82ea9717c5 + tag: ba6af65c547cf941af0a1d1645188f7b7f234de1 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 9fe726a02b..efad54fa9a 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."858fac7f4f821a2df6fbea03a1bfbb82ea9717c5" = "1fhzynf80db7h6y2wv61fsdfd80f0blja9ljsfh405r11yg2yxvi"; + "https://github.com/simplex-chat/simplexmq.git"."ba6af65c547cf941af0a1d1645188f7b7f234de1" = "0sqdj0yawjvgqf92vm0jzzckbi9b2xh8wl8j4ygxikg3d919ksdh"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 70809d41ae..24510ebc70 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.5.0.18 +version: 6.5.0.19 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat diff --git a/tests/Bots/DirectoryTests.hs b/tests/Bots/DirectoryTests.hs index 9961fa4c1a..68acec0493 100644 --- a/tests/Bots/DirectoryTests.hs +++ b/tests/Bots/DirectoryTests.hs @@ -2216,14 +2216,6 @@ testLinkCheckUpdatesCount ps = do superUser <## " Channel approved!" bob <# ("'SimpleX Directory'> The channel ID 1 (news) is approved and listed in directory - please moderate it!") bob <## "Please note: if you change the channel profile it will be hidden from directory until it is re-approved." - -- search shows initial count - bob #> "@'SimpleX Directory' news" - bob <# "'SimpleX Directory'> > news" - bob <## " Found 1 group(s)." - bob <# "'SimpleX Directory'> news" - bob <##. "Link to join channel: " - bob <## "You need SimpleX Chat app v6.5 to join." - bob <## "1 subscribers" -- link check updates count (bot joined) threadDelay 1000000 bob #> "@'SimpleX Directory' news"