android: UI to access servers via SOCKS proxy (#840)
* android: UI to access servers via SOCKS proxy * UI to connect via socks * add server hosts to contact info * ios: types for network/info commands
This commit is contained in:
committed by
GitHub
parent
e7f3dc3f41
commit
6069108bb9
@@ -332,6 +332,42 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getNetworkConfig(): 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 {
|
||||
val r = sendCmd(CC.APISetNetworkConfig(cfg))
|
||||
return when (r) {
|
||||
is CR.CmdOk -> true
|
||||
else -> {
|
||||
Log.e(TAG, "setNetworkConfig bad response: ${r.responseType} ${r.details}")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
generalGetString(R.string.error_setting_network_config),
|
||||
"${r.responseType}: ${r.details}"
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiContactInfo(contactId: Long): ConnectionStats? {
|
||||
val r = sendCmd(CC.APIContactInfo(contactId))
|
||||
if (r is CR.ContactInfo) return r.connectionStats
|
||||
Log.e(TAG, "apiContactInfo bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiGroupMemberInfo(groupId: Long, groupMemberId: Long): ConnectionStats? {
|
||||
val r = sendCmd(CC.APIGroupMemberInfo(groupId, groupMemberId))
|
||||
if (r is CR.GroupMemberInfo) return r.connectionStats_
|
||||
Log.e(TAG, "apiGroupMemberInfo bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun apiAddContact(): String? {
|
||||
val r = sendCmd(CC.AddContact())
|
||||
if (r is CR.Invitation) return r.connReqInvitation
|
||||
@@ -941,8 +977,12 @@ sealed class CC {
|
||||
class ApiRemoveMember(val groupId: Long, val memberId: Long): CC()
|
||||
class ApiLeaveGroup(val groupId: Long): CC()
|
||||
class ApiListMembers(val groupId: Long): CC()
|
||||
class GetUserSMPServers(): CC()
|
||||
class GetUserSMPServers: CC()
|
||||
class SetUserSMPServers(val smpServers: List<String>): CC()
|
||||
class APISetNetworkConfig(val networkConfig: NetCfg): CC()
|
||||
class APIGetNetworkConfig: CC()
|
||||
class APIContactInfo(val contactId: Long): CC()
|
||||
class APIGroupMemberInfo(val groupId: Long, val groupMemberId: Long): CC()
|
||||
class AddContact: CC()
|
||||
class Connect(val connReq: String): CC()
|
||||
class ApiDeleteChat(val type: ChatType, val id: Long): CC()
|
||||
@@ -987,6 +1027,10 @@ sealed class CC {
|
||||
is ApiListMembers -> "/_members #$groupId"
|
||||
is GetUserSMPServers -> "/smp_servers"
|
||||
is SetUserSMPServers -> "/smp_servers ${smpServersStr(smpServers)}"
|
||||
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
|
||||
is APIGetNetworkConfig -> "/network"
|
||||
is APIContactInfo -> "/_info @$contactId"
|
||||
is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId"
|
||||
is AddContact -> "/connect"
|
||||
is Connect -> "/connect $connReq"
|
||||
is ApiDeleteChat -> "/_delete ${chatRef(type, id)}"
|
||||
@@ -1032,6 +1076,10 @@ sealed class CC {
|
||||
is ApiListMembers -> "apiListMembers"
|
||||
is GetUserSMPServers -> "getUserSMPServers"
|
||||
is SetUserSMPServers -> "setUserSMPServers"
|
||||
is APISetNetworkConfig -> "/apiSetNetworkConfig"
|
||||
is APIGetNetworkConfig -> "/apiGetNetworkConfig"
|
||||
is APIContactInfo -> "apiContactInfo"
|
||||
is APIGroupMemberInfo -> "apiGroupMemberInfo"
|
||||
is AddContact -> "addContact"
|
||||
is Connect -> "connect"
|
||||
is ApiDeleteChat -> "apiDeleteChat"
|
||||
@@ -1069,6 +1117,9 @@ class ComposedMessage(val filePath: String?, val quotedItemId: Long?, val msgCon
|
||||
@Serializable
|
||||
class ArchiveConfig(val archivePath: String, val disableCompression: Boolean? = null, val parentTempDirectory: String? = null)
|
||||
|
||||
@Serializable
|
||||
class NetCfg(val socksProxy: String? = null, val tcpTimeout: Int)
|
||||
|
||||
val json = Json {
|
||||
prettyPrint = true
|
||||
ignoreUnknownKeys = true
|
||||
@@ -1106,6 +1157,9 @@ sealed class CR {
|
||||
@Serializable @SerialName("apiChats") class ApiChats(val chats: List<Chat>): CR()
|
||||
@Serializable @SerialName("apiChat") class ApiChat(val chat: Chat): CR()
|
||||
@Serializable @SerialName("userSMPServers") class UserSMPServers(val smpServers: List<String>): CR()
|
||||
@Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR()
|
||||
@Serializable @SerialName("contactInfo") class ContactInfo(val contact: Contact, val connectionStats: ConnectionStats): CR()
|
||||
@Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats?): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val connReqInvitation: String): CR()
|
||||
@Serializable @SerialName("sentConfirmation") class SentConfirmation: CR()
|
||||
@Serializable @SerialName("sentInvitation") class SentInvitation: CR()
|
||||
@@ -1187,6 +1241,9 @@ sealed class CR {
|
||||
is ApiChats -> "apiChats"
|
||||
is ApiChat -> "apiChat"
|
||||
is UserSMPServers -> "userSMPServers"
|
||||
is NetworkConfig -> "networkConfig"
|
||||
is ContactInfo -> "contactInfo"
|
||||
is GroupMemberInfo -> "groupMemberInfo"
|
||||
is Invitation -> "invitation"
|
||||
is SentConfirmation -> "sentConfirmation"
|
||||
is SentInvitation -> "sentInvitation"
|
||||
@@ -1266,6 +1323,9 @@ sealed class CR {
|
||||
is ApiChats -> json.encodeToString(chats)
|
||||
is ApiChat -> json.encodeToString(chat)
|
||||
is UserSMPServers -> json.encodeToString(smpServers)
|
||||
is NetworkConfig -> json.encodeToString(networkConfig)
|
||||
is ContactInfo -> "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}"
|
||||
is GroupMemberInfo -> "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats_)}"
|
||||
is Invitation -> connReqInvitation
|
||||
is SentConfirmation -> noDetails()
|
||||
is SentInvitation -> noDetails()
|
||||
@@ -1367,6 +1427,9 @@ abstract class TerminalItem {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class ConnectionStats(val rcvServers: List<String>?, val sndServers: List<String>?)
|
||||
|
||||
@Serializable
|
||||
sealed class ChatError {
|
||||
val string: String get() = when (this) {
|
||||
|
||||
@@ -22,12 +22,13 @@ import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
|
||||
@Composable
|
||||
fun ChatInfoView(chatModel: ChatModel, close: () -> Unit) {
|
||||
fun ChatInfoView(chatModel: ChatModel, connStats: ConnectionStats?, close: () -> Unit) {
|
||||
BackHandler(onBack = close)
|
||||
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
||||
if (chat != null) {
|
||||
ChatInfoLayout(
|
||||
chat,
|
||||
connStats,
|
||||
close = close,
|
||||
deleteContact = { deleteChatDialog(chat.chatInfo, chatModel, close) },
|
||||
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) }
|
||||
@@ -87,6 +88,7 @@ fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel) {
|
||||
@Composable
|
||||
fun ChatInfoLayout(
|
||||
chat: Chat,
|
||||
connStats: ConnectionStats?,
|
||||
close: () -> Unit,
|
||||
deleteContact: () -> Unit,
|
||||
clearChat: () -> Unit
|
||||
@@ -135,6 +137,10 @@ fun ChatInfoLayout(
|
||||
.padding(top = 16.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
)
|
||||
if (connStats != null) {
|
||||
SimplexServers("receiving via: ", connStats.rcvServers)
|
||||
SimplexServers("sending via: ", connStats.sndServers)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1F))
|
||||
@@ -178,6 +184,14 @@ fun ChatInfoLayout(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SimplexServers(text: String, servers: List<String>?) {
|
||||
if (servers != null) {
|
||||
val info = text + servers.joinToString(separator = ", ") { it.substringAfter("@") }
|
||||
Text(info, style = MaterialTheme.typography.body2)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServerImage(chat: Chat) {
|
||||
when (chat.serverInfo.networkStatus) {
|
||||
@@ -201,6 +215,7 @@ fun PreviewChatInfoLayout() {
|
||||
chatItems = arrayListOf(),
|
||||
serverInfo = Chat.ServerInfo(Chat.NetworkStatus.Error("agent BROKER TIMEOUT"))
|
||||
),
|
||||
connStats = null,
|
||||
close = {}, deleteContact = {}, clearChat = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,16 @@ fun ChatView(chatModel: ChatModel) {
|
||||
chatModel.chatItems,
|
||||
useLinkPreviews = useLinkPreviews,
|
||||
back = { chatModel.chatId.value = null },
|
||||
info = { ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, close) } },
|
||||
info = {
|
||||
withApi {
|
||||
var connStats: ConnectionStats? = null
|
||||
val cInfo = chat.chatInfo
|
||||
if (cInfo is ChatInfo.Direct) {
|
||||
connStats = chatModel.controller.apiContactInfo(cInfo.apiId)
|
||||
}
|
||||
ModalManager.shared.showCustomModal { close -> ChatInfoView(chatModel, connStats, close) }
|
||||
}
|
||||
},
|
||||
openDirectChat = { contactId ->
|
||||
val c = chatModel.chats.firstOrNull {
|
||||
it.chatInfo is ChatInfo.Direct && it.chatInfo.contact.contactId == contactId
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
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) }
|
||||
|
||||
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))
|
||||
useSocksProxy.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.setNetworkConfig(NetCfg(tcpTimeout = 5_000_000))
|
||||
useSocksProxy.value = 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
|
||||
)
|
||||
SettingsSectionView(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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,20 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit) {
|
||||
}
|
||||
} } },
|
||||
showCustomModal = { modalView -> { ModalManager.shared.showCustomModal { close -> modalView(chatModel, close) } } },
|
||||
showTerminal = { ModalManager.shared.showCustomModal { close -> TerminalView(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// showVideoChatPrototype = { ModalManager.shared.showCustomModal { close -> CallViewDebug(close) } },
|
||||
)
|
||||
}
|
||||
@@ -77,6 +90,7 @@ fun SettingsLayout(
|
||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||
showTerminal: () -> Unit,
|
||||
showNetworkSettings: () -> Unit
|
||||
// showVideoChatPrototype: () -> Unit
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
@@ -115,6 +129,8 @@ fun SettingsLayout(
|
||||
PrivateNotificationsItem(runServiceInBackground, setRunServiceInBackground, stopped)
|
||||
divider()
|
||||
SettingsActionItem(Icons.Outlined.Dns, stringResource(R.string.smp_servers), showModal { SMPServersView(it) }, disabled = stopped)
|
||||
divider()
|
||||
SettingsActionItem(Icons.Outlined.SettingsEthernet, stringResource(R.string.network_settings), showNetworkSettings, disabled = stopped)
|
||||
}
|
||||
spacer()
|
||||
|
||||
@@ -349,6 +365,7 @@ fun PreviewSettingsLayout() {
|
||||
showSettingsModal = { {} },
|
||||
showCustomModal = { {} },
|
||||
showTerminal = {},
|
||||
showNetworkSettings = {}
|
||||
// showVideoChatPrototype = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@
|
||||
<string name="description_via_contact_address_link">через ссылку-контакт</string>
|
||||
<string name="description_via_one_time_link">через одноразовую ссылку</string>
|
||||
|
||||
<!-- SMP Server Information - SimpleXAPI.kt -->
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Ошибка при сохранении SMP серверов</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется.</string>
|
||||
<string name="error_setting_network_config">Ошибка при сохранении настроек сети</string>
|
||||
|
||||
<!-- API Error Responses - SimpleXAPI.kt -->
|
||||
<string name="contact_already_exists">Существующий контакт</string>
|
||||
@@ -265,6 +266,13 @@
|
||||
<string name="enter_one_SMP_server_per_line">Введите SMP серверы, каждый сервер в отдельной строке:</string>
|
||||
<string name="how_to">Инфо</string>
|
||||
<string name="save_servers_button">Сохранить</string>
|
||||
<string name="network_settings">Настройки сети</string>
|
||||
<string name="network_settings_title">Настройки сети</string>
|
||||
<string name="network_socks_toggle">Использовать SOCKS прокси (порт 9050)</string>
|
||||
<string name="network_enable_socks">Использовать SOCKS прокси?</string>
|
||||
<string name="network_enable_socks_info">Соединяться с серверами через SOCKS прокси через порт 9050? Прокси должен быть запущен до включения этой опции.</string>
|
||||
<string name="network_disable_socks">Использовать прямое соединение с Интернет?</string>
|
||||
<string name="network_disable_socks_info">Если вы подтвердите, серверы смогут видеть ваш IP адрес, а провайдер - с какими серверами вы соединяетесь.</string>
|
||||
|
||||
<!-- Address Items - UserAddressView.kt -->
|
||||
<string name="create_address">Создать адрес</string>
|
||||
@@ -441,6 +449,7 @@
|
||||
<string name="settings_section_title_device">УСТРОЙСТВО</string>
|
||||
<string name="settings_section_title_chats">ЧАТЫ</string>
|
||||
<string name="settings_experimental_features">Экспериментальные функции</string>
|
||||
<string name="settings_section_title_socks">SOCKS ПРОКСИ</string>
|
||||
|
||||
<!-- DatabaseView.kt -->
|
||||
<string name="your_chat_database">Данные чата</string>
|
||||
|
||||
@@ -33,9 +33,10 @@
|
||||
<string name="description_via_contact_address_link">via contact address link</string>
|
||||
<string name="description_via_one_time_link">via one-time link</string>
|
||||
|
||||
<!-- SMP Server Information - SimpleXAPI.kt -->
|
||||
<!-- SimpleXAPI.kt -->
|
||||
<string name="error_saving_smp_servers">Error saving SMP servers</string>
|
||||
<string name="ensure_smp_server_address_are_correct_format_and_unique">Make sure SMP server addresses are in correct format, line separated and are not duplicated.</string>
|
||||
<string name="error_setting_network_config">Error updating network configuration</string>
|
||||
|
||||
<!-- API Error Responses - SimpleXAPI.kt -->
|
||||
<string name="contact_already_exists">Contact already exists</string>
|
||||
@@ -271,6 +272,13 @@
|
||||
<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_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>
|
||||
<string name="network_enable_socks_info">Access the servers via SOCKS proxy on port 9050? Proxy must be started before enabling this option.</string>
|
||||
<string name="network_disable_socks">Use direct Internet connection?</string>
|
||||
<string name="network_disable_socks_info">If you confirm, the messaging servers will be able to see your IP address, and your provider - which servers you are connecting to.</string>
|
||||
|
||||
<!-- Address Items - UserAddressView.kt -->
|
||||
<string name="create_address">Create address</string>
|
||||
@@ -443,6 +451,7 @@
|
||||
<string name="settings_section_title_device">DEVICE</string>
|
||||
<string name="settings_section_title_chats">CHATS</string>
|
||||
<string name="settings_experimental_features">Experimental features</string>
|
||||
<string name="settings_section_title_socks">SOCKS PROXY</string>
|
||||
|
||||
<!-- DatabaseView.kt -->
|
||||
<string name="your_chat_database">Your chat database</string>
|
||||
|
||||
Reference in New Issue
Block a user