Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2024-04-20 19:39:40 +01:00
19 changed files with 414 additions and 103 deletions
@@ -51,9 +51,10 @@ struct AdvancedNetworkSettings: View {
}
.disabled(currentNetCfg == NetCfg.proxyDefaults)
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 45_000000], label: secondsLabel)
timeoutSettingPicker("TCP connection timeout", selection: $netCfg.tcpConnectTimeout, values: [5_000000, 7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 40_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout", selection: $netCfg.tcpTimeout, values: [5_000000, 7_000000, 10_000000, 15_000000, 20_000000, 30_000000], label: secondsLabel)
timeoutSettingPicker("Protocol timeout per KB", selection: $netCfg.tcpTimeoutPerKb, values: [2_500, 5_000, 10_000, 15_000, 20_000, 30_000], label: secondsLabel)
intSettingPicker("Receiving concurrency", selection: $netCfg.rcvConcurrency, values: [1, 2, 4, 8, 12, 16, 24], label: "")
timeoutSettingPicker("PING interval", selection: $netCfg.smpPingInterval, values: [120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000, 3600_000000], label: secondsLabel)
intSettingPicker("PING count", selection: $netCfg.smpPingCount, values: [1, 2, 3, 5, 8], label: "")
Toggle("Enable TCP keep-alive", isOn: $enableKeepAlive)
+20 -20
View File
@@ -29,6 +29,11 @@
5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; };
5C13730B28156D2700F43030 /* ContactConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C13730A28156D2700F43030 /* ContactConnectionView.swift */; };
5C1A4C1E27A715B700EAD5AD /* ChatItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */; };
5C22177A2BD40D1800A8B0E7 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217752BD40D1800A8B0E7 /* libgmp.a */; };
5C22177B2BD40D1800A8B0E7 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217762BD40D1800A8B0E7 /* libgmpxx.a */; };
5C22177C2BD40D1800A8B0E7 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217772BD40D1800A8B0E7 /* libffi.a */; };
5C22177D2BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217782BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O-ghc9.6.3.a */; };
5C22177E2BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C2217792BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O.a */; };
5C2E260727A2941F00F70299 /* SimpleXAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260627A2941F00F70299 /* SimpleXAPI.swift */; };
5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260A27A30CFA00F70299 /* ChatListView.swift */; };
5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E260E27A30FDC00F70299 /* ChatView.swift */; };
@@ -110,11 +115,6 @@
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
5CC83D1A2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D152BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a */; };
5CC83D1B2BCC504B00A0C558 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D162BCC504B00A0C558 /* libgmpxx.a */; };
5CC83D1C2BCC504B00A0C558 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D172BCC504B00A0C558 /* libgmp.a */; };
5CC83D1D2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D182BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a */; };
5CC83D1E2BCC504B00A0C558 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC83D192BCC504B00A0C558 /* libffi.a */; };
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -284,6 +284,11 @@
5C13730A28156D2700F43030 /* ContactConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionView.swift; sourceTree = "<group>"; };
5C13730C2815740A00F43030 /* DebugJSON.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = DebugJSON.playground; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
5C1A4C1D27A715B700EAD5AD /* ChatItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemView.swift; sourceTree = "<group>"; };
5C2217752BD40D1800A8B0E7 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C2217762BD40D1800A8B0E7 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C2217772BD40D1800A8B0E7 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C2217782BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O-ghc9.6.3.a"; sourceTree = "<group>"; };
5C2217792BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O.a"; sourceTree = "<group>"; };
5C245F3C2B501E98001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = "<group>"; };
5C245F3D2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = "tr.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
5C245F3E2B501F13001CC39F /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -408,11 +413,6 @@
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
5CC83D152BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a"; sourceTree = "<group>"; };
5CC83D162BCC504B00A0C558 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5CC83D172BCC504B00A0C558 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5CC83D182BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a"; sourceTree = "<group>"; };
5CC83D192BCC504B00A0C558 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
@@ -535,13 +535,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5CC83D1D2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a in Frameworks */,
5CC83D1B2BCC504B00A0C558 /* libgmpxx.a in Frameworks */,
5CC83D1C2BCC504B00A0C558 /* libgmp.a in Frameworks */,
5CC83D1A2BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a in Frameworks */,
5CC83D1E2BCC504B00A0C558 /* libffi.a in Frameworks */,
5C22177B2BD40D1800A8B0E7 /* libgmpxx.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5C22177D2BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O-ghc9.6.3.a in Frameworks */,
5C22177A2BD40D1800A8B0E7 /* libgmp.a in Frameworks */,
5C22177C2BD40D1800A8B0E7 /* libffi.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5C22177E2BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -605,11 +605,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
5CC83D192BCC504B00A0C558 /* libffi.a */,
5CC83D172BCC504B00A0C558 /* libgmp.a */,
5CC83D162BCC504B00A0C558 /* libgmpxx.a */,
5CC83D182BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv-ghc9.6.4.a */,
5CC83D152BCC504B00A0C558 /* libHSsimplex-chat-5.7.0.0-AhbVfRKDsEZ5w5ND1HSTLv.a */,
5C2217772BD40D1800A8B0E7 /* libffi.a */,
5C2217752BD40D1800A8B0E7 /* libgmp.a */,
5C2217762BD40D1800A8B0E7 /* libgmpxx.a */,
5C2217782BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O-ghc9.6.3.a */,
5C2217792BD40D1800A8B0E7 /* libHSsimplex-chat-5.7.0.0-KdKN1sKiHR7tY2gjTFt3O.a */,
);
path = Libraries;
sourceTree = "<group>";
+5 -2
View File
@@ -1257,6 +1257,7 @@ public struct NetCfg: Codable, Equatable {
public var tcpConnectTimeout: Int // microseconds
public var tcpTimeout: Int // microseconds
public var tcpTimeoutPerKb: Int // microseconds
public var rcvConcurrency: Int // pool size
public var tcpKeepAlive: KeepAliveOpts?
public var smpPingInterval: Int // microseconds
public var smpPingCount: Int // times
@@ -1265,9 +1266,10 @@ public struct NetCfg: Codable, Equatable {
public static let defaults: NetCfg = NetCfg(
socksProxy: nil,
sessionMode: TransportSessionMode.user,
tcpConnectTimeout: 20_000_000,
tcpConnectTimeout: 10_000_000,
tcpTimeout: 15_000_000,
tcpTimeoutPerKb: 10_000,
rcvConcurrency: 12,
tcpKeepAlive: KeepAliveOpts.defaults,
smpPingInterval: 1200_000_000,
smpPingCount: 3,
@@ -1277,9 +1279,10 @@ public struct NetCfg: Codable, Equatable {
public static let proxyDefaults: NetCfg = NetCfg(
socksProxy: nil,
sessionMode: TransportSessionMode.user,
tcpConnectTimeout: 30_000_000,
tcpConnectTimeout: 20_000_000,
tcpTimeout: 20_000_000,
tcpTimeoutPerKb: 15_000,
rcvConcurrency: 8,
tcpKeepAlive: KeepAliveOpts.defaults,
smpPingInterval: 1200_000_000,
smpPingCount: 3,
+5
View File
@@ -29,6 +29,7 @@ let GROUP_DEFAULT_NETWORK_SESSION_MODE = "networkSessionMode"
let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout"
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout"
let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb"
let GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY = "networkRcvConcurrency"
let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval"
let GROUP_DEFAULT_NETWORK_SMP_PING_COUNT = "networkSMPPingCount"
let GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE = "networkEnableKeepAlive"
@@ -55,6 +56,7 @@ public func registerGroupDefaults() {
GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout,
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout,
GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB: NetCfg.defaults.tcpTimeoutPerKb,
GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY: NetCfg.defaults.rcvConcurrency,
GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL: NetCfg.defaults.smpPingInterval,
GROUP_DEFAULT_NETWORK_SMP_PING_COUNT: NetCfg.defaults.smpPingCount,
GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE: NetCfg.defaults.enableKeepAlive,
@@ -278,6 +280,7 @@ public func getNetCfg() -> NetCfg {
let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT)
let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT)
let tcpTimeoutPerKb = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT_PER_KB)
let rcvConcurrency = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY)
let smpPingInterval = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
let smpPingCount = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_COUNT)
let enableKeepAlive = groupDefaults.bool(forKey: GROUP_DEFAULT_NETWORK_ENABLE_KEEP_ALIVE)
@@ -297,6 +300,7 @@ public func getNetCfg() -> NetCfg {
tcpConnectTimeout: tcpConnectTimeout,
tcpTimeout: tcpTimeout,
tcpTimeoutPerKb: tcpTimeoutPerKb,
rcvConcurrency: rcvConcurrency,
tcpKeepAlive: tcpKeepAlive,
smpPingInterval: smpPingInterval,
smpPingCount: smpPingCount,
@@ -310,6 +314,7 @@ public func setNetCfg(_ cfg: NetCfg) {
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)
groupDefaults.set(cfg.rcvConcurrency, forKey: GROUP_DEFAULT_NETWORK_RCV_CONCURRENCY)
groupDefaults.set(cfg.smpPingInterval, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)
groupDefaults.set(cfg.smpPingCount, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_COUNT)
if let tcpKeepAlive = cfg.tcpKeepAlive {
@@ -0,0 +1,235 @@
package chat.simplex.common.views.call
import android.content.Context
import android.media.*
import android.media.AudioManager.OnCommunicationDeviceChangedListener
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.runtime.*
import chat.simplex.common.platform.*
import dev.icerock.moko.resources.ImageResource
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
import java.util.concurrent.Executors
interface CallAudioDeviceManagerInterface {
val devices: State<List<AudioDeviceInfo>>
val currentDevice: MutableState<AudioDeviceInfo?>
fun start()
fun stop()
// AudioDeviceInfo.AudioDeviceType
fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyNonEarpiece: Boolean)
// AudioDeviceInfo.AudioDeviceType
fun selectDevice(id: Int)
companion object {
fun new(): CallAudioDeviceManagerInterface =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PostSCallAudioDeviceManager()
} else {
PreSCallAudioDeviceManager()
}
}
}
@RequiresApi(Build.VERSION_CODES.S)
class PostSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
override val devices: MutableState<List<AudioDeviceInfo>> = mutableStateOf(emptyList())
override val currentDevice: MutableState<AudioDeviceInfo?> = mutableStateOf(null)
private val audioCallback = object: AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
super.onAudioDevicesAdded(addedDevices)
val oldDevices = devices.value
devices.value = am.availableCommunicationDevices
Log.d(TAG, "Added audio devices2: ${devices.value.map { it.type }}")
if (devices.value.size - oldDevices.size > 0) {
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, false)
}
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
super.onAudioDevicesRemoved(removedDevices)
devices.value = am.availableCommunicationDevices
}
}
private val listener: OnCommunicationDeviceChangedListener = OnCommunicationDeviceChangedListener { device ->
devices.value = am.availableCommunicationDevices
currentDevice.value = device
}
override fun start() {
am.registerAudioDeviceCallback(audioCallback, null)
am.addOnCommunicationDeviceChangedListener(Executors.newSingleThreadExecutor(), listener)
}
override fun stop() {
am.unregisterAudioDeviceCallback(audioCallback)
am.removeOnCommunicationDeviceChangedListener(listener)
}
override fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyNonEarpiece: Boolean) {
Log.d(TAG, "selectLastExternalDeviceOrDefault: set audio mode, speaker enabled: $speaker")
am.mode = AudioManager.MODE_IN_COMMUNICATION
val commDevice = am.communicationDevice
if (keepAnyNonEarpiece && commDevice != null && commDevice.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
// some external device or speaker selected already, no need to change it
return
}
val preferredSecondaryDevice = if (speaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
val externalDevice = devices.value.lastOrNull { it.type != AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && it.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE }
// External device already selected
if (externalDevice != null && externalDevice.type == am.communicationDevice?.type) {
return
}
if (externalDevice != null) {
am.setCommunicationDevice(externalDevice)
} else if (am.communicationDevice?.type != preferredSecondaryDevice) {
am.availableCommunicationDevices.firstOrNull { it.type == preferredSecondaryDevice }?.let {
am.setCommunicationDevice(it)
}
}
}
override fun selectDevice(id: Int) {
am.mode = AudioManager.MODE_IN_COMMUNICATION
val device = devices.value.lastOrNull { it.id == id }
if (device != null && am.communicationDevice?.id != id ) {
am.setCommunicationDevice(device)
}
}
}
class PreSCallAudioDeviceManager: CallAudioDeviceManagerInterface {
private val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
override val devices: MutableState<List<AudioDeviceInfo>> = mutableStateOf(emptyList())
override val currentDevice: MutableState<AudioDeviceInfo?> = mutableStateOf(null)
private val audioCallback = object: AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
super.onAudioDevicesAdded(addedDevices)
val wasSize = devices.value.size
devices.value += addedDevices.filter { it.hasSupportedType() }
val addedCount = devices.value.size - wasSize
//if (addedCount > 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
// Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, false)
//}
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
super.onAudioDevicesRemoved(removedDevices)
val wasSize = devices.value.size
devices.value = devices.value.filterNot { removedDevices.any { rm -> rm.id == it.id } }
//val removedCount = wasSize - devices.value.size
//if (devices.value.count { it.hasSupportedType() } == 2 && chatModel.activeCall.value?.callState == CallState.Connected) {
// Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
selectLastExternalDeviceOrDefault(chatModel.activeCall.value?.supportsVideo() == true, true)
//}
}
}
override fun start() {
am.registerAudioDeviceCallback(audioCallback, null)
}
override fun stop() {
am.unregisterAudioDeviceCallback(audioCallback)
}
override fun selectLastExternalDeviceOrDefault(speaker: Boolean, keepAnyNonEarpiece: Boolean) {
Log.d(TAG, "selectLastExternalDeviceOrDefault: set audio mode, speaker enabled: $speaker")
val preferredSecondaryDevice = if (speaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
val externalDevice = devices.value.lastOrNull { it.hasSupportedType() && it.isSource && it.isSink && it.type != AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && it.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE }
if (externalDevice != null) {
selectDevice(externalDevice.id)
} else {
am.stopBluetoothSco()
am.isWiredHeadsetOn = false
am.isSpeakerphoneOn = preferredSecondaryDevice == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
am.isBluetoothScoOn = false
val newCurrentDevice = devices.value.firstOrNull { it.type == preferredSecondaryDevice }
adaptToCurrentlyActiveDevice(newCurrentDevice)
}
}
override fun selectDevice(id: Int) {
val device = devices.value.lastOrNull { it.id == id }
val isExternalDevice = device != null && device.type != AudioDeviceInfo.TYPE_BUILTIN_SPEAKER && device.type != AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
if (isExternalDevice) {
am.isSpeakerphoneOn = false
if (device?.type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
am.isWiredHeadsetOn = true
am.stopBluetoothSco()
am.isBluetoothScoOn = false
} else {
am.isWiredHeadsetOn = false
am.startBluetoothSco()
am.isBluetoothScoOn = true
}
adaptToCurrentlyActiveDevice(device)
} else {
am.stopBluetoothSco()
am.isWiredHeadsetOn = false
am.isSpeakerphoneOn = device?.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
am.isBluetoothScoOn = false
adaptToCurrentlyActiveDevice(device)
}
}
private fun adaptToCurrentlyActiveDevice(newCurrentDevice: AudioDeviceInfo?) {
currentDevice.value = newCurrentDevice
}
private fun AudioDeviceInfo.hasSupportedType(): Boolean = when (type) {
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> true
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> true
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> true
AudioDeviceInfo.TYPE_BLE_HEADSET -> true
AudioDeviceInfo.TYPE_BLE_SPEAKER -> true
AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> true
else -> false
}
}
val AudioDeviceInfo.icon: ImageResource
get() = when (this.type) {
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> MR.images.ic_volume_down
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> MR.images.ic_volume_up
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
AudioDeviceInfo.TYPE_BLE_HEADSET,
AudioDeviceInfo.TYPE_BLE_SPEAKER -> MR.images.ic_bluetooth
AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> MR.images.ic_headphones
AudioDeviceInfo.TYPE_USB_HEADSET, AudioDeviceInfo.TYPE_USB_DEVICE -> MR.images.ic_usb
else -> MR.images.ic_brand_awareness_filled
}
val AudioDeviceInfo.name: StringResource?
get() = when (this.type) {
AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> MR.strings.audio_device_earpiece
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> MR.strings.audio_device_speaker
AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
AudioDeviceInfo.TYPE_BLE_HEADSET,
AudioDeviceInfo.TYPE_BLE_SPEAKER -> null // Use product name instead
AudioDeviceInfo.TYPE_WIRED_HEADPHONES -> MR.strings.audio_device_wired_headphones
AudioDeviceInfo.TYPE_USB_HEADSET, AudioDeviceInfo.TYPE_USB_DEVICE -> null // Use product name instead
else -> null // Use product name instead
}
@@ -26,8 +26,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -39,14 +38,14 @@ import androidx.webkit.WebViewAssetLoader
import androidx.webkit.WebViewClientCompat
import chat.simplex.common.helpers.showAllowPermissionInSettingsAlert
import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.Contact
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.google.accompanist.permissions.*
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
@@ -71,7 +70,6 @@ fun activeCallDestroyWebView() = withApi {
actual fun ActiveCallView() {
val call = remember { chatModel.activeCall }.value
val scope = rememberCoroutineScope()
val audioViaBluetooth = rememberSaveable { mutableStateOf(false) }
val proximityLock = remember {
val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager)
if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
@@ -87,41 +85,16 @@ actual fun ActiveCallView() {
wasConnected.value = true
}
}
val callAudioDeviceManager = remember { CallAudioDeviceManagerInterface.new() }
DisposableEffect(Unit) {
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
var btDeviceCount = 0
val audioCallback = object: AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Added audio devices: ${addedDevices.map { it.type }}")
super.onAudioDevicesAdded(addedDevices)
val addedCount = addedDevices.count { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
btDeviceCount += addedCount
audioViaBluetooth.value = btDeviceCount > 0
if (addedCount > 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
// Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
setCallSound(chatModel.activeCall.value?.soundSpeaker ?: return, audioViaBluetooth)
}
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
Log.d(TAG, "Removed audio devices: ${removedDevices.map { it.type }}")
super.onAudioDevicesRemoved(removedDevices)
val removedCount = removedDevices.count { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
btDeviceCount -= removedCount
audioViaBluetooth.value = btDeviceCount > 0
if (btDeviceCount == 0 && chatModel.activeCall.value?.callState == CallState.Connected) {
// Setting params in Connected state makes sure that Bluetooth will NOT be broken on Android < 12
setCallSound(chatModel.activeCall.value?.soundSpeaker ?: return, audioViaBluetooth)
}
}
}
am.registerAudioDeviceCallback(audioCallback, null)
callAudioDeviceManager.start()
onDispose {
CallSoundsPlayer.stop()
if (wasConnected.value) {
CallSoundsPlayer.vibrate()
}
dropAudioManagerOverrides()
am.unregisterAudioDeviceCallback(audioCallback)
callAudioDeviceManager.stop()
if (proximityLock?.isHeld == true) {
proximityLock.release()
}
@@ -147,7 +120,7 @@ actual fun ActiveCallView() {
val callType = CallType(call.localMedia, r.capabilities)
chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType)
updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) }
setCallSound(call.soundSpeaker, audioViaBluetooth)
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.soundSpeaker, true)
CallSoundsPlayer.startConnectingCallSound(scope)
activeCallWaitDeliveryReceipt(scope)
}
@@ -168,7 +141,7 @@ actual fun ActiveCallView() {
val callStatus = json.decodeFromString<WebRTCCallStatus>("\"${r.state.connectionState}\"")
if (callStatus == WebRTCCallStatus.Connected) {
updateActiveCall(call) { it.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) }
setCallSound(call.soundSpeaker, audioViaBluetooth)
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.soundSpeaker, true)
}
withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) }
} catch (e: Throwable) {
@@ -177,7 +150,7 @@ actual fun ActiveCallView() {
is WCallResponse.Connected -> {
updateActiveCall(call) { it.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo) }
scope.launch {
setCallSound(call.soundSpeaker, audioViaBluetooth)
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.soundSpeaker, true)
}
}
is WCallResponse.End -> {
@@ -223,7 +196,7 @@ actual fun ActiveCallView() {
else -> false
}
if (call != null && showOverlay) {
ActiveCallOverlay(call, chatModel, audioViaBluetooth)
ActiveCallOverlay(call, chatModel, callAudioDeviceManager)
}
}
val context = LocalContext.current
@@ -249,19 +222,21 @@ actual fun ActiveCallView() {
}
@Composable
private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetooth: MutableState<Boolean>) {
private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, callAudioDeviceManager: CallAudioDeviceManagerInterface) {
ActiveCallOverlayLayout(
call = call,
speakerCanBeEnabled = !audioViaBluetooth.value,
devices = remember { callAudioDeviceManager.devices }.value,
currentDevice = remember { callAudioDeviceManager.currentDevice },
dismiss = { withBGApi { chatModel.callManager.endCall(call) } },
toggleAudio = { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = !call.audioEnabled)) },
selectDevice = { callAudioDeviceManager.selectDevice(it.id) },
toggleVideo = { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Video, enable = !call.videoEnabled)) },
toggleSound = {
var call = chatModel.activeCall.value
if (call != null) {
call = call.copy(soundSpeaker = !call.soundSpeaker)
chatModel.activeCall.value = call
setCallSound(call.soundSpeaker, audioViaBluetooth)
callAudioDeviceManager.selectLastExternalDeviceOrDefault(call.soundSpeaker, true)
}
},
flipCamera = { chatModel.callCommand.add(WCallCommand.Camera(call.localCamera.flipped)) }
@@ -272,42 +247,18 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetoot
fun ActiveCallOverlayDisabled(call: Call) {
ActiveCallOverlayLayout(
call = call,
speakerCanBeEnabled = false,
devices = emptyList(),
currentDevice = remember { mutableStateOf(null) },
enabled = false,
dismiss = {},
toggleAudio = {},
selectDevice = {},
toggleVideo = {},
toggleSound = {},
flipCamera = {}
)
}
private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState<Boolean>) {
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker")
am.mode = AudioManager.MODE_IN_COMMUNICATION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val btDevice = am.availableCommunicationDevices.lastOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
val preferredSecondaryDevice = if (speaker) AudioDeviceInfo.TYPE_BUILTIN_SPEAKER else AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
if (btDevice != null) {
am.setCommunicationDevice(btDevice)
} else if (am.communicationDevice?.type != preferredSecondaryDevice) {
am.availableCommunicationDevices.firstOrNull { it.type == preferredSecondaryDevice }?.let {
am.setCommunicationDevice(it)
}
}
} else {
if (audioViaBluetooth.value) {
am.isSpeakerphoneOn = false
am.startBluetoothSco()
} else {
am.stopBluetoothSco()
am.isSpeakerphoneOn = speaker
}
am.isBluetoothScoOn = am.isBluetoothScoAvailableOffCall && audioViaBluetooth.value
}
}
private fun dropAudioManagerOverrides() {
val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.mode = AudioManager.MODE_NORMAL
@@ -323,10 +274,12 @@ private fun dropAudioManagerOverrides() {
@Composable
private fun ActiveCallOverlayLayout(
call: Call,
speakerCanBeEnabled: Boolean,
devices: List<AudioDeviceInfo>,
currentDevice: State<AudioDeviceInfo?>,
enabled: Boolean = true,
dismiss: () -> Unit,
toggleAudio: () -> Unit,
selectDevice: (AudioDeviceInfo) -> Unit,
toggleVideo: () -> Unit,
toggleSound: () -> Unit,
flipCamera: () -> Unit
@@ -339,6 +292,32 @@ private fun ActiveCallOverlayLayout(
}
}
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
@Composable
fun SelectSoundDevice() {
if (devices.size == 2 &&
devices.all { it.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE || it.type == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER } ||
currentDevice.value == null ||
devices.none { it.id == currentDevice.value?.id }
) {
ToggleSoundButton(call, enabled, toggleSound)
} else {
ExposedDropDownSettingWithIcon(
devices.map { Triple(it, it.icon, if (it.name != null) generalGetString(it.name!!) else it.productName.toString()) },
currentDevice,
fontSize = 18.sp,
iconSize = 40.dp,
listIconSize = 30.dp,
iconColor = Color(0xFFFFFFD8),
minWidth = 300.dp,
onSelected = {
if (it != null) {
selectDevice(it)
}
}
)
}
}
when (media) {
CallMediaType.Video -> {
VideoCallInfoView(call)
@@ -347,7 +326,7 @@ private fun ActiveCallOverlayLayout(
}
Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
ToggleAudioButton(call, enabled, toggleAudio)
Spacer(Modifier.size(40.dp))
SelectSoundDevice()
IconButton(onClick = dismiss, enabled = enabled) {
Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp))
}
@@ -385,7 +364,7 @@ private fun ActiveCallOverlayLayout(
}
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Box(Modifier.padding(end = 32.dp)) {
ToggleSoundButton(call, speakerCanBeEnabled && enabled, toggleSound)
SelectSoundDevice()
}
}
}
@@ -780,9 +759,11 @@ fun PreviewActiveCallOverlayVideo() {
RTCIceCandidate(RTCIceCandidateType.Host, "tcp")
)
),
speakerCanBeEnabled = true,
devices = emptyList(),
currentDevice = remember { mutableStateOf(null) },
dismiss = {},
toggleAudio = {},
selectDevice = {},
toggleVideo = {},
toggleSound = {},
flipCamera = {}
@@ -807,9 +788,11 @@ fun PreviewActiveCallOverlayAudio() {
RTCIceCandidate(RTCIceCandidateType.Host, "udp")
)
),
speakerCanBeEnabled = true,
devices = emptyList(),
currentDevice = remember { mutableStateOf(null) },
dismiss = {},
toggleAudio = {},
selectDevice = {},
toggleVideo = {},
toggleSound = {},
flipCamera = {}
@@ -135,6 +135,7 @@ class AppPreferences {
val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout)
val networkTCPTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT, NetCfg.defaults.tcpTimeout, NetCfg.proxyDefaults.tcpTimeout)
val networkTCPTimeoutPerKb = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB, NetCfg.defaults.tcpTimeoutPerKb, NetCfg.proxyDefaults.tcpTimeoutPerKb)
val networkRcvConcurrency = mkIntPreference(SHARED_PREFS_NETWORK_RCV_CONCURRENCY, NetCfg.defaults.rcvConcurrency)
val networkSMPPingInterval = mkLongPreference(SHARED_PREFS_NETWORK_SMP_PING_INTERVAL, NetCfg.defaults.smpPingInterval)
val networkSMPPingCount = mkIntPreference(SHARED_PREFS_NETWORK_SMP_PING_COUNT, NetCfg.defaults.smpPingCount)
val networkEnableKeepAlive = mkBoolPreference(SHARED_PREFS_NETWORK_ENABLE_KEEP_ALIVE, NetCfg.defaults.enableKeepAlive)
@@ -304,6 +305,7 @@ class AppPreferences {
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT = "NetworkTCPTimeout"
private const val SHARED_PREFS_NETWORK_TCP_TIMEOUT_PER_KB = "networkTCPTimeoutPerKb"
private const val SHARED_PREFS_NETWORK_RCV_CONCURRENCY = "networkRcvConcurrency"
private const val SHARED_PREFS_NETWORK_SMP_PING_INTERVAL = "NetworkSMPPingInterval"
private const val SHARED_PREFS_NETWORK_SMP_PING_COUNT = "NetworkSMPPingCount"
private const val SHARED_PREFS_NETWORK_ENABLE_KEEP_ALIVE = "NetworkEnableKeepAlive"
@@ -2305,6 +2307,7 @@ object ChatController {
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
val tcpTimeout = appPrefs.networkTCPTimeout.get()
val tcpTimeoutPerKb = appPrefs.networkTCPTimeoutPerKb.get()
val rcvConcurrency = appPrefs.networkRcvConcurrency.get()
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
val smpPingCount = appPrefs.networkSMPPingCount.get()
val enableKeepAlive = appPrefs.networkEnableKeepAlive.get()
@@ -2324,6 +2327,7 @@ object ChatController {
tcpConnectTimeout = tcpConnectTimeout,
tcpTimeout = tcpTimeout,
tcpTimeoutPerKb = tcpTimeoutPerKb,
rcvConcurrency = rcvConcurrency,
tcpKeepAlive = tcpKeepAlive,
smpPingInterval = smpPingInterval,
smpPingCount = smpPingCount
@@ -2341,6 +2345,7 @@ object ChatController {
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
appPrefs.networkTCPTimeoutPerKb.set(cfg.tcpTimeoutPerKb)
appPrefs.networkRcvConcurrency.set(cfg.rcvConcurrency)
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
appPrefs.networkSMPPingCount.set(cfg.smpPingCount)
if (cfg.tcpKeepAlive != null) {
@@ -3034,6 +3039,7 @@ data class NetCfg(
val tcpConnectTimeout: Long, // microseconds
val tcpTimeout: Long, // microseconds
val tcpTimeoutPerKb: Long, // microseconds
val rcvConcurrency: Int, // pool size
val tcpKeepAlive: KeepAliveOpts?,
val smpPingInterval: Long, // microseconds
val smpPingCount: Int,
@@ -3058,9 +3064,10 @@ data class NetCfg(
hostMode = HostMode.OnionViaSocks,
requiredHostMode = false,
sessionMode = TransportSessionMode.User,
tcpConnectTimeout = 20_000_000,
tcpConnectTimeout = 10_000_000,
tcpTimeout = 15_000_000,
tcpTimeoutPerKb = 10_000,
rcvConcurrency = 12,
tcpKeepAlive = KeepAliveOpts.defaults,
smpPingInterval = 1200_000_000,
smpPingCount = 3
@@ -3072,9 +3079,10 @@ data class NetCfg(
hostMode = HostMode.OnionViaSocks,
requiredHostMode = false,
sessionMode = TransportSessionMode.User,
tcpConnectTimeout = 30_000_000,
tcpConnectTimeout = 20_000_000,
tcpTimeout = 20_000_000,
tcpTimeoutPerKb = 15_000,
rcvConcurrency = 8,
tcpKeepAlive = KeepAliveOpts.defaults,
smpPingInterval = 1200_000_000,
smpPingCount = 3
@@ -14,6 +14,7 @@ import androidx.compose.ui.unit.*
import chat.simplex.res.MR
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.usersettings.SettingsActionItemWithContent
import dev.icerock.moko.resources.ImageResource
@Composable
fun <T> ExposedDropDownSetting(
@@ -79,6 +80,60 @@ fun <T> ExposedDropDownSetting(
}
}
@Composable
fun <T> ExposedDropDownSettingWithIcon(
values: List<Triple<T, ImageResource, String>>,
selection: State<T>,
fontSize: TextUnit = 16.sp,
iconSize: Dp = 40.dp,
listIconSize: Dp = 30.dp,
iconColor: Color = MenuTextColor,
enabled: State<Boolean> = mutableStateOf(true),
minWidth: Dp = 200.dp,
onSelected: (T) -> Unit
) {
val expanded = remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded.value,
onExpandedChange = {
expanded.value = !expanded.value && enabled.value
}
) {
Row(
Modifier.padding(start = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
val choice = values.first { it.first == selection.value }
Icon(painterResource(choice.second), choice.third, Modifier.size(iconSize), tint = iconColor)
}
DefaultExposedDropdownMenu(
modifier = Modifier.widthIn(min = minWidth),
expanded = expanded,
) {
values.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
onSelected(selectionOption.first)
expanded.value = false
},
contentPadding = PaddingValues(horizontal = DEFAULT_PADDING * 1.5f)
) {
Icon(painterResource(selectionOption.second), selectionOption.third, Modifier.size(listIconSize))
Spacer(Modifier.width(15.dp))
Text(
selectionOption.third,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = MenuTextColor,
fontSize = fontSize,
)
}
}
}
}
}
@Composable
fun <T> ExposedDropDownSettingRow(
title: String,
@@ -34,6 +34,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) }
val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) }
val networkTCPTimeoutPerKb = remember { mutableStateOf(currentCfgVal.tcpTimeoutPerKb) }
var networkRcvConcurrency = remember { mutableStateOf(currentCfgVal.rcvConcurrency) }
val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) }
val networkSMPPingCount = remember { mutableStateOf(currentCfgVal.smpPingCount) }
val networkEnableKeepAlive = remember { mutableStateOf(currentCfgVal.enableKeepAlive) }
@@ -68,6 +69,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
tcpConnectTimeout = networkTCPConnectTimeout.value,
tcpTimeout = networkTCPTimeout.value,
tcpTimeoutPerKb = networkTCPTimeoutPerKb.value,
rcvConcurrency = networkRcvConcurrency.value,
tcpKeepAlive = tcpKeepAlive,
smpPingInterval = networkSMPPingInterval.value,
smpPingCount = networkSMPPingCount.value
@@ -78,6 +80,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
networkTCPConnectTimeout.value = cfg.tcpConnectTimeout
networkTCPTimeout.value = cfg.tcpTimeout
networkTCPTimeoutPerKb.value = cfg.tcpTimeoutPerKb
networkRcvConcurrency.value = cfg.rcvConcurrency
networkSMPPingInterval.value = cfg.smpPingInterval
networkSMPPingCount.value = cfg.smpPingCount
networkEnableKeepAlive.value = cfg.enableKeepAlive
@@ -110,6 +113,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
networkTCPConnectTimeout,
networkTCPTimeout,
networkTCPTimeoutPerKb,
networkRcvConcurrency,
networkSMPPingInterval,
networkSMPPingCount,
networkEnableKeepAlive,
@@ -128,6 +132,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
networkTCPConnectTimeout: MutableState<Long>,
networkTCPTimeout: MutableState<Long>,
networkTCPTimeoutPerKb: MutableState<Long>,
networkRcvConcurrency: MutableState<Int>,
networkSMPPingInterval: MutableState<Long>,
networkSMPPingCount: MutableState<Int>,
networkEnableKeepAlive: MutableState<Boolean>,
@@ -154,7 +159,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_tcp_connection_timeout), networkTCPConnectTimeout,
listOf(7_500000, 10_000000, 15_000000, 20_000000, 30_000_000, 45_000_000), secondsLabel
listOf(5_000000, 7_500000, 10_000000, 15_000000, 20_000000, 30_000000, 40_000000), secondsLabel
)
}
SectionItemView {
@@ -170,6 +175,12 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
listOf(2_500, 5_000, 10_000, 15_000, 20_000, 30_000), secondsLabel
)
}
SectionItemView {
IntSettingRow(
stringResource(MR.strings.network_option_rcv_concurrency), networkRcvConcurrency,
listOf(1, 2, 4, 8, 12, 16, 24), ""
)
}
SectionItemView {
TimeoutSettingRow(
stringResource(MR.strings.network_option_ping_interval), networkSMPPingInterval,
@@ -418,6 +429,7 @@ fun PreviewAdvancedNetworkSettingsLayout() {
networkTCPConnectTimeout = remember { mutableStateOf(10_000000) },
networkTCPTimeout = remember { mutableStateOf(10_000000) },
networkTCPTimeoutPerKb = remember { mutableStateOf(10_000) },
networkRcvConcurrency = remember { mutableStateOf(8) },
networkSMPPingInterval = remember { mutableStateOf(10_000000) },
networkSMPPingCount = remember { mutableStateOf(3) },
networkEnableKeepAlive = remember { mutableStateOf(true) },
@@ -836,6 +836,9 @@
<string name="permissions_grant_in_settings">Grant in settings</string>
<string name="permissions_find_in_settings_and_grant">Find this permission in Android settings and grant it manually.</string>
<string name="permissions_open_settings">Open settings</string>
<string name="audio_device_earpiece">Earpiece</string>
<string name="audio_device_speaker">Speaker</string>
<string name="audio_device_wired_headphones">Headphones</string>
<!-- SimpleXInfo -->
<string name="next_generation_of_private_messaging">The next generation of private messaging</string>
@@ -1456,6 +1459,7 @@
<string name="network_option_tcp_connection_timeout">TCP connection timeout</string>
<string name="network_option_protocol_timeout">Protocol timeout</string>
<string name="network_option_protocol_timeout_per_kb">Protocol timeout per KB</string>
<string name="network_option_rcv_concurrency">Receiving concurrency</string>
<string name="network_option_ping_interval">PING interval</string>
<string name="network_option_ping_count">PING count</string>
<string name="network_option_enable_tcp_keep_alive">Enable TCP keep-alive</string>
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M451-98v-313L257.5-217.5 217-258l222.5-222L217-702.5l40.5-40.5L451-549.5v-313h28.5L691-651 520-480.5l171 171L479.5-98H451Zm57.5-451.5 102-101.5-102-99.5v201Zm0 339.5 102-99.5-102-101.5v201Z"/></svg>

After

Width:  |  Height:  |  Size: 295 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M653-454.5V-512h142.5v57.5H653Zm32.5 290-117-86 32.5-45 117 86-32.5 45Zm-84.5-500-32.5-45 117-86 32.5 45-117 86ZM125-363v-234h157.5l196-196v626l-196-196H125Z"/></svg>

After

Width:  |  Height:  |  Size: 263 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M327-124.5H182q-22.969 0-40.234-17.266Q124.5-159.031 124.5-182v-298q0-74.09 28-138.795 28-64.705 76-112.705t112.684-76q64.684-28 138.75-28t138.816 28q64.75 28 112.75 76t76 112.705q28 64.705 28 138.795v298q0 22.969-17.266 40.234Q800.969-124.5 778-124.5H633v-290h145V-480q0-124.193-86.937-211.096Q604.125-778 479.884-778t-211.063 86.904Q182-604.193 182-480v65.5h145v290ZM269.5-357H182v175h87.5v-175Zm421 0v175H778v-175h-87.5Zm-421 0H182h87.5Zm421 0H778h-87.5Z"/></svg>

After

Width:  |  Height:  |  Size: 563 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M670-462.5v-163L569.5-525 544-550.5 667.5-674 544-791l25.5-25.5L670-716v-163h16.5l118 121-86 84 86 92.5-118 119H670Zm39.5-74 44-45-44-44v89Zm0-179.5 44-42-44-51.5v93.5Zm81 591Q677-125 558-180.5T338-338Q236-439 180.5-557.75T125-790.692q0-18.808 12.714-31.558Q150.429-835 169.5-835H306q14 0 23.75 9.75t13.75 24.75l26.929 123.641Q372-663.5 369.5-652q-2.5 11.5-10.229 19.226L261-533q26 44 54.688 81.658Q344.375-413.683 379-380q36.5 38 77.25 69.323Q497-279.353 542-255l95.544-98q9.456-10.5 21.357-14.25T682.5-369l117.362 25.438Q815-340 825-327.801q10 12.198 10 27.301v131q0 19.071-12.714 31.786Q809.571-125 790.5-125ZM232-585.5l81-82-23.5-110H183q1.5 41.5 13 88.25t36 103.75Zm364 358q40 19 88.166 31t93.334 14v-107l-102-21.5-79.5 83.5Zm-364-358Zm364 358Z"/></svg>

After

Width:  |  Height:  |  Size: 855 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 -960 960 960" width="48"><path d="M480.25-84.5q-31.013 0-50.631-19.559Q410-123.619 410-155q0-21.5 10.75-39.25t30.75-28.197V-334H303q-22.969 0-40.234-17.266Q245.5-368.531 245.5-391.5v-109q-20.5-9-31.25-26.359-10.75-17.358-10.75-39.358 0-31.664 19.535-51.224Q242.569-637 273.916-637t50.965 19.559q19.619 19.56 19.619 51.352 0 22.089-10.75 39.339T303-500.5v109h148.5v-338H373l107-145 107 145h-78v338h148V-496h-41.5v-140.5h141V-496h-42v104.5q0 22.969-17.266 40.234Q679.969-334 657-334H509v111.5q19.95 10.152 30.975 28.733Q551-175.187 551-154.912q0 31.531-19.869 50.971Q511.263-84.5 480.25-84.5Z"/></svg>

After

Width:  |  Height:  |  Size: 662 B

+1 -1
View File
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: c00c223f3bb295a62d8507e453bbeac61d102e3a
tag: 3d40393ae83260245c7122b850fb42cd4243deba
source-repository-package
type: git
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplex-chat
version: 5.7.0.0
version: 5.7.0.1
#synopsis:
#description:
homepage: https://github.com/simplex-chat/simplex-chat#readme
+1 -1
View File
@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."c00c223f3bb295a62d8507e453bbeac61d102e3a" = "0zbsz70rjhvrlkkiwnw43v7lg6r05lp5rwk7jmnn21zfjib4l621";
"https://github.com/simplex-chat/simplexmq.git"."3d40393ae83260245c7122b850fb42cd4243deba" = "0hkfz5k8kn1lqq0ms0b266pzsk48pq1gkfm7wgw6w5mr1pw149ay";
"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";
+1 -1
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplex-chat
version: 5.7.0.0
version: 5.7.0.1
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat