android: advanced network settings (#895)

This commit is contained in:
JRoberts
2022-08-04 18:40:36 +04:00
committed by GitHub
parent 9508ea5c97
commit 497cf86eb0
7 changed files with 723 additions and 132 deletions
@@ -84,8 +84,15 @@ class AppPreferences(val context: Context) {
val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null)
val chatArchiveTime = mkDatePreference(SHARED_PREFS_CHAT_ARCHIVE_TIME, null)
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
val useSocksProxy = mkBoolPreference(SHARED_PREFS_USE_SOCKS_PROXY, false)
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
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 networkSMPPingInterval = mkLongPreference(SHARED_PREFS_NETWORK_SMP_PING_INTERVAL, NetCfg.defaults().smpPingInterval)
val networkEnableKeepAlive = mkBoolPreference(SHARED_PREFS_NETWORK_ENABLE_KEEP_ALIVE, NetCfg.defaults().enableKeepAlive)
val networkTCPKeepIdle = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_IDLE, KeepAliveOpts.defaults().keepIdle)
val networkTCPKeepIntvl = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_INTVL, KeepAliveOpts.defaults().keepIntvl)
val networkTCPKeepCnt = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_CNT, KeepAliveOpts.defaults().keepCnt)
private fun mkIntPreference(prefName: String, default: Int) =
Preference(
@@ -93,6 +100,20 @@ class AppPreferences(val context: Context) {
set = fun(value) = sharedPreferences.edit().putInt(prefName, value).apply()
)
private fun mkLongPreference(prefName: String, default: Long) =
Preference(
get = fun() = sharedPreferences.getLong(prefName, default),
set = fun(value) = sharedPreferences.edit().putLong(prefName, value).apply()
)
private fun mkTimeoutPreference(prefName: String, default: Long, proxyDefault: Long): Preference<Long> {
val d = if (networkUseSocksProxy.get()) proxyDefault else default
return Preference(
get = fun() = sharedPreferences.getLong(prefName, d),
set = fun(value) = sharedPreferences.edit().putLong(prefName, value).apply()
)
}
private fun mkBoolPreference(prefName: String, default: Boolean) =
Preference(
get = fun() = sharedPreferences.getBoolean(prefName, default),
@@ -130,8 +151,15 @@ class AppPreferences(val context: Context) {
private const val SHARED_PREFS_CHAT_ARCHIVE_NAME = "ChatArchiveName"
private const val SHARED_PREFS_CHAT_ARCHIVE_TIME = "ChatArchiveTime"
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
private const val SHARED_PREFS_USE_SOCKS_PROXY = "UseSocksProxy"
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
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_SMP_PING_INTERVAL = "NetworkSMPPingInterval"
private const val SHARED_PREFS_NETWORK_ENABLE_KEEP_ALIVE = "NetworkEnableKeepAlive"
private const val SHARED_PREFS_NETWORK_TCP_KEEP_IDLE = "NetworkTCPKeepIdle"
private const val SHARED_PREFS_NETWORK_TCP_KEEP_INTVL = "NetworkTCPKeepIntvl"
private const val SHARED_PREFS_NETWORK_TCP_KEEP_CNT = "NetworkTCPKeepCnt"
}
}
@@ -150,9 +178,7 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
Log.d(TAG, "user: $user")
try {
if (chatModel.chatRunning.value == true) return
if (chatModel.controller.appPrefs.useSocksProxy.get()) {
setNetworkConfig(NetCfg(socksProxy = ":9050", tcpTimeout = 10_000_000))
}
apiSetNetworkConfig(getNetCfg())
val justStarted = apiStartChat()
if (justStarted) {
apiSetFilesFolder(getAppFilesDirectory(appContext))
@@ -339,14 +365,14 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
}
}
suspend fun getNetworkConfig(): NetCfg? {
suspend fun apiGetNetworkConfig(): NetCfg? {
val r = sendCmd(CC.APIGetNetworkConfig())
if (r is CR.NetworkConfig) return r.networkConfig
Log.e(TAG, "getNetworkConfig bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun setNetworkConfig(cfg: NetCfg): Boolean {
suspend fun apiSetNetworkConfig(cfg: NetCfg): Boolean {
val r = sendCmd(CC.APISetNetworkConfig(cfg))
return when (r) {
is CR.CmdOk -> true
@@ -1023,6 +1049,45 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
context.startActivity(this)
}
}
fun getNetCfg(): NetCfg {
val useSocksProxy = appPrefs.networkUseSocksProxy.get()
val socksProxy = if (useSocksProxy) ":9050" else null
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
val tcpTimeout = appPrefs.networkTCPTimeout.get()
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
val enableKeepAlive = appPrefs.networkEnableKeepAlive.get()
val tcpKeepAlive = if (enableKeepAlive) {
val keepIdle = appPrefs.networkTCPKeepIdle.get()
val keepIntvl = appPrefs.networkTCPKeepIntvl.get()
val keepCnt = appPrefs.networkTCPKeepCnt.get()
KeepAliveOpts(keepIdle = keepIdle, keepIntvl = keepIntvl, keepCnt = keepCnt)
} else {
null
}
return NetCfg(
socksProxy = socksProxy,
tcpConnectTimeout = tcpConnectTimeout,
tcpTimeout = tcpTimeout,
tcpKeepAlive = tcpKeepAlive,
smpPingInterval = smpPingInterval
)
}
fun setNetCfg(cfg: NetCfg) {
appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy)
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
if (cfg.tcpKeepAlive != null) {
appPrefs.networkEnableKeepAlive.set(true)
appPrefs.networkTCPKeepIdle.set(cfg.tcpKeepAlive.keepIdle)
appPrefs.networkTCPKeepIntvl.set(cfg.tcpKeepAlive.keepIntvl)
appPrefs.networkTCPKeepCnt.set(cfg.tcpKeepAlive.keepCnt)
} else {
appPrefs.networkEnableKeepAlive.set(false)
}
}
}
class Preference<T>(val get: () -> T, val set: (T) -> Unit)
@@ -1197,7 +1262,48 @@ class ComposedMessage(val filePath: String?, val quotedItemId: Long?, val msgCon
class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null)
@Serializable
class NetCfg(val socksProxy: String? = null, val tcpTimeout: Int)
data class NetCfg(
val socksProxy: String? = null,
val tcpConnectTimeout: Long, // microseconds
val tcpTimeout: Long, // microseconds
val tcpKeepAlive: KeepAliveOpts?,
val smpPingInterval: Long // microseconds
) {
val useSocksProxy: Boolean get() = socksProxy != null
val enableKeepAlive: Boolean get() = tcpKeepAlive != null
companion object {
fun defaults(): NetCfg =
NetCfg(
socksProxy = null,
tcpConnectTimeout = 7_500_000,
tcpTimeout = 5_000_000,
tcpKeepAlive = KeepAliveOpts.defaults(),
smpPingInterval = 600_000_000
)
fun proxyDefaults(): NetCfg =
NetCfg(
socksProxy = ":9050",
tcpConnectTimeout = 15_000_000,
tcpTimeout = 10_000_000,
tcpKeepAlive = KeepAliveOpts.defaults(),
smpPingInterval = 600_000_000
)
}
}
@Serializable
data class KeepAliveOpts(
val keepIdle: Int, // seconds
val keepIntvl: Int, // seconds
val keepCnt: Int // times
) {
companion object {
fun defaults(): KeepAliveOpts =
KeepAliveOpts(keepIdle = 30, keepIntvl = 15, keepCnt = 4)
}
}
val json = Json {
prettyPrint = true
@@ -0,0 +1,432 @@
package chat.simplex.app.views.usersettings
import SectionCustomFooter
import SectionDivider
import SectionItemView
import SectionSpacer
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
import java.text.DecimalFormat
@Composable
fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
val currentCfg = remember { mutableStateOf(chatModel.controller.getNetCfg()) }
val currentCfgVal = currentCfg.value // used only on initialization
val networkTCPConnectTimeout = remember { mutableStateOf(currentCfgVal.tcpConnectTimeout) }
val networkTCPTimeout = remember { mutableStateOf(currentCfgVal.tcpTimeout) }
val networkSMPPingInterval = remember { mutableStateOf(currentCfgVal.smpPingInterval) }
val networkEnableKeepAlive = remember { mutableStateOf(currentCfgVal.enableKeepAlive) }
val networkTCPKeepIdle: MutableState<Int>
val networkTCPKeepIntvl: MutableState<Int>
val networkTCPKeepCnt: MutableState<Int>
if (currentCfgVal.tcpKeepAlive != null) {
networkTCPKeepIdle = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.keepIdle) }
networkTCPKeepIntvl = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.keepIntvl) }
networkTCPKeepCnt = remember { mutableStateOf(currentCfgVal.tcpKeepAlive.keepCnt) }
} else {
networkTCPKeepIdle = remember { mutableStateOf(KeepAliveOpts.defaults().keepIdle) }
networkTCPKeepIntvl = remember { mutableStateOf(KeepAliveOpts.defaults().keepIntvl) }
networkTCPKeepCnt = remember { mutableStateOf(KeepAliveOpts.defaults().keepCnt) }
}
fun buildCfg(): NetCfg {
val socksProxy = currentCfg.value.socksProxy
val tcpConnectTimeout = networkTCPConnectTimeout.value
val tcpTimeout = networkTCPTimeout.value
val smpPingInterval = networkSMPPingInterval.value
val enableKeepAlive = networkEnableKeepAlive.value
val tcpKeepAlive = if (enableKeepAlive) {
val keepIdle = networkTCPKeepIdle.value
val keepIntvl = networkTCPKeepIntvl.value
val keepCnt = networkTCPKeepCnt.value
KeepAliveOpts(keepIdle = keepIdle, keepIntvl = keepIntvl, keepCnt = keepCnt)
} else {
null
}
return NetCfg(
socksProxy = socksProxy,
tcpConnectTimeout = tcpConnectTimeout,
tcpTimeout = tcpTimeout,
tcpKeepAlive = tcpKeepAlive,
smpPingInterval = smpPingInterval
)
}
fun updateView(cfg: NetCfg) {
networkTCPConnectTimeout.value = cfg.tcpConnectTimeout
networkTCPTimeout.value = cfg.tcpTimeout
networkSMPPingInterval.value = cfg.smpPingInterval
networkEnableKeepAlive.value = cfg.enableKeepAlive
if (cfg.tcpKeepAlive != null) {
networkTCPKeepIdle.value = cfg.tcpKeepAlive.keepIdle
networkTCPKeepIntvl.value = cfg.tcpKeepAlive.keepIntvl
networkTCPKeepCnt.value = cfg.tcpKeepAlive.keepCnt
} else {
networkTCPKeepIdle.value = KeepAliveOpts.defaults().keepIdle
networkTCPKeepIntvl.value = KeepAliveOpts.defaults().keepIntvl
networkTCPKeepCnt.value = KeepAliveOpts.defaults().keepCnt
}
}
fun saveCfg(cfg: NetCfg) {
withApi {
chatModel.controller.apiSetNetworkConfig(cfg)
currentCfg.value = cfg
chatModel.controller.setNetCfg(cfg)
}
}
fun reset() {
val newCfg = if (currentCfg.value.useSocksProxy) NetCfg.proxyDefaults() else NetCfg.defaults()
updateView(newCfg)
saveCfg(newCfg)
}
fun updateSettingsDialog(action: () -> Unit) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.update_network_settings_question),
text = generalGetString(R.string.updating_settings_will_reconnect_client_to_all_servers),
confirmText = generalGetString(R.string.update_network_settings_confirmation),
onConfirm = action
)
}
AdvancedNetworkSettingsLayout(
networkTCPConnectTimeout,
networkTCPTimeout,
networkSMPPingInterval,
networkEnableKeepAlive,
networkTCPKeepIdle,
networkTCPKeepIntvl,
networkTCPKeepCnt,
resetDisabled = if (currentCfg.value.useSocksProxy) currentCfg.value == NetCfg.proxyDefaults() else currentCfg.value == NetCfg.defaults(),
reset = { updateSettingsDialog(::reset) },
footerDisabled = buildCfg() == currentCfg.value,
revert = { updateView(currentCfg.value) },
save = { updateSettingsDialog { saveCfg(buildCfg()) } }
)
}
@Composable fun AdvancedNetworkSettingsLayout(
networkTCPConnectTimeout: MutableState<Long>,
networkTCPTimeout: MutableState<Long>,
networkSMPPingInterval: MutableState<Long>,
networkEnableKeepAlive: MutableState<Boolean>,
networkTCPKeepIdle: MutableState<Int>,
networkTCPKeepIntvl: MutableState<Int>,
networkTCPKeepCnt: MutableState<Int>,
resetDisabled: Boolean,
reset: () -> Unit,
footerDisabled: Boolean,
revert: () -> Unit,
save: () -> Unit
) {
val secondsLabel = stringResource(R.string.network_option_seconds_label)
Column(
Modifier
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start,
) {
Text(
stringResource(R.string.network_settings_title),
Modifier.padding(start = 16.dp, bottom = 24.dp),
style = MaterialTheme.typography.h1
)
SectionSpacer()
SectionView {
SectionItemView {
ResetToDefaultsButton(reset, disabled = resetDisabled)
}
SectionDivider()
SectionItemView {
TimeoutSettingRow(
stringResource(R.string.network_option_tcp_connection_timeout), networkTCPConnectTimeout,
listOf(2_500000, 5_000000, 7_500000, 10_000000, 15_000000, 20_000000), secondsLabel
)
}
SectionDivider()
SectionItemView {
TimeoutSettingRow(
stringResource(R.string.network_option_protocol_timeout), networkTCPTimeout,
listOf(1_500000, 3_000000, 5_000000, 7_000000, 10_000000, 15_000000), secondsLabel
)
}
SectionDivider()
SectionItemView {
TimeoutSettingRow(
stringResource(R.string.network_option_ping_interval), networkSMPPingInterval,
listOf(120_000000, 300_000000, 600_000000, 1200_000000, 2400_000000), secondsLabel
)
}
SectionDivider()
SectionItemView {
EnableKeepAliveSwitch(networkEnableKeepAlive)
}
SectionDivider()
if (networkEnableKeepAlive.value) {
SectionItemView {
IntSettingRow("TCP_KEEPIDLE", networkTCPKeepIdle, listOf(15, 30, 60, 120, 180), secondsLabel)
}
SectionDivider()
SectionItemView {
IntSettingRow("TCP_KEEPINTVL", networkTCPKeepIntvl, listOf(5, 10, 15, 30, 60), secondsLabel)
}
SectionDivider()
SectionItemView {
IntSettingRow("TCP_KEEPCNT", networkTCPKeepCnt, listOf(1, 2, 4, 6, 8), "")
}
} else {
SectionItemView {
Text("TCP_KEEPIDLE", color = HighOrLowlight)
}
SectionDivider()
SectionItemView {
Text("TCP_KEEPINTVL", color = HighOrLowlight)
}
SectionDivider()
SectionItemView {
Text("TCP_KEEPCNT", color = HighOrLowlight)
}
}
}
SectionCustomFooter {
SettingsSectionFooter(revert, save, footerDisabled)
}
SectionSpacer()
}
}
@Composable
fun ResetToDefaultsButton(reset: () -> Unit, disabled: Boolean) {
val modifier = if (disabled) Modifier else Modifier.clickable { reset() }
Row(
modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically
) {
val color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
Text(stringResource(R.string.network_options_reset_to_defaults), color = color)
}
}
@Composable
fun EnableKeepAliveSwitch(
networkEnableKeepAlive: MutableState<Boolean>
) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(stringResource(R.string.network_option_enable_tcp_keep_alive))
Switch(
checked = networkEnableKeepAlive.value,
onCheckedChange = { networkEnableKeepAlive.value = it },
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
)
}
}
@Composable
fun IntSettingRow(title: String, selection: MutableState<Int>, values: List<Int>, label: String) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
var expanded by remember { mutableStateOf(false) }
Text(title)
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
Row(
Modifier.width(140.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Text(
"${selection.value} $label",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
)
Spacer(Modifier.size(4.dp))
Icon(
if (!expanded) Icons.Outlined.ExpandMore else Icons.Outlined.ExpandLess,
generalGetString(R.string.invite_to_group_button),
modifier = Modifier.padding(start = 8.dp),
tint = HighOrLowlight
)
}
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
values.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selection.value = selectionOption
expanded = false
}
) {
Text(
"$selectionOption $label",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
}
}
@Composable
fun TimeoutSettingRow(title: String, selection: MutableState<Long>, values: List<Long>, label: String) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
var expanded by remember { mutableStateOf(false) }
Text(title)
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
val df = DecimalFormat("#.#")
Row(
Modifier.width(140.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Text(
"${df.format(selection.value / 1_000_000.toDouble())} $label",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = HighOrLowlight
)
Spacer(Modifier.size(4.dp))
Icon(
if (!expanded) Icons.Outlined.ExpandMore else Icons.Outlined.ExpandLess,
generalGetString(R.string.invite_to_group_button),
modifier = Modifier.padding(start = 8.dp),
tint = HighOrLowlight
)
}
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
values.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
selection.value = selectionOption
expanded = false
}
) {
Text(
"${df.format(selectionOption / 1_000_000.toDouble())} $label",
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
}
}
}
@Composable
fun SettingsSectionFooter(revert: () -> Unit, save: () -> Unit, disabled: Boolean) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
FooterButton(Icons.Outlined.Replay, stringResource(R.string.network_options_revert), revert, disabled)
FooterButton(Icons.Outlined.Check, stringResource(R.string.network_options_save), save, disabled)
}
}
@Composable
fun FooterButton(icon: ImageVector, title: String, action: () -> Unit, disabled: Boolean) {
Surface(
shape = RoundedCornerShape(20.dp),
color = Color.Black.copy(alpha = 0f)
) {
val modifier = if (disabled) Modifier else Modifier.clickable { action() }
Row(
modifier.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
icon,
title,
tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
)
Text(
title,
color = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
)
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewAdvancedNetworkSettingsLayout() {
SimpleXTheme {
AdvancedNetworkSettingsLayout(
networkTCPConnectTimeout = remember { mutableStateOf(10_000000) },
networkTCPTimeout = remember { mutableStateOf(10_000000) },
networkSMPPingInterval = remember { mutableStateOf(10_000000) },
networkEnableKeepAlive = remember { mutableStateOf(true) },
networkTCPKeepIdle = remember { mutableStateOf(10) },
networkTCPKeepIntvl = remember { mutableStateOf(10) },
networkTCPKeepCnt = remember { mutableStateOf(10) },
resetDisabled = false,
reset = {},
footerDisabled = false,
revert = {},
save = {}
)
}
}
@@ -0,0 +1,144 @@
package chat.simplex.app.views.usersettings
import SectionDivider
import SectionItemView
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.NetCfg
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun NetworkAndServersView(
chatModel: ChatModel,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
) {
val netCfg: MutableState<NetCfg> = remember { mutableStateOf(chatModel.controller.getNetCfg()) }
val networkUseSocksProxy: MutableState<Boolean> = remember { mutableStateOf(netCfg.value.useSocksProxy) }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
NetworkAndServersLayout(
developerTools = developerTools,
networkUseSocksProxy = networkUseSocksProxy,
showModal = showModal,
showSettingsModal = showSettingsModal,
toggleSocksProxy = { enable ->
if (enable) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.network_enable_socks),
text = generalGetString(R.string.network_enable_socks_info),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
withApi {
chatModel.controller.apiSetNetworkConfig(NetCfg.proxyDefaults())
chatModel.controller.setNetCfg(NetCfg.proxyDefaults())
networkUseSocksProxy.value = true
}
}
)
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.network_disable_socks),
text = generalGetString(R.string.network_disable_socks_info),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
withApi {
chatModel.controller.apiSetNetworkConfig(NetCfg.defaults())
chatModel.controller.setNetCfg(NetCfg.defaults())
networkUseSocksProxy.value = false
}
}
)
}
}
)
}
@Composable fun NetworkAndServersLayout(
developerTools: Boolean,
networkUseSocksProxy: MutableState<Boolean>,
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
toggleSocksProxy: (Boolean) -> Unit
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.network_and_servers),
Modifier.padding(start = 16.dp, bottom = 24.dp),
style = MaterialTheme.typography.h1
)
SectionView {
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showModal { SMPServersView(it) })
SectionDivider()
SectionItemView {
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
}
if (developerTools) {
SectionDivider()
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
}
}
}
}
@Composable
fun UseSocksProxySwitch(
networkUseSocksProxy: MutableState<Boolean>,
toggleSocksProxy: (Boolean) -> Unit
) {
Row(
Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Outlined.SettingsEthernet,
stringResource(R.string.network_socks_toggle),
tint = HighOrLowlight
)
Text(stringResource(R.string.network_socks_toggle))
}
Switch(
checked = networkUseSocksProxy.value,
onCheckedChange = toggleSocksProxy,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
)
}
}
@Preview(showBackground = true)
@Composable
fun PreviewNetworkAndServersLayout() {
SimpleXTheme {
NetworkAndServersLayout(
developerTools = true,
networkUseSocksProxy = remember { mutableStateOf(true) },
showModal = { {} },
showSettingsModal = { {} },
toggleSocksProxy = {}
)
}
}
@@ -1,102 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionView
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.NetCfg
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
@Composable
fun NetworkSettingsView(chatModel: ChatModel, netCfg: NetCfg) {
val useSocksProxy = remember { mutableStateOf(netCfg.socksProxy != null) }
fun setSocksProxy(value: Boolean) {
chatModel.controller.appPrefs.useSocksProxy.set(value)
useSocksProxy.value = value
}
NetworkSettingsLayout(
useSocksProxy,
toggleSocksProxy = { enable ->
if (enable) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.network_enable_socks),
text = generalGetString(R.string.network_enable_socks_info),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
withApi {
chatModel.controller.setNetworkConfig(NetCfg(socksProxy = ":9050", tcpTimeout = 10_000_000))
setSocksProxy(true)
}
}
)
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.network_disable_socks),
text = generalGetString(R.string.network_disable_socks_info),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
withApi {
chatModel.controller.setNetworkConfig(NetCfg(tcpTimeout = 5_000_000))
setSocksProxy(false)
}
}
)
}
}
)
}
@Composable fun NetworkSettingsLayout(
useSocksProxy: MutableState<Boolean>,
toggleSocksProxy: (Boolean) -> Unit
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.network_settings_title),
Modifier.padding(start = 16.dp, bottom = 24.dp),
style = MaterialTheme.typography.h1
)
SectionView(stringResource(R.string.settings_section_title_socks)) {
Row(
Modifier.padding(start = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(stringResource(R.string.network_socks_toggle))
Spacer(Modifier.fillMaxWidth().weight(1f))
Switch(
checked = useSocksProxy.value,
onCheckedChange = toggleSocksProxy,
colors = SwitchDefaults.colors(
checkedThumbColor = MaterialTheme.colors.primary,
uncheckedThumbColor = HighOrLowlight
),
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewNetworkSettings() {
SimpleXTheme {
NetworkSettingsLayout(
useSocksProxy = remember { mutableStateOf(true) },
toggleSocksProxy = {}
)
}
}
@@ -63,19 +63,6 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
} } },
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } },
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(chatModel, close) } },
showNetworkSettings = {
withApi {
val cfg = chatModel.controller.getNetworkConfig()
if (cfg != null) {
ModalManager.shared.showCustomModal { close ->
ModalView(close = close, modifier = Modifier,
background = if (isSystemInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight) {
NetworkSettingsView(chatModel, cfg)
}
}
}
}
},
showAppearance = {
withApi {
ModalManager.shared.showCustomModal { close ->
@@ -118,7 +105,6 @@ fun SettingsLayout(
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
showTerminal: () -> Unit,
showNetworkSettings: () -> Unit,
showAppearance: () -> Unit
// showVideoChatPrototype: () -> Unit
) {
@@ -149,17 +135,15 @@ fun SettingsLayout(
SectionSpacer()
SectionView(stringResource(R.string.settings_section_title_settings)) {
PrivateNotificationsItem(runServiceInBackground, setRunServiceInBackground, stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Videocam, stringResource(R.string.settings_audio_video_calls), showSettingsModal { CallSettingsView(it) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Lock, stringResource(R.string.privacy_and_security), showSettingsModal { PrivacySettingsView(it, setPerformLA) }, disabled = stopped)
SectionDivider()
PrivateNotificationsItem(runServiceInBackground, setRunServiceInBackground, stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showModal { SMPServersView(it) }, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.SettingsEthernet, stringResource(R.string.network_settings), showNetworkSettings, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.LightMode, stringResource(R.string.appearance_settings), showAppearance, disabled = stopped)
SectionDivider()
SettingsActionItem(Icons.Outlined.WifiTethering, stringResource(R.string.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal) }, disabled = stopped)
}
SectionSpacer()
@@ -370,7 +354,6 @@ fun PreviewSettingsLayout() {
showSettingsModal = { {} },
showCustomModal = { {} },
showTerminal = {},
showNetworkSettings = {},
showAppearance = {},
// showVideoChatPrototype = {}
)
@@ -272,6 +272,7 @@
<string name="enter_one_SMP_server_per_line">Введите SMP серверы, каждый сервер в отдельной строке:</string>
<string name="how_to">Инфо</string>
<string name="save_servers_button">Сохранить</string>
<string name="network_and_servers">Сеть &amp; серверы</string>
<string name="network_settings">Настройки сети</string>
<string name="network_settings_title">Настройки сети</string>
<string name="network_socks_toggle">Использовать SOCKS прокси (порт 9050)</string>
@@ -612,4 +613,17 @@
<string name="group_profile_is_stored_on_members_devices">Профиль группы хранится на устройствах членов, а не на серверах.</string>
<string name="save_group_profile">Сохранить профиль группы</string>
<string name="error_saving_group_profile">Ошибка при сохранении профиля группы</string>
<!-- AdvancedNetworkSettings.kt -->
<string name="network_options_reset_to_defaults">Сбросить настройки</string>
<string name="network_option_seconds_label">сек</string>
<string name="network_option_tcp_connection_timeout">Таймаут TCP соединения</string>
<string name="network_option_protocol_timeout">Таймаут протокола</string>
<string name="network_option_ping_interval">Интервал PING</string>
<string name="network_option_enable_tcp_keep_alive">Включить TCP keep-alive</string>
<string name="network_options_revert">Отменить изменения</string>
<string name="network_options_save">Сохранить</string>
<string name="update_network_settings_question">Обновить настройки сети?</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Обновление настроек приведет к переподключению клиента ко всем серверам.</string>
<string name="update_network_settings_confirmation">Обновить</string>
</resources>
@@ -277,7 +277,8 @@
<string name="enter_one_SMP_server_per_line">Enter one SMP server per line:</string>
<string name="how_to">How to</string>
<string name="save_servers_button">Save</string>
<string name="network_settings">Network</string>
<string name="network_and_servers">Network &amp; servers</string>
<string name="network_settings">Advanced network settings</string>
<string name="network_settings_title">Network settings</string>
<string name="network_socks_toggle">Use SOCKS proxy (port 9050)</string>
<string name="network_enable_socks">Use SOCKS proxy?</string>
@@ -614,4 +615,17 @@
<string name="group_profile_is_stored_on_members_devices">Group profile is stored on members\' devices, not on the servers.</string>
<string name="save_group_profile">Save group profile</string>
<string name="error_saving_group_profile">Error saving group profile</string>
<!-- AdvancedNetworkSettings.kt -->
<string name="network_options_reset_to_defaults">Reset to defaults</string>
<string name="network_option_seconds_label">sec</string>
<string name="network_option_tcp_connection_timeout">TCP connection timeout</string>
<string name="network_option_protocol_timeout">Protocol timeout</string>
<string name="network_option_ping_interval">PING interval</string>
<string name="network_option_enable_tcp_keep_alive">Enable TCP keep-alive</string>
<string name="network_options_revert">Revert</string>
<string name="network_options_save">Save</string>
<string name="update_network_settings_question">Update network settings?</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Updating settings will re-connect the client to all servers.</string>
<string name="update_network_settings_confirmation">Update</string>
</resources>