From 981cbb8bf935a1e2803dc000d0a0f18d499d7cf3 Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Sat, 24 Aug 2024 14:00:56 +0000
Subject: [PATCH 1/8] android: target API level 34 (Android 14) (#4697)
---
apps/multiplatform/android/build.gradle.kts | 22 +++++-----
.../android/src/main/AndroidManifest.xml | 14 +++++-
.../main/java/chat/simplex/app/CallService.kt | 44 ++++++++++++++++---
.../java/chat/simplex/app/MainActivity.kt | 2 +-
.../main/java/chat/simplex/app/SimplexApp.kt | 5 ++-
.../java/chat/simplex/app/SimplexService.kt | 36 ++++++++++++---
.../simplex/app/model/NtfManager.android.kt | 4 +-
.../simplex/app/views/call/CallActivity.kt | 10 ++---
.../android/src/main/res/values/colors.xml | 1 -
apps/multiplatform/common/build.gradle.kts | 16 +++----
.../res/drawable/edit_text_cursor.xml | 2 +-
11 files changed, 112 insertions(+), 44 deletions(-)
diff --git a/apps/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts
index 5c2c786a21..250616ea5c 100644
--- a/apps/multiplatform/android/build.gradle.kts
+++ b/apps/multiplatform/android/build.gradle.kts
@@ -15,7 +15,7 @@ android {
namespace = "chat.simplex.app"
minSdk = 26
//noinspection OldTargetApi
- targetSdk = 33
+ targetSdk = 34
// !!!
// skip version code after release to F-Droid, as it uses two version codes
versionCode = (extra["android.version_code"] as String).toInt()
@@ -126,29 +126,29 @@ android {
dependencies {
implementation(project(":common"))
- implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.core:core-ktx:1.13.1")
//implementation("androidx.compose.ui:ui:${rootProject.extra["compose.version"] as String}")
//implementation("androidx.compose.material:material:$compose_version")
//implementation("androidx.compose.ui:ui-tooling-preview:$compose_version")
- implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
- implementation("androidx.lifecycle:lifecycle-process:2.7.0")
- implementation("androidx.activity:activity-compose:1.8.2")
- val workVersion = "2.9.0"
+ implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4")
+ implementation("androidx.lifecycle:lifecycle-process:2.8.4")
+ implementation("androidx.activity:activity-compose:1.9.1")
+ val workVersion = "2.9.1"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("androidx.work:work-multiprocess:$workVersion")
- implementation("com.jakewharton:process-phoenix:2.2.0")
+ implementation("com.jakewharton:process-phoenix:3.0.0")
//Camera Permission
- implementation("com.google.accompanist:accompanist-permissions:0.23.0")
+ implementation("com.google.accompanist:accompanist-permissions:0.34.0")
//implementation("androidx.compose.material:material-icons-extended:$compose_version")
//implementation("androidx.compose.ui:ui-util:$compose_version")
testImplementation("junit:junit:4.13.2")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("androidx.test.ext:junit:1.2.1")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
//androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
debugImplementation("androidx.compose.ui:ui-tooling:1.6.4")
}
diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml
index 073f1bf8c8..deb5d83e5f 100644
--- a/apps/multiplatform/android/src/main/AndroidManifest.xml
+++ b/apps/multiplatform/android/src/main/AndroidManifest.xml
@@ -21,6 +21,12 @@
+
+
+
+
+
+
+ android:stopWithTask="false"
+ android:foregroundServiceType="remoteMessaging"
+ />
@@ -141,7 +149,9 @@
android:name=".CallService"
android:enabled="true"
android:exported="false"
- android:stopWithTask="false"/>
+ android:stopWithTask="false"
+ android:foregroundServiceType="mediaPlayback|microphone|camera|remoteMessaging"
+ />
= 34) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ } else {
+ 0
+ }
+ } else if (Build.VERSION.SDK_INT >= 30) {
+ if (call.supportsVideo()) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA
+ } else {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
+ }
+ } else if (Build.VERSION.SDK_INT >= 29) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
+ } else {
+ 0
+ }
}
private fun createNotificationChannel(): NotificationManager? {
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
index 5a69d282b4..c63b6cb497 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MainActivity.kt
@@ -54,7 +54,7 @@ class MainActivity: FragmentActivity() {
SimplexApp.context.schedulePeriodicWakeUp()
}
- override fun onNewIntent(intent: Intent?) {
+ override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
processIntent(intent)
processExternalIntent(intent)
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt
index 4d3b390189..1ce30f6435 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt
@@ -119,7 +119,10 @@ class SimplexApp: Application(), LifecycleEventObserver {
* */
if (chatModel.chatRunning.value != false &&
chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete &&
- appPrefs.notificationsMode.get() == NotificationsMode.SERVICE
+ appPrefs.notificationsMode.get() == NotificationsMode.SERVICE &&
+ // New installation passes all checks above and tries to start the service which is not needed at all
+ // because preferred notification type is not yet chosen. So, check that the user has initialized db already
+ appPrefs.newDatabaseInitialized.get()
) {
SimplexService.start()
}
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt
index 7fc1bd151c..ce3f0825b8 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt
@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.*
import android.content.*
import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
import android.net.Uri
import android.os.*
import android.os.SystemClock
@@ -15,8 +16,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.app.NotificationCompat
+import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.work.*
+import chat.simplex.app.model.NtfManager
import chat.simplex.common.AppLock
import chat.simplex.common.helpers.requiresIgnoringBattery
import chat.simplex.common.model.ChatController
@@ -52,18 +55,15 @@ class SimplexService: Service() {
} else {
Log.d(TAG, "null intent. Probably restarted by the system.")
}
- startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
+ ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType())
return START_STICKY // to restart if killed
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Simplex service created")
- val title = generalGetString(MR.strings.simplex_service_notification_title)
- val text = generalGetString(MR.strings.simplex_service_notification_text)
- notificationManager = createNotificationChannel()
- serviceNotification = createNotification(title, text)
- startForeground(SIMPLEX_SERVICE_ID, serviceNotification)
+ createNotificationIfNeeded()
+ ServiceCompat.startForeground(this, SIMPLEX_SERVICE_ID, createNotificationIfNeeded(), foregroundServiceType())
/**
* The reason [stopAfterStart] exists is because when the service is not called [startForeground] yet, and
* we call [stopSelf] on the same service, [ForegroundServiceDidNotStartInTimeException] will be thrown.
@@ -103,6 +103,26 @@ class SimplexService: Service() {
super.onDestroy()
}
+ private fun createNotificationIfNeeded(): Notification {
+ val ntf = serviceNotification
+ if (ntf != null) return ntf
+
+ val title = generalGetString(MR.strings.simplex_service_notification_title)
+ val text = generalGetString(MR.strings.simplex_service_notification_text)
+ notificationManager = createNotificationChannel()
+ val newNtf = createNotification(title, text)
+ serviceNotification = newNtf
+ return newNtf
+ }
+
+ private fun foregroundServiceType(): Int {
+ return if (Build.VERSION.SDK_INT >= 34) {
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING
+ } else {
+ 0
+ }
+ }
+
private fun startService() {
Log.d(TAG, "SimplexService startService")
if (wakeLock != null || isCheckingNewMessages) return
@@ -292,6 +312,10 @@ class SimplexService: Service() {
}
private suspend fun serviceAction(action: Action) {
+ if (!NtfManager.areNotificationsEnabledInSystem()) {
+ Log.d(TAG, "SimplexService serviceAction: ${action.name}. Notifications are not enabled in OS yet, not starting service")
+ return
+ }
Log.d(TAG, "SimplexService serviceAction: ${action.name}")
withContext(Dispatchers.IO) {
Intent(androidAppContext, SimplexService::class.java).also {
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt
index 417a81a953..cf19589d4a 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt
@@ -53,7 +53,7 @@ object NtfManager {
private val msgNtfTimeoutMs = 30000L
init {
- if (manager.areNotificationsEnabled()) createNtfChannelsMaybeShowAlert()
+ if (areNotificationsEnabledInSystem()) createNtfChannelsMaybeShowAlert()
}
private fun callNotificationChannel(channelId: String, channelName: String): NotificationChannel {
@@ -287,6 +287,8 @@ object NtfManager {
}
}
+ fun areNotificationsEnabledInSystem() = manager.areNotificationsEnabled()
+
/**
* This function creates notifications channels. On Android 13+ calling it for the first time will trigger system alert,
* The alert asks a user to allow or disallow to show notifications for the app. That's why it should be called only when the user
diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt
index 323eb4417b..a9697069c0 100644
--- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt
+++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt
@@ -120,6 +120,7 @@ class CallActivity: ComponentActivity(), ServiceConnection {
return grantedAudio && grantedCamera
}
+ @Deprecated("Was deprecated in OS")
override fun onBackPressed() {
if (isOnLockScreenNow()) {
super.onBackPressed()
@@ -139,6 +140,7 @@ class CallActivity: ComponentActivity(), ServiceConnection {
}
override fun onUserLeaveHint() {
+ super.onUserLeaveHint()
// On Android 12+ PiP is enabled automatically when a user hides the app
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) {
enterPictureInPictureMode()
@@ -248,6 +250,9 @@ fun CallActivityView() {
)
if (permissionsState.allPermissionsGranted) {
ActiveCallView()
+ LaunchedEffect(Unit) {
+ activity.startServiceAndBind()
+ }
} else {
CallPermissionsView(remember { m.activeCallViewIsCollapsed }.value, callSupportsVideo()) {
withBGApi { chatModel.callManager.endCall(call) }
@@ -285,11 +290,6 @@ fun CallActivityView() {
AlertManager.shared.showInView()
}
}
- LaunchedEffect(call == null) {
- if (call != null) {
- activity.startServiceAndBind()
- }
- }
LaunchedEffect(invitation, call, switchingCall, showCallView) {
if (!switchingCall && invitation == null && (!showCallView || call == null)) {
Log.d(TAG, "CallActivityView: finishing activity")
diff --git a/apps/multiplatform/android/src/main/res/values/colors.xml b/apps/multiplatform/android/src/main/res/values/colors.xml
index e1a994e57f..1833a6d9a3 100644
--- a/apps/multiplatform/android/src/main/res/values/colors.xml
+++ b/apps/multiplatform/android/src/main/res/values/colors.xml
@@ -2,6 +2,5 @@
#FF000000
#FFFFFFFF
- #8b8786
#121212
\ No newline at end of file
diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts
index dd9c7ab161..1670672753 100644
--- a/apps/multiplatform/common/build.gradle.kts
+++ b/apps/multiplatform/common/build.gradle.kts
@@ -61,8 +61,8 @@ kotlin {
val androidMain by getting {
kotlin.srcDir("build/generated/moko/androidMain/src")
dependencies {
- implementation("androidx.activity:activity-compose:1.8.2")
- val workVersion = "2.9.0"
+ implementation("androidx.activity:activity-compose:1.9.1")
+ val workVersion = "2.9.1"
implementation("androidx.work:work-runtime-ktx:$workVersion")
implementation("com.google.accompanist:accompanist-insets:0.30.1")
@@ -78,22 +78,22 @@ kotlin {
//Camera Permission
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
- implementation("androidx.webkit:webkit:1.10.0")
+ implementation("androidx.webkit:webkit:1.11.0")
// GIFs support
implementation("io.coil-kt:coil-compose:2.6.0")
implementation("io.coil-kt:coil-gif:2.6.0")
- implementation("com.jakewharton:process-phoenix:2.2.0")
+ implementation("com.jakewharton:process-phoenix:3.0.0")
- val cameraXVersion = "1.3.2"
+ val cameraXVersion = "1.3.4"
implementation("androidx.camera:camera-core:${cameraXVersion}")
implementation("androidx.camera:camera-camera2:${cameraXVersion}")
implementation("androidx.camera:camera-lifecycle:${cameraXVersion}")
implementation("androidx.camera:camera-view:${cameraXVersion}")
// Calls lifecycle listener
- implementation("androidx.lifecycle:lifecycle-process:2.4.1")
+ implementation("androidx.lifecycle:lifecycle-process:2.8.4")
}
}
val desktopMain by getting {
@@ -119,8 +119,8 @@ android {
defaultConfig {
minSdk = 26
}
- testOptions.targetSdk = 33
- lint.targetSdk = 33
+ testOptions.targetSdk = 34
+ lint.targetSdk = 34
val isAndroid = gradle.startParameter.taskNames.find {
val lower = it.lowercase()
lower.contains("release") || lower.startsWith("assemble") || lower.startsWith("install")
diff --git a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml
index 683c3a4dd4..948ae4d4bf 100644
--- a/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml
+++ b/apps/multiplatform/common/src/androidMain/res/drawable/edit_text_cursor.xml
@@ -1,5 +1,5 @@
-
+
From 5b3aba9db2ad2c3d36a85a34af85988b0e166172 Mon Sep 17 00:00:00 2001
From: Evgeny
Date: Thu, 29 Aug 2024 13:40:55 +0100
Subject: [PATCH 2/8] ci: dont build when files in core do not change (#4797)
---
.github/workflows/build.yml | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c41fb4646a..6ad4f12ef9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,12 +5,22 @@ on:
branches:
- master
- stable
- - users
tags:
- "v*"
- "!*-fdroid"
- "!*-armv7a"
pull_request:
+ paths-ignore:
+ - "apps/ios"
+ - "apps/multiplatform"
+ - "blog"
+ - "docs"
+ - "fastlane"
+ - "images"
+ - "packages"
+ - "website"
+ - "README.md"
+ - "PRIVACY.md"
jobs:
prepare-release:
From d6dc35738e4df8f6509b2928603e9c515ebc8a04 Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin
Date: Tue, 24 Sep 2024 12:42:22 +0100
Subject: [PATCH 3/8] core: 6.0.5.0 (simplexmq 6.0.5.0)
---
cabal.project | 2 +-
package.yaml | 2 +-
scripts/nix/sha256map.nix | 2 +-
simplex-chat.cabal | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/cabal.project b/cabal.project
index 92f5d475e9..965a1722d0 100644
--- a/cabal.project
+++ b/cabal.project
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
- tag: fa772af6c63fab8f04d9d32d8e8397d75d7d0391
+ tag: 4268b90763c58358809a3ea7dd8bc7d78eeb3077
source-repository-package
type: git
diff --git a/package.yaml b/package.yaml
index 947589acd0..8990bfc3d3 100644
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
name: simplex-chat
-version: 6.0.4.0
+version: 6.0.5.0
#synopsis:
#description:
homepage: https://github.com/simplex-chat/simplex-chat#readme
diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix
index c3b85f9b53..d5edef4840 100644
--- a/scripts/nix/sha256map.nix
+++ b/scripts/nix/sha256map.nix
@@ -1,5 +1,5 @@
{
- "https://github.com/simplex-chat/simplexmq.git"."fa772af6c63fab8f04d9d32d8e8397d75d7d0391" = "07d0f89msb6p05y67q90ky9jr1rygg7v3xlkga7y255mmpjsqbip";
+ "https://github.com/simplex-chat/simplexmq.git"."4268b90763c58358809a3ea7dd8bc7d78eeb3077" = "0w444jbxxi5hgipf35xniwmffnpg4qb46sz112hrwxyf5syfi2k5";
"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 b3cde5ae9f..aa9ab0b9b3 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.0.4.0
+version: 6.0.5.0
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat
From 3b88ddbd4f3facb9224f7b6e820d6d9e6388c4af Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Thu, 12 Sep 2024 13:51:57 +0000
Subject: [PATCH 4/8] desktop: fix vlc dependency (2) (#4869)
---
apps/multiplatform/common/build.gradle.kts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts
index 1670672753..1aaa061daa 100644
--- a/apps/multiplatform/common/build.gradle.kts
+++ b/apps/multiplatform/common/build.gradle.kts
@@ -99,10 +99,15 @@ kotlin {
val desktopMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.8.0")
- implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8")
+ implementation("com.github.Dansoftowner:jSystemThemeDetector:3.8") {
+ exclude("net.java.dev.jna")
+ }
+ // For jSystemThemeDetector only
+ implementation("net.java.dev.jna:jna-platform:5.14.0")
implementation("com.sshtools:two-slices:0.9.0-SNAPSHOT")
implementation("org.slf4j:slf4j-simple:2.0.12")
implementation("uk.co.caprica:vlcj:4.8.3")
+ implementation("net.java.dev.jna:jna:5.14.0")
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a")
implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
From 93ab3076d497c0ae9b7371c4f6094987ff75df60 Mon Sep 17 00:00:00 2001
From: Evgeny
Date: Tue, 17 Sep 2024 17:34:24 +0100
Subject: [PATCH 5/8] ios: SOCKS proxy UI (#4893)
* ios: SOCKS proxy UI
* update network config
* proxy
* adapt
* move, dont default to localhost:9050
* move socks proxy to defaults
* sock proxy preference
* rename
* rename
* fix
* fix
---------
Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
---
.../Views/ChatList/ServersSummaryView.swift | 24 +++--
.../Views/Migration/MigrateFromDevice.swift | 6 ++
.../AdvancedNetworkSettings.swift | 97 +++++++++++++++++--
.../Views/UserSettings/AppSettings.swift | 11 ++-
.../Views/UserSettings/SettingsView.swift | 3 +
apps/ios/SimpleXChat/APITypes.swift | 66 ++++++++++++-
apps/ios/SimpleXChat/AppGroup.swift | 7 +-
7 files changed, 197 insertions(+), 17 deletions(-)
diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift
index 477a78e36d..22ea78f27b 100644
--- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift
+++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift
@@ -407,12 +407,18 @@ struct ServersSummaryView: View {
struct SubscriptionStatusIndicatorView: View {
@EnvironmentObject var m: ChatModel
+ @EnvironmentObject var theme: AppTheme
var subs: SMPServerSubs
var hasSess: Bool
var body: some View {
- let onionHosts = networkUseOnionHostsGroupDefault.get()
- let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess)
+ let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(
+ online: m.networkInfo.online,
+ usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil,
+ subs: subs,
+ hasSess: hasSess,
+ primaryColor: theme.colors.primary
+ )
if #available(iOS 16.0, *) {
Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue)
.foregroundColor(color)
@@ -425,26 +431,32 @@ struct SubscriptionStatusIndicatorView: View {
struct SubscriptionStatusPercentageView: View {
@EnvironmentObject var m: ChatModel
+ @EnvironmentObject var theme: AppTheme
var subs: SMPServerSubs
var hasSess: Bool
var body: some View {
- let onionHosts = networkUseOnionHostsGroupDefault.get()
- let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess)
+ let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(
+ online: m.networkInfo.online,
+ usesProxy: networkUseOnionHostsGroupDefault.get() != .no || groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY) != nil,
+ subs: subs,
+ hasSess: hasSess,
+ primaryColor: theme.colors.primary
+ )
Text(verbatim: "\(Int(floor(statusPercent * 100)))%")
.foregroundColor(.secondary)
.font(.caption)
}
}
-func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) {
+func subscriptionStatusColorAndPercentage(online: Bool, usesProxy: Bool, subs: SMPServerSubs, hasSess: Bool, primaryColor: Color) -> (Color, Double, Double, Double) {
func roundedToQuarter(_ n: Double) -> Double {
n >= 1 ? 1
: n <= 0 ? 0
: (n * 4).rounded() / 4
}
- let activeColor: Color = onionHosts == .require ? .indigo : .accentColor
+ let activeColor: Color = usesProxy ? .indigo : primaryColor
let noConnColorAndPercent: (Color, Double, Double, Double) = (Color(uiColor: .tertiaryLabel), 1, 1, 0)
let activeSubsRounded = roundedToQuarter(subs.shareOfActive)
diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift
index 1303a1247f..3c514d3529 100644
--- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift
+++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift
@@ -534,9 +534,15 @@ struct MigrateFromDevice: View {
}
case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs):
let cfg = getNetCfg()
+ let proxy: NetworkProxy? = if cfg.socksProxy == nil {
+ nil
+ } else {
+ networkProxyDefault.get()
+ }
let data = MigrationFileLinkData.init(
networkConfig: MigrationFileLinkData.NetworkConfig(
socksProxy: cfg.socksProxy,
+ networkProxy: proxy,
hostMode: cfg.hostMode,
requiredHostMode: cfg.requiredHostMode
)
diff --git a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift
index 99c0a588eb..9884c6e877 100644
--- a/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AdvancedNetworkSettings.swift
@@ -36,6 +36,10 @@ struct AdvancedNetworkSettings: View {
@State private var showSettingsAlert: NetworkSettingsAlert?
@State private var onionHosts: OnionHosts = .no
@State private var showSaveDialog = false
+ @State private var netProxy = networkProxyDefault.get()
+ @State private var currentNetProxy = networkProxyDefault.get()
+ @State private var useNetProxy = false
+ @State private var netProxyAuth = false
var body: some View {
VStack {
@@ -102,6 +106,76 @@ struct AdvancedNetworkSettings: View {
.foregroundColor(theme.colors.secondary)
}
+ Section {
+ Toggle("Use SOCKS proxy", isOn: $useNetProxy)
+ Group {
+ TextField("IP address", text: $netProxy.host)
+ TextField(
+ "Port",
+ text: Binding(
+ get: { netProxy.port > 0 ? "\(netProxy.port)" : "" },
+ set: { s in
+ netProxy.port = if let port = Int(s), port > 0 {
+ port
+ } else {
+ 0
+ }
+ }
+ )
+ )
+ Toggle("Proxy requires password", isOn: $netProxyAuth)
+ if netProxyAuth {
+ TextField("Username", text: $netProxy.username)
+ PassphraseField(
+ key: $netProxy.password,
+ placeholder: "Password",
+ valid: NetworkProxy.validCredential(netProxy.password)
+ )
+ }
+ }
+ .if(!useNetProxy) { $0.foregroundColor(theme.colors.secondary) }
+ .disabled(!useNetProxy)
+ } header: {
+ HStack {
+ Text("SOCKS proxy").foregroundColor(theme.colors.secondary)
+ if useNetProxy && !netProxy.valid {
+ Spacer()
+ Image(systemName: "exclamationmark.circle.fill").foregroundColor(.red)
+ }
+ }
+ } footer: {
+ if netProxyAuth {
+ Text("Your credentials may be sent unencrypted.")
+ .foregroundColor(theme.colors.secondary)
+ } else {
+ Text("Do not use credentials with proxy.")
+ .foregroundColor(theme.colors.secondary)
+ }
+ }
+ .onChange(of: useNetProxy) { useNetProxy in
+ netCfg.socksProxy = useNetProxy && currentNetProxy.valid
+ ? currentNetProxy.toProxyString()
+ : nil
+ netProxy = currentNetProxy
+ netProxyAuth = netProxy.username != "" || netProxy.password != ""
+ }
+ .onChange(of: netProxyAuth) { netProxyAuth in
+ if netProxyAuth {
+ netProxy.auth = currentNetProxy.auth
+ netProxy.username = currentNetProxy.username
+ netProxy.password = currentNetProxy.password
+ } else {
+ netProxy.auth = .username
+ netProxy.username = ""
+ netProxy.password = ""
+ }
+ }
+ .onChange(of: netProxy) { netProxy in
+ netCfg.socksProxy = useNetProxy && netProxy.valid
+ ? netProxy.toProxyString()
+ : nil
+ }
+
Section {
Picker("Use .onion hosts", selection: $onionHosts) {
ForEach(OnionHosts.values, id: \.self) { Text($0.text) }
@@ -156,19 +230,19 @@ struct AdvancedNetworkSettings: View {
Section {
Button("Reset to defaults") {
- updateNetCfgView(NetCfg.defaults)
+ updateNetCfgView(NetCfg.defaults, NetworkProxy.def)
}
.disabled(netCfg == NetCfg.defaults)
Button("Set timeouts for proxy/VPN") {
- updateNetCfgView(netCfg.withProxyTimeouts)
+ updateNetCfgView(netCfg.withProxyTimeouts, netProxy)
}
.disabled(netCfg.hasProxyTimeouts)
Button("Save and reconnect") {
showSettingsAlert = .update
}
- .disabled(netCfg == currentNetCfg)
+ .disabled(netCfg == currentNetCfg || (useNetProxy && !netProxy.valid))
}
}
}
@@ -182,7 +256,8 @@ struct AdvancedNetworkSettings: View {
if cfgLoaded { return }
cfgLoaded = true
currentNetCfg = getNetCfg()
- updateNetCfgView(currentNetCfg)
+ currentNetProxy = networkProxyDefault.get()
+ updateNetCfgView(currentNetCfg, currentNetProxy)
}
.alert(item: $showSettingsAlert) { a in
switch a {
@@ -206,7 +281,7 @@ struct AdvancedNetworkSettings: View {
if netCfg == currentNetCfg {
dismiss()
cfgLoaded = false
- } else {
+ } else if !useNetProxy || netProxy.valid {
showSaveDialog = true
}
})
@@ -221,18 +296,26 @@ struct AdvancedNetworkSettings: View {
}
}
- private func updateNetCfgView(_ cfg: NetCfg) {
+ private func updateNetCfgView(_ cfg: NetCfg, _ proxy: NetworkProxy) {
netCfg = cfg
+ netProxy = proxy
onionHosts = OnionHosts(netCfg: netCfg)
enableKeepAlive = netCfg.enableKeepAlive
keepAliveOpts = netCfg.tcpKeepAlive ?? KeepAliveOpts.defaults
+ useNetProxy = netCfg.socksProxy != nil
+ netProxyAuth = switch netProxy.auth {
+ case .username: netProxy.username != "" || netProxy.password != ""
+ case .isolate: false
+ }
}
private func saveNetCfg() -> Bool {
do {
try setNetworkConfig(netCfg)
currentNetCfg = netCfg
- setNetCfg(netCfg)
+ setNetCfg(netCfg, networkProxy: useNetProxy ? netProxy : nil)
+ currentNetProxy = netProxy
+ networkProxyDefault.set(netProxy)
return true
} catch let error {
let err = responseError(error)
diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift
index bd829552f4..19260ce573 100644
--- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift
@@ -19,9 +19,15 @@ extension AppSettings {
val.hostMode = .publicHost
val.requiredHostMode = true
}
- val.socksProxy = nil
- setNetCfg(val)
+ if val.socksProxy != nil {
+ val.socksProxy = networkProxy?.toProxyString()
+ setNetCfg(val, networkProxy: networkProxy)
+ } else {
+ val.socksProxy = nil
+ setNetCfg(val, networkProxy: nil)
+ }
}
+ if let val = networkProxy { networkProxyDefault.set(val) }
if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) }
if let val = privacyAskToApproveRelays { privacyAskToApproveRelaysGroupDefault.set(val) }
if let val = privacyAcceptImages {
@@ -63,6 +69,7 @@ extension AppSettings {
let def = UserDefaults.standard
var c = AppSettings.defaults
c.networkConfig = getNetCfg()
+ c.networkProxy = networkProxyDefault.get()
c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get()
c.privacyAskToApproveRelays = privacyAskToApproveRelaysGroupDefault.get()
c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get()
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index a4908f628f..5fb44e1ef8 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -73,6 +73,8 @@ let DEFAULT_SYSTEM_DARK_THEME = "systemDarkTheme"
let DEFAULT_CURRENT_THEME_IDS = "currentThemeIds"
let DEFAULT_THEME_OVERRIDES = "themeOverrides"
+let DEFAULT_NETWORK_PROXY = "networkProxy"
+
let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen"
let appDefaults: [String: Any] = [
@@ -245,6 +247,7 @@ public class CodableDefault {
}
}
+let networkProxyDefault: CodableDefault = CodableDefault(defaults: UserDefaults.standard, forKey: DEFAULT_NETWORK_PROXY, withDefault: NetworkProxy.def)
struct SettingsView: View {
@Environment(\.colorScheme) var colorScheme
diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift
index d7fc533e91..8245230771 100644
--- a/apps/ios/SimpleXChat/APITypes.swift
+++ b/apps/ios/SimpleXChat/APITypes.swift
@@ -8,6 +8,7 @@
import Foundation
import SwiftUI
+import Network
public let jsonDecoder = getJSONDecoder()
public let jsonEncoder = getJSONEncoder()
@@ -1476,6 +1477,63 @@ public struct KeepAliveOpts: Codable, Equatable {
public static let defaults: KeepAliveOpts = KeepAliveOpts(keepIdle: 30, keepIntvl: 15, keepCnt: 4)
}
+public struct NetworkProxy: Equatable, Codable {
+ public var host: String = ""
+ public var port: Int = 0
+ public var auth: NetworkProxyAuth = .username
+ public var username: String = ""
+ public var password: String = ""
+
+ public static var def: NetworkProxy {
+ NetworkProxy()
+ }
+
+ public var valid: Bool {
+ let hostOk = switch NWEndpoint.Host(host) {
+ case .ipv4: true
+ case .ipv6: true
+ default: false
+ }
+ return hostOk &&
+ port > 0 && port <= 65535 &&
+ NetworkProxy.validCredential(username) && NetworkProxy.validCredential(password)
+ }
+
+ public static func validCredential(_ s: String) -> Bool {
+ !s.contains(":") && !s.contains("@")
+ }
+
+ public func toProxyString() -> String? {
+ if !valid { return nil }
+ var res = ""
+ switch auth {
+ case .username:
+ let usernameTrimmed = username.trimmingCharacters(in: .whitespaces)
+ let passwordTrimmed = password.trimmingCharacters(in: .whitespaces)
+ if usernameTrimmed != "" || passwordTrimmed != "" {
+ res += usernameTrimmed + ":" + passwordTrimmed + "@"
+ } else {
+ res += "@"
+ }
+ case .isolate: ()
+ }
+ if host != "" {
+ if host.contains(":") {
+ res += "[\(host.trimmingCharacters(in: [" ", "[", "]"]))]"
+ } else {
+ res += host.trimmingCharacters(in: .whitespaces)
+ }
+ }
+ res += ":\(port)"
+ return res
+ }
+}
+
+public enum NetworkProxyAuth: String, Codable {
+ case username
+ case isolate
+}
+
public enum NetworkStatus: Decodable, Equatable {
case unknown
case connected
@@ -2099,11 +2157,13 @@ public struct MigrationFileLinkData: Codable {
public struct NetworkConfig: Codable {
let socksProxy: String?
+ let networkProxy: NetworkProxy?
let hostMode: HostMode?
let requiredHostMode: Bool?
- public init(socksProxy: String?, hostMode: HostMode?, requiredHostMode: Bool?) {
+ public init(socksProxy: String?, networkProxy: NetworkProxy?, hostMode: HostMode?, requiredHostMode: Bool?) {
self.socksProxy = socksProxy
+ self.networkProxy = networkProxy
self.hostMode = hostMode
self.requiredHostMode = requiredHostMode
}
@@ -2112,6 +2172,7 @@ public struct MigrationFileLinkData: Codable {
return if let hostMode, let requiredHostMode {
NetworkConfig(
socksProxy: nil,
+ networkProxy: nil,
hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode,
requiredHostMode: requiredHostMode
)
@@ -2131,6 +2192,7 @@ public struct MigrationFileLinkData: Codable {
public struct AppSettings: Codable, Equatable {
public var networkConfig: NetCfg? = nil
+ public var networkProxy: NetworkProxy? = nil
public var privacyEncryptLocalFiles: Bool? = nil
public var privacyAskToApproveRelays: Bool? = nil
public var privacyAcceptImages: Bool? = nil
@@ -2162,6 +2224,7 @@ public struct AppSettings: Codable, Equatable {
var empty = AppSettings()
let def = AppSettings.defaults
if networkConfig != def.networkConfig { empty.networkConfig = networkConfig }
+ if networkProxy != def.networkProxy { empty.networkProxy = networkProxy }
if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles }
if privacyAskToApproveRelays != def.privacyAskToApproveRelays { empty.privacyAskToApproveRelays = privacyAskToApproveRelays }
if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages }
@@ -2194,6 +2257,7 @@ public struct AppSettings: Codable, Equatable {
public static var defaults: AppSettings {
AppSettings (
networkConfig: NetCfg.defaults,
+ networkProxy: NetworkProxy.def,
privacyEncryptLocalFiles: true,
privacyAskToApproveRelays: true,
privacyAcceptImages: true,
diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift
index bd38f3568c..455607ddea 100644
--- a/apps/ios/SimpleXChat/AppGroup.swift
+++ b/apps/ios/SimpleXChat/AppGroup.swift
@@ -35,6 +35,7 @@ public let GROUP_DEFAULT_PRIVACY_ASK_TO_APPROVE_RELAYS = "privacyAskToApproveRel
// replaces DEFAULT_PROFILE_IMAGE_CORNER_RADIUS
public let GROUP_DEFAULT_PROFILE_IMAGE_CORNER_RADIUS = "profileImageCornerRadius"
let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount"
+public let GROUP_DEFAULT_NETWORK_SOCKS_PROXY = "networkSocksProxy"
let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts"
let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode"
let GROUP_DEFAULT_NETWORK_SMP_PROXY_MODE = "networkSMPProxyMode"
@@ -327,6 +328,7 @@ public class Default {
}
public func getNetCfg() -> NetCfg {
+ let socksProxy = groupDefaults.string(forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY)
let onionHosts = networkUseOnionHostsGroupDefault.get()
let (hostMode, requiredHostMode) = onionHosts.hostMode
let sessionMode = networkSessionModeGroupDefault.get()
@@ -349,6 +351,7 @@ public func getNetCfg() -> NetCfg {
tcpKeepAlive = nil
}
return NetCfg(
+ socksProxy: socksProxy,
hostMode: hostMode,
requiredHostMode: requiredHostMode,
sessionMode: sessionMode,
@@ -365,11 +368,13 @@ public func getNetCfg() -> NetCfg {
)
}
-public func setNetCfg(_ cfg: NetCfg) {
+public func setNetCfg(_ cfg: NetCfg, networkProxy: NetworkProxy?) {
networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg))
networkSessionModeGroupDefault.set(cfg.sessionMode)
networkSMPProxyModeGroupDefault.set(cfg.smpProxyMode)
networkSMPProxyFallbackGroupDefault.set(cfg.smpProxyFallback)
+ let socksProxy = networkProxy?.toProxyString()
+ groupDefaults.set(socksProxy, forKey: GROUP_DEFAULT_NETWORK_SOCKS_PROXY)
groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
groupDefaults.set(cfg.tcpTimeoutPerKb, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB)
From 5261886b31250b9289fa4cbfcb836cda7e206592 Mon Sep 17 00:00:00 2001
From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com>
Date: Tue, 17 Sep 2024 20:09:50 +0000
Subject: [PATCH 6/8] android, desktop: proxy configuration includes
credentials (#4892)
* android, desktop: proxy configuration includes credentials
* migration
* changes for disabled socks
* migration
* port
* new logic
* migration
* check validity of fields
* validity of host
* import changes proxy just in case
* send port always
* non-nullable
* Revert "send port always"
This reverts commit 14dd066d80f4c6c6b1061e8e6142bf18f83b97bb.
* string
---------
Co-authored-by: Evgeny Poberezkin
---
.../chat/simplex/common/model/SimpleXAPI.kt | 91 ++++--
.../views/migration/MigrateFromDevice.kt | 16 +-
.../common/views/migration/MigrateToDevice.kt | 177 ++++++-----
.../usersettings/AdvancedNetworkSettings.kt | 32 +-
.../views/usersettings/NetworkAndServers.kt | 285 ++++++++++++------
.../commonMain/resources/MR/base/strings.xml | 10 +
6 files changed, 377 insertions(+), 234 deletions(-)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index 4d5caea16a..9cd9376c8d 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -129,7 +129,22 @@ class AppPreferences {
val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
val networkShowSubscriptionPercentage = mkBoolPreference(SHARED_PREFS_NETWORK_SHOW_SUBSCRIPTION_PERCENTAGE, false)
- val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050")
+ private val _networkProxy = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, json.encodeToString(NetworkProxy()))
+ val networkProxy: SharedPreference = SharedPreference(
+ get = fun(): NetworkProxy {
+ val value = _networkProxy.get() ?: return NetworkProxy()
+ return try {
+ if (value.startsWith("{")) {
+ json.decodeFromString(value)
+ } else {
+ NetworkProxy(host = value.substringBefore(":").ifBlank { "localhost" }, port = value.substringAfter(":").toIntOrNull() ?: 9050)
+ }
+ } catch (e: Throwable) {
+ NetworkProxy()
+ }
+ },
+ set = fun(proxy: NetworkProxy) { _networkProxy.set(json.encodeToString(proxy)) }
+ )
private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name)
val networkSessionMode: SharedPreference = SharedPreference(
get = fun(): TransportSessionMode {
@@ -531,7 +546,7 @@ object ChatController {
suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? {
Log.d(TAG, "startChatWithTemporaryDatabase")
val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl)
- if (!apiSetNetworkConfig(netCfg, ctrl)) {
+ if (!apiSetNetworkConfig(netCfg, ctrl = ctrl)) {
Log.e(TAG, "Error setting network config, stopping migration")
return null
}
@@ -976,16 +991,18 @@ object ChatController {
throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}")
}
- suspend fun apiSetNetworkConfig(cfg: NetCfg, ctrl: ChatCtrl? = null): Boolean {
+ suspend fun apiSetNetworkConfig(cfg: NetCfg, showAlertOnError: Boolean = true, ctrl: ChatCtrl? = null): Boolean {
val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl)
return when (r) {
is CR.CmdOk -> true
else -> {
Log.e(TAG, "apiSetNetworkConfig bad response: ${r.responseType} ${r.details}")
- AlertManager.shared.showAlertMsg(
- generalGetString(MR.strings.error_setting_network_config),
- "${r.responseType}: ${r.details}"
- )
+ if (showAlertOnError) {
+ AlertManager.shared.showAlertMsg(
+ generalGetString(MR.strings.error_setting_network_config),
+ "${r.responseType}: ${r.details}"
+ )
+ }
false
}
}
@@ -2745,13 +2762,9 @@ object ChatController {
fun getNetCfg(): NetCfg {
val useSocksProxy = appPrefs.networkUseSocksProxy.get()
- val proxyHostPort = appPrefs.networkProxyHostPort.get()
+ val networkProxy = appPrefs.networkProxy.get()
val socksProxy = if (useSocksProxy) {
- if (proxyHostPort?.startsWith("localhost:") == true) {
- proxyHostPort.removePrefix("localhost")
- } else {
- proxyHostPort ?: ":9050"
- }
+ networkProxy.toProxyString()
} else {
null
}
@@ -2793,7 +2806,7 @@ object ChatController {
}
/**
- * [AppPreferences.networkProxyHostPort] is not changed here, use appPrefs to set it
+ * [AppPreferences.networkProxy] is not changed here, use appPrefs to set it
* */
fun setNetCfg(cfg: NetCfg) {
appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy)
@@ -3529,13 +3542,8 @@ data class NetCfg(
val useSocksProxy: Boolean get() = socksProxy != null
val enableKeepAlive: Boolean get() = tcpKeepAlive != null
- fun withHostPort(hostPort: String?, default: String? = ":9050"): NetCfg {
- val socksProxy = if (hostPort?.startsWith("localhost:") == true) {
- hostPort.removePrefix("localhost")
- } else {
- hostPort ?: default
- }
- return copy(socksProxy = socksProxy)
+ fun withProxy(proxy: NetworkProxy?, default: String? = ":9050"): NetCfg {
+ return copy(socksProxy = proxy?.toProxyString() ?: default)
}
companion object {
@@ -3577,6 +3585,39 @@ data class NetCfg(
}
}
+@Serializable
+data class NetworkProxy(
+ val username: String = "",
+ val password: String = "",
+ val auth: NetworkProxyAuth = NetworkProxyAuth.ISOLATE,
+ val host: String = "localhost",
+ val port: Int = 9050
+) {
+ fun toProxyString(): String {
+ var res = ""
+ if (auth == NetworkProxyAuth.USERNAME && (username.isNotBlank() || password.isNotBlank())) {
+ res += username.trim() + ":" + password.trim() + "@"
+ } else if (auth == NetworkProxyAuth.USERNAME) {
+ res += "@"
+ }
+ if (host != "localhost") {
+ res += if (host.contains(':')) "[${host.trim(' ', '[', ']')}]" else host.trim()
+ }
+ if (port != 9050 || res.isEmpty()) {
+ res += ":$port"
+ }
+ return res
+ }
+}
+
+@Serializable
+enum class NetworkProxyAuth {
+ @SerialName("isolate")
+ ISOLATE,
+ @SerialName("username")
+ USERNAME,
+}
+
enum class OnionHosts {
NEVER, PREFER, REQUIRED
}
@@ -6139,6 +6180,7 @@ enum class NotificationsMode() {
@Serializable
data class AppSettings(
var networkConfig: NetCfg? = null,
+ var networkProxy: NetworkProxy? = null,
var privacyEncryptLocalFiles: Boolean? = null,
var privacyAskToApproveRelays: Boolean? = null,
var privacyAcceptImages: Boolean? = null,
@@ -6170,6 +6212,7 @@ data class AppSettings(
val empty = AppSettings()
val def = defaults
if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig }
+ if (networkProxy != def.networkProxy) { empty.networkProxy = networkProxy }
if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles }
if (privacyAskToApproveRelays != def.privacyAskToApproveRelays) { empty.privacyAskToApproveRelays = privacyAskToApproveRelays }
if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages }
@@ -6207,8 +6250,12 @@ data class AppSettings(
if (net.hostMode == HostMode.Onion) {
net = net.copy(hostMode = HostMode.Public, requiredHostMode = true)
}
+ if (net.socksProxy != null) {
+ net = net.copy(socksProxy = networkProxy?.toProxyString())
+ }
setNetCfg(net)
}
+ networkProxy?.let { def.networkProxy.set(it) }
privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) }
privacyAskToApproveRelays?.let { def.privacyAskToApproveRelays.set(it) }
privacyAcceptImages?.let { def.privacyAcceptImages.set(it) }
@@ -6241,6 +6288,7 @@ data class AppSettings(
val defaults: AppSettings
get() = AppSettings(
networkConfig = NetCfg.defaults,
+ networkProxy = null,
privacyEncryptLocalFiles = true,
privacyAskToApproveRelays = true,
privacyAcceptImages = true,
@@ -6274,6 +6322,7 @@ data class AppSettings(
val def = appPreferences
return defaults.copy(
networkConfig = getNetCfg(),
+ networkProxy = def.networkProxy.get(),
privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(),
privacyAskToApproveRelays = def.privacyAskToApproveRelays.get(),
privacyAcceptImages = def.privacyAcceptImages.get(),
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt
index a71503e315..4cc7899cc8 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt
@@ -4,7 +4,6 @@ import SectionBottomSpacer
import SectionSpacer
import SectionTextFooter
import SectionView
-import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
@@ -17,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
+import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.startChat
import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase
@@ -38,7 +38,6 @@ import kotlinx.serialization.*
import java.io.File
import java.net.URLEncoder
import kotlin.math.max
-import kotlin.math.sqrt
@Serializable
data class MigrationFileLinkData(
@@ -46,16 +45,20 @@ data class MigrationFileLinkData(
) {
@Serializable
data class NetworkConfig(
- val socksProxy: String?,
+ // Legacy. Remove in 2025
+ @SerialName("socksProxy")
+ val legacySocksProxy: String?,
+ val networkProxy: NetworkProxy?,
val hostMode: HostMode?,
val requiredHostMode: Boolean?
) {
- fun hasOnionConfigured(): Boolean = socksProxy != null || hostMode == HostMode.Onion
+ fun hasProxyConfigured(): Boolean = networkProxy != null || legacySocksProxy != null || hostMode == HostMode.Onion
fun transformToPlatformSupported(): NetworkConfig {
return if (hostMode != null && requiredHostMode != null) {
NetworkConfig(
- socksProxy = if (hostMode == HostMode.Onion) socksProxy ?: NetCfg.proxyDefaults.socksProxy else socksProxy,
+ legacySocksProxy = if (hostMode == HostMode.Onion) legacySocksProxy ?: NetCfg.proxyDefaults.socksProxy else legacySocksProxy,
+ networkProxy = if (hostMode == HostMode.Onion) networkProxy ?: NetworkProxy() else networkProxy,
hostMode = if (hostMode == HostMode.Onion) HostMode.OnionViaSocks else hostMode,
requiredHostMode = requiredHostMode
)
@@ -570,7 +573,8 @@ private fun MutableState.startUploading(
val cfg = getNetCfg()
val data = MigrationFileLinkData(
networkConfig = MigrationFileLinkData.NetworkConfig(
- socksProxy = cfg.socksProxy,
+ legacySocksProxy = null,
+ networkProxy = if (appPrefs.networkUseSocksProxy.get()) appPrefs.networkProxy.get() else null,
hostMode = cfg.hostMode,
requiredHostMode = cfg.requiredHostMode
)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt
index 8312c213ec..415f5cdd57 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt
@@ -5,16 +5,15 @@ import SectionItemView
import SectionSpacer
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.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import chat.simplex.common.model.*
import chat.simplex.common.model.AppPreferences.Companion.SHARED_PREFS_MIGRATION_TO_STAGE
+import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatController.getNetCfg
import chat.simplex.common.model.ChatController.startChat
import chat.simplex.common.model.ChatCtrl
@@ -41,10 +40,10 @@ import kotlin.math.max
@Serializable
sealed class MigrationToDeviceState {
- @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState()
- @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState()
- @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState()
- @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg): MigrationToDeviceState()
+ @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val networkProxy: NetworkProxy?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState()
+ @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState()
+ @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState()
+ @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToDeviceState()
companion object {
// Here we check whether it's needed to show migration process after app restart or not
@@ -66,10 +65,10 @@ sealed class MigrationToDeviceState {
null
} else {
val archivePath = File(getMigrationTempFilesDirectory(), state.archiveName)
- MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg)
+ MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg, state.networkProxy)
}
}
- is Passphrase -> MigrationToState.Passphrase("", state.netCfg)
+ is Passphrase -> MigrationToState.Passphrase("", state.netCfg, state.networkProxy)
}
if (initial == null) {
settings.remove(SHARED_PREFS_MIGRATION_TO_STAGE)
@@ -91,16 +90,24 @@ sealed class MigrationToDeviceState {
@Serializable
sealed class MigrationToState {
@Serializable object PasteOrScanLink: MigrationToState()
- @Serializable data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToState()
- @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg): MigrationToState()
- @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg): MigrationToState()
- @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val ctrl: ChatCtrl?): MigrationToState()
- @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg): MigrationToState()
- @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg): MigrationToState()
- @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg): MigrationToState()
- @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg): MigrationToState()
- @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState()
- @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState()
+ @Serializable data class Onion(
+ val link: String,
+ // Legacy, remove in 2025
+ @SerialName("socksProxy")
+ val legacySocksProxy: String?,
+ val networkProxy: NetworkProxy?,
+ val hostMode: HostMode,
+ val requiredHostMode: Boolean
+ ): MigrationToState()
+ @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?, val ctrl: ChatCtrl?): MigrationToState()
+ @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
+ @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg, val networkProxy: NetworkProxy?): MigrationToState()
}
private var MutableState.state: MigrationToState?
@@ -175,16 +182,16 @@ private fun ModalData.SectionByState(
when (val s = migrationState.value) {
null -> {}
is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView()
- is MigrationToState.Onion -> OnionView(s.link, s.socksProxy, s.hostMode, s.requiredHostMode, migrationState)
- is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg)
- is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg)
+ is MigrationToState.Onion -> OnionView(s.link, s.legacySocksProxy, s.networkProxy, s.hostMode, s.requiredHostMode, migrationState)
+ is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg, s.networkProxy)
+ is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg, s.networkProxy)
is MigrationToState.DownloadProgress -> DownloadProgressView(s.downloadedBytes, totalBytes = s.totalBytes)
- is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg)
- is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg)
- is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg)
- is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg)
- is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg)
- is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, close)
+ is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg, s.networkProxy)
+ is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg, s.networkProxy)
+ is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg, s.networkProxy)
+ is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg, s.networkProxy)
+ is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg, s.networkProxy)
+ is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, s.networkProxy, close)
}
}
@@ -216,21 +223,24 @@ private fun MutableState.PasteLinkView() {
}
@Composable
-private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) {
+private fun ModalData.OnionView(link: String, legacyLinkSocksProxy: String?, linkNetworkProxy: NetworkProxy?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState) {
val onionHosts = remember { stateGetOrPut("onionHosts") {
- getNetCfg().copy(socksProxy = socksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts
+ getNetCfg().copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts
} }
- val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { socksProxy != null } }
+ val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { linkNetworkProxy != null || legacyLinkSocksProxy != null } }
val sessionMode = remember { stateGetOrPut("sessionMode") { TransportSessionMode.User} }
- val networkProxyHostPort = remember { stateGetOrPut("networkHostProxyPort") {
- var proxy = (socksProxy ?: chatModel.controller.appPrefs.networkProxyHostPort.get())
- if (proxy?.startsWith(":") == true) proxy = "localhost$proxy"
- proxy
- }
+ val networkProxy = remember { stateGetOrPut("networkProxy") {
+ linkNetworkProxy
+ ?: if (legacyLinkSocksProxy != null) {
+ NetworkProxy(host = legacyLinkSocksProxy.substringBefore(":").ifBlank { "localhost" }, port = legacyLinkSocksProxy.substringAfter(":").toIntOrNull() ?: 9050)
+ } else {
+ appPrefs.networkProxy.get()
+ }
+ }
}
val netCfg = rememberSaveable(stateSaver = serializableSaver()) {
- mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = socksProxy, sessionMode = sessionMode.value))
+ mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = linkNetworkProxy?.toProxyString() ?: legacyLinkSocksProxy, sessionMode = sessionMode.value))
}
SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings).uppercase()) {
@@ -241,12 +251,12 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos
click = {
val updated = netCfg.value
.withOnionHosts(onionHosts.value)
- .withHostPort(if (networkUseSocksProxy.value) networkProxyHostPort.value else null, null)
+ .withProxy(if (networkUseSocksProxy.value) networkProxy.value else null, null)
.copy(
sessionMode = sessionMode.value
)
withBGApi {
- state.value = MigrationToState.DatabaseInit(link, updated)
+ state.value = MigrationToState.DatabaseInit(link, updated, if (networkUseSocksProxy.value) networkProxy.value else null)
}
}
){}
@@ -255,8 +265,8 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos
SectionSpacer()
- val networkProxyHostPortPref = SharedPreference(get = { networkProxyHostPort.value }, set = {
- networkProxyHostPort.value = it
+ val networkProxyPref = SharedPreference(get = { networkProxy.value }, set = {
+ networkProxy.value = it
})
SectionView(stringResource(MR.strings.network_settings_title).uppercase()) {
OnionRelatedLayout(
@@ -264,13 +274,10 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos
networkUseSocksProxy,
onionHosts,
sessionMode,
- networkProxyHostPortPref,
+ networkProxyPref,
toggleSocksProxy = { enable ->
networkUseSocksProxy.value = enable
},
- useOnion = {
- onionHosts.value = it
- },
updateSessionMode = {
sessionMode.value = it
}
@@ -279,13 +286,13 @@ private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: Hos
}
@Composable
-private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg) {
+private fun MutableState.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg, networkProxy: NetworkProxy?) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_database_init).uppercase()) {}
ProgressView()
}
LaunchedEffect(Unit) {
- prepareDatabase(link, tempDatabaseFile, netCfg)
+ prepareDatabase(link, tempDatabaseFile, netCfg, networkProxy)
}
}
@@ -297,14 +304,15 @@ private fun MutableState.LinkDownloadingView(
archivePath: String,
tempDatabaseFile: File,
chatReceiver: MutableState,
- netCfg: NetCfg
+ netCfg: NetCfg,
+ networkProxy: NetworkProxy?
) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_downloading_details).uppercase()) {}
ProgressView()
}
LaunchedEffect(Unit) {
- startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg)
+ startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg, networkProxy)
}
}
@@ -319,14 +327,14 @@ private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) {
}
@Composable
-private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg) {
+private fun MutableState.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
SectionView(stringResource(MR.strings.migrate_to_device_download_failed).uppercase()) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_download),
text = stringResource(MR.strings.migrate_to_device_repeat_download),
textColor = MaterialTheme.colors.primary,
click = {
- state = MigrationToState.DatabaseInit(link, netCfg)
+ state = MigrationToState.DatabaseInit(link, netCfg, networkProxy)
}
) {}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again))
@@ -339,25 +347,25 @@ private fun MutableState.DownloadFailedView(link: String, cha
}
@Composable
-private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg) {
+private fun MutableState.ArchiveImportView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {}
ProgressView()
}
LaunchedEffect(Unit) {
- importArchive(archivePath, netCfg)
+ importArchive(archivePath, netCfg, networkProxy)
}
}
@Composable
-private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg) {
+private fun MutableState.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) {
SettingsActionItemWithContent(
icon = painterResource(MR.images.ic_download),
text = stringResource(MR.strings.migrate_to_device_repeat_import),
textColor = MaterialTheme.colors.primary,
click = {
- state = MigrationToState.ArchiveImport(archivePath, netCfg)
+ state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy)
}
) {}
SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again))
@@ -365,7 +373,7 @@ private fun MutableState.ArchiveImportFailedView(archivePath:
}
@Composable
-private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg) {
+private fun MutableState.PassphraseEnteringView(currentKey: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
val currentKey = rememberSaveable { mutableStateOf(currentKey) }
val verifyingPassphrase = rememberSaveable { mutableStateOf(false) }
val useKeychain = rememberSaveable { mutableStateOf(appPreferences.storeDBPassphrase.get()) }
@@ -395,9 +403,9 @@ private fun MutableState.PassphraseEnteringView(currentKey: S
val (status, _) = chatInitTemporaryDatabase(dbAbsolutePrefixPath, key = currentKey.value, confirmation = MigrationConfirmation.YesUp)
val success = status == DBMigrationResult.OK || status == DBMigrationResult.InvalidConfirmation
if (success) {
- state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg)
+ state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg, networkProxy)
} else if (status is DBMigrationResult.ErrorMigration) {
- state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg)
+ state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg, networkProxy)
} else {
showErrorOnMigrationIfNeeded(status)
}
@@ -414,7 +422,7 @@ private fun MutableState.PassphraseEnteringView(currentKey: S
}
@Composable
-private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg) {
+private fun MutableState.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?) {
data class Tuple4(val a: A, val b: B, val c: C, val d: D)
val (header: String, button: String?, footer: String, confirmation: MigrationConfirmation?) = when (status) {
is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) {
@@ -449,7 +457,7 @@ private fun MutableState.MigrationConfirmationView(status: DB
text = button,
textColor = MaterialTheme.colors.primary,
click = {
- state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg)
+ state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg, networkProxy)
}
) {}
}
@@ -458,13 +466,13 @@ private fun MutableState.MigrationConfirmationView(status: DB
}
@Composable
-private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) {
+private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) {
Box {
SectionView(stringResource(MR.strings.migrate_to_device_migrating).uppercase()) {}
ProgressView()
}
LaunchedEffect(Unit) {
- startChat(passphrase, confirmation, useKeychain, netCfg, close)
+ startChat(passphrase, confirmation, useKeychain, netCfg, networkProxy, close)
}
}
@@ -476,19 +484,21 @@ private fun ProgressView() {
private suspend fun MutableState.checkUserLink(link: String) {
if (strHasSimplexFileLink(link.trim())) {
val data = MigrationFileLinkData.readFromLink(link)
- val hasOnionConfigured = data?.networkConfig?.hasOnionConfigured() ?: false
+ val hasProxyConfigured = data?.networkConfig?.hasProxyConfigured() ?: false
val networkConfig = data?.networkConfig?.transformToPlatformSupported()
// If any of iOS or Android had onion enabled, show onion screen
- if (hasOnionConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) {
- state = MigrationToState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode)
- MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode))
+ if (hasProxyConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) {
+ state = MigrationToState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode)
+ MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.legacySocksProxy, networkConfig.networkProxy, networkConfig.hostMode, networkConfig.requiredHostMode))
} else {
val current = getNetCfg()
state = MigrationToState.DatabaseInit(link.trim(), current.copy(
- socksProxy = networkConfig?.socksProxy,
+ socksProxy = null,
hostMode = networkConfig?.hostMode ?: current.hostMode,
requiredHostMode = networkConfig?.requiredHostMode ?: current.requiredHostMode
- ))
+ ),
+ networkProxy = null
+ )
}
} else {
AlertManager.shared.showAlertMsg(
@@ -502,6 +512,7 @@ private fun MutableState.prepareDatabase(
link: String,
tempDatabaseFile: File,
netCfg: NetCfg,
+ networkProxy: NetworkProxy?
) {
withLongRunningApi {
val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, netCfg)
@@ -513,7 +524,7 @@ private fun MutableState.prepareDatabase(
}
val (ctrl, user) = ctrlAndUser
- state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg)
+ state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg, networkProxy)
}
}
@@ -526,13 +537,14 @@ private fun MutableState.startDownloading(
link: String,
archivePath: String,
netCfg: NetCfg,
+ networkProxy: NetworkProxy?
) {
withBGApi {
chatReceiver.value = MigrationToChatReceiver(ctrl, tempDatabaseFile) { msg ->
when (msg) {
is CR.RcvFileProgressXFTP -> {
- state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, ctrl)
- MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg))
+ state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, networkProxy, ctrl)
+ MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg, networkProxy))
}
is CR.RcvStandaloneFileComplete -> {
delay(500)
@@ -540,8 +552,8 @@ private fun MutableState.startDownloading(
if (state == null) {
MigrationToDeviceState.save(null)
} else {
- state = MigrationToState.ArchiveImport(archivePath, netCfg)
- MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg))
+ state = MigrationToState.ArchiveImport(archivePath, netCfg, networkProxy)
+ MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg, networkProxy))
}
}
is CR.RcvFileError -> {
@@ -549,7 +561,7 @@ private fun MutableState.startDownloading(
generalGetString(MR.strings.migrate_to_device_download_failed),
generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid)
)
- state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg)
+ state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy)
}
is CR.ChatRespError -> {
if (msg.chatError is ChatError.ChatErrorChat && msg.chatError.errorType is ChatErrorType.NoRcvFileUser) {
@@ -557,7 +569,7 @@ private fun MutableState.startDownloading(
generalGetString(MR.strings.migrate_to_device_download_failed),
generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid)
)
- state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg)
+ state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy)
} else {
Log.d(TAG, "unsupported error: ${msg.responseType}, ${json.encodeToString(msg.chatError)}")
}
@@ -569,7 +581,7 @@ private fun MutableState.startDownloading(
val (res, error) = controller.downloadStandaloneFile(user, link, CryptoFile.plain(File(archivePath).path), ctrl)
if (res == null) {
- state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg)
+ state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg, networkProxy)
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.migrate_to_device_error_downloading_archive),
error
@@ -578,7 +590,7 @@ private fun MutableState.startDownloading(
}
}
-private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg) {
+private fun MutableState.importArchive(archivePath: String, netCfg: NetCfg, networkProxy: NetworkProxy?) {
withLongRunningApi {
try {
if (ChatController.ctrl == null || ChatController.ctrl == -1L) {
@@ -592,14 +604,14 @@ private fun MutableState.importArchive(archivePath: String, n
if (archiveErrors.isNotEmpty()) {
showArchiveImportedWithErrorsAlert(archiveErrors)
}
- state = MigrationToState.Passphrase("", netCfg)
- MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg))
+ state = MigrationToState.Passphrase("", netCfg, networkProxy)
+ MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg, networkProxy))
} catch (e: Exception) {
- state = MigrationToState.ArchiveImportFailed(archivePath, netCfg)
+ state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy)
AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_importing_database), e.stackTraceToString())
}
} catch (e: Exception) {
- state = MigrationToState.ArchiveImportFailed(archivePath, netCfg)
+ state = MigrationToState.ArchiveImportFailed(archivePath, netCfg, networkProxy)
AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_deleting_database), e.stackTraceToString())
}
}
@@ -609,7 +621,7 @@ private suspend fun stopArchiveDownloading(fileId: Long, ctrl: ChatCtrl) {
controller.apiCancelFile(null, fileId, ctrl)
}
-private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) {
+private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, networkProxy: NetworkProxy?, close: () -> Unit) {
if (useKeychain) {
ksDatabasePassword.set(passphrase)
} else {
@@ -621,7 +633,8 @@ private fun startChat(passphrase: String, confirmation: MigrationConfirmation, u
try {
initChatController(useKey = passphrase, confirmMigrations = confirmation) { CompletableDeferred(false) }
val appSettings = controller.apiGetAppSettings(AppSettings.current.prepareForExport()).copy(
- networkConfig = netCfg
+ networkConfig = netCfg,
+ networkProxy = networkProxy
)
finishMigration(appSettings, close)
} catch (e: Exception) {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt
index 3c8ab2b70a..6dc0f74df3 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt
@@ -1,10 +1,10 @@
package chat.simplex.common.views.usersettings
import SectionBottomSpacer
-import SectionCustomFooter
import SectionDividerSpaced
import SectionItemView
import SectionItemWithValue
+import SectionTextFooter
import SectionView
import SectionViewSelectableCards
import androidx.compose.desktop.ui.tooling.preview.Preview
@@ -40,12 +40,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
val currentCfg = remember { stateGetOrPut("currentCfg") { controller.getNetCfg() } }
val currentCfgVal = currentCfg.value // used only on initialization
- val onionHosts = remember { mutableStateOf(currentCfgVal.onionHosts) }
val sessionMode = remember { mutableStateOf(currentCfgVal.sessionMode) }
val smpProxyMode = remember { mutableStateOf(currentCfgVal.smpProxyMode) }
val smpProxyFallback = remember { mutableStateOf(currentCfgVal.smpProxyFallback) }
- val networkUseSocksProxy: MutableState = remember { mutableStateOf(currentCfgVal.useSocksProxy) }
val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) }
val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) }
val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) }
@@ -90,11 +88,10 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
tcpKeepAlive = tcpKeepAlive,
smpPingInterval = networkSMPPingInterval.value,
smpPingCount = networkSMPPingCount.value
- ).withOnionHosts(onionHosts.value)
+ ).withOnionHosts(currentCfg.value.onionHosts)
}
fun updateView(cfg: NetCfg) {
- onionHosts.value = cfg.onionHosts
sessionMode.value = cfg.sessionMode
smpProxyMode.value = cfg.smpProxyMode
smpProxyFallback.value = cfg.smpProxyFallback
@@ -148,10 +145,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
) {
AdvancedNetworkSettingsLayout(
currentRemoteHost = currentRemoteHost,
- networkUseSocksProxy = networkUseSocksProxy,
developerTools = developerTools,
- onionHosts = onionHosts,
- useOnion = { onionHosts.value = it; currentCfg.value = currentCfg.value.withOnionHosts(it) },
sessionMode = sessionMode,
smpProxyMode = smpProxyMode,
smpProxyFallback = smpProxyFallback,
@@ -183,10 +177,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
@Composable fun AdvancedNetworkSettingsLayout(
currentRemoteHost: RemoteHostInfo?,
- networkUseSocksProxy: State,
developerTools: Boolean,
- onionHosts: MutableState,
- useOnion: (OnionHosts) -> Unit,
sessionMode: MutableState,
smpProxyMode: MutableState,
smpProxyFallback: MutableState,
@@ -223,21 +214,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { derivedStateOf { smpProxyMode.value != SMPProxyMode.Never } })
SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy)
}
- SectionCustomFooter {
- Text(stringResource(MR.strings.private_routing_explanation))
- }
- SectionDividerSpaced(maxTopPadding = true)
- }
-
- if (currentRemoteHost == null && networkUseSocksProxy.value) {
- SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) {
- UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion)
- SectionCustomFooter {
- Column {
- Text(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported))
- }
- }
- }
+ SectionTextFooter(stringResource(MR.strings.private_routing_explanation))
SectionDividerSpaced(maxTopPadding = true)
}
@@ -562,7 +539,6 @@ fun PreviewAdvancedNetworkSettingsLayout() {
SimpleXTheme {
AdvancedNetworkSettingsLayout(
currentRemoteHost = null,
- networkUseSocksProxy = remember { mutableStateOf(false) },
developerTools = false,
sessionMode = remember { mutableStateOf(TransportSessionMode.User) },
smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) },
@@ -577,8 +553,6 @@ fun PreviewAdvancedNetworkSettingsLayout() {
networkTCPKeepIdle = remember { mutableStateOf(10) },
networkTCPKeepIntvl = remember { mutableStateOf(10) },
networkTCPKeepCnt = remember { mutableStateOf(10) },
- onionHosts = remember { mutableStateOf(OnionHosts.PREFER) },
- useOnion = {},
updateSessionMode = {},
updateSMPProxyMode = {},
updateSMPProxyFallback = {},
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt
index 5bcb0a545d..5272353c20 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt
@@ -1,10 +1,10 @@
package chat.simplex.common.views.usersettings
import SectionBottomSpacer
-import SectionCustomFooter
import SectionDividerSpaced
import SectionItemView
import SectionItemWithValue
+import SectionTextFooter
import SectionView
import SectionViewSelectable
import TextIconSpaced
@@ -37,10 +37,11 @@ fun NetworkAndServersView() {
val netCfg = remember { chatModel.controller.getNetCfg() }
val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) }
- val proxyPort = remember { derivedStateOf { chatModel.controller.appPrefs.networkProxyHostPort.state.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } }
+ val proxyPort = remember { derivedStateOf { appPrefs.networkProxy.state.value.port } }
NetworkAndServersLayout(
currentRemoteHost = currentRemoteHost,
networkUseSocksProxy = networkUseSocksProxy,
+ onionHosts = remember { mutableStateOf(netCfg.onionHosts) },
toggleSocksProxy = { enable ->
val def = NetCfg.defaults
val proxyDef = NetCfg.proxyDefaults
@@ -51,7 +52,7 @@ fun NetworkAndServersView() {
confirmText = generalGetString(MR.strings.confirm_verb),
onConfirm = {
withBGApi {
- var conf = controller.getNetCfg().withHostPort(controller.appPrefs.networkProxyHostPort.get())
+ var conf = controller.getNetCfg().withProxy(controller.appPrefs.networkProxy.get())
if (conf.tcpConnectTimeout == def.tcpConnectTimeout) {
conf = conf.copy(tcpConnectTimeout = proxyDef.tcpConnectTimeout)
}
@@ -104,6 +105,7 @@ fun NetworkAndServersView() {
@Composable fun NetworkAndServersLayout(
currentRemoteHost: RemoteHostInfo?,
networkUseSocksProxy: MutableState,
+ onionHosts: MutableState,
toggleSocksProxy: (Boolean) -> Unit,
) {
val m = chatModel
@@ -120,14 +122,10 @@ fun NetworkAndServersView() {
if (currentRemoteHost == null) {
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
- SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxyHostPort, false, it) }})
+ SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, appPrefs.networkProxy, onionHosts, sessionMode = appPrefs.networkSessionMode.get(), false, it) }})
SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showCustomModal { AdvancedNetworkSettingsView(showModal, it) } })
if (networkUseSocksProxy.value) {
- SectionCustomFooter {
- Column {
- Text(annotatedStringResource(MR.strings.socks_proxy_setting_limitations))
- }
- }
+ SectionTextFooter(annotatedStringResource(MR.strings.socks_proxy_setting_limitations))
SectionDividerSpaced(maxTopPadding = true)
} else {
SectionDividerSpaced()
@@ -158,16 +156,14 @@ fun NetworkAndServersView() {
networkUseSocksProxy: MutableState,
onionHosts: MutableState,
sessionMode: MutableState,
- networkProxyHostPort: SharedPreference,
+ networkProxy: SharedPreference,
toggleSocksProxy: (Boolean) -> Unit,
- useOnion: (OnionHosts) -> Unit,
updateSessionMode: (TransportSessionMode) -> Unit,
) {
val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.fullscreen.showModal(content = it) }
val showCustomModal = { it: @Composable (close: () -> Unit) -> Unit -> ModalManager.fullscreen.showCustomModal { close -> it(close) }}
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
- SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxyHostPort, true, it) } })
- UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion)
+ SettingsActionItem(painterResource(MR.images.ic_settings_ethernet), stringResource(MR.strings.network_socks_proxy_settings), { showCustomModal { SocksProxySettings(networkUseSocksProxy.value, networkProxy, onionHosts, sessionMode.value, true, it) } })
if (developerTools) {
SessionModePicker(sessionMode, showModal, updateSessionMode)
}
@@ -205,46 +201,98 @@ fun UseSocksProxySwitch(
@Composable
fun SocksProxySettings(
networkUseSocksProxy: Boolean,
- networkProxyHostPort: SharedPreference = appPrefs.networkProxyHostPort,
+ networkProxy: SharedPreference,
+ onionHosts: MutableState,
+ sessionMode: TransportSessionMode,
migration: Boolean,
close: () -> Unit
) {
- val defaultHostPort = remember { "localhost:9050" }
- val hostPortSaved by remember { networkProxyHostPort.state }
+ val networkProxySaved by remember { networkProxy.state }
+ val onionHostsSaved = remember { mutableStateOf(onionHosts.value) }
+
+ val usernameUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
+ mutableStateOf(TextFieldValue(networkProxySaved.username))
+ }
+ val passwordUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
+ mutableStateOf(TextFieldValue(networkProxySaved.password))
+ }
val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
- mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost"))
+ mutableStateOf(TextFieldValue(networkProxySaved.host))
}
val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
- mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050"))
+ mutableStateOf(TextFieldValue(networkProxySaved.port.toString()))
}
- val save = {
- val oldValue = networkProxyHostPort.get()
- networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text)
- if (networkUseSocksProxy && !migration) {
- withBGApi {
- if (!controller.apiSetNetworkConfig(controller.getNetCfg())) {
- networkProxyHostPort.set(oldValue)
- }
+ val proxyAuthRandomUnsaved = rememberSaveable { mutableStateOf(networkProxySaved.auth == NetworkProxyAuth.ISOLATE) }
+ LaunchedEffect(proxyAuthRandomUnsaved.value) {
+ if (!proxyAuthRandomUnsaved.value && onionHosts.value != OnionHosts.NEVER) {
+ onionHosts.value = OnionHosts.NEVER
+ }
+ }
+ val proxyAuthModeUnsaved = remember(proxyAuthRandomUnsaved.value, usernameUnsaved.value.text, passwordUnsaved.value.text) {
+ derivedStateOf {
+ if (proxyAuthRandomUnsaved.value) {
+ NetworkProxyAuth.ISOLATE
+ } else {
+ NetworkProxyAuth.USERNAME
}
}
}
- val saveAndClose = {
- val oldValue = networkProxyHostPort.get()
- networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text)
+
+ val save: (Boolean) -> Unit = { closeOnSuccess ->
+ val oldValue = networkProxy.get()
+ usernameUnsaved.value = usernameUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) usernameUnsaved.value.text.trim() else "")
+ passwordUnsaved.value = passwordUnsaved.value.copy(if (proxyAuthModeUnsaved.value == NetworkProxyAuth.USERNAME) passwordUnsaved.value.text.trim() else "")
+ hostUnsaved.value = hostUnsaved.value.copy(hostUnsaved.value.text.trim())
+ portUnsaved.value = portUnsaved.value.copy(portUnsaved.value.text.trim())
+
+ networkProxy.set(
+ NetworkProxy(
+ username = usernameUnsaved.value.text,
+ password = passwordUnsaved.value.text,
+ host = hostUnsaved.value.text,
+ port = portUnsaved.value.text.toIntOrNull() ?: 9050,
+ auth = proxyAuthModeUnsaved.value
+ )
+ )
+ val oldCfg = controller.getNetCfg()
+ val cfg = oldCfg.withOnionHosts(onionHosts.value)
+ val oldOnionHosts = onionHostsSaved.value
+ onionHostsSaved.value = onionHosts.value
+
+ if (!migration) {
+ controller.setNetCfg(cfg)
+ }
if (networkUseSocksProxy && !migration) {
withBGApi {
- if (controller.apiSetNetworkConfig(controller.getNetCfg())) {
- close()
+ if (controller.apiSetNetworkConfig(cfg, showAlertOnError = false)) {
+ onionHosts.value = cfg.onionHosts
+ onionHostsSaved.value = onionHosts.value
+ if (closeOnSuccess) {
+ close()
+ }
} else {
- networkProxyHostPort.set(oldValue)
+ controller.setNetCfg(oldCfg)
+ networkProxy.set(oldValue)
+ onionHostsSaved.value = oldOnionHosts
+ showWrongProxyConfigAlert()
}
}
}
}
- val saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) ||
- remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value ||
- remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value
- val resetDisabled = hostUnsaved.value.text + ":" + portUnsaved.value.text == defaultHostPort
+ val saveDisabled =
+ (
+ networkProxySaved.username == usernameUnsaved.value.text.trim() &&
+ networkProxySaved.password == passwordUnsaved.value.text.trim() &&
+ networkProxySaved.host == hostUnsaved.value.text.trim() &&
+ networkProxySaved.port.toString() == portUnsaved.value.text.trim() &&
+ networkProxySaved.auth == proxyAuthModeUnsaved.value &&
+ onionHosts.value == onionHostsSaved.value
+ ) ||
+ !validCredential(usernameUnsaved.value.text) ||
+ !validCredential(passwordUnsaved.value.text) ||
+ !validHost(hostUnsaved.value.text) ||
+ !validPort(portUnsaved.value.text)
+ val resetDisabled = hostUnsaved.value.text.trim() == "localhost" && portUnsaved.value.text.trim() == "9050" && proxyAuthRandomUnsaved.value && onionHosts.value == NetCfg.defaults.onionHosts
ModalView(
close = {
if (saveDisabled) {
@@ -252,7 +300,7 @@ fun SocksProxySettings(
} else {
showUnsavedSocksHostPortAlert(
confirmText = generalGetString(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save),
- save = saveAndClose,
+ save = { save(true) },
close = close
)
}
@@ -263,38 +311,78 @@ fun SocksProxySettings(
.fillMaxWidth()
) {
AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings))
- SectionView(contentPadding = PaddingValues(horizontal = DEFAULT_PADDING)) {
- DefaultConfigurableTextField(
- hostUnsaved,
- stringResource(MR.strings.host_verb),
- modifier = Modifier.fillMaxWidth(),
- isValid = ::validHost,
- keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
- keyboardType = KeyboardType.Text,
- )
- DefaultConfigurableTextField(
- portUnsaved,
- stringResource(MR.strings.port_verb),
- modifier = Modifier.fillMaxWidth(),
- isValid = ::validPort,
- keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save() }),
- keyboardType = KeyboardType.Number,
- )
+ SectionView(stringResource(MR.strings.network_socks_proxy).uppercase()) {
+ Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
+ DefaultConfigurableTextField(
+ hostUnsaved,
+ stringResource(MR.strings.host_verb),
+ modifier = Modifier.fillMaxWidth(),
+ isValid = ::validHost,
+ keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
+ keyboardType = KeyboardType.Text,
+ )
+ DefaultConfigurableTextField(
+ portUnsaved,
+ stringResource(MR.strings.port_verb),
+ modifier = Modifier.fillMaxWidth(),
+ isValid = ::validPort,
+ keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done); save(false) }),
+ keyboardType = KeyboardType.Number,
+ )
+ }
+
+ UseOnionHosts(onionHosts, rememberUpdatedState(networkUseSocksProxy && proxyAuthRandomUnsaved.value)) {
+ onionHosts.value = it
+ }
+ SectionTextFooter(annotatedStringResource(MR.strings.disable_onion_hosts_when_not_supported))
}
- SectionDividerSpaced(maxBottomPadding = false)
+ SectionDividerSpaced(maxTopPadding = true)
+
+ SectionView(stringResource(MR.strings.network_proxy_auth).uppercase()) {
+ PreferenceToggle(
+ stringResource(MR.strings.network_proxy_random_credentials),
+ checked = proxyAuthRandomUnsaved.value,
+ onChange = { proxyAuthRandomUnsaved.value = it }
+ )
+ if (!proxyAuthRandomUnsaved.value) {
+ Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
+ DefaultConfigurableTextField(
+ usernameUnsaved,
+ stringResource(MR.strings.network_proxy_username),
+ modifier = Modifier.fillMaxWidth(),
+ isValid = ::validCredential,
+ keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
+ keyboardType = KeyboardType.Text,
+ )
+ DefaultConfigurableTextField(
+ passwordUnsaved,
+ stringResource(MR.strings.network_proxy_password),
+ modifier = Modifier.fillMaxWidth(),
+ isValid = ::validCredential,
+ keyboardActions = KeyboardActions(onNext = { defaultKeyboardAction(ImeAction.Next) }),
+ keyboardType = KeyboardType.Password,
+ )
+ }
+ }
+ SectionTextFooter(proxyAuthFooter(usernameUnsaved.value.text, passwordUnsaved.value.text, proxyAuthModeUnsaved.value, sessionMode))
+ }
+
+ SectionDividerSpaced(maxBottomPadding = false, maxTopPadding = true)
SectionView {
SectionItemView({
- val newHost = defaultHostPort.split(":").first()
- val newPort = defaultHostPort.split(":").last()
- hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length))
- portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length))
+ hostUnsaved.value = hostUnsaved.value.copy("localhost", TextRange(9))
+ portUnsaved.value = portUnsaved.value.copy("9050", TextRange(4))
+ usernameUnsaved.value = TextFieldValue()
+ passwordUnsaved.value = TextFieldValue()
+ proxyAuthRandomUnsaved.value = true
+ onionHosts.value = NetCfg.defaults.onionHosts
}, disabled = resetDisabled) {
Text(stringResource(MR.strings.network_options_reset_to_defaults), color = if (resetDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
}
SectionItemView(
- click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save() } else save() },
+ click = { if (networkUseSocksProxy && !migration) showUpdateNetworkSettingsDialog { save(false) } else save(false) },
disabled = saveDisabled
) {
Text(stringResource(if (networkUseSocksProxy && !migration) MR.strings.network_options_save_and_reconnect else MR.strings.network_options_save), color = if (saveDisabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
@@ -305,6 +393,12 @@ fun SocksProxySettings(
}
}
+private fun proxyAuthFooter(username: String, password: String, auth: NetworkProxyAuth, sessionMode: TransportSessionMode): String = when {
+ auth == NetworkProxyAuth.ISOLATE -> generalGetString(if (sessionMode == TransportSessionMode.User) MR.strings.network_proxy_auth_mode_isolate_by_auth_user else MR.strings.network_proxy_auth_mode_isolate_by_auth_entity)
+ username.isBlank() && password.isBlank() -> generalGetString(MR.strings.network_proxy_auth_mode_no_auth)
+ else -> generalGetString(MR.strings.network_proxy_auth_mode_username_password)
+}
+
private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit, close: () -> Unit) {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.update_network_settings_question),
@@ -319,7 +413,6 @@ private fun showUnsavedSocksHostPortAlert(confirmText: String, save: () -> Unit,
fun UseOnionHosts(
onionHosts: MutableState,
enabled: State,
- showModal: (@Composable ModalData.() -> Unit) -> Unit,
useOnion: (OnionHosts) -> Unit,
) {
val values = remember {
@@ -331,36 +424,29 @@ fun UseOnionHosts(
}
}
}
- val onSelected = {
- showModal {
- ColumnWithScrollBar(
- Modifier.fillMaxWidth(),
- ) {
- AppBarTitle(stringResource(MR.strings.network_use_onion_hosts))
- SectionViewSelectable(null, onionHosts, values, useOnion)
- }
- }
- }
- if (enabled.value) {
- SectionItemWithValue(
- generalGetString(MR.strings.network_use_onion_hosts),
- onionHosts,
- values,
- icon = painterResource(MR.images.ic_security),
- enabled = enabled,
- onSelected = onSelected
- )
- } else {
- // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before
- SectionItemWithValue(
- generalGetString(MR.strings.network_use_onion_hosts),
- remember { mutableStateOf(OnionHosts.NEVER) },
- listOf(ValueTitleDesc(OnionHosts.NEVER, generalGetString(MR.strings.network_use_onion_hosts_no), AnnotatedString(generalGetString(MR.strings.network_use_onion_hosts_no_desc)))),
- icon = painterResource(MR.images.ic_security),
- enabled = enabled,
- onSelected = {}
- )
+ Column {
+ if (enabled.value) {
+ ExposedDropDownSettingRow(
+ generalGetString(MR.strings.network_use_onion_hosts),
+ values.map { it.value to it.title },
+ onionHosts,
+ icon = painterResource(MR.images.ic_security),
+ enabled = enabled,
+ onSelected = useOnion
+ )
+ } else {
+ // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before
+ ExposedDropDownSettingRow(
+ generalGetString(MR.strings.network_use_onion_hosts),
+ listOf(OnionHosts.NEVER to generalGetString(MR.strings.network_use_onion_hosts_no)),
+ remember { mutableStateOf(OnionHosts.NEVER) },
+ icon = painterResource(MR.images.ic_security),
+ enabled = enabled,
+ onSelected = {}
+ )
+ }
+ SectionTextFooter(values.first { it.value == onionHosts.value }.description)
}
}
@@ -398,12 +484,8 @@ fun SessionModePicker(
)
}
-// https://stackoverflow.com/a/106223
-private fun validHost(s: String): Boolean {
- val validIp = Regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])[.]){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")
- val validHostname = Regex("^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])[.])*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$");
- return s.matches(validIp) || s.matches(validHostname)
-}
+private fun validHost(s: String): Boolean =
+ !s.contains('@')
// https://ihateregex.io/expr/port/
fun validPort(s: String): Boolean {
@@ -411,6 +493,16 @@ fun validPort(s: String): Boolean {
return s.isNotBlank() && s.matches(validPort)
}
+private fun validCredential(s: String): Boolean =
+ !s.contains(':') && !s.contains('@')
+
+fun showWrongProxyConfigAlert() {
+ AlertManager.shared.showAlertMsg(
+ title = generalGetString(MR.strings.network_proxy_incorrect_config_title),
+ text = generalGetString(MR.strings.network_proxy_incorrect_config_desc),
+ )
+}
+
fun showUpdateNetworkSettingsDialog(
title: String,
startsWith: String = "",
@@ -435,6 +527,7 @@ fun PreviewNetworkAndServersLayout() {
NetworkAndServersLayout(
currentRemoteHost = null,
networkUseSocksProxy = remember { mutableStateOf(true) },
+ onionHosts = remember { mutableStateOf(OnionHosts.PREFER) },
toggleSocksProxy = {},
)
}
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 b81f733cd1..7ebfb7ce54 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -765,7 +765,17 @@
SOCKS proxy
SOCKS proxy settings
Use SOCKS proxy
+ Proxy authentication
+ Use random credentials
+ Use different proxy credentials for each profile.
+ Use different proxy credentials for each connection.
+ Do not use credentials with proxy.
+ Your credentials may be sent unencrypted.
+ Username
+ Password
port %d
+ Error saving proxy
+ Make sure proxy configuration is correct.
Host
Port
Use SOCKS proxy?
From fe0013c4a92dfb039eccacbe1ac791ee68780abf Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin
Date: Tue, 24 Sep 2024 17:51:34 +0100
Subject: [PATCH 7/8] ios: update core library
---
apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++-----------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj
index 399d88b39f..3dcdba3865 100644
--- a/apps/ios/SimpleX.xcodeproj/project.pbxproj
+++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj
@@ -216,11 +216,11 @@
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
- E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84482C8220D0008C24D1 /* libffi.a */; };
- E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD84492C8220D0008C24D1 /* libgmpxx.a */; };
- E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */; };
- E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */; };
- E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5BD844C2C8220D0008C24D1 /* libgmp.a */; };
+ E5CC47842CA31C3A00551ACF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC477F2CA31C3900551ACF /* libgmpxx.a */; };
+ E5CC47852CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47802CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a */; };
+ E5CC47862CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47812CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a */; };
+ E5CC47872CA31C3A00551ACF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47822CA31C3A00551ACF /* libgmp.a */; };
+ E5CC47882CA31C3A00551ACF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5CC47832CA31C3A00551ACF /* libffi.a */; };
E5DCF8DB2C56FAC1007928CC /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; };
@@ -554,11 +554,11 @@
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = ""; };
- E5BD84482C8220D0008C24D1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; };
- E5BD84492C8220D0008C24D1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; };
- E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a"; sourceTree = ""; };
- E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a"; sourceTree = ""; };
- E5BD844C2C8220D0008C24D1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; };
+ E5CC477F2CA31C3900551ACF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; };
+ E5CC47802CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a"; sourceTree = ""; };
+ E5CC47812CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a"; sourceTree = ""; };
+ E5CC47822CA31C3A00551ACF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; };
+ E5CC47832CA31C3A00551ACF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; };
E5DCF9702C590272007928CC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
E5DCF9722C590274007928CC /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = ""; };
E5DCF9732C590275007928CC /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; };
@@ -649,14 +649,14 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ E5CC47852CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a in Frameworks */,
+ E5CC47872CA31C3A00551ACF /* libgmp.a in Frameworks */,
+ E5CC47842CA31C3A00551ACF /* libgmpxx.a in Frameworks */,
+ E5CC47882CA31C3A00551ACF /* libffi.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
- E5BD844E2C8220D0008C24D1 /* libgmpxx.a in Frameworks */,
- E5BD84512C8220D0008C24D1 /* libgmp.a in Frameworks */,
+ E5CC47862CA31C3A00551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
- E5BD84502C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a in Frameworks */,
- E5BD844D2C8220D0008C24D1 /* libffi.a in Frameworks */,
- E5BD844F2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -733,11 +733,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
- E5BD84482C8220D0008C24D1 /* libffi.a */,
- E5BD844C2C8220D0008C24D1 /* libgmp.a */,
- E5BD84492C8220D0008C24D1 /* libgmpxx.a */,
- E5BD844A2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob-ghc9.6.3.a */,
- E5BD844B2C8220D0008C24D1 /* libHSsimplex-chat-6.0.4.0-2x1D8vVukGZOGJwEVzeob.a */,
+ E5CC47832CA31C3A00551ACF /* libffi.a */,
+ E5CC47822CA31C3A00551ACF /* libgmp.a */,
+ E5CC477F2CA31C3900551ACF /* libgmpxx.a */,
+ E5CC47812CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP-ghc9.6.3.a */,
+ E5CC47802CA31C3900551ACF /* libHSsimplex-chat-6.0.5.0-3qcee2iGFVOIynW0cRTIaP.a */,
);
path = Libraries;
sourceTree = "";
From 2f730d54e9858452e87e641b7fd618c669da68aa Mon Sep 17 00:00:00 2001
From: Evgeny Poberezkin
Date: Tue, 24 Sep 2024 21:48:30 +0100
Subject: [PATCH 8/8] 6.0.5: ios 239, android 241, desktop 68
---
apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++++-----------
apps/multiplatform/gradle.properties | 8 ++---
2 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj
index 3dcdba3865..3f35bc3904 100644
--- a/apps/ios/SimpleX.xcodeproj/project.pbxproj
+++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj
@@ -1887,7 +1887,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1912,7 +1912,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES_THIN;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1936,7 +1936,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1961,7 +1961,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1977,11 +1977,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -1997,11 +1997,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2022,7 +2022,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2037,7 +2037,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2059,7 +2059,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2074,7 +2074,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2096,7 +2096,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2122,7 +2122,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2147,7 +2147,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2173,7 +2173,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2198,7 +2198,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2213,7 +2213,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2232,7 +2232,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 236;
+ CURRENT_PROJECT_VERSION = 239;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2247,7 +2247,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 6.0.4;
+ MARKETING_VERSION = 6.0.5;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties
index ac2fce0e12..47d9233de7 100644
--- a/apps/multiplatform/gradle.properties
+++ b/apps/multiplatform/gradle.properties
@@ -26,11 +26,11 @@ android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
-android.version_name=6.0.4
-android.version_code=237
+android.version_name=6.0.5
+android.version_code=241
-desktop.version_name=6.0.4
-desktop.version_code=65
+desktop.version_name=6.0.5
+desktop.version_code=68
kotlin.version=1.9.23
gradle.plugin.version=8.2.0