mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-29 04:39:53 +00:00
Merge branch 'master' into sqlcipher
This commit is contained in:
commit
6597400f61
36 changed files with 574 additions and 128 deletions
24
README.md
24
README.md
|
@ -79,18 +79,14 @@ You can use SimpleX with your own servers and still communicate with people usin
|
||||||
|
|
||||||
## News and updates
|
## News and updates
|
||||||
|
|
||||||
Selected updates:
|
Recent updates:
|
||||||
|
|
||||||
[Aug 8, 2022. v3.1: secret chat groups, access via Tor, reduced battery and traffic usage, advanced netwrok settings, etc.](./blog/20220808-simplex-chat-v3.1-chat-groups.md)
|
[Sep 1, 2022. v3.2: incognito mode, support .onion server hostnames, setting contact names, changing color scheme, etc. Implementation audit is arranged for October!](./blog/20220901-simplex-chat-v3.2-incognito-mode.md)
|
||||||
|
|
||||||
|
[Aug 8, 2022. v3.1: secret chat groups, access via Tor, reduced battery and traffic usage, advanced network settings, etc.](./blog/20220808-simplex-chat-v3.1-chat-groups.md)
|
||||||
|
|
||||||
[Jul 11, 2022. v3.0: instant push notifications for iOS, e2e encrypted WebRTC audio/video calls, chat database export/import, privacy and performance improvements](./blog/20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md)
|
[Jul 11, 2022. v3.0: instant push notifications for iOS, e2e encrypted WebRTC audio/video calls, chat database export/import, privacy and performance improvements](./blog/20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md)
|
||||||
|
|
||||||
[May 11, 2022. v2.0 released - sending images and files in mobile apps](./blog/20220511-simplex-chat-v2-images-files.md)
|
|
||||||
|
|
||||||
[Mar 08, 2022 Mobile apps for iOS and Android released](./blog/20220308-simplex-chat-mobile-apps.md)
|
|
||||||
|
|
||||||
[Jan 12, 2022. SimpleX v1 released: the only messaging and application platform without user identities](./20220112-simplex-chat-v1-released.md)
|
|
||||||
|
|
||||||
[All updates](./blog)
|
[All updates](./blog)
|
||||||
|
|
||||||
## Make a private connection
|
## Make a private connection
|
||||||
|
@ -177,10 +173,14 @@ If you are considering developing with SimpleX platform please get in touch for
|
||||||
- ✅ Chat database export and import
|
- ✅ Chat database export and import
|
||||||
- ✅ Chat groups in mobile apps.
|
- ✅ Chat groups in mobile apps.
|
||||||
- ✅ Connecting to messaging servers via Tor.
|
- ✅ Connecting to messaging servers via Tor.
|
||||||
- 🏗 Dual server addresses to access messaging servers as v3 hidden services (in progress).
|
- ✅ Dual server addresses to access messaging servers as v3 hidden services.
|
||||||
- 🏗 Chat server and TypeScript client SDK to develop chat interfaces, integrations and chat bots (in progress).
|
- ✅ Chat server and TypeScript client SDK to develop chat interfaces, integrations and chat bots (ready for announcement).
|
||||||
- Chat database encryption.
|
- ✅ Incognito mode to share a new random name with each contact.
|
||||||
|
- 🏗 Chat database encryption.
|
||||||
|
- 🏗 Links to join groups and improve groups stability.
|
||||||
- Disappearing messages, with mutual agreement.
|
- Disappearing messages, with mutual agreement.
|
||||||
|
- Voice messages
|
||||||
|
- Video messages
|
||||||
- Web widgets for custom interactivity in the chats.
|
- Web widgets for custom interactivity in the chats.
|
||||||
- SMP protocol improvements:
|
- SMP protocol improvements:
|
||||||
- SMP queue redundancy and rotation.
|
- SMP queue redundancy and rotation.
|
||||||
|
@ -191,6 +191,8 @@ If you are considering developing with SimpleX platform please get in touch for
|
||||||
- the server doesn't have information about your contacts and groups.
|
- the server doesn't have information about your contacts and groups.
|
||||||
- Channels server for large groups and broadcast channels.
|
- Channels server for large groups and broadcast channels.
|
||||||
- Media server to optimize sending large files to groups.
|
- Media server to optimize sending large files to groups.
|
||||||
|
- Desktop client.
|
||||||
|
- Using the same profile on multiple devices.
|
||||||
|
|
||||||
## Help us pay for 3rd party security audit
|
## Help us pay for 3rd party security audit
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ android {
|
||||||
applicationId "chat.simplex.app"
|
applicationId "chat.simplex.app"
|
||||||
minSdk 29
|
minSdk 29
|
||||||
targetSdk 32
|
targetSdk 32
|
||||||
versionCode 49
|
versionCode 52
|
||||||
versionName "3.2"
|
versionName "3.2.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
ndk {
|
ndk {
|
||||||
|
|
|
@ -15,6 +15,7 @@ import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Replay
|
import androidx.compose.material.icons.outlined.Replay
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
@ -51,7 +52,11 @@ class MainActivity: FragmentActivity(), LifecycleEventObserver {
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
|
||||||
// testJson()
|
// testJson()
|
||||||
val m = vm.chatModel
|
val m = vm.chatModel
|
||||||
processNotificationIntent(intent, m)
|
// When call ended and orientation changes, it re-process old intent, it's unneeded.
|
||||||
|
// Only needed to be processed on first creation of activity
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
processNotificationIntent(intent, m)
|
||||||
|
}
|
||||||
setContent {
|
setContent {
|
||||||
SimpleXTheme {
|
SimpleXTheme {
|
||||||
Surface(
|
Surface(
|
||||||
|
@ -223,7 +228,7 @@ fun MainPage(
|
||||||
showLANotice: () -> Unit
|
showLANotice: () -> Unit
|
||||||
) {
|
) {
|
||||||
// this with LaunchedEffect(userAuthorized.value) fixes bottom sheet visibly collapsing after authentication
|
// this with LaunchedEffect(userAuthorized.value) fixes bottom sheet visibly collapsing after authentication
|
||||||
var chatsAccessAuthorized by remember { mutableStateOf(false) }
|
var chatsAccessAuthorized by rememberSaveable { mutableStateOf(false) }
|
||||||
LaunchedEffect(userAuthorized.value) {
|
LaunchedEffect(userAuthorized.value) {
|
||||||
if (chatModel.controller.appPrefs.performLA.get()) {
|
if (chatModel.controller.appPrefs.performLA.get()) {
|
||||||
delay(500L)
|
delay(500L)
|
||||||
|
|
|
@ -67,6 +67,7 @@ class ChatModel(val controller: ChatController) {
|
||||||
|
|
||||||
fun hasChat(id: String): Boolean = chats.firstOrNull { it.id == id } != null
|
fun hasChat(id: String): Boolean = chats.firstOrNull { it.id == id } != null
|
||||||
fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id }
|
fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id }
|
||||||
|
fun getContactChat(contactId: Long): Chat? = chats.firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId }
|
||||||
private fun getChatIndex(id: String): Int = chats.indexOfFirst { it.id == id }
|
private fun getChatIndex(id: String): Int = chats.indexOfFirst { it.id == id }
|
||||||
fun addChat(chat: Chat) = chats.add(index = 0, chat)
|
fun addChat(chat: Chat) = chats.add(index = 0, chat)
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,8 @@ class AppPreferences(val context: Context) {
|
||||||
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
|
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
|
||||||
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
||||||
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
||||||
|
val networkHostMode = mkStrPreference(SHARED_PREFS_NETWORK_HOST_MODE, HostMode.OnionViaSocks.name)
|
||||||
|
val networkRequiredHostMode = mkBoolPreference(SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE, false)
|
||||||
val networkTCPConnectTimeout = mkTimeoutPreference(SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT, NetCfg.defaults.tcpConnectTimeout, NetCfg.proxyDefaults.tcpConnectTimeout)
|
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 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 networkSMPPingInterval = mkLongPreference(SHARED_PREFS_NETWORK_SMP_PING_INTERVAL, NetCfg.defaults.smpPingInterval)
|
||||||
|
@ -159,6 +161,8 @@ class AppPreferences(val context: Context) {
|
||||||
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
|
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
|
||||||
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
||||||
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
||||||
|
private const val SHARED_PREFS_NETWORK_HOST_MODE = "NetworkHostMode"
|
||||||
|
private const val SHARED_PREFS_NETWORK_REQUIRED_HOST_MODE = "NetworkRequiredHostMode"
|
||||||
private const val SHARED_PREFS_NETWORK_TCP_CONNECT_TIMEOUT = "NetworkTCPConnectTimeout"
|
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 = "NetworkTCPTimeout"
|
||||||
private const val SHARED_PREFS_NETWORK_SMP_PING_INTERVAL = "NetworkSMPPingInterval"
|
private const val SHARED_PREFS_NETWORK_SMP_PING_INTERVAL = "NetworkSMPPingInterval"
|
||||||
|
@ -1103,6 +1107,8 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
|
||||||
fun getNetCfg(): NetCfg {
|
fun getNetCfg(): NetCfg {
|
||||||
val useSocksProxy = appPrefs.networkUseSocksProxy.get()
|
val useSocksProxy = appPrefs.networkUseSocksProxy.get()
|
||||||
val socksProxy = if (useSocksProxy) ":9050" else null
|
val socksProxy = if (useSocksProxy) ":9050" else null
|
||||||
|
val hostMode = HostMode.valueOf(appPrefs.networkHostMode.get()!!)
|
||||||
|
val requiredHostMode = appPrefs.networkRequiredHostMode.get()
|
||||||
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
|
val tcpConnectTimeout = appPrefs.networkTCPConnectTimeout.get()
|
||||||
val tcpTimeout = appPrefs.networkTCPTimeout.get()
|
val tcpTimeout = appPrefs.networkTCPTimeout.get()
|
||||||
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
|
val smpPingInterval = appPrefs.networkSMPPingInterval.get()
|
||||||
|
@ -1117,6 +1123,8 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
|
||||||
}
|
}
|
||||||
return NetCfg(
|
return NetCfg(
|
||||||
socksProxy = socksProxy,
|
socksProxy = socksProxy,
|
||||||
|
hostMode = hostMode,
|
||||||
|
requiredHostMode = requiredHostMode,
|
||||||
tcpConnectTimeout = tcpConnectTimeout,
|
tcpConnectTimeout = tcpConnectTimeout,
|
||||||
tcpTimeout = tcpTimeout,
|
tcpTimeout = tcpTimeout,
|
||||||
tcpKeepAlive = tcpKeepAlive,
|
tcpKeepAlive = tcpKeepAlive,
|
||||||
|
@ -1126,6 +1134,8 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
|
||||||
|
|
||||||
fun setNetCfg(cfg: NetCfg) {
|
fun setNetCfg(cfg: NetCfg) {
|
||||||
appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy)
|
appPrefs.networkUseSocksProxy.set(cfg.useSocksProxy)
|
||||||
|
appPrefs.networkHostMode.set(cfg.hostMode.name)
|
||||||
|
appPrefs.networkRequiredHostMode.set(cfg.requiredHostMode)
|
||||||
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
|
appPrefs.networkTCPConnectTimeout.set(cfg.tcpConnectTimeout)
|
||||||
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
|
appPrefs.networkTCPTimeout.set(cfg.tcpTimeout)
|
||||||
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
|
appPrefs.networkSMPPingInterval.set(cfg.smpPingInterval)
|
||||||
|
@ -1370,6 +1380,26 @@ data class NetCfg(
|
||||||
smpPingInterval = 600_000_000
|
smpPingInterval = 600_000_000
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val onionHosts: OnionHosts get() = when {
|
||||||
|
hostMode == HostMode.Public && requiredHostMode -> OnionHosts.NEVER
|
||||||
|
hostMode == HostMode.OnionViaSocks && !requiredHostMode -> OnionHosts.PREFER
|
||||||
|
hostMode == HostMode.OnionViaSocks && requiredHostMode -> OnionHosts.REQUIRED
|
||||||
|
else -> OnionHosts.PREFER
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withOnionHosts(mode: OnionHosts): NetCfg = when (mode) {
|
||||||
|
OnionHosts.NEVER ->
|
||||||
|
this.copy(hostMode = HostMode.Public, requiredHostMode = true)
|
||||||
|
OnionHosts.PREFER ->
|
||||||
|
this.copy(hostMode = HostMode.OnionViaSocks, requiredHostMode = false)
|
||||||
|
OnionHosts.REQUIRED ->
|
||||||
|
this.copy(hostMode = HostMode.OnionViaSocks, requiredHostMode = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class OnionHosts {
|
||||||
|
NEVER, PREFER, REQUIRED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package chat.simplex.app.views.call
|
package chat.simplex.app.views.call
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -41,6 +44,7 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
|
||||||
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
@Composable
|
@Composable
|
||||||
fun ActiveCallView(chatModel: ChatModel) {
|
fun ActiveCallView(chatModel: ChatModel) {
|
||||||
BackHandler(onBack = {
|
BackHandler(onBack = {
|
||||||
|
@ -122,6 +126,17 @@ fun ActiveCallView(chatModel: ChatModel) {
|
||||||
val call = chatModel.activeCall.value
|
val call = chatModel.activeCall.value
|
||||||
if (call != null) ActiveCallOverlay(call, chatModel)
|
if (call != null) ActiveCallOverlay(call, chatModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val activity = context as? Activity ?: return@DisposableEffect onDispose {}
|
||||||
|
// Lock orientation to portrait in order to have good experience with calls
|
||||||
|
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||||
|
onDispose {
|
||||||
|
// Unlock orientation
|
||||||
|
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -337,6 +352,8 @@ fun WebRTCView(callCommand: MutableState<WCallCommand?>, onResponse: (WVAPIMessa
|
||||||
val wv = webView.value
|
val wv = webView.value
|
||||||
if (wv != null) processCommand(wv, WCallCommand.End)
|
if (wv != null) processCommand(wv, WCallCommand.End)
|
||||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
webView.value?.destroy()
|
||||||
|
webView.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(callCommand.value, webView.value) {
|
LaunchedEffect(callCommand.value, webView.value) {
|
||||||
|
|
|
@ -46,15 +46,18 @@ fun IncomingCallAlertLayout(
|
||||||
acceptCall: () -> Unit
|
acceptCall: () -> Unit
|
||||||
) {
|
) {
|
||||||
val color = if (isInDarkTheme()) IncomingCallDark else IncomingCallLight
|
val color = if (isInDarkTheme()) IncomingCallDark else IncomingCallLight
|
||||||
Column(Modifier.background(color).padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 8.dp)) {
|
Column(Modifier.fillMaxWidth().background(color).padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 8.dp)) {
|
||||||
IncomingCallInfo(invitation)
|
IncomingCallInfo(invitation)
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
ProfilePreview(profileOf = invitation.contact, size = 64.dp, color = Color.White)
|
Row(Modifier.fillMaxWidth().weight(1f), verticalAlignment = Alignment.CenterVertically) {
|
||||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
ProfilePreview(profileOf = invitation.contact, size = 64.dp, color = Color.White)
|
||||||
CallButton(stringResource(R.string.reject), Icons.Filled.CallEnd, Color.Red, rejectCall)
|
}
|
||||||
CallButton(stringResource(R.string.ignore), Icons.Filled.Close, MaterialTheme.colors.primary, ignoreCall)
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
CallButton(stringResource(R.string.accept), Icons.Filled.Check, SimplexGreen, acceptCall)
|
CallButton(stringResource(R.string.reject), Icons.Filled.CallEnd, Color.Red, rejectCall)
|
||||||
|
CallButton(stringResource(R.string.ignore), Icons.Filled.Close, MaterialTheme.colors.primary, ignoreCall)
|
||||||
|
CallButton(stringResource(R.string.accept), Icons.Filled.Check, SimplexGreen, acceptCall)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
@ -34,6 +35,8 @@ import chat.simplex.app.SimplexApp
|
||||||
import chat.simplex.app.model.*
|
import chat.simplex.app.model.*
|
||||||
import chat.simplex.app.ui.theme.*
|
import chat.simplex.app.ui.theme.*
|
||||||
import chat.simplex.app.views.helpers.*
|
import chat.simplex.app.views.helpers.*
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatInfoView(
|
fun ChatInfoView(
|
||||||
|
@ -250,10 +253,10 @@ fun ChatInfoHeader(cInfo: ChatInfo, contact: Contact) {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LocalAliasEditor(initialValue: String, updateValue: (String) -> Unit) {
|
private fun LocalAliasEditor(initialValue: String, updateValue: (String) -> Unit) {
|
||||||
var value by remember { mutableStateOf(initialValue) }
|
var value by rememberSaveable { mutableStateOf(initialValue) }
|
||||||
DefaultBasicTextField(
|
DefaultBasicTextField(
|
||||||
Modifier.fillMaxWidth().padding(horizontal = 10.dp),
|
Modifier.fillMaxWidth().padding(horizontal = 10.dp),
|
||||||
initialValue,
|
value,
|
||||||
{
|
{
|
||||||
Text(
|
Text(
|
||||||
generalGetString(R.string.text_field_set_contact_placeholder),
|
generalGetString(R.string.text_field_set_contact_placeholder),
|
||||||
|
@ -268,8 +271,17 @@ private fun LocalAliasEditor(initialValue: String, updateValue: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
value = it
|
value = it
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
snapshotFlow { value }
|
||||||
|
.onEach { delay(500) } // wait a little after every new character, don't emit until user stops typing
|
||||||
|
.conflate() // get the latest value
|
||||||
|
.filter { it == value } // don't process old ones
|
||||||
|
.collect {
|
||||||
|
updateValue(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose { updateValue(value) }
|
onDispose { updateValue(value) } // just in case snapshotFlow will be canceled when user presses Back too fast
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,7 @@ import chat.simplex.app.R
|
||||||
import chat.simplex.app.model.*
|
import chat.simplex.app.model.*
|
||||||
import chat.simplex.app.ui.theme.*
|
import chat.simplex.app.ui.theme.*
|
||||||
import chat.simplex.app.views.call.*
|
import chat.simplex.app.views.call.*
|
||||||
import chat.simplex.app.views.chat.group.AddGroupMembersView
|
import chat.simplex.app.views.chat.group.*
|
||||||
import chat.simplex.app.views.chat.group.GroupChatInfoView
|
|
||||||
import chat.simplex.app.views.chat.item.ChatItemView
|
import chat.simplex.app.views.chat.item.ChatItemView
|
||||||
import chat.simplex.app.views.chat.item.ItemAction
|
import chat.simplex.app.views.chat.item.ItemAction
|
||||||
import chat.simplex.app.views.chatlist.*
|
import chat.simplex.app.views.chatlist.*
|
||||||
|
@ -48,11 +47,13 @@ import kotlinx.datetime.Clock
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatView(chatModel: ChatModel) {
|
fun ChatView(chatModel: ChatModel) {
|
||||||
var activeChat by remember { mutableStateOf(chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }) }
|
var activeChat by remember { mutableStateOf(chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }) }
|
||||||
val searchText = remember { mutableStateOf("") }
|
val searchText = rememberSaveable { mutableStateOf("") }
|
||||||
val user = chatModel.currentUser.value
|
val user = chatModel.currentUser.value
|
||||||
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get()
|
||||||
val composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = useLinkPreviews)) }
|
val composeState = rememberSaveable(saver = ComposeState.saver()) {
|
||||||
val attachmentOption = remember { mutableStateOf<AttachmentOption?>(null) }
|
mutableStateOf(ComposeState(useLinkPreviews = useLinkPreviews))
|
||||||
|
}
|
||||||
|
val attachmentOption = rememberSaveable { mutableStateOf<AttachmentOption?>(null) }
|
||||||
val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
@ -135,12 +136,17 @@ fun ChatView(chatModel: ChatModel) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openDirectChat = { contactId ->
|
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
||||||
val c = chatModel.chats.firstOrNull {
|
withApi {
|
||||||
it.chatInfo is ChatInfo.Direct && it.chatInfo.contact.contactId == contactId
|
val stats = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
||||||
}
|
ModalManager.shared.showCustomModal { close ->
|
||||||
if (c != null) {
|
ModalView(
|
||||||
withApi { openChat(c.chatInfo, chatModel) }
|
close = close, modifier = Modifier,
|
||||||
|
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
||||||
|
) {
|
||||||
|
GroupMemberInfoView(groupInfo, member, stats, chatModel, close, close)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loadPrevMessages = { cInfo ->
|
loadPrevMessages = { cInfo ->
|
||||||
|
@ -194,7 +200,7 @@ fun ChatView(chatModel: ChatModel) {
|
||||||
close = close, modifier = Modifier,
|
close = close, modifier = Modifier,
|
||||||
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
||||||
) {
|
) {
|
||||||
AddGroupMembersView(groupInfo, chatModel, true, close)
|
AddGroupMembersView(groupInfo, chatModel, close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,7 +244,7 @@ fun ChatLayout(
|
||||||
chatModelIncognito: Boolean,
|
chatModelIncognito: Boolean,
|
||||||
back: () -> Unit,
|
back: () -> Unit,
|
||||||
info: () -> Unit,
|
info: () -> Unit,
|
||||||
openDirectChat: (Long) -> Unit,
|
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||||
loadPrevMessages: (ChatInfo) -> Unit,
|
loadPrevMessages: (ChatInfo) -> Unit,
|
||||||
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
||||||
receiveFile: (Long) -> Unit,
|
receiveFile: (Long) -> Unit,
|
||||||
|
@ -281,7 +287,7 @@ fun ChatLayout(
|
||||||
BoxWithConstraints(Modifier.fillMaxHeight().padding(contentPadding)) {
|
BoxWithConstraints(Modifier.fillMaxHeight().padding(contentPadding)) {
|
||||||
ChatItemsList(
|
ChatItemsList(
|
||||||
user, chat, unreadCount, composeState, chatItems, searchValue,
|
user, chat, unreadCount, composeState, chatItems, searchValue,
|
||||||
useLinkPreviews, chatModelIncognito, openDirectChat, loadPrevMessages, deleteMessage,
|
useLinkPreviews, chatModelIncognito, showMemberInfo, loadPrevMessages, deleteMessage,
|
||||||
receiveFile, joinGroup, acceptCall, markRead, setFloatingButton
|
receiveFile, joinGroup, acceptCall, markRead, setFloatingButton
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -300,8 +306,8 @@ fun ChatInfoToolbar(
|
||||||
addMembers: (GroupInfo) -> Unit,
|
addMembers: (GroupInfo) -> Unit,
|
||||||
onSearchValueChanged: (String) -> Unit,
|
onSearchValueChanged: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by rememberSaveable { mutableStateOf(false) }
|
||||||
var showSearch by remember { mutableStateOf(false) }
|
var showSearch by rememberSaveable { mutableStateOf(false) }
|
||||||
val onBackClicked = {
|
val onBackClicked = {
|
||||||
if (!showSearch) {
|
if (!showSearch) {
|
||||||
back()
|
back()
|
||||||
|
@ -423,7 +429,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||||
searchValue: State<String>,
|
searchValue: State<String>,
|
||||||
useLinkPreviews: Boolean,
|
useLinkPreviews: Boolean,
|
||||||
chatModelIncognito: Boolean,
|
chatModelIncognito: Boolean,
|
||||||
openDirectChat: (Long) -> Unit,
|
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
|
||||||
loadPrevMessages: (ChatInfo) -> Unit,
|
loadPrevMessages: (ChatInfo) -> Unit,
|
||||||
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
deleteMessage: (Long, CIDeleteMode) -> Unit,
|
||||||
receiveFile: (Long) -> Unit,
|
receiveFile: (Long) -> Unit,
|
||||||
|
@ -510,9 +516,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
|
||||||
Modifier
|
Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.clickable {
|
.clickable {
|
||||||
openDirectChat(contactId)
|
showMemberInfo(chat.chatInfo.groupInfo, member)
|
||||||
// Scroll to first unread message when direct chat will be loaded
|
|
||||||
shouldAutoScroll = true
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
MemberImage(member)
|
MemberImage(member)
|
||||||
|
@ -826,7 +830,7 @@ fun PreviewChatLayout() {
|
||||||
chatModelIncognito = false,
|
chatModelIncognito = false,
|
||||||
back = {},
|
back = {},
|
||||||
info = {},
|
info = {},
|
||||||
openDirectChat = {},
|
showMemberInfo = {_, _ -> },
|
||||||
loadPrevMessages = { _ -> },
|
loadPrevMessages = { _ -> },
|
||||||
deleteMessage = { _, _ -> },
|
deleteMessage = { _, _ -> },
|
||||||
receiveFile = {},
|
receiveFile = {},
|
||||||
|
@ -883,7 +887,7 @@ fun PreviewGroupChatLayout() {
|
||||||
chatModelIncognito = false,
|
chatModelIncognito = false,
|
||||||
back = {},
|
back = {},
|
||||||
info = {},
|
info = {},
|
||||||
openDirectChat = {},
|
showMemberInfo = {_, _ -> },
|
||||||
loadPrevMessages = { _ -> },
|
loadPrevMessages = { _ -> },
|
||||||
deleteMessage = { _, _ -> },
|
deleteMessage = { _, _ -> },
|
||||||
receiveFile = {},
|
receiveFile = {},
|
||||||
|
|
|
@ -19,13 +19,13 @@ import androidx.annotation.CallSuper
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.AttachFile
|
import androidx.compose.material.icons.filled.AttachFile
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.outlined.Reply
|
import androidx.compose.material.icons.outlined.Reply
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
@ -42,21 +42,26 @@ import chat.simplex.app.views.chat.item.*
|
||||||
import chat.simplex.app.views.helpers.*
|
import chat.simplex.app.views.helpers.*
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@Serializable
|
||||||
sealed class ComposePreview {
|
sealed class ComposePreview {
|
||||||
object NoPreview: ComposePreview()
|
@Serializable object NoPreview: ComposePreview()
|
||||||
class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview()
|
@Serializable class CLinkPreview(val linkPreview: LinkPreview?): ComposePreview()
|
||||||
class ImagePreview(val image: String): ComposePreview()
|
@Serializable class ImagePreview(val image: String): ComposePreview()
|
||||||
class FilePreview(val fileName: String): ComposePreview()
|
@Serializable class FilePreview(val fileName: String): ComposePreview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
sealed class ComposeContextItem {
|
sealed class ComposeContextItem {
|
||||||
object NoContextItem: ComposeContextItem()
|
@Serializable object NoContextItem: ComposeContextItem()
|
||||||
class QuotedItem(val chatItem: ChatItem): ComposeContextItem()
|
@Serializable class QuotedItem(val chatItem: ChatItem): ComposeContextItem()
|
||||||
class EditingItem(val chatItem: ChatItem): ComposeContextItem()
|
@Serializable class EditingItem(val chatItem: ChatItem): ComposeContextItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class ComposeState(
|
data class ComposeState(
|
||||||
val message: String = "",
|
val message: String = "",
|
||||||
val preview: ComposePreview = ComposePreview.NoPreview,
|
val preview: ComposePreview = ComposePreview.NoPreview,
|
||||||
|
@ -99,6 +104,15 @@ data class ComposeState(
|
||||||
is ComposePreview.CLinkPreview -> preview.linkPreview
|
is ComposePreview.CLinkPreview -> preview.linkPreview
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun saver(): Saver<MutableState<ComposeState>, *> = Saver(
|
||||||
|
save = { json.encodeToString(serializer(), it.value) },
|
||||||
|
restore = {
|
||||||
|
mutableStateOf(json.decodeFromString(it))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun chatItemPreview(chatItem: ChatItem): ComposePreview {
|
fun chatItemPreview(chatItem: ChatItem): ComposePreview {
|
||||||
|
|
|
@ -31,7 +31,7 @@ import chat.simplex.app.views.chat.ChatInfoToolbarTitle
|
||||||
import chat.simplex.app.views.helpers.*
|
import chat.simplex.app.views.helpers.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddGroupMembersView(groupInfo: GroupInfo, chatModel: ChatModel, showFooterCounter: Boolean = true, close: () -> Unit) {
|
fun AddGroupMembersView(groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) {
|
||||||
val selectedContacts = remember { mutableStateListOf<Long>() }
|
val selectedContacts = remember { mutableStateListOf<Long>() }
|
||||||
val selectedRole = remember { mutableStateOf(GroupMemberRole.Admin) }
|
val selectedRole = remember { mutableStateOf(GroupMemberRole.Admin) }
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ fun AddGroupMembersView(groupInfo: GroupInfo, chatModel: ChatModel, showFooterCo
|
||||||
contactsToAdd = getContactsToAdd(chatModel),
|
contactsToAdd = getContactsToAdd(chatModel),
|
||||||
selectedContacts = selectedContacts,
|
selectedContacts = selectedContacts,
|
||||||
selectedRole = selectedRole,
|
selectedRole = selectedRole,
|
||||||
showFooterCounter = showFooterCounter,
|
|
||||||
inviteMembers = {
|
inviteMembers = {
|
||||||
withApi {
|
withApi {
|
||||||
selectedContacts.forEach {
|
selectedContacts.forEach {
|
||||||
|
@ -79,7 +78,6 @@ fun AddGroupMembersLayout(
|
||||||
contactsToAdd: List<Contact>,
|
contactsToAdd: List<Contact>,
|
||||||
selectedContacts: SnapshotStateList<Long>,
|
selectedContacts: SnapshotStateList<Long>,
|
||||||
selectedRole: MutableState<GroupMemberRole>,
|
selectedRole: MutableState<GroupMemberRole>,
|
||||||
showFooterCounter: Boolean,
|
|
||||||
inviteMembers: () -> Unit,
|
inviteMembers: () -> Unit,
|
||||||
clearSelection: () -> Unit,
|
clearSelection: () -> Unit,
|
||||||
addContact: (Long) -> Unit,
|
addContact: (Long) -> Unit,
|
||||||
|
@ -124,10 +122,8 @@ fun AddGroupMembersLayout(
|
||||||
InviteMembersButton(inviteMembers, disabled = selectedContacts.isEmpty())
|
InviteMembersButton(inviteMembers, disabled = selectedContacts.isEmpty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showFooterCounter) {
|
SectionCustomFooter {
|
||||||
SectionCustomFooter {
|
InviteSectionFooter(selectedContactsCount = selectedContacts.count(), clearSelection)
|
||||||
InviteSectionFooter(selectedContactsCount = selectedContacts.count(), clearSelection)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SectionSpacer()
|
SectionSpacer()
|
||||||
|
|
||||||
|
@ -351,7 +347,6 @@ fun PreviewAddGroupMembersLayout() {
|
||||||
contactsToAdd = listOf(Contact.sampleData, Contact.sampleData, Contact.sampleData),
|
contactsToAdd = listOf(Contact.sampleData, Contact.sampleData, Contact.sampleData),
|
||||||
selectedContacts = remember { mutableStateListOf() },
|
selectedContacts = remember { mutableStateListOf() },
|
||||||
selectedRole = remember { mutableStateOf(GroupMemberRole.Admin) },
|
selectedRole = remember { mutableStateOf(GroupMemberRole.Admin) },
|
||||||
showFooterCounter = true,
|
|
||||||
inviteMembers = {},
|
inviteMembers = {},
|
||||||
clearSelection = {},
|
clearSelection = {},
|
||||||
addContact = {},
|
addContact = {},
|
||||||
|
|
|
@ -51,7 +51,7 @@ fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) {
|
||||||
close = close, modifier = Modifier,
|
close = close, modifier = Modifier,
|
||||||
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
||||||
) {
|
) {
|
||||||
AddGroupMembersView(groupInfo, chatModel, true, close)
|
AddGroupMembersView(groupInfo, chatModel, close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,12 +59,12 @@ fun GroupChatInfoView(chatModel: ChatModel, close: () -> Unit) {
|
||||||
showMemberInfo = { member ->
|
showMemberInfo = { member ->
|
||||||
withApi {
|
withApi {
|
||||||
val stats = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
val stats = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
|
||||||
ModalManager.shared.showCustomModal { close ->
|
ModalManager.shared.showCustomModal { closeCurrent ->
|
||||||
ModalView(
|
ModalView(
|
||||||
close = close, modifier = Modifier,
|
close = closeCurrent, modifier = Modifier,
|
||||||
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
||||||
) {
|
) {
|
||||||
GroupMemberInfoView(groupInfo, member, stats, chatModel, close)
|
GroupMemberInfoView(groupInfo, member, stats, chatModel, closeCurrent) { closeCurrent(); close() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import chat.simplex.app.R
|
||||||
import chat.simplex.app.model.*
|
import chat.simplex.app.model.*
|
||||||
import chat.simplex.app.ui.theme.*
|
import chat.simplex.app.ui.theme.*
|
||||||
import chat.simplex.app.views.chat.SimplexServers
|
import chat.simplex.app.views.chat.SimplexServers
|
||||||
|
import chat.simplex.app.views.chatlist.openChat
|
||||||
import chat.simplex.app.views.helpers.*
|
import chat.simplex.app.views.helpers.*
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -32,7 +33,8 @@ fun GroupMemberInfoView(
|
||||||
member: GroupMember,
|
member: GroupMember,
|
||||||
connStats: ConnectionStats?,
|
connStats: ConnectionStats?,
|
||||||
chatModel: ChatModel,
|
chatModel: ChatModel,
|
||||||
close: () -> Unit
|
close: () -> Unit,
|
||||||
|
closeAll: () -> Unit, // Close all open windows up to ChatView
|
||||||
) {
|
) {
|
||||||
BackHandler(onBack = close)
|
BackHandler(onBack = close)
|
||||||
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
||||||
|
@ -43,6 +45,22 @@ fun GroupMemberInfoView(
|
||||||
member,
|
member,
|
||||||
connStats,
|
connStats,
|
||||||
developerTools,
|
developerTools,
|
||||||
|
openDirectChat = {
|
||||||
|
withApi {
|
||||||
|
val oldChat = chatModel.getContactChat(member.memberContactId ?: return@withApi)
|
||||||
|
if (oldChat != null) {
|
||||||
|
openChat(oldChat.chatInfo, chatModel)
|
||||||
|
} else {
|
||||||
|
var newChat = chatModel.controller.apiGetChat(ChatType.Direct, member.memberContactId) ?: return@withApi
|
||||||
|
// TODO it's not correct to blindly set network status to connected - we should manage network status in model / backend
|
||||||
|
newChat = newChat.copy(serverInfo = Chat.ServerInfo(networkStatus = Chat.NetworkStatus.Connected()))
|
||||||
|
chatModel.addChat(newChat)
|
||||||
|
chatModel.chatItems.clear()
|
||||||
|
chatModel.chatId.value = newChat.id
|
||||||
|
}
|
||||||
|
closeAll()
|
||||||
|
}
|
||||||
|
},
|
||||||
removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) }
|
removeMember = { removeMemberDialog(groupInfo, member, chatModel, close) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -71,6 +89,7 @@ fun GroupMemberInfoLayout(
|
||||||
member: GroupMember,
|
member: GroupMember,
|
||||||
connStats: ConnectionStats?,
|
connStats: ConnectionStats?,
|
||||||
developerTools: Boolean,
|
developerTools: Boolean,
|
||||||
|
openDirectChat: () -> Unit,
|
||||||
removeMember: () -> Unit,
|
removeMember: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
|
@ -87,6 +106,13 @@ fun GroupMemberInfoLayout(
|
||||||
}
|
}
|
||||||
SectionSpacer()
|
SectionSpacer()
|
||||||
|
|
||||||
|
SectionView {
|
||||||
|
SectionItemView {
|
||||||
|
OpenChatButton(openDirectChat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SectionSpacer()
|
||||||
|
|
||||||
SectionView(title = stringResource(R.string.member_info_section_title_member)) {
|
SectionView(title = stringResource(R.string.member_info_section_title_member)) {
|
||||||
InfoRow(stringResource(R.string.info_row_group), groupInfo.displayName)
|
InfoRow(stringResource(R.string.info_row_group), groupInfo.displayName)
|
||||||
val conn = member.activeConn
|
val conn = member.activeConn
|
||||||
|
@ -181,6 +207,25 @@ fun RemoveMemberButton(removeMember: () -> Unit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OpenChatButton(onClick: () -> Unit) {
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clickable { onClick() },
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Outlined.Message,
|
||||||
|
stringResource(R.string.button_send_direct_message),
|
||||||
|
Modifier.padding(top = 5.dp),
|
||||||
|
tint = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
Text(stringResource(R.string.button_send_direct_message), color = MaterialTheme.colors.primary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewGroupMemberInfoLayout() {
|
fun PreviewGroupMemberInfoLayout() {
|
||||||
|
@ -190,6 +235,7 @@ fun PreviewGroupMemberInfoLayout() {
|
||||||
member = GroupMember.sampleData,
|
member = GroupMember.sampleData,
|
||||||
connStats = null,
|
connStats = null,
|
||||||
developerTools = false,
|
developerTools = false,
|
||||||
|
openDirectChat = {},
|
||||||
removeMember = {}
|
removeMember = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.material.TextFieldDefaults.textFieldWithLabelPadding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
@ -20,8 +21,7 @@ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.*
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.app.R
|
import chat.simplex.app.R
|
||||||
|
@ -30,7 +30,7 @@ import kotlinx.coroutines.delay
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchTextField(modifier: Modifier, placeholder: String, onValueChange: (String) -> Unit) {
|
fun SearchTextField(modifier: Modifier, placeholder: String, onValueChange: (String) -> Unit) {
|
||||||
var searchText by remember { mutableStateOf("") }
|
var searchText by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val keyboard = LocalSoftwareKeyboardController.current
|
val keyboard = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ fun SearchTextField(modifier: Modifier, placeholder: String, onValueChange: (Str
|
||||||
),
|
),
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
searchText = it
|
searchText = it
|
||||||
onValueChange(it)
|
onValueChange(it.text)
|
||||||
},
|
},
|
||||||
cursorBrush = SolidColor(colors.cursorColor(false).value),
|
cursorBrush = SolidColor(colors.cursorColor(false).value),
|
||||||
visualTransformation = VisualTransformation.None,
|
visualTransformation = VisualTransformation.None,
|
||||||
|
@ -75,13 +75,13 @@ fun SearchTextField(modifier: Modifier, placeholder: String, onValueChange: (Str
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
decorationBox = @Composable { innerTextField ->
|
decorationBox = @Composable { innerTextField ->
|
||||||
TextFieldDefaults.TextFieldDecorationBox(
|
TextFieldDefaults.TextFieldDecorationBox(
|
||||||
value = searchText,
|
value = searchText.text,
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(placeholder)
|
Text(placeholder)
|
||||||
},
|
},
|
||||||
trailingIcon = if (searchText.isNotEmpty()) {{
|
trailingIcon = if (searchText.text.isNotEmpty()) {{
|
||||||
IconButton({ searchText = ""; onValueChange("") }) {
|
IconButton({ searchText = TextFieldValue(""); onValueChange("") }) {
|
||||||
Icon(Icons.Default.Close, stringResource(R.string.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
|
Icon(Icons.Default.Close, stringResource(R.string.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
|
||||||
}
|
}
|
||||||
}} else null,
|
}} else null,
|
||||||
|
|
|
@ -50,7 +50,7 @@ fun AddGroupView(chatModel: ChatModel, close: () -> Unit) {
|
||||||
close = close, modifier = Modifier,
|
close = close, modifier = Modifier,
|
||||||
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
background = if (isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight
|
||||||
) {
|
) {
|
||||||
AddGroupMembersView(groupInfo, chatModel, false, close)
|
AddGroupMembersView(groupInfo, chatModel, close)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,14 @@ import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.app.R
|
import chat.simplex.app.R
|
||||||
import chat.simplex.app.model.ChatModel
|
import chat.simplex.app.model.*
|
||||||
import chat.simplex.app.model.NetCfg
|
|
||||||
import chat.simplex.app.ui.theme.*
|
import chat.simplex.app.ui.theme.*
|
||||||
import chat.simplex.app.views.helpers.*
|
import chat.simplex.app.views.helpers.*
|
||||||
|
|
||||||
|
@ -25,16 +27,19 @@ fun NetworkAndServersView(
|
||||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
|
||||||
) {
|
) {
|
||||||
val netCfg: MutableState<NetCfg> = remember { mutableStateOf(chatModel.controller.getNetCfg()) }
|
// It's not a state, just a one-time value. Shouldn't be used in any state-related situations
|
||||||
val networkUseSocksProxy: MutableState<Boolean> = remember { mutableStateOf(netCfg.value.useSocksProxy) }
|
val netCfg = remember { chatModel.controller.getNetCfg() }
|
||||||
|
val networkUseSocksProxy: MutableState<Boolean> = remember { mutableStateOf(netCfg.useSocksProxy) }
|
||||||
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
val developerTools = chatModel.controller.appPrefs.developerTools.get()
|
||||||
|
val onionHosts = remember { mutableStateOf(netCfg.onionHosts) }
|
||||||
|
|
||||||
NetworkAndServersLayout(
|
NetworkAndServersLayout(
|
||||||
developerTools = developerTools,
|
developerTools = developerTools,
|
||||||
networkUseSocksProxy = networkUseSocksProxy,
|
networkUseSocksProxy = networkUseSocksProxy,
|
||||||
|
onionHosts = onionHosts,
|
||||||
showModal = showModal,
|
showModal = showModal,
|
||||||
showSettingsModal = showSettingsModal,
|
showSettingsModal = showSettingsModal,
|
||||||
toggleSocksProxy = { enable ->
|
toggleSocksProxy = { enable ->
|
||||||
if (enable) {
|
if (enable) {
|
||||||
AlertManager.shared.showAlertMsg(
|
AlertManager.shared.showAlertMsg(
|
||||||
title = generalGetString(R.string.network_enable_socks),
|
title = generalGetString(R.string.network_enable_socks),
|
||||||
|
@ -45,6 +50,7 @@ fun NetworkAndServersView(
|
||||||
chatModel.controller.apiSetNetworkConfig(NetCfg.proxyDefaults)
|
chatModel.controller.apiSetNetworkConfig(NetCfg.proxyDefaults)
|
||||||
chatModel.controller.setNetCfg(NetCfg.proxyDefaults)
|
chatModel.controller.setNetCfg(NetCfg.proxyDefaults)
|
||||||
networkUseSocksProxy.value = true
|
networkUseSocksProxy.value = true
|
||||||
|
onionHosts.value = NetCfg.proxyDefaults.onionHosts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -58,10 +64,29 @@ fun NetworkAndServersView(
|
||||||
chatModel.controller.apiSetNetworkConfig(NetCfg.defaults)
|
chatModel.controller.apiSetNetworkConfig(NetCfg.defaults)
|
||||||
chatModel.controller.setNetCfg(NetCfg.defaults)
|
chatModel.controller.setNetCfg(NetCfg.defaults)
|
||||||
networkUseSocksProxy.value = false
|
networkUseSocksProxy.value = false
|
||||||
|
onionHosts.value = NetCfg.defaults.onionHosts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
useOnion = {
|
||||||
|
val prevValue = onionHosts.value
|
||||||
|
onionHosts.value = it
|
||||||
|
updateNetworkSettingsDialog(onDismiss = {
|
||||||
|
onionHosts.value = prevValue
|
||||||
|
}) {
|
||||||
|
withApi {
|
||||||
|
val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it)
|
||||||
|
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
|
||||||
|
if (res) {
|
||||||
|
chatModel.controller.setNetCfg(newCfg)
|
||||||
|
onionHosts.value = it
|
||||||
|
} else {
|
||||||
|
onionHosts.value = prevValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -69,9 +94,11 @@ fun NetworkAndServersView(
|
||||||
@Composable fun NetworkAndServersLayout(
|
@Composable fun NetworkAndServersLayout(
|
||||||
developerTools: Boolean,
|
developerTools: Boolean,
|
||||||
networkUseSocksProxy: MutableState<Boolean>,
|
networkUseSocksProxy: MutableState<Boolean>,
|
||||||
|
onionHosts: MutableState<OnionHosts>,
|
||||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
toggleSocksProxy: (Boolean) -> Unit
|
toggleSocksProxy: (Boolean) -> Unit,
|
||||||
|
useOnion: (OnionHosts) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
|
@ -89,6 +116,10 @@ fun NetworkAndServersView(
|
||||||
SectionItemView {
|
SectionItemView {
|
||||||
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
|
UseSocksProxySwitch(networkUseSocksProxy, toggleSocksProxy)
|
||||||
}
|
}
|
||||||
|
SectionDivider()
|
||||||
|
SectionItemView {
|
||||||
|
UseOnionHosts(onionHosts, networkUseSocksProxy, useOnion)
|
||||||
|
}
|
||||||
if (developerTools) {
|
if (developerTools) {
|
||||||
SectionDivider()
|
SectionDivider()
|
||||||
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
|
SettingsActionItem(Icons.Outlined.Cable, stringResource(R.string.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
|
||||||
|
@ -129,6 +160,116 @@ fun UseSocksProxySwitch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun UseOnionHosts(onionHosts: MutableState<OnionHosts>, enabled: State<Boolean>, useOnion: (OnionHosts) -> Unit) {
|
||||||
|
val values = remember {
|
||||||
|
OnionHosts.values().map {
|
||||||
|
when (it) {
|
||||||
|
OnionHosts.NEVER -> OnionHosts.NEVER to generalGetString(R.string.network_use_onion_hosts_no)
|
||||||
|
OnionHosts.PREFER -> OnionHosts.PREFER to generalGetString(R.string.network_use_onion_hosts_prefer)
|
||||||
|
OnionHosts.REQUIRED -> OnionHosts.REQUIRED to generalGetString(R.string.network_use_onion_hosts_required)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExposedDropDownSettingRow(
|
||||||
|
generalGetString(R.string.network_use_onion_hosts),
|
||||||
|
values,
|
||||||
|
onionHosts,
|
||||||
|
icon = Icons.Outlined.Security,
|
||||||
|
enabled = enabled,
|
||||||
|
onSelected = useOnion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> ExposedDropDownSettingRow(
|
||||||
|
title: String,
|
||||||
|
values: List<Pair<T, String>>,
|
||||||
|
selection: State<T>,
|
||||||
|
label: String? = null,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
iconTint: Color = HighOrLowlight,
|
||||||
|
enabled: State<Boolean> = mutableStateOf(true),
|
||||||
|
onSelected: (T) -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
"",
|
||||||
|
Modifier.padding(end = 8.dp),
|
||||||
|
tint = iconTint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(title, color = if (enabled.value) Color.Unspecified else HighOrLowlight)
|
||||||
|
|
||||||
|
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||||
|
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = expanded,
|
||||||
|
onExpandedChange = {
|
||||||
|
expanded = !expanded && enabled.value
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
Modifier.padding(start = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
values.first { it.first == selection.value }.second + (if (label != null) " $label" else ""),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = HighOrLowlight
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(12.dp))
|
||||||
|
Icon(
|
||||||
|
if (!expanded) Icons.Outlined.ExpandMore else Icons.Outlined.ExpandLess,
|
||||||
|
generalGetString(R.string.icon_descr_more_button),
|
||||||
|
tint = HighOrLowlight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
modifier = Modifier.widthIn(min = 200.dp),
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = {
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
values.forEach { selectionOption ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
onSelected(selectionOption.first)
|
||||||
|
expanded = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
selectionOption.second + (if (label != null) " $label" else ""),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNetworkSettingsDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
|
||||||
|
AlertManager.shared.showAlertDialog(
|
||||||
|
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),
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
onConfirm = onConfirm,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Composable
|
@Composable
|
||||||
fun PreviewNetworkAndServersLayout() {
|
fun PreviewNetworkAndServersLayout() {
|
||||||
|
@ -138,7 +279,9 @@ fun PreviewNetworkAndServersLayout() {
|
||||||
networkUseSocksProxy = remember { mutableStateOf(true) },
|
networkUseSocksProxy = remember { mutableStateOf(true) },
|
||||||
showModal = { {} },
|
showModal = { {} },
|
||||||
showSettingsModal = { {} },
|
showSettingsModal = { {} },
|
||||||
toggleSocksProxy = {}
|
toggleSocksProxy = {},
|
||||||
|
onionHosts = remember { mutableStateOf(OnionHosts.PREFER) },
|
||||||
|
useOnion = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.platform.UriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.app.*
|
import chat.simplex.app.*
|
||||||
|
@ -342,11 +343,15 @@ fun MaintainIncognitoState(chatModel: ChatModel) {
|
||||||
profileOf.displayName,
|
profileOf.displayName,
|
||||||
style = MaterialTheme.typography.caption,
|
style = MaterialTheme.typography.caption,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = if (stopped) HighOrLowlight else Color.Unspecified
|
color = if (stopped) HighOrLowlight else Color.Unspecified,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
profileOf.fullName,
|
profileOf.fullName,
|
||||||
color = if (stopped) HighOrLowlight else Color.Unspecified
|
color = if (stopped) HighOrLowlight else Color.Unspecified,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,6 +297,10 @@
|
||||||
<string name="network_enable_socks_info">Соединяться с серверами через SOCKS прокси через порт 9050? Прокси должен быть запущен до включения этой опции.</string>
|
<string name="network_enable_socks_info">Соединяться с серверами через SOCKS прокси через порт 9050? Прокси должен быть запущен до включения этой опции.</string>
|
||||||
<string name="network_disable_socks">Использовать прямое соединение с Интернет?</string>
|
<string name="network_disable_socks">Использовать прямое соединение с Интернет?</string>
|
||||||
<string name="network_disable_socks_info">Если вы подтвердите, серверы смогут видеть ваш IP адрес, а провайдер - с какими серверами вы соединяетесь.</string>
|
<string name="network_disable_socks_info">Если вы подтвердите, серверы смогут видеть ваш IP адрес, а провайдер - с какими серверами вы соединяетесь.</string>
|
||||||
|
<string name="network_use_onion_hosts">Использовать .onion хосты</string>
|
||||||
|
<string name="network_use_onion_hosts_prefer">Когда возможно</string>
|
||||||
|
<string name="network_use_onion_hosts_no">Нет</string>
|
||||||
|
<string name="network_use_onion_hosts_required">Обязательно</string>
|
||||||
<string name="appearance_settings">Интерфейс</string>
|
<string name="appearance_settings">Интерфейс</string>
|
||||||
|
|
||||||
<!-- Address Items - UserAddressView.kt -->
|
<!-- Address Items - UserAddressView.kt -->
|
||||||
|
@ -614,6 +618,7 @@
|
||||||
|
|
||||||
<!-- GroupMemberInfoView.kt -->
|
<!-- GroupMemberInfoView.kt -->
|
||||||
<string name="button_remove_member">Удалить члена группы</string>
|
<string name="button_remove_member">Удалить члена группы</string>
|
||||||
|
<string name="button_send_direct_message">Отправить сообщение</string>
|
||||||
<string name="member_will_be_removed_from_group_cannot_be_undone">Член группы будет удален - это действие нельзя отменить!</string>
|
<string name="member_will_be_removed_from_group_cannot_be_undone">Член группы будет удален - это действие нельзя отменить!</string>
|
||||||
<string name="remove_member_confirmation">Удалить</string>
|
<string name="remove_member_confirmation">Удалить</string>
|
||||||
<string name="member_info_section_title_member">ЧЛЕН ГРУППЫ</string>
|
<string name="member_info_section_title_member">ЧЛЕН ГРУППЫ</string>
|
||||||
|
|
|
@ -301,6 +301,10 @@
|
||||||
<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_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">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>
|
<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>
|
||||||
|
<string name="network_use_onion_hosts">Use .onion hosts</string>
|
||||||
|
<string name="network_use_onion_hosts_prefer">When available</string>
|
||||||
|
<string name="network_use_onion_hosts_no">No</string>
|
||||||
|
<string name="network_use_onion_hosts_required">Required</string>
|
||||||
<string name="appearance_settings">Appearance</string>
|
<string name="appearance_settings">Appearance</string>
|
||||||
|
|
||||||
<!-- Address Items - UserAddressView.kt -->
|
<!-- Address Items - UserAddressView.kt -->
|
||||||
|
@ -615,6 +619,7 @@
|
||||||
|
|
||||||
<!-- GroupMemberInfoView.kt -->
|
<!-- GroupMemberInfoView.kt -->
|
||||||
<string name="button_remove_member">Remove member</string>
|
<string name="button_remove_member">Remove member</string>
|
||||||
|
<string name="button_send_direct_message">Send direct message</string>
|
||||||
<string name="member_will_be_removed_from_group_cannot_be_undone">Member will be removed from group - this cannot be undone!</string>
|
<string name="member_will_be_removed_from_group_cannot_be_undone">Member will be removed from group - this cannot be undone!</string>
|
||||||
<string name="remove_member_confirmation">Remove</string>
|
<string name="remove_member_confirmation">Remove</string>
|
||||||
<string name="member_info_section_title_member">MEMBER</string>
|
<string name="member_info_section_title_member">MEMBER</string>
|
||||||
|
|
|
@ -25,7 +25,7 @@ struct GroupProfileView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
return VStack(alignment: .leading) {
|
return VStack(alignment: .leading) {
|
||||||
Text("Group profile is stored on members' devices, not on the servers.")
|
Text("Group profile is stored on members' devices, not on the servers.")
|
||||||
.padding(.bottom)
|
.padding(.vertical)
|
||||||
|
|
||||||
ZStack(alignment: .center) {
|
ZStack(alignment: .center) {
|
||||||
ZStack(alignment: .topTrailing) {
|
ZStack(alignment: .topTrailing) {
|
||||||
|
@ -109,7 +109,7 @@ struct GroupProfileView: View {
|
||||||
.onTapGesture { hideKeyboard() }
|
.onTapGesture { hideKeyboard() }
|
||||||
}
|
}
|
||||||
|
|
||||||
func profileNameTextEdit(_ label: String, _ name: Binding<String>) -> some View {
|
func profileNameTextEdit(_ label: LocalizedStringKey, _ name: Binding<String>) -> some View {
|
||||||
TextField(label, text: name)
|
TextField(label, text: name)
|
||||||
.textInputAutocapitalization(.never)
|
.textInputAutocapitalization(.never)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
||||||
<file original="en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
|
<file original="en.lproj/Localizable.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||||
<header>
|
<header>
|
||||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/>
|
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.4.1" build-num="13F100"/>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id=" " xml:space="preserve">
|
<trans-unit id=" " xml:space="preserve">
|
||||||
|
@ -346,7 +346,7 @@
|
||||||
<trans-unit id="Chats" xml:space="preserve">
|
<trans-unit id="Chats" xml:space="preserve">
|
||||||
<source>Chats</source>
|
<source>Chats</source>
|
||||||
<target>Chats</target>
|
<target>Chats</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>back button to return to chats list</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="Choose file" xml:space="preserve">
|
<trans-unit id="Choose file" xml:space="preserve">
|
||||||
<source>Choose file</source>
|
<source>Choose file</source>
|
||||||
|
@ -533,6 +533,11 @@
|
||||||
<target>Currently maximum supported file size is %@.</target>
|
<target>Currently maximum supported file size is %@.</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Dark" xml:space="preserve">
|
||||||
|
<source>Dark</source>
|
||||||
|
<target>Dark</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="Database ID" xml:space="preserve">
|
<trans-unit id="Database ID" xml:space="preserve">
|
||||||
<source>Database ID</source>
|
<source>Database ID</source>
|
||||||
<target>Database ID</target>
|
<target>Database ID</target>
|
||||||
|
@ -1143,6 +1148,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||||
<target>Leave group?</target>
|
<target>Leave group?</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Light" xml:space="preserve">
|
||||||
|
<source>Light</source>
|
||||||
|
<target>Light</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="Limitations" xml:space="preserve">
|
<trans-unit id="Limitations" xml:space="preserve">
|
||||||
<source>Limitations</source>
|
<source>Limitations</source>
|
||||||
<target>Limitations</target>
|
<target>Limitations</target>
|
||||||
|
@ -1728,6 +1738,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||||
<target>Stop chat?</target>
|
<target>Stop chat?</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="System" xml:space="preserve">
|
||||||
|
<source>System</source>
|
||||||
|
<target>System</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||||
<source>TCP connection timeout</source>
|
<source>TCP connection timeout</source>
|
||||||
<target>TCP connection timeout</target>
|
<target>TCP connection timeout</target>
|
||||||
|
@ -1828,6 +1843,11 @@ We will be adding server redundancy to prevent lost messages.</target>
|
||||||
<target>The sender will NOT be notified</target>
|
<target>The sender will NOT be notified</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Theme" xml:space="preserve">
|
||||||
|
<source>Theme</source>
|
||||||
|
<target>Theme</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
|
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
|
||||||
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
|
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
|
||||||
<target>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</target>
|
<target>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</target>
|
||||||
|
@ -2670,7 +2690,7 @@ SimpleX servers cannot see your profile.</target>
|
||||||
</file>
|
</file>
|
||||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
|
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||||
<header>
|
<header>
|
||||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/>
|
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.4.1" build-num="13F100"/>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||||
|
@ -2702,7 +2722,7 @@ SimpleX servers cannot see your profile.</target>
|
||||||
</file>
|
</file>
|
||||||
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
|
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="en" datatype="plaintext">
|
||||||
<header>
|
<header>
|
||||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/>
|
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.4.1" build-num="13F100"/>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
"project" : "SimpleX.xcodeproj",
|
"project" : "SimpleX.xcodeproj",
|
||||||
"targetLocale" : "en",
|
"targetLocale" : "en",
|
||||||
"toolInfo" : {
|
"toolInfo" : {
|
||||||
"toolBuildNumber" : "13E113",
|
"toolBuildNumber" : "13F100",
|
||||||
"toolID" : "com.apple.dt.xcode",
|
"toolID" : "com.apple.dt.xcode",
|
||||||
"toolName" : "Xcode",
|
"toolName" : "Xcode",
|
||||||
"toolVersion" : "13.3"
|
"toolVersion" : "13.4.1"
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
|
||||||
<file original="en.lproj/Localizable.strings" source-language="en" target-language="ru" datatype="plaintext">
|
<file original="en.lproj/Localizable.strings" source-language="en" target-language="ru" datatype="plaintext">
|
||||||
<header>
|
<header>
|
||||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/>
|
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.4.1" build-num="13F100"/>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id=" " xml:space="preserve">
|
<trans-unit id=" " xml:space="preserve">
|
||||||
|
@ -346,7 +346,7 @@
|
||||||
<trans-unit id="Chats" xml:space="preserve">
|
<trans-unit id="Chats" xml:space="preserve">
|
||||||
<source>Chats</source>
|
<source>Chats</source>
|
||||||
<target>Чаты</target>
|
<target>Чаты</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>back button to return to chats list</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="Choose file" xml:space="preserve">
|
<trans-unit id="Choose file" xml:space="preserve">
|
||||||
<source>Choose file</source>
|
<source>Choose file</source>
|
||||||
|
@ -533,6 +533,11 @@
|
||||||
<target>Максимальный размер файла - %@.</target>
|
<target>Максимальный размер файла - %@.</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Dark" xml:space="preserve">
|
||||||
|
<source>Dark</source>
|
||||||
|
<target>Тёмная</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="Database ID" xml:space="preserve">
|
<trans-unit id="Database ID" xml:space="preserve">
|
||||||
<source>Database ID</source>
|
<source>Database ID</source>
|
||||||
<target>ID базы данных</target>
|
<target>ID базы данных</target>
|
||||||
|
@ -1143,6 +1148,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||||
<target>Выйти из группы?</target>
|
<target>Выйти из группы?</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Light" xml:space="preserve">
|
||||||
|
<source>Light</source>
|
||||||
|
<target>Светлая</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="Limitations" xml:space="preserve">
|
<trans-unit id="Limitations" xml:space="preserve">
|
||||||
<source>Limitations</source>
|
<source>Limitations</source>
|
||||||
<target>Ограничения</target>
|
<target>Ограничения</target>
|
||||||
|
@ -1728,6 +1738,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||||
<target>Остановить чат?</target>
|
<target>Остановить чат?</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="System" xml:space="preserve">
|
||||||
|
<source>System</source>
|
||||||
|
<target>Системная</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||||
<source>TCP connection timeout</source>
|
<source>TCP connection timeout</source>
|
||||||
<target>Таймаут TCP соединения</target>
|
<target>Таймаут TCP соединения</target>
|
||||||
|
@ -1828,6 +1843,11 @@ We will be adding server redundancy to prevent lost messages.</source>
|
||||||
<target>Отправитель не будет уведомлён</target>
|
<target>Отправитель не будет уведомлён</target>
|
||||||
<note>No comment provided by engineer.</note>
|
<note>No comment provided by engineer.</note>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="Theme" xml:space="preserve">
|
||||||
|
<source>Theme</source>
|
||||||
|
<target>Тема</target>
|
||||||
|
<note>No comment provided by engineer.</note>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
|
<trans-unit id="This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." xml:space="preserve">
|
||||||
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
|
<source>This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.</source>
|
||||||
<target>Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.</target>
|
<target>Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.</target>
|
||||||
|
@ -2670,7 +2690,7 @@ SimpleX серверы не могут получить доступ к ваше
|
||||||
</file>
|
</file>
|
||||||
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
|
<file original="en.lproj/SimpleX--iOS--InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
|
||||||
<header>
|
<header>
|
||||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/>
|
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.4.1" build-num="13F100"/>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="CFBundleName" xml:space="preserve">
|
<trans-unit id="CFBundleName" xml:space="preserve">
|
||||||
|
@ -2702,7 +2722,7 @@ SimpleX серверы не могут получить доступ к ваше
|
||||||
</file>
|
</file>
|
||||||
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
|
<file original="SimpleX NSE/en.lproj/InfoPlist.strings" source-language="en" target-language="ru" datatype="plaintext">
|
||||||
<header>
|
<header>
|
||||||
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.3" build-num="13E113"/>
|
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="13.4.1" build-num="13F100"/>
|
||||||
</header>
|
</header>
|
||||||
<body>
|
<body>
|
||||||
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
"project" : "SimpleX.xcodeproj",
|
"project" : "SimpleX.xcodeproj",
|
||||||
"targetLocale" : "ru",
|
"targetLocale" : "ru",
|
||||||
"toolInfo" : {
|
"toolInfo" : {
|
||||||
"toolBuildNumber" : "13E113",
|
"toolBuildNumber" : "13F100",
|
||||||
"toolID" : "com.apple.dt.xcode",
|
"toolID" : "com.apple.dt.xcode",
|
||||||
"toolName" : "Xcode",
|
"toolName" : "Xcode",
|
||||||
"toolVersion" : "13.3"
|
"toolVersion" : "13.4.1"
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
}
|
}
|
|
@ -13,6 +13,11 @@
|
||||||
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
|
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */; };
|
||||||
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
|
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CDBCF4727FF621E00354CDD /* CILinkView.swift */; };
|
||||||
5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; };
|
5C00164428A26FBC0094D739 /* ContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C00164328A26FBC0094D739 /* ContextMenu.swift */; };
|
||||||
|
5C00166A28C119300094D739 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00166528C119300094D739 /* libgmp.a */; };
|
||||||
|
5C00166B28C119300094D739 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00166628C119300094D739 /* libffi.a */; };
|
||||||
|
5C00166C28C119300094D739 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00166728C119300094D739 /* libgmpxx.a */; };
|
||||||
|
5C00166D28C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00166828C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme-ghc8.10.7.a */; };
|
||||||
|
5C00166E28C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C00166928C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme.a */; };
|
||||||
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA72837DBB3004A9677 /* CICallItemView.swift */; };
|
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA72837DBB3004A9677 /* CICallItemView.swift */; };
|
||||||
5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; };
|
5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; };
|
||||||
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; };
|
5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; };
|
||||||
|
@ -127,11 +132,6 @@
|
||||||
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
|
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
|
||||||
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; };
|
64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; };
|
||||||
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
|
64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; };
|
||||||
64F1CC4128B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F1CC3C28B3A99900CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5.a */; };
|
|
||||||
64F1CC4228B3A99A00CD1FB1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F1CC3D28B3A99900CD1FB1 /* libgmpxx.a */; };
|
|
||||||
64F1CC4328B3A99A00CD1FB1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F1CC3E28B3A99900CD1FB1 /* libgmp.a */; };
|
|
||||||
64F1CC4428B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F1CC3F28B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5-ghc8.10.7.a */; };
|
|
||||||
64F1CC4528B3A99A00CD1FB1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64F1CC4028B3A99A00CD1FB1 /* libffi.a */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
@ -197,6 +197,11 @@
|
||||||
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = "<group>"; };
|
3CDBCF4127FAE51000354CDD /* ComposeLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeLinkView.swift; sourceTree = "<group>"; };
|
||||||
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
|
3CDBCF4727FF621E00354CDD /* CILinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CILinkView.swift; sourceTree = "<group>"; };
|
||||||
5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = "<group>"; };
|
5C00164328A26FBC0094D739 /* ContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextMenu.swift; sourceTree = "<group>"; };
|
||||||
|
5C00166528C119300094D739 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||||
|
5C00166628C119300094D739 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||||
|
5C00166728C119300094D739 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||||
|
5C00166828C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||||
|
5C00166928C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme.a"; sourceTree = "<group>"; };
|
||||||
5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = "<group>"; };
|
5C029EA72837DBB3004A9677 /* CICallItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CICallItemView.swift; sourceTree = "<group>"; };
|
||||||
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
|
5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = "<group>"; };
|
||||||
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
|
5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = "<group>"; };
|
||||||
|
@ -314,11 +319,6 @@
|
||||||
64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
|
64DAE1502809D9F5000DA960 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
|
||||||
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = "<group>"; };
|
64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = "<group>"; };
|
||||||
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = "<group>"; };
|
64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = "<group>"; };
|
||||||
64F1CC3C28B3A99900CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5.a"; sourceTree = "<group>"; };
|
|
||||||
64F1CC3D28B3A99900CD1FB1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
|
||||||
64F1CC3E28B3A99900CD1FB1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
|
||||||
64F1CC3F28B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5-ghc8.10.7.a"; sourceTree = "<group>"; };
|
|
||||||
64F1CC4028B3A99A00CD1FB1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -351,13 +351,13 @@
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
64F1CC4428B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5-ghc8.10.7.a in Frameworks */,
|
5C00166E28C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme.a in Frameworks */,
|
||||||
64F1CC4228B3A99A00CD1FB1 /* libgmpxx.a in Frameworks */,
|
|
||||||
64F1CC4328B3A99A00CD1FB1 /* libgmp.a in Frameworks */,
|
|
||||||
64F1CC4128B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5.a in Frameworks */,
|
|
||||||
64F1CC4528B3A99A00CD1FB1 /* libffi.a in Frameworks */,
|
|
||||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||||
|
5C00166C28C119300094D739 /* libgmpxx.a in Frameworks */,
|
||||||
|
5C00166A28C119300094D739 /* libgmp.a in Frameworks */,
|
||||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||||
|
5C00166B28C119300094D739 /* libffi.a in Frameworks */,
|
||||||
|
5C00166D28C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme-ghc8.10.7.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -412,11 +412,11 @@
|
||||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
64F1CC4028B3A99A00CD1FB1 /* libffi.a */,
|
5C00166628C119300094D739 /* libffi.a */,
|
||||||
64F1CC3E28B3A99900CD1FB1 /* libgmp.a */,
|
5C00166528C119300094D739 /* libgmp.a */,
|
||||||
64F1CC3D28B3A99900CD1FB1 /* libgmpxx.a */,
|
5C00166728C119300094D739 /* libgmpxx.a */,
|
||||||
64F1CC3F28B3A99A00CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5-ghc8.10.7.a */,
|
5C00166828C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme-ghc8.10.7.a */,
|
||||||
64F1CC3C28B3A99900CD1FB1 /* libHSsimplex-chat-3.2.0-6p2ah0FJ9icAh1HFBZcXP5.a */,
|
5C00166928C119300094D739 /* libHSsimplex-chat-3.2.1-DtA3whUOI1LFNbOU0tXQme.a */,
|
||||||
);
|
);
|
||||||
path = Libraries;
|
path = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
|
@ -404,6 +404,9 @@
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"Currently maximum supported file size is %@." = "Максимальный размер файла - %@.";
|
"Currently maximum supported file size is %@." = "Максимальный размер файла - %@.";
|
||||||
|
|
||||||
|
/* No comment provided by engineer. */
|
||||||
|
"Dark" = "Тёмная";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"Database export & import" = "Экспорт и импорт архива чата";
|
"Database export & import" = "Экспорт и импорт архива чата";
|
||||||
|
|
||||||
|
@ -824,6 +827,9 @@
|
||||||
/* rcv group event chat item */
|
/* rcv group event chat item */
|
||||||
"left" = "покинул(а) группу";
|
"left" = "покинул(а) группу";
|
||||||
|
|
||||||
|
/* No comment provided by engineer. */
|
||||||
|
"Light" = "Светлая";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"Limitations" = "Ограничения";
|
"Limitations" = "Ограничения";
|
||||||
|
|
||||||
|
@ -1232,6 +1238,9 @@
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"strike" = "зачеркнуть";
|
"strike" = "зачеркнуть";
|
||||||
|
|
||||||
|
/* No comment provided by engineer. */
|
||||||
|
"System" = "Системная";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"Take picture" = "Сделать фото";
|
"Take picture" = "Сделать фото";
|
||||||
|
|
||||||
|
@ -1292,6 +1301,9 @@
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"The sender will NOT be notified" = "Отправитель не будет уведомлён";
|
"The sender will NOT be notified" = "Отправитель не будет уведомлён";
|
||||||
|
|
||||||
|
/* No comment provided by engineer. */
|
||||||
|
"Theme" = "Тема";
|
||||||
|
|
||||||
/* No comment provided by engineer. */
|
/* No comment provided by engineer. */
|
||||||
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.";
|
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны.";
|
||||||
|
|
||||||
|
|
98
blog/20220901-simplex-chat-v3.2-incognito-mode.md
Normal file
98
blog/20220901-simplex-chat-v3.2-incognito-mode.md
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
# SimpleX Chat v3.2 is released
|
||||||
|
|
||||||
|
**Published:** Sep 1, 2022
|
||||||
|
|
||||||
|
## What's new
|
||||||
|
|
||||||
|
- [Incognito mode](#incognito-mode)
|
||||||
|
- [assign names to your contacts](#assign-names-to-your-contacts)
|
||||||
|
- [use .onion server addresses with Tor](#using-onion-server-addresses-with-tor)
|
||||||
|
- [endless scrolling and search in chats](#endless-scrolling-and-search-in-chats)
|
||||||
|
- [choose accent color and dark mode](#choose-accent-color-and-dark-mode)
|
||||||
|
- disable notifications per contact / group
|
||||||
|
- on Android:
|
||||||
|
- swipe to reply
|
||||||
|
- reduced APK size for direct download and in F-Droid repo from 200 to 50Mb!
|
||||||
|
|
||||||
|
[Implementation audit is arranged for October](#we-ask-you-to-help-us-pay-for-3rd-party-security-audit)!
|
||||||
|
|
||||||
|
### Incognito mode
|
||||||
|
|
||||||
|
<img src="./images/20220901-incognito1.png" width="330"> <img src="./images/20220901-incognito2.png" width="330"> <img src="./images/20220901-incognito3.png" width="330">
|
||||||
|
|
||||||
|
_SimpleX is already private, so why do we need an incognito mode_, you may ask.
|
||||||
|
|
||||||
|
You indeed can choose a pseudonym as your main profile name, but there are several problems:
|
||||||
|
|
||||||
|
- many users want to have their real name as their main profile, so that their friends recognise them. SimpleX objective is to provide anonimity from the network operators, but not necessarily from your contacts.
|
||||||
|
- even if you choose a pseudonym, it would be used for all your contacts. And if two of them meet, while they cannot prove they are talking to the same person, as they use different addresses in SimpleX network to send you the messages, they could _suspect it_.
|
||||||
|
- any pseudonym you manually choose leaks some information about you, as it's not really random.
|
||||||
|
|
||||||
|
You could also use multiple chat profiles - currently you can only switch between them via export/import, we will make it easier very soon! But there are problems with multiple profiles too:
|
||||||
|
|
||||||
|
- if you make many anonymous connections, each in its own user profile, you would end up having too many profiles - it is very inconvenient to manage.
|
||||||
|
- sometimes, as your relationship with your contact evolves, you may want to share your main profile with them and have them among your friends - multiple profiles don't make it possible.
|
||||||
|
|
||||||
|
So, the new Incognito mode allows having a new random name shared with each new contact, while having them all in the same user profile, and without the hassle of managing it manually. It's like a private mode in the browsers, where you can temporarily enable it when you connect to somebody you don't trust, and then disable it when connecting to the friend who knows you. It can be turned on via the app settings - see the pictures.
|
||||||
|
|
||||||
|
I don't know any other messenger with this feature, and I always wanted to have this mode, so we are really looking forward to your feedback about it!
|
||||||
|
|
||||||
|
### Assign names to your contacts
|
||||||
|
|
||||||
|
You can now change the name under which your contacts appear in the chats. This is particularly useful when somebody connected to you using a random name – you can change it to be related to the context of the connection.
|
||||||
|
|
||||||
|
### Using .onion server addresses with Tor
|
||||||
|
|
||||||
|
<img src="./images/20220901-onion1.png" width="330"> <img src="./images/20220901-onion2.png" width="330">
|
||||||
|
|
||||||
|
We have released support for using SOCKS proxy to access messaging servers via Tor, but previously the servers were still available via their public Internet addresses. It means that while your IP address was protected from the server, the whole Tor circuit could have been observed by some actors, and for some communication scenarios it is not desirable.
|
||||||
|
|
||||||
|
This release adds support for servers with multiple hostnames - all servers provided by SimpleX Chat now have dual addresses (one public and one .onion), and you can have your own servers available via two addresses as well - all you have to do is to install Tor client on your server and register its address with Tor. If you server has both public and .onion address, it is not really hidden, so you should enable HiddenServiceSingleHopMode to reduce the latency of connection - it protects anonymity of the people who connect to the server, but not of the server itself. The server address would include both its public and onion address, as you can see in the server addresses in the app (in the contacts pages) - you should use the same format for the addresses of your servers.
|
||||||
|
|
||||||
|
Both android and iOS app allow managing whether .onion addresses are used, and you can also enforce using .onion addresses - in this case the app will not connect to the server unless one of its hostname is .onion address. On Android, .onion addresses are used by default when SOCKS proxy is enabled.
|
||||||
|
|
||||||
|
### Endless scrolling and search in chats
|
||||||
|
|
||||||
|
Now you can access the full chat history via the app - it's embarrassing how long it took us to add it! And you can search the messages as well.
|
||||||
|
|
||||||
|
### Choose accent color and dark mode
|
||||||
|
|
||||||
|
Many of you said that blue is the worst possible color, so you can now make the app buttons and links look like you want! My favourite colours are green and orange.
|
||||||
|
|
||||||
|
And you can choose dark or light mode independently of the system settings.
|
||||||
|
|
||||||
|
## SimpleX platform
|
||||||
|
|
||||||
|
Some links to answer the most common questions:
|
||||||
|
|
||||||
|
[How can SimpleX deliver messages without user identifiers](./20220511-simplex-chat-v2-images-files.md#the-first-messaging-platform-without-user-identifiers).
|
||||||
|
|
||||||
|
[What are the risks to have identifiers assigned to the users](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md#why-having-users-identifiers-is-bad-for-the-users).
|
||||||
|
|
||||||
|
[Technical details and limitations](./20220723-simplex-chat-v3.1-tor-groups-efficiency.md#privacy-technical-details-and-limitations).
|
||||||
|
|
||||||
|
[How SimpleX is different from Session, Matrix, Signal, etc.](../README.md#frequently-asked-questions).
|
||||||
|
|
||||||
|
## We ask you to help us pay for 3rd party security audit
|
||||||
|
|
||||||
|
Our great news is that we have already signed the agreement and paid for the security audit!
|
||||||
|
|
||||||
|
It is planned in October, and if there are no major issues we will publish this report straight away, otherwise - once we fix them.
|
||||||
|
|
||||||
|
This is a major expense for use - over $20,000 - I would really appreciate if you could help us cover some part of this cost with the donations.
|
||||||
|
|
||||||
|
Our promise to our users is that SimpleX protocols are and will remain open, and in public domain, - so anybody can build the future implementations of the clients and the servers. We will be establishing a legal framework this year to ensure that it doesn't change if the ownership of SimpleX Chat Ltd changes at any future point.
|
||||||
|
|
||||||
|
Please consider making a donation - it will help us to raise more funds. Donating any amount, even the price of the cup of coffee, would make a huge difference for us.
|
||||||
|
|
||||||
|
It is possible to donate via:
|
||||||
|
|
||||||
|
- [GitHub](https://github.com/sponsors/simplex-chat): it is commission-free for us.
|
||||||
|
- [OpenCollective](https://opencollective.com/simplex-chat): it also accepts donations in crypto-currencies, but charges a commission.
|
||||||
|
- Monero wallet: 8568eeVjaJ1RQ65ZUn9PRQ8ENtqeX9VVhcCYYhnVLxhV4JtBqw42so2VEUDQZNkFfsH5sXCuV7FN8VhRQ21DkNibTZP57Qt
|
||||||
|
|
||||||
|
Thank you,
|
||||||
|
|
||||||
|
Evgeny
|
||||||
|
|
||||||
|
SimpleX Chat founder
|
|
@ -1,24 +1,32 @@
|
||||||
# Blog
|
# Blog
|
||||||
|
|
||||||
Aug 8, 2022 [SimpleX Chat v3.1 released](./20220808-simplex-chat-v3.1-chat-groups.md)
|
Sep 1, 2022 [v3.2: Incognito mode](./20220901-simplex-chat-v3.2-incognito-mode.md)
|
||||||
|
|
||||||
- finally, secret chat groups!
|
- Incognito mode - use a new random profile name for each contact
|
||||||
|
- use .onion server addresses with Tor
|
||||||
|
- endless scrolling and search
|
||||||
|
- choose accent color and dark mode
|
||||||
|
- reduced APK size for direct download and in F-Droid repo from 200 to 46Mb!
|
||||||
|
|
||||||
|
Implementation audit is arranged for October!
|
||||||
|
|
||||||
|
Aug 8, 2022 [v3.1: chat groups](./20220808-simplex-chat-v3.1-chat-groups.md)
|
||||||
|
|
||||||
|
- finally, secret chat groups - nobody but members know they exist!
|
||||||
- access to messaging servers via Tor on all platforms
|
- access to messaging servers via Tor on all platforms
|
||||||
- advanced network settings to optimize traffic usage
|
- advanced network settings to optimize traffic usage
|
||||||
- published chat protocol
|
- published chat protocol
|
||||||
- new app icons
|
- new app icons
|
||||||
|
|
||||||
Jul 23, 2022 [SimpleX Chat v3.1-beta released](./20220723-simplex-chat-v3.1-tor-groups-efficiency.md)
|
Jul 23, 2022 [v3.1-beta: access servers via Tor](./20220723-simplex-chat-v3.1-tor-groups-efficiency.md)
|
||||||
|
|
||||||
- terminal app: access to messaging servers via SOCKS5 proxy (e.g., Tor).
|
- terminal app: access to messaging servers via SOCKS5 proxy (e.g., Tor).
|
||||||
- mobile apps: join and leave chat groups.
|
- mobile apps: join and leave chat groups.
|
||||||
- optimized battery and traffic usage - up to 90x reduction!
|
- optimized battery and traffic usage - up to 90x reduction!
|
||||||
- two docker configurations for self-hosted SMP servers.
|
- two docker configurations for self-hosted SMP servers.
|
||||||
|
|
||||||
Jul 11, 2022 [SimpleX Chat v3 released](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md):
|
Jul 11, 2022 [v3: instant push notifications for iOS and audio/video calls](./20220711-simplex-chat-v3-released-ios-notifications-audio-video-calls-database-export-import-protocol-improvements.md):
|
||||||
|
|
||||||
- instant push notifications for iOS
|
|
||||||
- e2e encrypted WebRTC audio/video calls
|
|
||||||
- chat database export and import
|
- chat database export and import
|
||||||
- protocol privacy and performance improvements
|
- protocol privacy and performance improvements
|
||||||
|
|
||||||
|
|
BIN
blog/images/20220901-incognito1.png
Normal file
BIN
blog/images/20220901-incognito1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 330 KiB |
BIN
blog/images/20220901-incognito2.png
Normal file
BIN
blog/images/20220901-incognito2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 501 KiB |
BIN
blog/images/20220901-incognito3.png
Normal file
BIN
blog/images/20220901-incognito3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 294 KiB |
BIN
blog/images/20220901-onion1.png
Normal file
BIN
blog/images/20220901-onion1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 434 KiB |
BIN
blog/images/20220901-onion2.png
Normal file
BIN
blog/images/20220901-onion2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 704 KiB |
|
@ -1,5 +1,5 @@
|
||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 3.2.0
|
version: 3.2.1
|
||||||
#synopsis:
|
#synopsis:
|
||||||
#description:
|
#description:
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
|
|
|
@ -26,6 +26,7 @@ rm $ORIG_NAME
|
||||||
(cd apk && zip -r -q -$level ../$ORIG_NAME .)
|
(cd apk && zip -r -q -$level ../$ORIG_NAME .)
|
||||||
# Shouldn't be compressed because of Android requirement
|
# Shouldn't be compressed because of Android requirement
|
||||||
(cd apk && zip -r -q -0 ../$ORIG_NAME resources.arsc)
|
(cd apk && zip -r -q -0 ../$ORIG_NAME resources.arsc)
|
||||||
|
(cd apk && zip -r -q -0 ../$ORIG_NAME res)
|
||||||
#(cd apk && 7z a -r -mx=$level -tzip -x!resources.arsc ../$ORIG_NAME .)
|
#(cd apk && 7z a -r -mx=$level -tzip -x!resources.arsc ../$ORIG_NAME .)
|
||||||
#(cd apk && 7z a -r -mx=0 -tzip ../$ORIG_NAME resources.arsc)
|
#(cd apk && 7z a -r -mx=0 -tzip ../$ORIG_NAME resources.arsc)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 3.2.0
|
version: 3.2.1
|
||||||
category: Web, System, Services, Cryptography
|
category: Web, System, Services, Cryptography
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
author: simplex.chat
|
author: simplex.chat
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue