From acd3467d10dceeb4261c3d47b54d808f0d999229 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Tue, 9 Jul 2024 03:23:25 +0700 Subject: [PATCH 01/57] desktop: change libffi dependency (#4416) --- scripts/desktop/build-lib-linux.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index c27b672e40..da645c6e86 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -29,6 +29,11 @@ cabal build lib:simplex-chat --ghc-options='-optl-Wl,-rpath,$ORIGIN -flink-rts - cd $BUILD_DIR/build #patchelf --add-needed libHSrts_thr-ghc${GHC_VERSION}.so libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so #patchelf --add-rpath '$ORIGIN' libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so + +# GitHub's Ubuntu 20.04 runner started to set libffi.so.7 as a dependency while Ubuntu 20.04 on user's devices may not have it +# but libffi.so.8 is shipped as an external library with other libs +patchelf --replace-needed "libffi.so.7" "libffi.so.8" libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so + mkdir deps 2> /dev/null || true ldd libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so | grep "ghc" | cut -d' ' -f 3 | xargs -I {} cp {} ./deps/ From 3e623684bc9dd5221872841f018b17bf9457e077 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:45:09 +0400 Subject: [PATCH 02/57] android, desktop: servers summary (#4398) * multiplatform: subscription icon (#4397) * multiplatform: added network setting to control display of subscription percentage * multiplatform: moved filter to search bar and scan to button * multiplatform: added types and calls for new apis * Update apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> * chore: minor changes in API connections and usage * fix: removed show sub setting from net cfg * multiplatform: added subscription status to chats screen * multiplatform: added reconnect all servers api * multiplatform: added basic view for single user messages * multiplatform: added basic xftp server info view * multiplatform: added detailed stats view for SMP server * multiplatform: added detailed stats view for XFTP server * multiplatform: added individual server view for XFTP servers * multiplatform: added individual server view for SMP servers * multiplatform: added custom coloring for connections * multiplatform: added all translation strings * multiplatform: added support for multi user in serve summary * multiplatform: added missing translations * multiplatform: added share button to servers info * better type safety for server summaries * multiplatform: fixed action arrow paddings in server summary views * multiplatform: serverSummaryView padding and icon fixes * multiplatform: reused shared section divider * move and rename * remove tab icons, text * colors * filter button * paddings * fix translation keys * text * fix buttons clickable area, alerts * stats view * remove chevrons * colors * remove id, fix open server button * don't log terminal items * desktop left modal * single timer, pass state variable * remove unused * fix no summary view * net cfg * ability to hide servers screen by clicking outside servers screen * addressed review feedback * move user/all users selector under tabs * disable horizontal scroll on desktop --------- Co-authored-by: Diogo Cunha Co-authored-by: Avently <7953703+avently@users.noreply.github.com> Co-authored-by: Evgeny Poberezkin --- .../kotlin/chat/simplex/common/App.kt | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 205 +++- .../common/views/chatlist/ChatListView.kt | 136 ++- .../views/chatlist/ServersSummaryView.kt | 977 ++++++++++++++++++ .../simplex/common/views/helpers/ModalView.kt | 3 + .../simplex/common/views/helpers/Section.kt | 59 +- .../views/helpers/SubscriptionStatusIcon.kt | 41 + .../common/views/newchat/NewChatSheet.kt | 12 +- .../common/views/newchat/NewChatView.kt | 2 +- .../views/usersettings/NetworkAndServers.kt | 10 + .../commonMain/resources/MR/base/strings.xml | 83 ++ .../images/ic_radiowaves_up_forward_1_bar.svg | 6 + .../images/ic_radiowaves_up_forward_2_bar.svg | 6 + .../images/ic_radiowaves_up_forward_3_bar.svg | 6 + .../images/ic_radiowaves_up_forward_4_bar.svg | 6 + 15 files changed, 1486 insertions(+), 68 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index e7dda42ade..b215b2b6cd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -353,7 +353,7 @@ fun DesktopScreen(settingsState: SettingsViewState) { } val (userPickerState, scaffoldState ) = settingsState val scope = rememberCoroutineScope() - if (scaffoldState.drawerState.isOpen) { + if (scaffoldState.drawerState.isOpen || (ModalManager.start.hasModalsOpen && !ModalManager.center.hasModalsOpen)) { Box( Modifier .fillMaxSize() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 9367fe5f71..0a42541c75 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -127,6 +127,7 @@ class AppPreferences { val showSlowApiCalls = mkBoolPreference(SHARED_PREFS_SHOW_SLOW_API_CALLS, false) val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false) + val networkShowSubscriptionPercentage = mkBoolPreference(SHARED_PREFS_NETWORK_SHOW_SUBSCRIPTION_PERCENTAGE, false) val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050") private val _networkSessionMode = mkStrPreference(SHARED_PREFS_NETWORK_SESSION_MODE, TransportSessionMode.default.name) val networkSessionMode: SharedPreference = SharedPreference( @@ -338,6 +339,7 @@ class AppPreferences { private const val SHARED_PREFS_SHOW_SLOW_API_CALLS = "ShowSlowApiCalls" private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible" private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy" + private const val SHARED_PREFS_NETWORK_SHOW_SUBSCRIPTION_PERCENTAGE = "ShowSubscriptionPercentage" private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort" private const val SHARED_PREFS_NETWORK_SESSION_MODE = "NetworkSessionMode" private const val SHARED_PREFS_NETWORK_SMP_PROXY_MODE = "NetworkSMPProxyMode" @@ -411,6 +413,18 @@ object ChatController { fun hasChatCtrl() = ctrl != -1L && ctrl != null + suspend fun getAgentServersSummary(rh: Long?): PresentedServersSummary? { + val userId = currentUserId("getAgentServersSummary") + + val r = sendCmd(rh, CC.GetAgentServersSummary(userId), log = false) + + if (r is CR.AgentServersSummary) return r.serversSummary + Log.e(TAG, "getAgentServersSummary bad response: ${r.responseType} ${r.details}") + return null + } + + suspend fun resetAgentServersStats(rh: Long?): Boolean = sendCommandOkResp(rh, CC.ResetAgentServersStats()) + private suspend fun currentUserId(funcName: String): Long = changingActiveUserMutex.withLock { val userId = chatModel.currentUser.value?.userId if (userId == null) { @@ -569,20 +583,24 @@ object ChatController { } } - suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null): CR { + suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null, log: Boolean = true): CR { val ctrl = otherCtrl ?: ctrl ?: throw Exception("Controller is not initialized") return withContext(Dispatchers.IO) { val c = cmd.cmdString - chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) - Log.d(TAG, "sendCmd: ${cmd.cmdType}") + if (log) { + chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) + Log.d(TAG, "sendCmd: ${cmd.cmdType}") + } val json = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) val r = APIResponse.decodeStr(json) - Log.d(TAG, "sendCmd response type ${r.resp.responseType}") - if (r.resp is CR.Response || r.resp is CR.Invalid) { - Log.d(TAG, "sendCmd response json $json") + if (log) { + Log.d(TAG, "sendCmd response type ${r.resp.responseType}") + if (r.resp is CR.Response || r.resp is CR.Invalid) { + Log.d(TAG, "sendCmd response json $json") + } + chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp)) } - chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp)) r.resp } } @@ -920,6 +938,14 @@ object ChatController { } } + suspend fun reconnectServer(rh: Long?, server: String): Boolean { + val userId = currentUserId("reconnectServer") + + return sendCommandOkResp(rh, CC.ReconnectServer(userId, server)) + } + + suspend fun reconnectAllServers(rh: Long?): Boolean = sendCommandOkResp(rh, CC.ReconnectAllServers()) + suspend fun apiSetSettings(rh: Long?, type: ChatType, id: Long, settings: ChatSettings): Boolean { val r = sendCmd(rh, CC.APISetChatSettings(type, id, settings)) return when (r) { @@ -2577,6 +2603,8 @@ sealed class CC { class APISetNetworkConfig(val networkConfig: NetCfg): CC() class APIGetNetworkConfig: CC() class APISetNetworkInfo(val networkInfo: UserNetworkInfo): CC() + class ReconnectServer(val userId: Long, val server: String): CC() + class ReconnectAllServers: CC() class APISetChatSettings(val type: ChatType, val id: Long, val chatSettings: ChatSettings): CC() class ApiSetMemberSettings(val groupId: Long, val groupMemberId: Long, val memberSettings: GroupMemberSettings): CC() class APIContactInfo(val contactId: Long): CC() @@ -2648,6 +2676,8 @@ sealed class CC { class ApiStandaloneFileInfo(val url: String): CC() // misc class ShowVersion(): CC() + class ResetAgentServersStats(): CC() + class GetAgentServersSummary(val userId: Long): CC() val cmdString: String get() = when (this) { is Console -> cmd @@ -2724,6 +2754,8 @@ sealed class CC { is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}" is APIGetNetworkConfig -> "/network" is APISetNetworkInfo -> "/_network info ${json.encodeToString(networkInfo)}" + is ReconnectServer -> "/reconnect $userId $server" + is ReconnectAllServers -> "/reconnect" is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}" is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}" is APIContactInfo -> "/_info @$contactId" @@ -2804,6 +2836,8 @@ sealed class CC { is ApiDownloadStandaloneFile -> "/_download $userId $url ${file.filePath}" is ApiStandaloneFileInfo -> "/_download info $url" is ShowVersion -> "/version" + is ResetAgentServersStats -> "/reset servers stats" + is GetAgentServersSummary -> "/get servers summary $userId" } val cmdType: String get() = when (this) { @@ -2864,6 +2898,8 @@ sealed class CC { is APISetNetworkConfig -> "apiSetNetworkConfig" is APIGetNetworkConfig -> "apiGetNetworkConfig" is APISetNetworkInfo -> "apiSetNetworkInfo" + is ReconnectServer -> "reconnectServer" + is ReconnectAllServers -> "reconnectAllServers" is APISetChatSettings -> "apiSetChatSettings" is ApiSetMemberSettings -> "apiSetMemberSettings" is APIContactInfo -> "apiContactInfo" @@ -2933,6 +2969,8 @@ sealed class CC { is ApiDownloadStandaloneFile -> "apiDownloadStandaloneFile" is ApiStandaloneFileInfo -> "apiStandaloneFileInfo" is ShowVersion -> "showVersion" + is ResetAgentServersStats -> "resetAgentServersStats" + is GetAgentServersSummary -> "getAgentServersSummary" } class ItemRange(val from: Long, val to: Long) @@ -3203,7 +3241,7 @@ data class NetCfg( val tcpKeepAlive: KeepAliveOpts?, val smpPingInterval: Long, // microseconds val smpPingCount: Int, - val logTLSErrors: Boolean = false + val logTLSErrors: Boolean = false, ) { val useSocksProxy: Boolean get() = socksProxy != null val enableKeepAlive: Boolean get() = tcpKeepAlive != null @@ -3428,6 +3466,154 @@ data class TimedMessagesPreference( } } +@Serializable +data class PresentedServersSummary( + val statsStartedAt: Instant, + val allUsersSMP: SMPServersSummary, + val allUsersXFTP: XFTPServersSummary, + val currentUserSMP: SMPServersSummary, + val currentUserXFTP: XFTPServersSummary +) + +@Serializable +data class SMPServersSummary( + val smpTotals: SMPTotals, + val currentlyUsedSMPServers: List, + val previouslyUsedSMPServers: List, + val onlyProxiedSMPServers: List +) + +@Serializable +data class SMPTotals( + val sessions: ServerSessions, + val subs: SMPServerSubs, + val stats: AgentSMPServerStatsData +) + +@Serializable +data class SMPServerSummary( + val smpServer: String, + val known: Boolean? = null, + val sessions: ServerSessions? = null, + val subs: SMPServerSubs? = null, + val stats: AgentSMPServerStatsData? = null +) { + val hasSubs: Boolean + get() = subs != null + + val sessionsOrNew: ServerSessions + get() = sessions ?: ServerSessions.newServerSessions + + val subsOrNew: SMPServerSubs + get() = subs ?: SMPServerSubs.newSMPServerSubs +} + +@Serializable +data class ServerSessions( + val ssConnected: Int, + val ssErrors: Int, + val ssConnecting: Int +) { + companion object { + val newServerSessions = ServerSessions( + ssConnected = 0, + ssErrors = 0, + ssConnecting = 0 + ) + } +} + +@Serializable +data class SMPServerSubs( + val ssActive: Int, + val ssPending: Int +) { + companion object { + val newSMPServerSubs = SMPServerSubs( + ssActive = 0, + ssPending = 0 + ) + } + + val total: Int + get() = ssActive + ssPending + + val shareOfActive: Float + get() = if (total != 0) ssActive.toFloat() / total else 0f +} + +@Serializable +data class AgentSMPServerStatsData( + val _sentDirect: Int, + val _sentViaProxy: Int, + val _sentProxied: Int, + val _sentDirectAttempts: Int, + val _sentViaProxyAttempts: Int, + val _sentProxiedAttempts: Int, + val _sentAuthErrs: Int, + val _sentQuotaErrs: Int, + val _sentExpiredErrs: Int, + val _sentOtherErrs: Int, + val _recvMsgs: Int, + val _recvDuplicates: Int, + val _recvCryptoErrs: Int, + val _recvErrs: Int, + val _ackMsgs: Int, + val _ackAttempts: Int, + val _ackNoMsgErrs: Int, + val _ackOtherErrs: Int, + val _connCreated: Int, + val _connSecured: Int, + val _connCompleted: Int, + val _connDeleted: Int, + val _connDelAttempts: Int, + val _connDelErrs: Int, + val _connSubscribed: Int, + val _connSubAttempts: Int, + val _connSubIgnored: Int, + val _connSubErrs: Int +) + +@Serializable +data class XFTPServersSummary( + val xftpTotals: XFTPTotals, + val currentlyUsedXFTPServers: List, + val previouslyUsedXFTPServers: List +) + +@Serializable +data class XFTPTotals( + val sessions: ServerSessions, + val stats: AgentXFTPServerStatsData +) + +@Serializable +data class XFTPServerSummary( + val xftpServer: String, + val known: Boolean? = null, + val sessions: ServerSessions? = null, + val stats: AgentXFTPServerStatsData? = null, + val rcvInProgress: Boolean, + val sndInProgress: Boolean, + val delInProgress: Boolean +) {} + +@Serializable +data class AgentXFTPServerStatsData( + val _uploads: Int, + val _uploadsSize: Long, + val _uploadAttempts: Int, + val _uploadErrs: Int, + val _downloads: Int, + val _downloadsSize: Long, + val _downloadAttempts: Int, + val _downloadAuthErrs: Int, + val _downloadErrs: Int, + val _deletions: Int, + val _deleteAttempts: Int, + val _deleteErrs: Int +) + sealed class CustomTimeUnit { object Second: CustomTimeUnit() object Minute: CustomTimeUnit() @@ -4439,6 +4625,7 @@ sealed class CR { @Serializable @SerialName("chatError") class ChatRespError(val user_: UserRef?, val chatError: ChatError): CR() @Serializable @SerialName("archiveImported") class ArchiveImported(val archiveErrors: List): CR() @Serializable @SerialName("appSettings") class AppSettingsR(val appSettings: AppSettings): CR() + @Serializable @SerialName("agentServersSummary") class AgentServersSummary(val user: UserRef, val serversSummary: PresentedServersSummary): CR() // general @Serializable class Response(val type: String, val json: String): CR() @Serializable class Invalid(val str: String): CR() @@ -4598,6 +4785,7 @@ sealed class CR { is ContactPQAllowed -> "contactPQAllowed" is ContactPQEnabled -> "contactPQEnabled" is VersionInfo -> "versionInfo" + is AgentServersSummary -> "agentServersSummary" is CmdOk -> "cmdOk" is ChatCmdError -> "chatCmdError" is ChatRespError -> "chatError" @@ -4776,6 +4964,7 @@ sealed class CR { is RemoteCtrlStopped -> "rcsState: $rcsState\nrcsStopReason: $rcStopReason" is ContactPQAllowed -> withUser(user, "contact: ${contact.id}\npqEncryption: $pqEncryption") is ContactPQEnabled -> withUser(user, "contact: ${contact.id}\npqEnabled: $pqEnabled") + is AgentServersSummary -> withUser(user, json.encodeToString(serversSummary)) is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" + "chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" + "agent migrations: ${json.encodeToString(agentMigrations.map { it.upName })}" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index d03b8a708d..3417333370 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -35,7 +35,10 @@ import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.serialization.json.Json import java.net.URI +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds @Composable fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerformLA: (Boolean) -> Unit, stopped: Boolean) { @@ -69,7 +72,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } val scope = rememberCoroutineScope() val (userPickerState, scaffoldState ) = settingsState - Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(searchText, scaffoldState.drawerState, userPickerState, stopped)} }, + Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(scaffoldState.drawerState, userPickerState, stopped)} }, scaffoldState = scaffoldState, drawerContent = { tryOrShowError("Settings", error = { ErrorSettingsView() }) { @@ -181,8 +184,10 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { } @Composable -private fun ChatListToolbar(searchInList: State, drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean) { +private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean) { + val serversSummary: MutableState = remember { mutableStateOf(null) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() + if (stopped) { barButtons.add { IconButton(onClick = { @@ -200,6 +205,7 @@ private fun ChatListToolbar(searchInList: State, drawerState: Dr } } val scope = rememberCoroutineScope() + val clipboard = LocalClipboardManager.current DefaultTopAppBar( navigationButton = { if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) { @@ -219,20 +225,33 @@ private fun ChatListToolbar(searchInList: State, drawerState: Dr } }, title = { - Row(verticalAlignment = Alignment.CenterVertically) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON)) { Text( stringResource(MR.strings.your_chats), color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.SemiBold, ) - if (chatModel.chats.size > 0) { - val enabled = remember { derivedStateOf { searchInList.value.text.isEmpty() } } - if (enabled.value) { - ToggleFilterEnabledButton() - } else { - ToggleFilterDisabledButton() + SubscriptionStatusIndicator( + serversSummary = serversSummary, + click = { + ModalManager.start.closeModals() + ModalManager.start.showModalCloseable( + endButtons = { + val summary = serversSummary.value + if (summary != null) { + ShareButton { + val json = Json { + prettyPrint = true + } + + val text = json.encodeToString(PresentedServersSummary.serializer(), summary) + clipboard.shareText(text) + } + } + } + ) { ServersSummaryView(chatModel.currentRemoteHost.value, serversSummary) } } - } + ) } }, onTitleClick = null, @@ -243,6 +262,53 @@ private fun ChatListToolbar(searchInList: State, drawerState: Dr Divider(Modifier.padding(top = AppBarHeight)) } +@Composable +fun SubscriptionStatusIndicator(serversSummary: MutableState, click: (() -> Unit)) { + var subs by remember { mutableStateOf(SMPServerSubs.newSMPServerSubs) } + var sess by remember { mutableStateOf(ServerSessions.newServerSessions) } + var timer: Job? by remember { mutableStateOf(null) } + + val fetchInterval: Duration = 1.seconds + + val scope = rememberCoroutineScope() + + fun setServersSummary() { + withBGApi { + serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) + + serversSummary.value?.let { + subs = it.allUsersSMP.smpTotals.subs + sess = it.allUsersSMP.smpTotals.sessions + } + } + } + + LaunchedEffect(Unit) { + setServersSummary() + timer = timer ?: scope.launch { + while (true) { + delay(fetchInterval.inWholeMilliseconds) + setServersSummary() + } + } + } + + fun stopTimer() { + timer?.cancel() + timer = null + } + + DisposableEffect(Unit) { + onDispose { + stopTimer() + } + } + + SimpleButtonFrame(click = click) { + SubscriptionStatusIndicatorView(subs = subs, sess = sess) + } +} + @Composable fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> Unit) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -292,27 +358,11 @@ private fun ToggleFilterEnabledButton() { Icon( painterResource(MR.images.ic_filter_list), null, - tint = if (pref.state.value) MaterialTheme.colors.background else MaterialTheme.colors.primary, + tint = if (pref.state.value) MaterialTheme.colors.background else MaterialTheme.colors.secondary, modifier = Modifier .padding(3.dp) .background(color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50)) - .border(width = 1.dp, color = MaterialTheme.colors.primary, shape = RoundedCornerShape(50)) - .padding(3.dp) - .size(16.dp) - ) - } -} - -@Composable -private fun ToggleFilterDisabledButton() { - IconButton({}, enabled = false) { - Icon( - painterResource(MR.images.ic_filter_list), - null, - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .padding(3.dp) - .border(width = 1.dp, color = MaterialTheme.colors.secondary, shape = RoundedCornerShape(50)) + .border(width = 1.dp, color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50)) .padding(3.dp) .size(16.dp) ) @@ -357,33 +407,11 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState hideSearchOnBack() } } else { - Row { - val padding = if (appPlatform.isDesktop) 0.dp else 7.dp - val clipboard = LocalClipboardManager.current - val clipboardHasText = remember(focused) { chatModel.clipboardHasText }.value - if (clipboardHasText) { - IconButton( - onClick = { searchText.value = searchText.value.copy(clipboard.getText()?.text ?: return@IconButton) }, - Modifier.size(30.dp).desktopPointerHoverIconHand() - ) { - Icon(painterResource(MR.images.ic_article), null, tint = MaterialTheme.colors.secondary) - } - } - Spacer(Modifier.width(padding)) - IconButton( - onClick = { - val fixedRhId = chatModel.currentRemoteHost.value - ModalManager.center.closeModals() - ModalManager.center.showModalCloseable { close -> - NewChatView(fixedRhId, selection = NewChatOption.CONNECT, showQRCodeScanner = true, close = close) - } - }, - Modifier.size(30.dp).desktopPointerHoverIconHand() - ) { - Icon(painterResource(MR.images.ic_qr_code), null, tint = MaterialTheme.colors.secondary) - } - Spacer(Modifier.width(padding)) + val padding = if (appPlatform.isDesktop) 0.dp else 7.dp + if (chatModel.chats.size > 0) { + ToggleFilterEnabledButton() } + Spacer(Modifier.width(padding)) } val focusManager = LocalFocusManager.current val keyboardState = getKeyboardState() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt new file mode 100644 index 0000000000..22478c18f9 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -0,0 +1,977 @@ +package chat.simplex.common.views.chatlist + +import InfoRow +import InfoRowTwoValues +import SectionBottomSpacer +import SectionDividerSpaced +import SectionItemView +import SectionTextFooter +import SectionView +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.common.model.AgentSMPServerStatsData +import chat.simplex.common.model.AgentXFTPServerStatsData +import chat.simplex.common.model.ChatController.chatModel +import chat.simplex.common.model.ChatModel.controller +import chat.simplex.common.model.PresentedServersSummary +import chat.simplex.common.model.RemoteHostInfo +import chat.simplex.common.model.SMPServerSubs +import chat.simplex.common.model.SMPServerSummary +import chat.simplex.common.model.SMPTotals +import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress +import chat.simplex.common.model.ServerProtocol +import chat.simplex.common.model.ServerSessions +import chat.simplex.common.model.XFTPServerSummary +import chat.simplex.common.model.localTimestamp +import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.appPlatform +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.usersettings.ProtocolServersView +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.datetime.Instant +import numOrDash +import java.text.DecimalFormat +import kotlin.math.floor +import kotlin.math.roundToInt + +enum class SubscriptionColorType { + ACTIVE, ACTIVE_SOCKS_PROXY, DISCONNECTED, ACTIVE_DISCONNECTED +} + +data class SubscriptionStatus( + val color: SubscriptionColorType, + val variableValue: Float, + val opacity: Float, + val statusPercent: Float +) + +fun subscriptionStatusColorAndPercentage( + online: Boolean, + socksProxy: String?, + subs: SMPServerSubs, + sess: ServerSessions +): SubscriptionStatus { + + fun roundedToQuarter(n: Float): Float = when { + n >= 1 -> 1f + n <= 0 -> 0f + else -> (n * 4).roundToInt() / 4f + } + + val activeColor: SubscriptionColorType = if (socksProxy != null) SubscriptionColorType.ACTIVE_SOCKS_PROXY else SubscriptionColorType.ACTIVE + val noConnColorAndPercent = SubscriptionStatus(SubscriptionColorType.DISCONNECTED, 1f, 1f, 0f) + val activeSubsRounded = roundedToQuarter(subs.shareOfActive) + + return if (online && subs.total > 0) { + if (subs.ssActive == 0) { + if (sess.ssConnected == 0) + noConnColorAndPercent + else + SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + } else { // ssActive > 0 + if (sess.ssConnected == 0) + // This would mean implementation error + SubscriptionStatus(SubscriptionColorType.ACTIVE_DISCONNECTED, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + else + SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + } + } else noConnColorAndPercent +} + +@Composable +private fun SubscriptionStatusIndicatorPercentage(percentageText: String) { + Text( + percentageText, + color = MaterialTheme.colors.secondary, + fontSize = 12.sp, + style = MaterialTheme.typography.caption + ) +} + +@Composable +fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, sess: ServerSessions, leadingPercentage: Boolean = false) { + val netCfg = rememberUpdatedState(chatModel.controller.getNetCfg()) + val statusColorAndPercentage = subscriptionStatusColorAndPercentage(chatModel.networkInfo.value.online, netCfg.value.socksProxy, subs, sess) + val pref = remember { chatModel.controller.appPrefs.networkShowSubscriptionPercentage } + val percentageText = "${(floor(statusColorAndPercentage.statusPercent * 100)).toInt()}%" + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON) + ) { + if (pref.state.value && leadingPercentage) SubscriptionStatusIndicatorPercentage(percentageText) + SubscriptionStatusIcon( + color = when(statusColorAndPercentage.color) { + SubscriptionColorType.ACTIVE -> MaterialTheme.colors.primary + SubscriptionColorType.ACTIVE_SOCKS_PROXY -> Indigo + SubscriptionColorType.ACTIVE_DISCONNECTED -> WarningOrange + SubscriptionColorType.DISCONNECTED -> MaterialTheme.colors.secondary + }, + modifier = Modifier.size(16.dp), + variableValue = statusColorAndPercentage.variableValue) + if (pref.state.value && !leadingPercentage) SubscriptionStatusIndicatorPercentage(percentageText) + } +} + +enum class PresentedUserCategory { + CURRENT_USER, ALL_USERS +} + +enum class PresentedServerType { + SMP, XFTP +} + +@Composable +private fun ServerSessionsView(sess: ServerSessions) { + SectionView(generalGetString(MR.strings.servers_info_transport_sessions_section_header).uppercase()) { + InfoRow( + generalGetString(MR.strings.servers_info_sessions_connected), + numOrDash(sess.ssConnected) + ) + InfoRow( + generalGetString(MR.strings.servers_info_sessions_errors), + numOrDash(sess.ssErrors) + ) + InfoRow( + generalGetString(MR.strings.servers_info_sessions_connecting), + numOrDash(sess.ssConnecting) + ) + } +} + +private fun serverAddress(server: String): String { + val address = parseServerAddress(server) + + return address?.hostnames?.first() ?: server +} + +@Composable +private fun SMPServerView(srvSumm: SMPServerSummary, statsStartedAt: Instant, rh: RemoteHostInfo?) { + SectionItemView( + click = { + ModalManager.start.showCustomModal { close -> + SMPServerSummaryView( + rh = rh, + close = close, + summary = srvSumm, + statsStartedAt = statsStartedAt + ) + } + } + ) { + Text( + serverAddress(srvSumm.smpServer), + modifier = Modifier.weight(10f, fill = true) + ) + if (srvSumm.subs != null) { + Spacer(Modifier.fillMaxWidth().weight(1f)) + SubscriptionStatusIndicatorView(subs = srvSumm.subs, sess = srvSumm.sessionsOrNew, leadingPercentage = true) + } else if (srvSumm.sessions != null) { + Spacer(Modifier.fillMaxWidth().weight(1f)) + Icon(painterResource(MR.images.ic_arrow_upward), contentDescription = null, tint = SessIconColor(srvSumm.sessions)) + } + } +} + +@Composable +private fun SessIconColor(sess: ServerSessions): Color { + val online = chatModel.networkInfo.value.online + return if (online && sess.ssConnected > 0) SessionActiveColor() else MaterialTheme.colors.secondary +} + +@Composable +private fun SessionActiveColor(): Color { + val netCfg = rememberUpdatedState(chatModel.controller.getNetCfg()) + return if (netCfg.value.socksProxy != null) Indigo else MaterialTheme.colors.primary +} + +@Composable +private fun SMPServersListView(servers: List, statsStartedAt: Instant, header: String? = null, footer: String? = null, rh: RemoteHostInfo?) { + val sortedServers = servers.sortedWith(compareBy { !it.hasSubs } + .thenBy { serverAddress(it.smpServer) }) + + SectionView(header) { + sortedServers.map { svr -> SMPServerView(srvSumm = svr, statsStartedAt = statsStartedAt, rh = rh) } + } + if (footer != null) { + SectionTextFooter( + footer + ) + } +} + +fun prettySize(sizeInKB: Long): String { + if (sizeInKB == 0L) { + return "-" + } + + val sizeInBytes = sizeInKB * 1024 + val units = arrayOf("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + var size = sizeInBytes.toDouble() + var unitIndex = 0 + + while (size >= 1024 && unitIndex < units.size - 1) { + size /= 1024 + unitIndex++ + } + + val formatter = DecimalFormat("#,##0.#") + return "${formatter.format(size)} ${units[unitIndex]}" +} + +@Composable +private fun XFTPServerView(srvSumm: XFTPServerSummary, statsStartedAt: Instant, rh: RemoteHostInfo?) { + SectionItemView( + click = { + ModalManager.start.showCustomModal { close -> + XFTPServerSummaryView( + rh = rh, + close = close, + summary = srvSumm, + statsStartedAt = statsStartedAt + ) + } + } + ) { + Text( + serverAddress(srvSumm.xftpServer), + modifier = Modifier.weight(10f, fill = true) + ) + if (srvSumm.rcvInProgress || srvSumm.sndInProgress || srvSumm.delInProgress) { + Spacer(Modifier.fillMaxWidth().weight(1f)) + XFTPServerInProgressIcon(srvSumm) + } + } +} + +@Composable +private fun XFTPServerInProgressIcon(srvSumm: XFTPServerSummary) { + return when { + srvSumm.rcvInProgress && !srvSumm.sndInProgress && !srvSumm.delInProgress -> Icon(painterResource(MR.images.ic_arrow_downward),"download", tint = SessionActiveColor()) + !srvSumm.rcvInProgress && srvSumm.sndInProgress && !srvSumm.delInProgress -> Icon(painterResource(MR.images.ic_arrow_upward), "upload", tint = SessionActiveColor()) + !srvSumm.rcvInProgress && !srvSumm.sndInProgress && srvSumm.delInProgress -> Icon(painterResource(MR.images.ic_delete), "deletion", tint = SessionActiveColor()) + else -> Icon(painterResource(MR.images.ic_expand_all), "upload and download", tint = SessionActiveColor()) + } +} + +@Composable +private fun XFTPServersListView(servers: List, statsStartedAt: Instant, header: String? = null, rh: RemoteHostInfo?) { + val sortedServers = servers.sortedBy { serverAddress(it.xftpServer) } + + SectionView(header) { + sortedServers.map { svr -> XFTPServerView(svr, statsStartedAt, rh) } + } +} + +@Composable +private fun SMPStatsView(stats: AgentSMPServerStatsData, statsStartedAt: Instant, remoteHostInfo: RemoteHostInfo?) { + SectionView(generalGetString(MR.strings.servers_info_statistics_section_header).uppercase()) { + InfoRow( + generalGetString(MR.strings.servers_info_messages_sent), + numOrDash(stats._sentDirect + stats._sentViaProxy) + ) + InfoRow( + generalGetString(MR.strings.servers_info_messages_received), + numOrDash(stats._recvMsgs) + ) + SectionItemView( + click = { + ModalManager.start.showCustomModal { close -> DetailedSMPStatsView( + rh = remoteHostInfo, + close = close, + stats = stats, + statsStartedAt = statsStartedAt) + } + } + ) { + Text(text = generalGetString(MR.strings.servers_info_details), color = MaterialTheme.colors.onBackground) + } + } + SectionTextFooter( + String.format(stringResource(MR.strings.servers_info_private_data_disclaimer), localTimestamp(statsStartedAt)) + ) +} + +@Composable +private fun SMPSubscriptionsSection(totals: SMPTotals) { + Column { + Row( + Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON * 2) + ) { + Text( + generalGetString(MR.strings.servers_info_subscriptions_section_header).uppercase(), + color = MaterialTheme.colors.secondary, + style = MaterialTheme.typography.body2, + fontSize = 12.sp + ) + SubscriptionStatusIndicatorView(totals.subs, totals.sessions) + } + Column(Modifier.padding(PaddingValues()).fillMaxWidth()) { + InfoRow( + generalGetString(MR.strings.servers_info_subscriptions_connections_subscribed), + numOrDash(totals.subs.ssActive) + ) + InfoRow( + generalGetString(MR.strings.servers_info_subscriptions_total), + numOrDash(totals.subs.total) + ) + } + } +} + +@Composable +private fun SMPSubscriptionsSection(subs: SMPServerSubs, summary: SMPServerSummary, rh: RemoteHostInfo?) { + Column { + Row( + Modifier.padding(start = DEFAULT_PADDING, bottom = 5.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON * 2) + ) { + Text( + generalGetString(MR.strings.servers_info_subscriptions_section_header).uppercase(), + color = MaterialTheme.colors.secondary, + style = MaterialTheme.typography.body2, + fontSize = 12.sp + ) + SubscriptionStatusIndicatorView(subs, summary.sessionsOrNew) + } + Column(Modifier.padding(PaddingValues()).fillMaxWidth()) { + InfoRow( + generalGetString(MR.strings.servers_info_subscriptions_connections_subscribed), + numOrDash(subs.ssActive) + ) + InfoRow( + generalGetString(MR.strings.servers_info_subscriptions_connections_pending), + numOrDash(subs.ssPending) + ) + InfoRow( + generalGetString(MR.strings.servers_info_subscriptions_total), + numOrDash(subs.total) + ) + ReconnectServerButton(rh, summary.smpServer) + } + } +} + +@Composable +private fun ReconnectServerButton(rh: RemoteHostInfo?, server: String) { + SectionItemView(click = { reconnectServerAlert(rh, server) }) { + Text( + stringResource(MR.strings.reconnect), + color = MaterialTheme.colors.primary + ) + } +} + +private fun reconnectServerAlert(rh: RemoteHostInfo?, server: String) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.servers_info_reconnect_server_title), + text = generalGetString(MR.strings.servers_info_reconnect_server_message), + onConfirm = { + withBGApi { + val success = controller.reconnectServer(rh?.remoteHostId, server) + + if (!success) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.servers_info_modal_error_title), + text = generalGetString(MR.strings.servers_info_reconnect_server_error) + ) + } + } + } + ) +} + +@Composable +fun XFTPStatsView(stats: AgentXFTPServerStatsData, statsStartedAt: Instant, rh: RemoteHostInfo?) { + SectionView(generalGetString(MR.strings.servers_info_statistics_section_header).uppercase()) { + InfoRow( + generalGetString(MR.strings.servers_info_uploaded), + prettySize(stats._uploadsSize) + ) + InfoRow( + generalGetString(MR.strings.servers_info_downloaded), + prettySize(stats._downloadsSize) + ) + SectionItemView ( + click = { + ModalManager.start.showCustomModal { close -> DetailedXFTPStatsView( + rh = rh, + close = close, + stats = stats, + statsStartedAt = statsStartedAt) + } + } + ) { + Text(text = generalGetString(MR.strings.servers_info_details), color = MaterialTheme.colors.onBackground) + } + } + SectionTextFooter( + String.format(stringResource(MR.strings.servers_info_private_data_disclaimer), localTimestamp(statsStartedAt)) + ) +} + +@Composable +private fun IndentedInfoRow(title: String, desc: String) { + InfoRow(title, desc, padding = PaddingValues(start = 24.dp + DEFAULT_PADDING, end = DEFAULT_PADDING)) +} + +@Composable +fun DetailedSMPStatsLayout(stats: AgentSMPServerStatsData, statsStartedAt: Instant) { + SectionView(generalGetString(MR.strings.servers_info_detailed_statistics_sent_messages_header).uppercase()) { + InfoRow(generalGetString(MR.strings.servers_info_detailed_statistics_sent_messages_total), numOrDash(stats._sentDirect + stats._sentViaProxy)) + InfoRowTwoValues(generalGetString(MR.strings.sent_directly), generalGetString(MR.strings.attempts_label), stats._sentDirect, stats._sentDirectAttempts) + InfoRowTwoValues(generalGetString(MR.strings.sent_via_proxy), generalGetString(MR.strings.attempts_label), stats._sentViaProxy, stats._sentViaProxyAttempts) + InfoRowTwoValues(generalGetString(MR.strings.proxied), generalGetString(MR.strings.attempts_label), stats._sentProxied, stats._sentProxiedAttempts) + SectionItemView { + Text(generalGetString(MR.strings.send_errors), color = MaterialTheme.colors.onBackground) + } + IndentedInfoRow("AUTH", numOrDash(stats._sentAuthErrs)) + IndentedInfoRow("QUOTA", numOrDash(stats._sentQuotaErrs)) + IndentedInfoRow(generalGetString(MR.strings.expired_label), numOrDash(stats._sentExpiredErrs)) + IndentedInfoRow(generalGetString(MR.strings.other_label), numOrDash(stats._sentOtherErrs)) + } + + SectionDividerSpaced() + + SectionView(generalGetString(MR.strings.servers_info_detailed_statistics_received_messages_header).uppercase()) { + InfoRow(generalGetString(MR.strings.servers_info_detailed_statistics_received_total), numOrDash(stats._recvMsgs)) + SectionItemView { + Text(generalGetString(MR.strings.servers_info_detailed_statistics_receive_errors), color = MaterialTheme.colors.onBackground) + } + IndentedInfoRow(generalGetString(MR.strings.duplicates_label), numOrDash(stats._recvDuplicates)) + IndentedInfoRow(generalGetString(MR.strings.decryption_errors), numOrDash(stats._recvCryptoErrs)) + IndentedInfoRow(generalGetString(MR.strings.other_errors), numOrDash(stats._recvErrs)) + InfoRowTwoValues(generalGetString(MR.strings.acknowledged), generalGetString(MR.strings.attempts_label), stats._ackMsgs, stats._ackAttempts) + SectionItemView { + Text(generalGetString(MR.strings.acknowledgement_errors), color = MaterialTheme.colors.onBackground) + } + IndentedInfoRow("NO_MSG errors", numOrDash(stats._ackNoMsgErrs)) + IndentedInfoRow(generalGetString(MR.strings.other_errors), numOrDash(stats._ackOtherErrs)) + } + + SectionDividerSpaced() + + SectionView(generalGetString(MR.strings.connections).uppercase()) { + InfoRow(generalGetString(MR.strings.created), numOrDash(stats._connCreated)) + InfoRow(generalGetString(MR.strings.secured), numOrDash(stats._connSecured)) + InfoRow(generalGetString(MR.strings.completed), numOrDash(stats._connCompleted)) + InfoRowTwoValues(generalGetString(MR.strings.deleted), generalGetString(MR.strings.attempts_label), stats._connDeleted, stats._connDelAttempts) + InfoRow(generalGetString(MR.strings.deletion_errors), numOrDash(stats._connDelErrs)) + InfoRowTwoValues(generalGetString(MR.strings.subscribed), generalGetString(MR.strings.attempts_label), stats._connSubscribed, stats._connSubAttempts) + InfoRow(generalGetString(MR.strings.subscription_results_ignored), numOrDash(stats._connSubIgnored)) + InfoRow(generalGetString(MR.strings.subscription_errors), numOrDash(stats._connSubErrs)) + } + SectionTextFooter( + String.format(stringResource(MR.strings.servers_info_starting_from), localTimestamp(statsStartedAt)) + ) + + SectionBottomSpacer() +} + +@Composable +fun DetailedXFTPStatsLayout(stats: AgentXFTPServerStatsData, statsStartedAt: Instant) { + SectionView(generalGetString(MR.strings.uploaded_files).uppercase()) { + InfoRow(generalGetString(MR.strings.size), prettySize(stats._uploadsSize)) + InfoRowTwoValues(generalGetString(MR.strings.chunks_uploaded), generalGetString(MR.strings.attempts_label), stats._uploads, stats._uploadAttempts) + InfoRow(generalGetString(MR.strings.upload_errors), numOrDash(stats._uploadErrs)) + InfoRowTwoValues(generalGetString(MR.strings.chunks_deleted), generalGetString(MR.strings.attempts_label), stats._deletions, stats._deleteAttempts) + InfoRow(generalGetString(MR.strings.deletion_errors), numOrDash(stats._deleteErrs)) + } + SectionDividerSpaced() + SectionView(generalGetString(MR.strings.downloaded_files).uppercase()) { + InfoRow(generalGetString(MR.strings.size), prettySize(stats._downloadsSize)) + InfoRowTwoValues(generalGetString(MR.strings.chunks_downloaded), generalGetString(MR.strings.attempts_label), stats._downloads, stats._downloadAttempts) + SectionItemView { + Text(generalGetString(MR.strings.download_errors), color = MaterialTheme.colors.onBackground) + } + IndentedInfoRow("AUTH", numOrDash(stats._downloadAuthErrs)) + IndentedInfoRow(generalGetString(MR.strings.other_label), numOrDash(stats._downloadErrs)) + } + SectionTextFooter( + String.format(stringResource(MR.strings.servers_info_starting_from), localTimestamp(statsStartedAt)) + ) + + SectionBottomSpacer() +} + +@Composable +fun XFTPServerSummaryLayout(summary: XFTPServerSummary, statsStartedAt: Instant, rh: RemoteHostInfo?) { + SectionView(generalGetString(MR.strings.server_address).uppercase()) { + SelectionContainer { + Text( + summary.xftpServer, + Modifier.padding(start = DEFAULT_PADDING, top = 5.dp, end = DEFAULT_PADDING, bottom = 10.dp), + style = TextStyle( + fontFamily = FontFamily.Monospace, fontSize = 16.sp, + color = MaterialTheme.colors.secondary + ) + ) + } + if (summary.known == true) { + SectionItemView(click = { + ModalManager.start.showCustomModal { close -> ProtocolServersView(chatModel, rhId = rh?.remoteHostId, ServerProtocol.XFTP, close) } + }) { + Text(generalGetString(MR.strings.open_server_settings_button)) + } + } + + if (summary.stats != null) { + SectionDividerSpaced() + XFTPStatsView(stats = summary.stats, rh = rh, statsStartedAt = statsStartedAt) + } + + if (summary.sessions != null) { + SectionDividerSpaced() + ServerSessionsView(summary.sessions) + } + } + + SectionBottomSpacer() +} + +@Composable +fun SMPServerSummaryLayout(summary: SMPServerSummary, statsStartedAt: Instant, rh: RemoteHostInfo?) { + SectionView(generalGetString(MR.strings.server_address).uppercase()) { + SelectionContainer { + Text( + summary.smpServer, + Modifier.padding(start = DEFAULT_PADDING, top = 5.dp, end = DEFAULT_PADDING, bottom = 10.dp), + style = TextStyle( + fontFamily = FontFamily.Monospace, fontSize = 16.sp, + color = MaterialTheme.colors.secondary + ) + ) + } + if (summary.known == true) { + SectionItemView(click = { + ModalManager.start.showCustomModal { close -> ProtocolServersView(chatModel, rhId = rh?.remoteHostId, ServerProtocol.SMP, close) } + }) { + Text(generalGetString(MR.strings.open_server_settings_button)) + } + } + + if (summary.stats != null) { + SectionDividerSpaced() + SMPStatsView(stats = summary.stats, remoteHostInfo = rh, statsStartedAt = statsStartedAt) + } + + if (summary.subs != null) { + SectionDividerSpaced() + SMPSubscriptionsSection(subs = summary.subs, summary = summary, rh = rh) + } + + if (summary.sessions != null) { + SectionDividerSpaced() + ServerSessionsView(summary.sessions) + } + } + + SectionBottomSpacer() +} + +@Composable +fun ModalData.SMPServerSummaryView( + rh: RemoteHostInfo?, + close: () -> Unit, + summary: SMPServerSummary, + statsStartedAt: Instant +) { + ModalView( + close = close + ) { + ColumnWithScrollBar( + Modifier.fillMaxSize(), + ) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.smp_server), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) + } + SMPServerSummaryLayout(summary, statsStartedAt, rh) + } + } +} + + +@Composable +fun ModalData.DetailedXFTPStatsView( + rh: RemoteHostInfo?, + close: () -> Unit, + stats: AgentXFTPServerStatsData, + statsStartedAt: Instant +) { + ModalView( + close = close + ) { + ColumnWithScrollBar( + Modifier.fillMaxSize(), + ) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.servers_info_detailed_statistics), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) + } + DetailedXFTPStatsLayout(stats, statsStartedAt) + } + } +} + +@Composable +fun ModalData.DetailedSMPStatsView( + rh: RemoteHostInfo?, + close: () -> Unit, + stats: AgentSMPServerStatsData, + statsStartedAt: Instant +) { + ModalView( + close = close + ) { + ColumnWithScrollBar( + Modifier.fillMaxSize(), + ) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.servers_info_detailed_statistics), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) + } + DetailedSMPStatsLayout(stats, statsStartedAt) + } + } +} + +@Composable +fun ModalData.XFTPServerSummaryView( + rh: RemoteHostInfo?, + close: () -> Unit, + summary: XFTPServerSummary, + statsStartedAt: Instant +) { + ModalView( + close = close + ) { + ColumnWithScrollBar( + Modifier.fillMaxSize(), + ) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.xftp_server), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) + } + XFTPServerSummaryLayout(summary, statsStartedAt, rh) + } + } +} + +@Composable +fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableState) { + Column( + Modifier.fillMaxSize(), + ) { + var showUserSelection by remember { mutableStateOf(false) } + val selectedUserCategory = + remember { stateGetOrPut("selectedUserCategory") { PresentedUserCategory.ALL_USERS } } + val selectedServerType = + remember { stateGetOrPut("serverTypeSelection") { PresentedServerType.SMP } } + val scope = rememberCoroutineScope() + + LaunchedEffect(Unit) { + if (chatModel.users.count { u -> u.user.activeUser || !u.user.hidden } == 1 + ) { + selectedUserCategory.value = PresentedUserCategory.CURRENT_USER + } else { + showUserSelection = true + } + } + + Column( + Modifier.fillMaxSize(), + ) { + Box(contentAlignment = Alignment.Center) { + val bottomPadding = DEFAULT_PADDING + AppBarTitle( + stringResource(MR.strings.servers_info), + hostDevice(rh?.remoteHostId), + bottomPadding = bottomPadding + ) + } + if (serversSummary.value == null) { + Box( + modifier = Modifier + .fillMaxSize() + ) { + Text(generalGetString(MR.strings.servers_info_missing), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary) + } + } else { + val userOptions by remember { + mutableStateOf( + listOf( + PresentedUserCategory.ALL_USERS to generalGetString(MR.strings.all_users), + PresentedUserCategory.CURRENT_USER to generalGetString(MR.strings.current_user), + ) + ) + } + val serverTypeTabTitles = PresentedServerType.entries.map { + when (it) { + PresentedServerType.SMP -> + stringResource(MR.strings.messages_section_title) + + PresentedServerType.XFTP -> + stringResource(MR.strings.servers_info_files_tab) + } + } + val serverTypePagerState = rememberPagerState( + initialPage = selectedServerType.value.ordinal, + initialPageOffsetFraction = 0f + ) { PresentedServerType.entries.size } + + KeyChangeEffect(serverTypePagerState.currentPage) { + selectedServerType.value = PresentedServerType.values()[serverTypePagerState.currentPage] + } + TabRow( + selectedTabIndex = serverTypePagerState.currentPage, + backgroundColor = Color.Transparent, + contentColor = MaterialTheme.colors.primary, + ) { + serverTypeTabTitles.forEachIndexed { index, it -> + Tab( + selected = serverTypePagerState.currentPage == index, + onClick = { + scope.launch { + serverTypePagerState.animateScrollToPage(index) + } + }, + text = { Text(it, fontSize = 13.sp) }, + selectedContentColor = MaterialTheme.colors.primary, + unselectedContentColor = MaterialTheme.colors.secondary, + ) + } + } + + HorizontalPager( + state = serverTypePagerState, + Modifier.fillMaxSize(), + verticalAlignment = Alignment.Top, + userScrollEnabled = appPlatform.isAndroid + ) { index -> + ColumnWithScrollBar( + Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.Top + ) { + Spacer(Modifier.height(DEFAULT_PADDING)) + if (showUserSelection) { + ExposedDropDownSettingRow( + generalGetString(MR.strings.servers_info_target), + userOptions, + selectedUserCategory, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { + selectedUserCategory.value = it + } + ) + SectionDividerSpaced() + } + when (index) { + PresentedServerType.SMP.ordinal -> { + serversSummary.value?.let { + val smpSummary = + if (selectedUserCategory.value == PresentedUserCategory.CURRENT_USER) it.currentUserSMP else it.allUsersSMP; + val totals = smpSummary.smpTotals + val currentlyUsedSMPServers = smpSummary.currentlyUsedSMPServers + val previouslyUsedSMPServers = smpSummary.previouslyUsedSMPServers + val proxySMPServers = smpSummary.onlyProxiedSMPServers + val statsStartedAt = it.statsStartedAt + + SMPStatsView(totals.stats, statsStartedAt, rh) + SectionDividerSpaced() + SMPSubscriptionsSection(totals) + SectionDividerSpaced() + + if (currentlyUsedSMPServers.isNotEmpty()) { + SMPServersListView( + servers = currentlyUsedSMPServers, + statsStartedAt = statsStartedAt, + header = generalGetString(MR.strings.servers_info_connected_servers_section_header).uppercase(), + rh = rh + ) + SectionDividerSpaced() + } + + if (previouslyUsedSMPServers.isNotEmpty()) { + SMPServersListView( + servers = previouslyUsedSMPServers, + statsStartedAt = statsStartedAt, + header = generalGetString(MR.strings.servers_info_previously_connected_servers_section_header).uppercase(), + rh = rh + ) + SectionDividerSpaced() + } + + if (proxySMPServers.isNotEmpty()) { + SMPServersListView( + servers = proxySMPServers, + statsStartedAt = statsStartedAt, + header = generalGetString(MR.strings.servers_info_proxied_servers_section_header).uppercase(), + footer = generalGetString(MR.strings.servers_info_proxied_servers_section_footer), + rh = rh + ) + SectionDividerSpaced() + } + + ServerSessionsView(totals.sessions) + } + } + + PresentedServerType.XFTP.ordinal -> { + serversSummary.value?.let { + val xftpSummary = + if (selectedUserCategory.value == PresentedUserCategory.CURRENT_USER) it.currentUserXFTP else it.allUsersXFTP + val totals = xftpSummary.xftpTotals + val statsStartedAt = it.statsStartedAt + val currentlyUsedXFTPServers = xftpSummary.currentlyUsedXFTPServers + val previouslyUsedXFTPServers = xftpSummary.previouslyUsedXFTPServers + + XFTPStatsView(totals.stats, statsStartedAt, rh) + SectionDividerSpaced() + + if (currentlyUsedXFTPServers.isNotEmpty()) { + XFTPServersListView( + currentlyUsedXFTPServers, + statsStartedAt, + generalGetString(MR.strings.servers_info_connected_servers_section_header).uppercase(), + rh + ) + SectionDividerSpaced() + } + + if (previouslyUsedXFTPServers.isNotEmpty()) { + XFTPServersListView( + previouslyUsedXFTPServers, + statsStartedAt, + generalGetString(MR.strings.servers_info_previously_connected_servers_section_header).uppercase(), + rh + ) + SectionDividerSpaced() + } + + ServerSessionsView(totals.sessions) + } + } + } + + SectionDividerSpaced() + + SectionView { + ReconnectAllServersButton(rh) + ResetStatisticsButton(rh) + } + + SectionBottomSpacer() + } + } + } + } + } +} + +@Composable +private fun ReconnectAllServersButton(rh: RemoteHostInfo?) { + SectionItemView(click = { reconnectAllServersAlert(rh) }) { + Text( + stringResource(MR.strings.servers_info_reconnect_all_servers_button), + color = MaterialTheme.colors.primary + ) + } +} + +private fun reconnectAllServersAlert(rh: RemoteHostInfo?) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.servers_info_reconnect_servers_title), + text = generalGetString(MR.strings.servers_info_reconnect_servers_message), + onConfirm = { + withBGApi { + val success = controller.reconnectAllServers(rh?.remoteHostId) + + if (!success) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.servers_info_modal_error_title), + text = generalGetString(MR.strings.servers_info_reconnect_servers_error) + ) + } + } + } + ) +} + +@Composable +private fun ResetStatisticsButton(rh: RemoteHostInfo?) { + SectionItemView(click = { resetStatisticsAlert(rh) }) { + Text( + stringResource(MR.strings.servers_info_reset_stats), + color = MaterialTheme.colors.primary + ) + } +} + +private fun resetStatisticsAlert(rh: RemoteHostInfo?) { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.servers_info_reset_stats_alert_title), + text = generalGetString(MR.strings.servers_info_reset_stats_alert_message), + confirmText = generalGetString(MR.strings.servers_info_reset_stats_alert_confirm), + destructive = true, + onConfirm = { + withBGApi { + val success = controller.resetAgentServersStats(rh?.remoteHostId) + + if (!success) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.servers_info_modal_error_title), + text = generalGetString(MR.strings.servers_info_reset_stats_alert_error_title) + ) + } + } + } + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 6ee60c4596..d7116d11b6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -100,6 +100,9 @@ class ModalManager(private val placement: ModalPlacement? = null) { fun hasModalsOpen() = modalCount.value > 0 + val hasModalsOpen: Boolean + @Composable get () = remember { modalCount }.value > 0 + fun closeModal() { if (modalViews.isNotEmpty()) { if (modalViews.lastOrNull()?.first == false) modalViews.removeAt(modalViews.lastIndex) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index 94affad0e7..d3a3a0ff9c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -276,8 +276,8 @@ fun TextIconSpaced(extraPadding: Boolean = false) { } @Composable -fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null, textColor: Color = MaterialTheme.colors.onBackground) { - SectionItemViewSpaceBetween { +fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color? = null, textColor: Color = MaterialTheme.colors.onBackground, padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING)) { + SectionItemViewSpaceBetween(padding = padding) { Row { val iconSize = with(LocalDensity.current) { 21.sp.toDp() } if (icon != null) Icon(icon, title, Modifier.padding(end = 8.dp).size(iconSize), tint = iconTint ?: MaterialTheme.colors.secondary) @@ -287,6 +287,61 @@ fun InfoRow(title: String, value: String, icon: Painter? = null, iconTint: Color } } +fun numOrDash(n: Number): String = if (n.toLong() == 0L) "-" else n.toString() + +@Composable +fun InfoRowTwoValues( + title: String, + title2: String, + value: Int, + value2: Int, + textColor: Color = MaterialTheme.colors.onBackground +) { + SectionItemViewSpaceBetween { + Row( + verticalAlignment = Alignment.Bottom + ) { + Text( + text = title, + color = textColor, + ) + Text( + text = " / ", + fontSize = 12.sp, + ) + Text( + text = title2, + color = textColor, + fontSize = 12.sp, + ) + } + Row(verticalAlignment = Alignment.Bottom) { + if (value == 0 && value2 == 0) { + Text( + text = "-", + color = MaterialTheme.colors.secondary + ) + } else { + Text( + text = numOrDash(value), + color = MaterialTheme.colors.secondary, + ) + Text( + text = " / ", + color = MaterialTheme.colors.secondary, + fontSize = 12.sp, + ) + Text( + text = numOrDash(value2), + color = MaterialTheme.colors.secondary, + fontSize = 12.sp, + ) + } + } + } +} + + @Composable fun InfoRowEllipsis(title: String, value: String, onClick: () -> Unit) { SectionItemViewSpaceBetween(onClick) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt new file mode 100644 index 0000000000..e0e61b598e --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/SubscriptionStatusIcon.kt @@ -0,0 +1,41 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.foundation.layout.Box +import androidx.compose.material.Icon +import androidx.compose.ui.graphics.Color +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource + +@Composable +fun SubscriptionStatusIcon( + color: Color, + variableValue: Float, + modifier: Modifier = Modifier +) { + @Composable + fun ZeroIcon() { + Icon(painterResource(MR.images.ic_radiowaves_up_forward_4_bar), null, tint = color.copy(alpha = 0.33f), modifier = modifier) + } + + when { + variableValue <= 0f -> ZeroIcon() + variableValue > 0f && variableValue <= 0.25f -> Box { + ZeroIcon() + Icon(painterResource(MR.images.ic_radiowaves_up_forward_1_bar), null, tint = color, modifier = modifier) + } + + variableValue > 0.25f && variableValue <= 0.5f -> Box { + ZeroIcon() + Icon(painterResource(MR.images.ic_radiowaves_up_forward_2_bar), null, tint = color, modifier = modifier) + } + + variableValue > 0.5f && variableValue <= 0.75f -> Box { + ZeroIcon() + Icon(painterResource(MR.images.ic_radiowaves_up_forward_3_bar), null, tint = color, modifier = modifier) + } + + else -> Icon(painterResource(MR.images.ic_radiowaves_up_forward_4_bar), null, tint = color, modifier = modifier) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 26c5422623..9f47a5cdf9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -44,6 +44,11 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = close) } }, + scanPaste = { + closeNewChatSheet(false) + ModalManager.center.closeModals() + ModalManager.center.showModalCloseable { close -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.CONNECT, showQRCodeScanner = true, close = close) } + }, createGroup = { closeNewChatSheet(false) ModalManager.center.closeModals() @@ -55,15 +60,17 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow, stopped: Boolean, addContact: () -> Unit, + scanPaste: () -> Unit, createGroup: () -> Unit, closeNewChatSheet: (animated: Boolean) -> Unit, ) { @@ -102,7 +109,7 @@ private fun NewChatSheetLayout( verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.End ) { - val actions = remember { listOf(addContact, createGroup) } + val actions = remember { listOf(addContact, scanPaste, createGroup) } val backgroundColor = if (isInDarkTheme()) blendARGB(MaterialTheme.colors.primary, Color.Black, 0.7F) else @@ -264,6 +271,7 @@ private fun PreviewNewChatSheet() { MutableStateFlow(AnimatedViewState.VISIBLE), stopped = false, addContact = {}, + scanPaste = {}, createGroup = {}, closeNewChatSheet = {}, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index f341d59305..a877e123b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -140,7 +140,7 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC } } - HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top) { index -> + HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top, userScrollEnabled = appPlatform.isAndroid) { index -> // LALAL SCROLLBAR DOESN'T WORK ColumnWithScrollBar( Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 61c8e1b75f..e7033e88f4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -42,6 +42,7 @@ fun NetworkAndServersView() { // It's not a state, just a one-time value. Shouldn't be used in any state-related situations val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } + val networkShowSubscriptionPercentage: MutableState = remember { mutableStateOf(chatModel.controller.appPrefs.networkShowSubscriptionPercentage.get()) } val developerTools = chatModel.controller.appPrefs.developerTools.get() val onionHosts = remember { mutableStateOf(netCfg.onionHosts) } val sessionMode = remember { mutableStateOf(netCfg.sessionMode) } @@ -53,6 +54,7 @@ fun NetworkAndServersView() { currentRemoteHost = currentRemoteHost, developerTools = developerTools, networkUseSocksProxy = networkUseSocksProxy, + networkShowSubscriptionPercentage = networkShowSubscriptionPercentage, onionHosts = onionHosts, sessionMode = sessionMode, smpProxyMode = smpProxyMode, @@ -117,6 +119,9 @@ fun NetworkAndServersView() { ) } }, + toggleNetworkShowSubscriptionPercentage = { enable -> + networkShowSubscriptionPercentage.value = enable + }, useOnion = { if (onionHosts.value == it) return@NetworkAndServersLayout val prevValue = onionHosts.value @@ -230,12 +235,14 @@ fun NetworkAndServersView() { currentRemoteHost: RemoteHostInfo?, developerTools: Boolean, networkUseSocksProxy: MutableState, + networkShowSubscriptionPercentage: MutableState, onionHosts: MutableState, sessionMode: MutableState, smpProxyMode: MutableState, smpProxyFallback: MutableState, proxyPort: State, toggleSocksProxy: (Boolean) -> Unit, + toggleNetworkShowSubscriptionPercentage: (Boolean) -> Unit, useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, updateSMPProxyMode: (SMPProxyMode) -> Unit, @@ -256,6 +263,7 @@ fun NetworkAndServersView() { SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) } }) if (currentRemoteHost == null) { + SettingsPreferenceItem(painterResource(MR.images.ic_radiowaves_up_forward_4_bar),stringResource(MR.strings.subscription_percentage), chatModel.controller.appPrefs.networkShowSubscriptionPercentage) UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showModal, chatModel.controller.appPrefs.networkProxyHostPort, false) UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) if (developerTools) { @@ -677,8 +685,10 @@ fun PreviewNetworkAndServersLayout() { currentRemoteHost = null, developerTools = true, networkUseSocksProxy = remember { mutableStateOf(true) }, + networkShowSubscriptionPercentage = remember { mutableStateOf(false) }, proxyPort = remember { mutableStateOf(9050) }, toggleSocksProxy = {}, + toggleNetworkShowSubscriptionPercentage = {}, onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 996ecb11da..24c94a5ddc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -617,6 +617,7 @@ New chat Add contact + Scan / Paste link One-time invitation link 1-time link SimpleX address @@ -685,6 +686,7 @@ The servers for new connections of your current chat profile Save servers? XFTP servers + Subscription percentage Install SimpleX Chat for terminal Star on GitHub Contribute @@ -2075,4 +2077,85 @@ WiFi Wired ethernet Other + + + Servers info + Files + No info, try to reload + Showing info for + All users + Current user + Transport sessions + Connected + Connecting + Errors + Statistics + Messages sent + Messages received + Details + Starting from %s.\nAll data is private to your device. + Messages subscriptions + Connections subscribed + Pending + Total + Connected servers + Previously connected servers + Proxied servers + You are not connected to these servers. Private routing is used to deliver messages to them. + Reconnect servers? + Reconnect all connected servers to force message delivery. It uses additional traffic. + Reconnect server? + Reconnect server to force message delivery. It uses additional traffic. + Error reconnecting servers + Error reconnecting server + Error + Reconnect all servers + Reset all statistics + Reset all statistics? + Servers statistics will be reset - this cannot be undone! + Reset + Error resetting statistics + Uploaded + Downloaded + Detailed statistics + Sent messages + Sent total + Received messages + Received total + Receive errors + Starting from %s. + SMP server + XFTP server + Reconnect + attempts + Sent directly + Sent via proxy + Proxied + Send errors + expired + other + duplicates + decryption errors + other errors + Acknowledged + Acknowledgement errors + Connections + Created + Secured + Completed + Deleted + Deletion errors + Subscribed + Subscriptions ignored + Subscription errors + Uploaded files + Size + Chunks uploaded + Upload errors + Chunks deleted + Chunks downloaded + Downloaded files + Download errors + Server address + Open server settings \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg new file mode 100644 index 0000000000..db1cb48d53 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_1_bar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg new file mode 100644 index 0000000000..6f9a3211dd --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_2_bar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg new file mode 100644 index 0000000000..1962851519 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_3_bar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg new file mode 100644 index 0000000000..ff7a146284 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_radiowaves_up_forward_4_bar.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From a9d2535292d54f6e68c36cf0a2c45fbc15e5729f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 9 Jul 2024 21:29:36 +0400 Subject: [PATCH 03/57] core: differentiate inactive and forwarded group snd statuses (#4420) * core: GroupSndStatus * rfc * encoding, db apis * pending, forwarded statuses * encoding --- docs/rfcs/2024-07-09-group-snd-status.md | 47 ++++++++++++++++ src/Simplex/Chat.hs | 68 +++++++++++++++--------- src/Simplex/Chat/Messages.hs | 57 ++++++++++++++++++-- src/Simplex/Chat/Store/Messages.hs | 8 +-- 4 files changed, 146 insertions(+), 34 deletions(-) create mode 100644 docs/rfcs/2024-07-09-group-snd-status.md diff --git a/docs/rfcs/2024-07-09-group-snd-status.md b/docs/rfcs/2024-07-09-group-snd-status.md new file mode 100644 index 0000000000..43f491880d --- /dev/null +++ b/docs/rfcs/2024-07-09-group-snd-status.md @@ -0,0 +1,47 @@ +# Group messages sending status + +## Problem + +Currently in UI chat item info: +- There's no differentiation between sent messages and pending messages. +- There's no differentiation between pending messages reasons (establishing connection or member inactivity). + - Since the former is usually not a case due to group forwarding, this can be ignored. +- Messages to be forwarded by admin are not accounted. + +## Solution + +Differentiate new statuses for group sending in chat item info: +- forwarded +- inactive / pending + +Option 1 is to add statuses to CIStatus / ACIStatus types. + +Pros: +- simple. + +Cons: +- further muddies type of statuses for chat item with impossible states / different dimension, as it's not applicable directly to chat item but a technicality of group sending process. + +Option 2 is to create a new type, GroupSndStatus. + +```haskell +data GroupSndStatus + = GSSNew + | GSSForwarded + | GSSInactive + | GSSSent + | GSSRcvd {msgRcptStatus :: MsgReceiptStatus} + | GSSError {agentError :: SndError} + | GSSWarning {agentError :: SndError} + | GSSInvalid {text :: Text} +``` + +Most statuses repeat CIStatus sending statuses, with addition of forwarded and inactive for group sending process. + +Pros: +- separates concerns of chat item presentation from group sending process. +- allows future extension without further muddying CIStatus types. + +Cons: +- more work. +- requires backwards compatible decoding with ACIStatus to read previous data from db. diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 131fbb7089..786f065ae0 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2786,14 +2786,19 @@ processChatCommand' vr = \case (fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer g (length $ filter memberCurrent ms) timed_ <- sndGroupCITimed live gInfo itemTTL (msgContainer, quotedItem_) <- prepareGroupMsg user gInfo mc quotedItemId_ itemForwarded fInv_ timed_ live - (msg, sentToMembers) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) + (msg, groupSndResult) <- sendGroupMessage user gInfo ms (XMsgNew msgContainer) ci <- saveSndChatItem' user (CDGroupSnd gInfo) msg (CISndMsgContent mc) ciFile_ quotedItem_ itemForwarded timed_ live - withStore' $ \db -> - forM_ sentToMembers $ \GroupMember {groupMemberId} -> - createGroupSndStatus db (chatItemId' ci) groupMemberId CISSndNew + withStore' $ \db -> do + let GroupSndResult {sentTo, pending, forwarded} = groupSndResult + createMemberSndStatuses db ci sentTo GSSNew + createMemberSndStatuses db ci forwarded GSSForwarded + createMemberSndStatuses db ci pending GSSInactive forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) pure $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) + where + createMemberSndStatuses db ci ms' gss = + forM_ ms' $ \GroupMember {groupMemberId} -> createGroupSndStatus db (chatItemId' ci) groupMemberId gss notAllowedError f = pure $ chatCmdError (Just user) ("feature not allowed " <> T.unpack (groupFeatureNameText f)) setupSndFileTransfer :: Group -> Int -> CM (Maybe (FileInvitation, CIFile 'MDSnd)) setupSndFileTransfer g n = forM file_ $ \file -> do @@ -4574,7 +4579,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = continued <- continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId - updateGroupItemStatus gInfo m conn msgId (CISSndSent SSPComplete) (Just $ isJust proxy) + updateGroupItemStatus gInfo m conn msgId GSSSent (Just $ isJust proxy) when continued $ sendPendingGroupMessages user m conn SWITCH qd phase cStats -> do toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) @@ -4615,15 +4620,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn MWARN msgId err -> do - withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (CISSndWarning $ agentSndError err) + withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (GSSWarning $ agentSndError err) processConnMWARN connEntity conn err MERR msgId err -> do - withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (CISSndError $ agentSndError err) + withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (GSSError $ agentSndError err) -- group errors are silenced to reduce load on UI event log -- toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err MERRS msgIds err -> do - let newStatus = CISSndError $ agentSndError err + let newStatus = GSSError $ agentSndError err -- error cannot be AUTH error here withStore' $ \db -> forM_ msgIds $ \msgId -> updateGroupItemErrorStatus db msgId (groupMemberId' m) newStatus `catchAll_` pure () @@ -4634,7 +4639,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () where - updateGroupItemErrorStatus :: DB.Connection -> AgentMsgId -> GroupMemberId -> CIStatus 'MDSnd -> IO () + updateGroupItemErrorStatus :: DB.Connection -> AgentMsgId -> GroupMemberId -> GroupSndStatus -> IO () updateGroupItemErrorStatus db msgId groupMemberId newStatus = do chatItemId_ <- getChatItemIdByAgentMsgId db connId msgId forM_ chatItemId_ $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus @@ -6290,7 +6295,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus - updateGroupItemStatus gInfo m conn agentMsgId (CISSndRcvd msgRcptStatus SSPComplete) Nothing + updateGroupItemStatus gInfo m conn agentMsgId (GSSRcvd msgRcptStatus) Nothing updateDirectItemsStatus :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM () updateDirectItemsStatus ct conn msgIds newStatus = do @@ -6314,20 +6319,20 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | otherwise -> Just <$> updateDirectChatItemStatus db user ct itemId newStatus _ -> pure Nothing - updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> CM Bool + updateGroupMemSndStatus :: ChatItemId -> GroupMemberId -> GroupSndStatus -> CM Bool updateGroupMemSndStatus itemId groupMemberId newStatus = withStore' $ \db -> updateGroupMemSndStatus' db itemId groupMemberId newStatus - updateGroupMemSndStatus' :: DB.Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO Bool + updateGroupMemSndStatus' :: DB.Connection -> ChatItemId -> GroupMemberId -> GroupSndStatus -> IO Bool updateGroupMemSndStatus' db itemId groupMemberId newStatus = runExceptT (getGroupSndStatus db itemId groupMemberId) >>= \case - Right (CISSndRcvd _ _) -> pure False + Right (GSSRcvd _) -> pure False Right memStatus | memStatus == newStatus -> pure False | otherwise -> updateGroupSndStatus db itemId groupMemberId newStatus $> True _ -> pure False - updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> CIStatus 'MDSnd -> Maybe Bool -> CM () + updateGroupItemStatus :: GroupInfo -> GroupMember -> Connection -> AgentMsgId -> GroupSndStatus -> Maybe Bool -> CM () updateGroupItemStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus viaProxy_ = withStore' (\db -> getGroupChatItemByAgentMsgId db user groupId connId msgId) >>= \case Just (CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure () @@ -6778,7 +6783,7 @@ deliverMessagesB msgReqs = do -- TODO combine profile update and message into one batch -- Take into account that it may not fit, and that we currently don't support sending multiple messages to the same connection in one call. -sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, [GroupMember]) +sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResult) sendGroupMessage user gInfo members chatMsgEvent = do when shouldSendProfileUpdate $ sendProfileUpdate `catchChatError` (\e -> toView (CRChatError (Just user) e)) @@ -6800,12 +6805,18 @@ sendGroupMessage user gInfo members chatMsgEvent = do currentTs <- liftIO getCurrentTime withStore' $ \db -> updateUserMemberProfileSentAt db user gInfo currentTs -sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, [GroupMember]) +data GroupSndResult = GroupSndResult + { sentTo :: [GroupMember], + pending :: [GroupMember], + forwarded :: [GroupMember] + } + +sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResult) sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId) recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) let msgFlags = MsgFlags {notification = hasNotification $ toCMEventTag chatMsgEvent} - (toSend, pending, _, dups) = foldr addMember ([], [], S.empty, 0 :: Int) recipientMembers + (toSend, pending, forwarded, _, dups) = foldr addMember ([], [], [], S.empty, 0 :: Int) recipientMembers -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here msgReqs = map (\(_, conn) -> (conn, msgFlags, msgBody, msgId)) toSend when (dups /= 0) $ logError $ "sendGroupMessage: " <> tshow dups <> " duplicate members" @@ -6813,8 +6824,13 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do let errors = lefts delivered unless (null errors) $ toView $ CRChatErrors (Just user) errors stored <- lift . withStoreBatch' $ \db -> map (\m -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) pending - let sentToMembers = filterSent delivered toSend fst <> filterSent stored pending id - pure (msg, sentToMembers) + let gsr = + GroupSndResult + { sentTo = filterSent delivered toSend fst, + pending = filterSent stored pending id, + forwarded + } + pure (msg, gsr) where shuffleMembers :: [GroupMember] -> IO [GroupMember] shuffleMembers ms = do @@ -6822,12 +6838,13 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do liftM2 (<>) (shuffle adminMs) (shuffle otherMs) where isAdmin GroupMember {memberRole} = memberRole >= GRAdmin - addMember m acc@(toSend, pending, !mIds, !dups) = case memberSendAction chatMsgEvent members m of + addMember m acc@(toSend, pending, forwarded, !mIds, !dups) = case memberSendAction chatMsgEvent members m of Just a - | mId `S.member` mIds -> (toSend, pending, mIds, dups + 1) + | mId `S.member` mIds -> (toSend, pending, forwarded, mIds, dups + 1) | otherwise -> case a of - MSASend conn -> ((m, conn) : toSend, pending, mIds', dups) - MSAPending -> (toSend, m : pending, mIds', dups) + MSASend conn -> ((m, conn) : toSend, pending, forwarded, mIds', dups) + MSAPending -> (toSend, m : pending, forwarded, mIds', dups) + MSAForwarded -> (toSend, pending, m : forwarded, mIds', dups) Nothing -> acc where mId = groupMemberId' m @@ -6835,7 +6852,7 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do filterSent :: [Either ChatError a] -> [mem] -> (mem -> GroupMember) -> [GroupMember] filterSent rs ms mem = [mem m | (Right _, m) <- zip rs ms] -data MemberSendAction = MSASend Connection | MSAPending +data MemberSendAction = MSASend Connection | MSAPending | MSAForwarded memberSendAction :: ChatMsgEvent e -> [GroupMember] -> GroupMember -> Maybe MemberSendAction memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = case memberConn m of @@ -6847,7 +6864,7 @@ memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = c | otherwise -> pendingOrForwarded where pendingOrForwarded - | forwardSupported && isForwardedGroupMsg chatMsgEvent = Nothing + | forwardSupported && isForwardedGroupMsg chatMsgEvent = Just MSAForwarded | isXGrpMsgForward chatMsgEvent = Nothing | otherwise = Just MSAPending where @@ -6872,6 +6889,7 @@ sendGroupMemberMessage user m@GroupMember {groupMemberId} chatMsgEvent groupId i messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction chatMsgEvent [m] m) $ \case MSASend conn -> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId >> postDeliver MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ + MSAForwarded -> pure () -- TODO ensure order - pending messages interleave with user input messages sendPendingGroupMessages :: User -> GroupMember -> Connection -> CM () diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 9e0eccbbde..e59429b7ea 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -873,7 +873,7 @@ ciCreateStatus content = case msgDirection @d of SMDSnd -> ciStatusNew SMDRcv -> if ciRequiresAttention content then ciStatusNew else CISRcvRead -membersGroupItemStatus :: [(CIStatus 'MDSnd, Int)] -> CIStatus 'MDSnd +membersGroupItemStatus :: [(GroupSndStatus, Int)] -> CIStatus 'MDSnd membersGroupItemStatus memStatusCounts | rcvdOk == total = CISSndRcvd MROk SSPComplete | rcvdOk + rcvdBad == total = CISSndRcvd MRBadMsgHash SSPComplete @@ -884,9 +884,9 @@ membersGroupItemStatus memStatusCounts | otherwise = CISSndNew where total = sum $ map snd memStatusCounts - rcvdOk = fromMaybe 0 $ lookup (CISSndRcvd MROk SSPComplete) memStatusCounts - rcvdBad = fromMaybe 0 $ lookup (CISSndRcvd MRBadMsgHash SSPComplete) memStatusCounts - sent = fromMaybe 0 $ lookup (CISSndSent SSPComplete) memStatusCounts + rcvdOk = fromMaybe 0 $ lookup (GSSRcvd MROk) memStatusCounts + rcvdBad = fromMaybe 0 $ lookup (GSSRcvd MRBadMsgHash) memStatusCounts + sent = fromMaybe 0 $ lookup GSSSent memStatusCounts data SndCIStatusProgress = SSPPartial @@ -903,6 +903,47 @@ instance StrEncoding SndCIStatusProgress where "complete" -> pure SSPComplete _ -> fail "bad SndCIStatusProgress" +data GroupSndStatus + = GSSNew + | GSSForwarded + | GSSInactive + | GSSSent + | GSSRcvd {msgRcptStatus :: MsgReceiptStatus} + | GSSError {agentError :: SndError} + | GSSWarning {agentError :: SndError} + | GSSInvalid {text :: Text} + +deriving instance Eq GroupSndStatus + +deriving instance Show GroupSndStatus + +-- Preserve CIStatus encoding for backwards compatibility +instance StrEncoding GroupSndStatus where + strEncode = \case + GSSNew -> "snd_new" + GSSForwarded -> "snd_forwarded" + GSSInactive -> "snd_inactive" + GSSSent -> "snd_sent complete" + GSSRcvd msgRcptStatus -> "snd_rcvd " <> strEncode msgRcptStatus <> " complete" + GSSError sndErr -> "snd_error " <> strEncode sndErr + GSSWarning sndErr -> "snd_warning " <> strEncode sndErr + GSSInvalid {} -> "invalid" + strP = + (statusP <* A.endOfInput) -- see ACIStatus decoding + <|> (GSSInvalid . safeDecodeUtf8 <$> A.takeByteString) + where + statusP = + A.takeTill (== ' ') >>= \case + "snd_new" -> pure GSSNew + "snd_forwarded" -> pure GSSForwarded + "snd_inactive" -> pure GSSInactive + "snd_sent" -> GSSSent <$ " complete" + "snd_rcvd" -> GSSRcvd <$> (_strP <* " complete") + "snd_error_auth" -> pure $ GSSError SndErrAuth + "snd_error" -> GSSError <$> (A.space *> strP) + "snd_warning" -> GSSWarning <$> (A.space *> strP) + _ -> fail "bad status" + type ChatItemId = Int64 type ChatItemTs = UTCTime @@ -1176,7 +1217,7 @@ mkItemVersion ChatItem {content, meta} = version <$> ciMsgContent content data MemberDeliveryStatus = MemberDeliveryStatus { groupMemberId :: GroupMemberId, - memberDeliveryStatus :: CIStatus 'MDSnd, + memberDeliveryStatus :: GroupSndStatus, sentViaProxy :: Maybe Bool } deriving (Eq, Show) @@ -1234,6 +1275,12 @@ instance (Typeable d, MsgDirectionI d) => FromField (CIStatus d) where fromField instance FromField ACIStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8 +$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GSS") ''GroupSndStatus) + +instance ToField GroupSndStatus where toField = toField . decodeLatin1 . strEncode + +instance FromField GroupSndStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8 + $(JQ.deriveJSON defaultJSON ''MemberDeliveryStatus) $(JQ.deriveJSON defaultJSON ''ChatItemVersion) diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 853d34995a..5d9f5a7619 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -2531,14 +2531,14 @@ deleteCIModeration db GroupInfo {groupId} itemMemberId (Just sharedMsgId) = "DELETE FROM chat_item_moderations WHERE group_id = ? AND item_member_id = ? AND shared_msg_id = ?" (groupId, itemMemberId, sharedMsgId) -createGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO () +createGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> GroupSndStatus -> IO () createGroupSndStatus db itemId memberId status = DB.execute db "INSERT INTO group_snd_item_statuses (chat_item_id, group_member_id, group_snd_item_status) VALUES (?,?,?)" (itemId, memberId, status) -getGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> ExceptT StoreError IO (CIStatus 'MDSnd) +getGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> ExceptT StoreError IO GroupSndStatus getGroupSndStatus db itemId memberId = ExceptT . firstRow fromOnly (SENoGroupSndStatus itemId memberId) $ DB.query @@ -2551,7 +2551,7 @@ getGroupSndStatus db itemId memberId = |] (itemId, memberId) -updateGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> CIStatus 'MDSnd -> IO () +updateGroupSndStatus :: DB.Connection -> ChatItemId -> GroupMemberId -> GroupSndStatus -> IO () updateGroupSndStatus db itemId memberId status = do currentTs <- liftIO getCurrentTime DB.execute @@ -2589,7 +2589,7 @@ getGroupSndStatuses db itemId = memStatus (groupMemberId, memberDeliveryStatus, sentViaProxy) = MemberDeliveryStatus {groupMemberId, memberDeliveryStatus, sentViaProxy} -getGroupSndStatusCounts :: DB.Connection -> ChatItemId -> IO [(CIStatus 'MDSnd, Int)] +getGroupSndStatusCounts :: DB.Connection -> ChatItemId -> IO [(GroupSndStatus, Int)] getGroupSndStatusCounts db itemId = DB.query db From f4be0278b6c5bfa1dbc33393e27fa89f6cf7f83c Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:41:23 +0700 Subject: [PATCH 04/57] desktop: zoom and font size (#4421) * desktop: font scale * new line * moved to slider * default value highlighting * clickable * more places with adapted scale * attachment and edit icons * verified * icons in chat view * zoom * new chat button size * preview icons * android support * preview * text scale in chat view's text field * paddings --------- Co-authored-by: Evgeny Poberezkin --- .../platform/PlatformTextField.android.kt | 5 +- .../common/views/call/CallView.android.kt | 2 +- .../views/usersettings/Appearance.android.kt | 3 + .../kotlin/chat/simplex/common/App.kt | 14 ++-- .../chat/simplex/common/model/SimpleXAPI.kt | 4 ++ .../chat/simplex/common/ui/theme/Theme.kt | 4 ++ .../simplex/common/views/chat/ChatView.kt | 12 ++-- .../simplex/common/views/chat/ComposeView.kt | 4 +- .../simplex/common/views/chat/SendMsgView.kt | 4 +- .../common/views/chatlist/ChatListView.kt | 13 ++-- .../common/views/chatlist/ChatPreviewView.kt | 42 +++++++----- .../views/chatlist/ServersSummaryView.kt | 4 +- .../common/views/chatlist/UserPicker.kt | 18 ++--- .../common/views/helpers/CloseSheetBar.kt | 8 +-- .../common/views/helpers/DefaultTopAppBar.kt | 7 +- .../simplex/common/views/helpers/ModalView.kt | 4 +- .../simplex/common/views/helpers/Utils.kt | 10 +++ .../views/migration/MigrateFromDevice.kt | 5 +- .../common/views/newchat/NewChatSheet.kt | 12 ++-- .../common/views/usersettings/Appearance.kt | 65 ++++++++++++++++++- .../common/views/usersettings/SettingsView.kt | 7 +- .../commonMain/resources/MR/base/strings.xml | 2 + .../kotlin/chat/simplex/common/DesktopApp.kt | 5 +- .../views/usersettings/Appearance.desktop.kt | 61 +++++++++++++++++ 24 files changed, 245 insertions(+), 70 deletions(-) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index 9e28c4f2bc..f49196c64a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -29,6 +29,7 @@ import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doOnTextChanged import chat.simplex.common.R import chat.simplex.common.helpers.toURI +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.views.chat.* @@ -107,7 +108,7 @@ actual fun PlatformTextField( editText.maxLines = 16 editText.inputType = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES or editText.inputType editText.setTextColor(textColor.toArgb()) - editText.textSize = textStyle.value.fontSize.value + editText.textSize = textStyle.value.fontSize.value * appPrefs.fontScale.get() val drawable = androidAppContext.getDrawable(R.drawable.send_msg_view_background)!! DrawableCompat.setTint(drawable, tintColor.toArgb()) editText.background = drawable @@ -135,7 +136,7 @@ actual fun PlatformTextField( editText }) { it.setTextColor(textColor.toArgb()) - it.textSize = textStyle.value.fontSize.value + it.textSize = textStyle.value.fontSize.value * appPrefs.fontScale.get() DrawableCompat.setTint(it.background, tintColor.toArgb()) it.isFocusable = composeState.value.preview !is ComposePreview.VoicePreview it.isFocusableInTouchMode = it.isFocusable diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index d6af35432d..8d4ab3206a 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -554,7 +554,7 @@ fun CallPermissionsView(pipActive: Boolean, hasVideo: Boolean, cancel: () -> Uni } } else { ColumnWithScrollBar(Modifier.fillMaxSize()) { - Spacer(Modifier.height(AppBarHeight)) + Spacer(Modifier.height(AppBarHeight * fontSizeSqrtMultiplier)) AppBarTitle(stringResource(MR.strings.permissions_required)) Spacer(Modifier.weight(1f)) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index 7cb5c77f6e..9601152773 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -137,6 +137,9 @@ fun AppearanceScope.AppearanceLayout( } } + SectionDividerSpaced(maxBottomPadding = true) + FontScaleSection() + SectionBottomSpacer() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index b215b2b6cd..bd35594ac0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.CreateFirstProfile @@ -36,6 +37,7 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +import kotlin.math.sqrt data class SettingsViewState( val userPickerState: MutableStateFlow, @@ -333,21 +335,21 @@ fun EndPartOfScreen() { fun DesktopScreen(settingsState: SettingsViewState) { Box { // 56.dp is a size of unused space of settings drawer - Box(Modifier.width(DEFAULT_START_MODAL_WIDTH + 56.dp)) { + Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier + 56.dp)) { StartPartOfScreen(settingsState) } - Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH)) { + Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) { ModalManager.start.showInView() SwitchingUsersView() } - Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH).clipToBounds()) { + Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) { CenterPartOfScreen() } if (ModalManager.end.hasModalsOpen()) { VerticalDivider() } - Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH).clipToBounds()) { + Box(Modifier.widthIn(max = DEFAULT_END_MODAL_WIDTH * fontSizeSqrtMultiplier).clipToBounds()) { EndPartOfScreen() } } @@ -357,14 +359,14 @@ fun DesktopScreen(settingsState: SettingsViewState) { Box( Modifier .fillMaxSize() - .padding(start = DEFAULT_START_MODAL_WIDTH) + .padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) .clickable(interactionSource = remember { MutableInteractionSource() }, indication = null, onClick = { ModalManager.start.closeModals() scope.launch { settingsState.scaffoldState.drawerState.close() } }) ) } - VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH)) + VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) tryOrShowError("UserPicker", error = {}) { UserPicker(chatModel, userPickerState) { scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 0a42541c75..7633b0c808 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -198,6 +198,8 @@ class AppPreferences { }, settingsThemes) val themeOverrides = mkThemeOverridesPreference() val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f) + val fontScale = mkFloatPreference(SHARED_PREFS_FONT_SCALE, 1f) + val densityScale = mkFloatPreference(SHARED_PREFS_DENSITY_SCALE, 1f) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0) @@ -380,6 +382,8 @@ class AppPreferences { private const val SHARED_PREFS_THEMES_OLD = "Themes" private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides" private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius" + private const val SHARED_PREFS_FONT_SCALE = "FontScale" + private const val SHARED_PREFS_DENSITY_SCALE = "DensityScale" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode" private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index f6e113ea13..fb922fe52b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -6,6 +6,8 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController import chat.simplex.common.model.ChatController.appPrefs @@ -777,6 +779,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { typography = Typography, shapes = Shapes, content = { + val density = Density(LocalDensity.current.density * desktopDensityScaleMultiplier, LocalDensity.current.fontScale * fontSizeMultiplier) val rememberedAppColors = remember { // Explicitly creating a new object here so we don't mutate the initial [appColors] // provided, and overwrite the values set in it. @@ -791,6 +794,7 @@ fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { LocalContentColor provides MaterialTheme.colors.onBackground, LocalAppColors provides rememberedAppColors, LocalAppWallpaper provides rememberedWallpaper, + LocalDensity provides density, content = content) } ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index af9d77b1d0..ec34a73d31 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -821,9 +821,9 @@ fun ChatInfoToolbar( buttons = barButtons ) - Divider(Modifier.padding(top = AppBarHeight)) + Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier)) - Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight)) { + Box(Modifier.fillMaxWidth().wrapContentSize(Alignment.TopEnd).offset(y = AppBarHeight * fontSizeSqrtMultiplier)) { DefaultDropdownMenu(showMenu) { menuItems.forEach { it() } } @@ -837,9 +837,9 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo verticalAlignment = Alignment.CenterVertically ) { if (cInfo.incognito) { - IncognitoImage(size = 36.dp, Indigo) + IncognitoImage(size = 36.dp * fontSizeSqrtMultiplier, Indigo) } - ChatInfoImage(cInfo, size = imageSize, iconColor) + ChatInfoImage(cInfo, size = imageSize * fontSizeSqrtMultiplier, iconColor) Column( Modifier.padding(start = 8.dp), horizontalAlignment = Alignment.CenterHorizontally @@ -865,7 +865,7 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo @Composable private fun ContactVerifiedShield() { - Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(18.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(18.dp * fontSizeSqrtMultiplier).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) } data class CIListState(val scrolled: Boolean, val itemCount: Int, val keyboardState: KeyboardState) @@ -1283,7 +1283,7 @@ val MEMBER_IMAGE_SIZE: Dp = 38.dp @Composable fun MemberImage(member: GroupMember) { - ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background) + ProfileImage(MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 24533247ba..a0e1bf8107 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -13,10 +13,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontStyle import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.filesToDelete @@ -884,7 +886,7 @@ fun ComposeView( && !nextSendGrpInv.value IconButton( attachmentClicked, - Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else 7.dp), + Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else with(LocalDensity.current) { 7.sp.toDp() }), enabled = attachmentEnabled ) { Icon( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index 779371a07c..192f1b005f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -15,10 +15,12 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* @@ -99,7 +101,7 @@ fun SendMsgView( if (showDeleteTextButton.value) { DeleteTextButton(composeState) } - Box(Modifier.align(Alignment.BottomEnd).padding(bottom = if (appPlatform.isAndroid) 0.dp else 5.dp)) { + Box(Modifier.align(Alignment.BottomEnd).padding(bottom = if (appPlatform.isAndroid) 0.dp else with(LocalDensity.current) { 5.sp.toDp() } * fontSizeSqrtMultiplier)) { val sendButtonSize = remember { Animatable(36f) } val sendButtonAlpha = remember { Animatable(1f) } val scope = rememberCoroutineScope() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 3417333370..13e88c3b2f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -91,7 +91,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf if (newChatSheetState.value.isVisible()) hideNewChatSheet(true) else showNewChatSheet() } }, - Modifier.padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp), + Modifier.padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp).size(AppBarHeight * fontSizeSqrtMultiplier), elevation = FloatingActionButtonDefaults.elevation( defaultElevation = 0.dp, pressedElevation = 0.dp, @@ -101,7 +101,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf backgroundColor = if (!stopped) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, contentColor = Color.White ) { - Icon(if (!newChatSheetState.collectAsState().value.isVisible()) painterResource(MR.images.ic_edit_filled) else painterResource(MR.images.ic_close), stringResource(MR.strings.add_contact_or_create_group)) + Icon(if (!newChatSheetState.collectAsState().value.isVisible()) painterResource(MR.images.ic_edit_filled) else painterResource(MR.images.ic_close), stringResource(MR.strings.add_contact_or_create_group), Modifier.size(24.dp * fontSizeSqrtMultiplier)) } } } @@ -259,7 +259,7 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt onSearchValueChanged = {}, buttons = barButtons ) - Divider(Modifier.padding(top = AppBarHeight)) + Divider(Modifier.padding(top = AppBarHeight * fontSizeSqrtMultiplier)) } @Composable @@ -316,7 +316,7 @@ fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> U Box { ProfileImage( image = image, - size = 37.dp, + size = 37.dp * fontSizeSqrtMultiplier, color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f) ) if (!allRead) { @@ -355,6 +355,7 @@ private fun BoxScope.unreadBadge(text: String? = "") { private fun ToggleFilterEnabledButton() { val pref = remember { ChatController.appPrefs.showUnreadAndFavorites } IconButton(onClick = { pref.set(!pref.get()) }) { + val sp16 = with(LocalDensity.current) { 16.sp.toDp() } Icon( painterResource(MR.images.ic_filter_list), null, @@ -364,7 +365,7 @@ private fun ToggleFilterEnabledButton() { .background(color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50)) .border(width = 1.dp, color = if (pref.state.value) MaterialTheme.colors.primary else Color.Unspecified, shape = RoundedCornerShape(50)) .padding(3.dp) - .size(16.dp) + .size(sp16) ) } } @@ -388,7 +389,7 @@ private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState Row(verticalAlignment = Alignment.CenterVertically) { val focusRequester = remember { FocusRequester() } var focused by remember { mutableStateOf(false) } - Icon(painterResource(MR.images.ic_search), null, Modifier.padding(horizontal = DEFAULT_PADDING_HALF), tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_search), null, Modifier.padding(horizontal = DEFAULT_PADDING_HALF).size(24.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.secondary) SearchTextField( Modifier.weight(1f).onFocusChanged { focused = it.hasFocus }.focusRequester(focusRequester), placeholder = stringResource(MR.strings.search_or_paste_simplex_link), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index 336d104d2d..0e215724f7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -47,10 +47,11 @@ fun ChatPreviewView( @Composable fun inactiveIcon() { + val sp18 = with(LocalDensity.current) { 18.sp.toDp() } Icon( painterResource(MR.images.ic_cancel_filled), stringResource(MR.strings.icon_descr_group_inactive), - Modifier.size(18.dp).background(MaterialTheme.colors.background, CircleShape), + Modifier.size(sp18).background(MaterialTheme.colors.background, CircleShape), tint = MaterialTheme.colors.secondary ) } @@ -87,10 +88,11 @@ fun ChatPreviewView( @Composable fun VerifiedIcon() { - Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(19.dp).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) + val sp19 = with(LocalDensity.current) { 19.sp.toDp() } + Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(sp19).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) } - fun messageDraft(draft: ComposeState): Pair Unit, Map> { + fun messageDraft(draft: ComposeState, sp20: Dp): Pair Unit, Map> { fun attachment(): Pair? = when (draft.preview) { is ComposePreview.FilePreview -> MR.images.ic_draft_filled to draft.preview.fileName @@ -115,12 +117,12 @@ fun ChatPreviewView( "editIcon" to InlineTextContent( Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter) ) { - Icon(painterResource(MR.images.ic_edit_note), null, tint = MaterialTheme.colors.primary) + Icon(painterResource(MR.images.ic_edit_note), null, Modifier.size(sp20), tint = MaterialTheme.colors.primary) }, "attachmentIcon" to InlineTextContent( Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.TextCenter) ) { - Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(MR.images.ic_edit_note), null, tint = MaterialTheme.colors.secondary) + Icon(if (attachment?.first != null) painterResource(attachment.first) else painterResource(MR.images.ic_edit_note), null, Modifier.size(sp20), tint = MaterialTheme.colors.secondary) } ) return inlineContentBuilder to inlineContent @@ -167,8 +169,9 @@ fun ChatPreviewView( val ci = chat.chatItems.lastOrNull() if (ci != null) { if (showChatPreviews || (chatModelDraftChatId == chat.id && chatModelDraft != null)) { + val sp20 = with(LocalDensity.current) { 20.sp.toDp() } val (text: CharSequence, inlineTextContent) = when { - chatModelDraftChatId == chat.id && chatModelDraft != null -> remember(chatModelDraft) { chatModelDraft.message to messageDraft(chatModelDraft) } + chatModelDraftChatId == chat.id && chatModelDraft != null -> remember(chatModelDraft) { chatModelDraft.message to messageDraft(chatModelDraft, sp20) } ci.meta.itemDeleted == null -> ci.text to null else -> markedDeletedText(ci.meta) to null } @@ -220,10 +223,11 @@ fun ChatPreviewView( @Composable fun progressView() { + val sp15 = with(LocalDensity.current) { 15.sp.toDp() } CircularProgressIndicator( Modifier .padding(horizontal = 2.dp) - .size(15.dp), + .size(sp15), color = MaterialTheme.colors.secondary, strokeWidth = 1.5.dp ) @@ -231,6 +235,7 @@ fun ChatPreviewView( @Composable fun chatStatusImage() { + val sp19 = with(LocalDensity.current) { 19.sp.toDp() } if (cInfo is ChatInfo.Direct) { if (cInfo.contact.active && cInfo.contact.activeConn != null) { val descr = contactNetworkStatus?.statusString @@ -244,7 +249,7 @@ fun ChatPreviewView( contentDescription = descr, tint = MaterialTheme.colors.secondary, modifier = Modifier - .size(19.dp) + .size(sp19) ) else -> @@ -266,7 +271,7 @@ fun ChatPreviewView( Row { Box(contentAlignment = Alignment.BottomEnd) { - ChatInfoImage(cInfo, size = 72.dp) + ChatInfoImage(cInfo, size = 72.dp * fontSizeSqrtMultiplier) Box(Modifier.padding(end = 6.dp, bottom = 6.dp)) { chatPreviewImageOverlayIcon() } @@ -295,9 +300,13 @@ fun ChatPreviewView( ) val n = chat.chatStats.unreadCount val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group) + val sp17 = with(LocalDensity.current) { 17.sp.toDp() } + val sp21 = with(LocalDensity.current) { 21.sp.toDp() } + val sp23 = with(LocalDensity.current) { 23.sp.toDp() } + val sp46 = with(LocalDensity.current) { 46.sp.toDp() } if (n > 0 || chat.chatStats.unreadChat) { Box( - Modifier.padding(top = 24.dp), + Modifier.padding(top = sp23, end = with(LocalDensity.current) { 3.sp.toDp() }), contentAlignment = Alignment.Center ) { Text( @@ -313,7 +322,7 @@ fun ChatPreviewView( } } else if (showNtfsIcon) { Box( - Modifier.padding(top = 24.dp), + Modifier.padding(top = sp21), contentAlignment = Alignment.Center ) { Icon( @@ -323,12 +332,12 @@ fun ChatPreviewView( modifier = Modifier .padding(horizontal = 3.dp) .padding(vertical = 1.dp) - .size(17.dp) + .size(sp17) ) } } else if (chat.chatInfo.chatSettings?.favorite == true) { Box( - Modifier.padding(top = 24.dp), + Modifier.padding(top = sp21), contentAlignment = Alignment.Center ) { Icon( @@ -338,12 +347,12 @@ fun ChatPreviewView( modifier = Modifier .padding(horizontal = 3.dp) .padding(vertical = 1.dp) - .size(17.dp) + .size(sp17) ) } } Box( - Modifier.padding(top = 50.dp), + Modifier.padding(top = sp46), contentAlignment = Alignment.Center ) { chatStatusImage() @@ -355,12 +364,13 @@ fun ChatPreviewView( @Composable fun IncognitoIcon(incognito: Boolean) { if (incognito) { + val sp21 = with(LocalDensity.current) { 21.sp.toDp() } Icon( painterResource(MR.images.ic_theater_comedy), contentDescription = null, tint = MaterialTheme.colors.secondary, modifier = Modifier - .size(21.dp) + .size(sp21) ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index 22478c18f9..5a3f09919e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp @@ -126,6 +127,7 @@ fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, sess: ServerSessions, l horizontalArrangement = Arrangement.spacedBy(DEFAULT_SPACE_AFTER_ICON) ) { if (pref.state.value && leadingPercentage) SubscriptionStatusIndicatorPercentage(percentageText) + val sp16 = with(LocalDensity.current) { 16.sp.toDp() } SubscriptionStatusIcon( color = when(statusColorAndPercentage.color) { SubscriptionColorType.ACTIVE -> MaterialTheme.colors.primary @@ -133,7 +135,7 @@ fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, sess: ServerSessions, l SubscriptionColorType.ACTIVE_DISCONNECTED -> WarningOrange SubscriptionColorType.DISCONNECTED -> MaterialTheme.colors.secondary }, - modifier = Modifier.size(16.dp), + modifier = Modifier.size(sp16), variableValue = statusColorAndPercentage.variableValue) if (pref.state.value && !leadingPercentage) SubscriptionStatusIndicatorPercentage(percentageText) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 23272fcc0c..8199a3f2d6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -309,7 +309,7 @@ fun UserProfileRow(u: User, enabled: Boolean = chatModel.chatRunning.value == tr ) { ProfileImage( image = u.image, - size = 54.dp + size = 54.dp * fontSizeSqrtMultiplier ) Text( u.displayName, @@ -354,7 +354,7 @@ fun RemoteHostRow(h: RemoteHostInfo) { .padding(start = 17.dp), verticalAlignment = Alignment.CenterVertically ) { - Icon(painterResource(MR.images.ic_smartphone_300), h.hostDeviceName, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_smartphone_300), h.hostDeviceName, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Text( h.hostDeviceName, modifier = Modifier.padding(start = 26.dp, end = 8.dp), @@ -395,7 +395,7 @@ fun LocalDeviceRow(active: Boolean) { .padding(start = 17.dp, end = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically ) { - Icon(painterResource(MR.images.ic_desktop), stringResource(MR.strings.this_device), Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_desktop), stringResource(MR.strings.this_device), Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Text( stringResource(MR.strings.this_device), modifier = Modifier.padding(start = 26.dp, end = 8.dp), @@ -409,7 +409,7 @@ fun LocalDeviceRow(active: Boolean) { private fun UseFromDesktopPickerItem(onClick: () -> Unit) { SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { val text = generalGetString(MR.strings.settings_section_title_use_from_desktop).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_desktop), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) Text(text, color = MenuTextColor) } @@ -419,7 +419,7 @@ private fun UseFromDesktopPickerItem(onClick: () -> Unit) { private fun LinkAMobilePickerItem(onClick: () -> Unit) { SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { val text = generalGetString(MR.strings.link_a_mobile) - Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_smartphone_300), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) Text(text, color = MenuTextColor) } @@ -429,7 +429,7 @@ private fun LinkAMobilePickerItem(onClick: () -> Unit) { private fun CreateInitialProfile(onClick: () -> Unit) { SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { val text = generalGetString(MR.strings.create_chat_profile) - Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) Text(text, color = MenuTextColor) } @@ -439,7 +439,7 @@ private fun CreateInitialProfile(onClick: () -> Unit) { private fun SettingsPickerItem(onClick: () -> Unit) { SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { val text = generalGetString(MR.strings.settings_section_title_settings).lowercase().capitalize(Locale.current) - Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_settings), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) Text(text, color = MenuTextColor) } @@ -449,7 +449,7 @@ private fun SettingsPickerItem(onClick: () -> Unit) { private fun CancelPickerItem(onClick: () -> Unit) { SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) { val text = generalGetString(MR.strings.cancel_verb) - Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground) + Icon(painterResource(MR.images.ic_close), text, Modifier.size(20.dp * fontSizeSqrtMultiplier), tint = MaterialTheme.colors.onBackground) Spacer(Modifier.width(DEFAULT_PADDING + 6.dp)) Text(text, color = MenuTextColor) } @@ -459,7 +459,7 @@ private fun CancelPickerItem(onClick: () -> Unit) { fun HostDisconnectButton(onClick: (() -> Unit)?) { val interactionSource = remember { MutableInteractionSource() } val hovered = interactionSource.collectIsHoveredAsState().value - IconButton(onClick ?: {}, Modifier.requiredSize(20.dp), enabled = onClick != null) { + IconButton(onClick ?: {}, Modifier.requiredSize(20.dp * fontSizeSqrtMultiplier), enabled = onClick != null) { Icon( painterResource(if (onClick == null) MR.images.ic_desktop else if (hovered) MR.images.ic_wifi_off else MR.images.ic_wifi), null, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index b26a047b0e..d92dccddc2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -22,15 +22,13 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co Column( Modifier .fillMaxWidth() - .heightIn(min = AppBarHeight) - .padding(horizontal = AppBarHorizontalPadding), + .heightIn(min = AppBarHeight * fontSizeSqrtMultiplier) + .padding(horizontal = AppBarHorizontalPadding) ) { Row( - Modifier - .padding(top = 4.dp), // Like in DefaultAppBar content = { Row( - Modifier.fillMaxWidth().height(TextFieldDefaults.MinHeight), + Modifier.fillMaxWidth().height(AppBarHeight * fontSizeSqrtMultiplier), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 577411c7e3..3d6d242832 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* +import androidx.compose.ui.unit.Dp import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp @@ -44,10 +45,10 @@ fun DefaultTopAppBar( } @Composable -fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) { +fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, height: Dp = 24.dp) { IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) { Icon( - painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = tintColor + painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), Modifier.height(height), tint = tintColor ) } } @@ -84,7 +85,7 @@ private fun TopAppBar( Box( modifier .fillMaxWidth() - .height(AppBarHeight) + .height(AppBarHeight * fontSizeSqrtMultiplier) .background(backgroundColor) .padding(horizontal = 4.dp), contentAlignment = Alignment.CenterStart, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index d7116d11b6..4acb18561a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -8,12 +8,14 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import kotlinx.coroutines.flow.MutableStateFlow import java.util.concurrent.atomic.AtomicBoolean import kotlin.math.min +import kotlin.math.sqrt @Composable fun ModalView( @@ -89,7 +91,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { if (placement == ModalPlacement.CENTER) { ChatModel.chatId.value = null } else if (placement == ModalPlacement.END) { - desktopExpandWindowToWidth(DEFAULT_START_MODAL_WIDTH + DEFAULT_MIN_CENTER_MODAL_WIDTH + DEFAULT_END_MODAL_WIDTH) + desktopExpandWindowToWidth(DEFAULT_START_MODAL_WIDTH * sqrt(appPrefs.fontScale.get()) + DEFAULT_MIN_CENTER_MODAL_WIDTH + DEFAULT_END_MODAL_WIDTH * sqrt(appPrefs.fontScale.get())) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index 884551f600..ccef86c343 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.platform.* import androidx.compose.ui.text.* import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.ThemeOverrides import chat.simplex.common.views.chatlist.connectIfOpenedViaUri @@ -519,6 +520,15 @@ fun includeMoreFailedComposables() { lastExecutedComposables.clear() } +val fontSizeMultiplier: Float + @Composable get() = remember { appPrefs.fontScale.state }.value + +val fontSizeSqrtMultiplier: Float + @Composable get() = sqrt(remember { appPrefs.fontScale.state }.value) + +val desktopDensityScaleMultiplier: Float + @Composable get() = if (appPlatform.isDesktop) remember { appPrefs.densityScale.state }.value else 1f + @Composable fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) { DisposableEffect(Unit) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt index f8e3d48625..3cc5468bbb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatController.getNetCfg import chat.simplex.common.model.ChatController.startChat import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase @@ -38,6 +39,7 @@ import kotlinx.serialization.* import java.io.File import java.net.URLEncoder import kotlin.math.max +import kotlin.math.sqrt @Serializable data class MigrationFileLinkData( @@ -426,7 +428,8 @@ fun LargeProgressView(value: Float, title: String, description: String) { Box(Modifier.padding(DEFAULT_PADDING).fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator( progress = value, - (if (appPlatform.isDesktop) Modifier.size(DEFAULT_START_MODAL_WIDTH) else Modifier.size(windowWidth() - DEFAULT_PADDING * 2)) + (if (appPlatform.isDesktop) Modifier.size(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier) else Modifier.size(windowWidth() - DEFAULT_PADDING * + 2)) .rotate(-90f), color = MaterialTheme.colors.primary, strokeWidth = 25.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt index 9f47a5cdf9..9faee4532a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatSheet.kt @@ -125,11 +125,11 @@ private fun NewChatSheetLayout( Box(contentAlignment = Alignment.CenterEnd) { Button( actions[index], - shape = RoundedCornerShape(21.dp), + shape = RoundedCornerShape(21.dp * fontSizeSqrtMultiplier), colors = ButtonDefaults.textButtonColors(backgroundColor = backgroundColor), elevation = null, contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF), - modifier = Modifier.height(42.dp) + modifier = Modifier.height(42.dp * fontSizeSqrtMultiplier) ) { Text( stringResource(titles[index]), @@ -140,7 +140,7 @@ private fun NewChatSheetLayout( Icon( painterResource(icons[index]), stringResource(titles[index]), - Modifier.size(42.dp), + Modifier.size(42.dp * fontSizeSqrtMultiplier), tint = if (isInDarkTheme()) MaterialTheme.colors.primary else MaterialTheme.colors.primary ) } @@ -152,7 +152,7 @@ private fun NewChatSheetLayout( } FloatingActionButton( onClick = { if (!stopped) closeNewChatSheet(true) }, - Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING), + Modifier.padding(end = DEFAULT_PADDING, bottom = DEFAULT_PADDING).size(AppBarHeight * fontSizeSqrtMultiplier), elevation = FloatingActionButtonDefaults.elevation( defaultElevation = 0.dp, pressedElevation = 0.dp, @@ -164,11 +164,11 @@ private fun NewChatSheetLayout( ) { Icon( painterResource(MR.images.ic_edit_filled), stringResource(MR.strings.add_contact_or_create_group), - Modifier.graphicsLayer { alpha = 1 - animatedFloat.value } + Modifier.graphicsLayer { alpha = 1 - animatedFloat.value }.size(24.dp * fontSizeSqrtMultiplier) ) Icon( painterResource(MR.images.ic_close), stringResource(MR.strings.add_contact_or_create_group), - Modifier.graphicsLayer { alpha = animatedFloat.value } + Modifier.graphicsLayer { alpha = animatedFloat.value }.size(24.dp * fontSizeSqrtMultiplier) ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 84fe333ba8..5fda4b7d1d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.* import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.* import dev.icerock.moko.resources.compose.painterResource @@ -82,6 +83,66 @@ object AppearanceScope { } } + @Composable + fun FontScaleSection() { + val localFontScale = remember { mutableStateOf(appPrefs.fontScale.get()) } + SectionView(stringResource(MR.strings.appearance_font_size).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { + Box(Modifier.size(60.dp) + .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) + .clip(RoundedCornerShape(percent = 22)) + .clickable { + localFontScale.value = 1f + appPrefs.fontScale.set(localFontScale.value) + }, + contentAlignment = Alignment.Center) { + CompositionLocalProvider( + LocalDensity provides Density(LocalDensity.current.density, localFontScale.value) + ) { + Text("Aa", color = if (localFontScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground) + } + } + Spacer(Modifier.width(10.dp)) + // Text("${(localFontScale.value * 100).roundToInt()}%", Modifier.width(70.dp), textAlign = TextAlign.Center, fontSize = 12.sp) + if (appPlatform.isAndroid) { + Slider( + localFontScale.value, + valueRange = 0.75f..1.25f, + steps = 11, + onValueChange = { + val diff = it % 0.05f + localFontScale.value = String.format(Locale.US, "%.2f", it + (if (diff >= 0.025f) -diff + 0.05f else -diff)).toFloatOrNull() ?: 1f + }, + onValueChangeFinished = { + appPrefs.fontScale.set(localFontScale.value) + }, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent, + ) + ) + } else { + Slider( + localFontScale.value, + valueRange = 0.7f..1.5f, + steps = 9, + onValueChange = { + val diff = it % 0.1f + localFontScale.value = String.format(Locale.US, "%.1f", it + (if (diff >= 0.05f) -diff + 0.1f else -diff)).toFloatOrNull() ?: 1f + }, + onValueChangeFinished = { + appPrefs.fontScale.set(localFontScale.value) + }, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent, + ) + ) + } + } + } + } + @Composable fun ChatThemePreview( theme: DefaultTheme, @@ -225,8 +286,8 @@ object AppearanceScope { } if (appPlatform.isDesktop) { - val itemWidth = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4 - val itemHeight = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2) / 4 + val itemWidth = (DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4 + val itemHeight = (DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier - DEFAULT_PADDING * 2) / 4 val rows = ceil((PresetWallpaper.entries.size + 2) / 4f).roundToInt() LazyVerticalGrid( columns = GridCells.Fixed(4), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 0b5033aca3..cef457de2e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -174,11 +174,14 @@ fun SettingsLayout( Box( Modifier .fillMaxWidth() + .height(AppBarHeight * fontSizeSqrtMultiplier) .background(MaterialTheme.colors.background) .background(if (isInDarkTheme()) ToolbarDark else ToolbarLight) - .padding(start = 4.dp, top = 8.dp) + .padding(start = 4.dp, top = 8.dp), + contentAlignment = Alignment.CenterStart ) { - NavigationButtonBack(closeSettings) + val sp24 = with(LocalDensity.current) { 24.sp.toDp() } + NavigationButtonBack(closeSettings, height = sp24) } } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 24c94a5ddc..3452e6ff60 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1606,6 +1606,8 @@ Wallpaper background Wallpaper accent Remove image + Font size + Zoom Good afternoon! diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 4128982f78..df8887c4e1 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH import chat.simplex.common.ui.theme.SimpleXTheme @@ -26,6 +27,7 @@ import kotlinx.coroutines.* import java.awt.event.WindowEvent import java.awt.event.WindowFocusListener import java.io.File +import kotlin.math.sqrt import kotlin.system.exitProcess val simplexWindowState = SimplexWindowState() @@ -195,7 +197,8 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { if (remember { ChatController.appPrefs.developerTools.state }.value && remember { ChatController.appPrefs.terminalAlwaysVisible.state }.value && remember { ChatController.appPrefs.appLanguage.state }.value != "") { var hiddenUntilRestart by remember { mutableStateOf(false) } if (!hiddenUntilRestart) { - val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH, height = 768.dp) + val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier, height = + 768.dp) Window(state = cWindowState, onCloseRequest = { hiddenUntilRestart = true }, title = stringResource(MR.strings.chat_console)) { SimpleXTheme { TerminalView(ChatModel) { hiddenUntilRestart = true } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 669dd1949d..9f7f613835 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -3,18 +3,29 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer import SectionDividerSpaced import SectionView +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel import chat.simplex.common.model.SharedPreference import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.delay import java.util.Locale +import kotlin.math.roundToInt @Composable actual fun AppearanceView(m: ChatModel) { @@ -55,6 +66,56 @@ fun AppearanceScope.AppearanceLayout( SectionDividerSpaced(maxTopPadding = true) ProfileImageSection() + SectionDividerSpaced(maxBottomPadding = true) + FontScaleSection() + + SectionDividerSpaced(maxBottomPadding = true) + DensityScaleSection() + SectionBottomSpacer() } } + +@Composable +fun DensityScaleSection() { + val localDensityScale = remember { mutableStateOf(appPrefs.densityScale.get()) } + SectionView(stringResource(MR.strings.appearance_zoom).uppercase(), padding = PaddingValues(horizontal = DEFAULT_PADDING)) { + Row(Modifier.padding(top = 10.dp), verticalAlignment = Alignment.CenterVertically) { + Box(Modifier.size(60.dp) + .background(MaterialTheme.colors.surface, RoundedCornerShape(percent = 22)) + .clip(RoundedCornerShape(percent = 22)) + .clickable { + localDensityScale.value = 1f + appPrefs.densityScale.set(localDensityScale.value) + }, + contentAlignment = Alignment.Center) { + CompositionLocalProvider( + LocalDensity provides Density(LocalDensity.current.density * localDensityScale.value, LocalDensity.current.fontScale) + ) { + Text("${localDensityScale.value}", + color = if (localDensityScale.value == 1f) MaterialTheme.colors.primary else MaterialTheme.colors.onBackground, + fontSize = 12.sp, + maxLines = 1 + ) + } + } + Spacer(Modifier.width(10.dp)) + Slider( + localDensityScale.value, + valueRange = 1f..2f, + steps = 11, + onValueChange = { + val diff = it % 0.1f + localDensityScale.value = String.format(Locale.US, "%.1f", it + (if (diff >= 0.05f) -diff + 0.1f else -diff)).toFloatOrNull() ?: 1f + }, + onValueChangeFinished = { + appPrefs.densityScale.set(localDensityScale.value) + }, + colors = SliderDefaults.colors( + activeTickColor = Color.Transparent, + inactiveTickColor = Color.Transparent, + ) + ) + } + } +} From e6ee5df158e92cfe216c1165a4af451b43d56934 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 9 Jul 2024 19:07:44 +0100 Subject: [PATCH 05/57] core: update simplexmq 6.0.0.0 --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 4efa8ad2fd..dbf6fd8556 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 9d0774a58e3307892e0b38500fe96fb5d15d9def + tag: 017469b2de65c7e3ef1d680c2da466b320d1b061 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 06ecfab261..126ed9af03 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."9d0774a58e3307892e0b38500fe96fb5d15d9def" = "1g8794xspbvlsk0qdv76xjpi7ippa2l87mvkiz0ryks6j7zzcrw0"; + "https://github.com/simplex-chat/simplexmq.git"."017469b2de65c7e3ef1d680c2da466b320d1b061" = "1c6aap31sa4wdv7x771q2l8bs499qg58rrnx80blj33y2zl877c9"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 49c29c74df073baab0db3cdeefc5423d7124a5e9 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 9 Jul 2024 19:41:42 +0100 Subject: [PATCH 06/57] core: 6.0.0.0 --- package.yaml | 2 +- simplex-chat.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.yaml b/package.yaml index 0bf1c1e858..82ab5a7447 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.8.2.0 +version: 6.0.0.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 48cd8a67d4..ee154eceac 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.8.2.0 +version: 6.0.0.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From 93a4c0854e3278d92b9b4b033cc208e0d7526da2 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:28:01 +0400 Subject: [PATCH 07/57] desktop, android: show inactive and forwarded group message status; show inactive members (#4426) * multiplatform: differentiate inactive and forwarded group snd statuses; show member connection disabled/inactive * forward icon --------- Co-authored-by: Evgeny Poberezkin --- .../chat/simplex/common/model/ChatModel.kt | 52 +++++++++++++++++-- .../common/views/chat/ChatItemInfoView.kt | 25 +++------ .../views/chat/group/GroupChatInfoView.kt | 14 ++++- .../views/chat/group/GroupMemberInfoView.kt | 14 ++--- .../commonMain/resources/MR/base/strings.xml | 7 +++ .../MR/images/ic_chevron_right_2.svg | 10 ++++ .../resources/MR/images/ic_person_off.svg | 1 + 7 files changed, 94 insertions(+), 29 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right_2.svg create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index c155010a28..795baece0c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1172,18 +1172,22 @@ data class Connection( val pqSndEnabled: Boolean? = null, val pqRcvEnabled: Boolean? = null, val connectionStats: ConnectionStats? = null, - val authErrCounter: Int + val authErrCounter: Int, + val quotaErrCounter: Int ) { val id: ChatId get() = ":$connId" val connDisabled: Boolean get() = authErrCounter >= 10 // authErrDisableCount in core + val connInactive: Boolean + get() = quotaErrCounter >= 5 // quotaErrInactiveCount in core + val connPQEnabled: Boolean get() = pqSndEnabled == true && pqRcvEnabled == true companion object { - val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null, pqSupport = false, pqEncryption = false, authErrCounter = 0) + val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null, pqSupport = false, pqEncryption = false, authErrCounter = 0, quotaErrCounter = 0) } } @@ -2352,6 +2356,48 @@ enum class SndCIStatusProgress { @SerialName("complete") Complete; } +@Serializable +sealed class GroupSndStatus { + @Serializable @SerialName("new") class New: GroupSndStatus() + @Serializable @SerialName("forwarded") class Forwarded: GroupSndStatus() + @Serializable @SerialName("inactive") class Inactive: GroupSndStatus() + @Serializable @SerialName("sent") class Sent: GroupSndStatus() + @Serializable @SerialName("rcvd") class Rcvd(val msgRcptStatus: MsgReceiptStatus): GroupSndStatus() + @Serializable @SerialName("error") class Error(val agentError: SndError): GroupSndStatus() + @Serializable @SerialName("warning") class Warning(val agentError: SndError): GroupSndStatus() + @Serializable @SerialName("invalid") class Invalid(val text: String): GroupSndStatus() + + fun statusIcon( + primaryColor: Color, + metaColor: Color = CurrentColors.value.colors.secondary, + paleMetaColor: Color = CurrentColors.value.colors.secondary + ): Pair = + when (this) { + is New -> MR.images.ic_more_horiz to metaColor + is Forwarded -> MR.images.ic_chevron_right_2 to metaColor + is Inactive -> MR.images.ic_person_off to metaColor + is Sent -> MR.images.ic_check_filled to metaColor + is Rcvd -> when(this.msgRcptStatus) { + MsgReceiptStatus.Ok -> MR.images.ic_double_check to metaColor + MsgReceiptStatus.BadMsgHash -> MR.images.ic_double_check to Color.Red + } + is Error -> MR.images.ic_close to Color.Red + is Warning -> MR.images.ic_warning_filled to WarningOrange + is Invalid -> MR.images.ic_question_mark to metaColor + } + + val statusInto: Pair? get() = when (this) { + is New -> null + is Forwarded -> generalGetString(MR.strings.message_forwarded_title) to generalGetString(MR.strings.message_forwarded_desc) + is Inactive -> generalGetString(MR.strings.member_inactive_title) to generalGetString(MR.strings.member_inactive_desc) + is Sent -> null + is Rcvd -> null + is Error -> generalGetString(MR.strings.message_delivery_error_title) to agentError.errorInfo + is Warning -> generalGetString(MR.strings.message_delivery_warning_title) to agentError.errorInfo + is Invalid -> "Invalid status" to this.text + } +} + @Serializable sealed class CIDeleted { @Serializable @SerialName("deleted") class Deleted(val deletedTs: Instant?): CIDeleted() @@ -3466,7 +3512,7 @@ data class ChatItemVersion( @Serializable data class MemberDeliveryStatus( val groupMemberId: Long, - val memberDeliveryStatus: CIStatus, + val memberDeliveryStatus: GroupSndStatus, val sentViaProxy: Boolean? ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index c23a6ad00e..797d8e66b8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -330,7 +330,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools } @Composable - fun MemberDeliveryStatusView(member: GroupMember, status: CIStatus, sentViaProxy: Boolean?) { + fun MemberDeliveryStatusView(member: GroupMember, status: GroupSndStatus, sentViaProxy: Boolean?) { SectionItemView( padding = PaddingValues(horizontal = 0.dp) ) { @@ -355,7 +355,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools ) } } - val statusIcon = status.statusIcon(MaterialTheme.colors.primary, CurrentColors.value.colors.secondary) + val (icon, statusColor) = status.statusIcon(MaterialTheme.colors.primary, CurrentColors.value.colors.secondary) var modifier = Modifier.size(36.dp).clip(RoundedCornerShape(20.dp)) val info = status.statusInto if (info != null) { @@ -367,20 +367,11 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools } } Box(modifier, contentAlignment = Alignment.Center) { - if (statusIcon != null) { - val (icon, statusColor) = statusIcon - Icon( - painterResource(icon), - contentDescription = null, - tint = statusColor - ) - } else { - Icon( - painterResource(MR.images.ic_more_horiz), - contentDescription = null, - tint = CurrentColors.value.colors.secondary - ) - } + Icon( + painterResource(icon), + contentDescription = null, + tint = statusColor + ) } } } @@ -520,7 +511,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools } } -private fun membersStatuses(chatModel: ChatModel, memberDeliveryStatuses: List): List> { +private fun membersStatuses(chatModel: ChatModel, memberDeliveryStatuses: List): List> { return memberDeliveryStatuses.mapNotNull { mds -> chatModel.getGroupMember(mds.groupMemberId)?.let { mem -> Triple(mem, mds.memberDeliveryStatus, mds.sentViaProxy) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index b60c1f2496..c91bc3bcfc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -390,6 +390,16 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() - } } + fun memberConnStatus(): String { + return if (member.activeConn?.connDisabled == true) { + generalGetString(MR.strings.member_info_member_disabled) + } else if (member.activeConn?.connDisabled == true) { + generalGetString(MR.strings.member_info_member_inactive) + } else { + member.memberStatus.shortText + } + } + Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, @@ -412,8 +422,8 @@ private fun MemberRow(member: GroupMember, user: Boolean = false, onClick: (() - color = if (member.memberIncognito) Indigo else Color.Unspecified ) } - val s = member.memberStatus.shortText - val statusDescr = if (user) String.format(generalGetString(MR.strings.group_info_member_you), s) else s + val statusDescr = + if (user) String.format(generalGetString(MR.strings.group_info_member_you), member.memberStatus.shortText) else memberConnStatus() Text( statusDescr, color = MaterialTheme.colors.secondary, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 2799904b71..98b9ca729f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -358,13 +358,6 @@ fun GroupMemberInfoLayout( } else { InfoRow(stringResource(MR.strings.role_in_group), member.memberRole.text) } - val conn = member.activeConn - if (conn != null) { - val connLevelDesc = - if (conn.connLevel == 0) stringResource(MR.strings.conn_level_desc_direct) - else String.format(generalGetString(MR.strings.conn_level_desc_indirect), conn.connLevel) - InfoRow(stringResource(MR.strings.info_row_connection), connLevelDesc) - } } if (cStats != null) { SectionDividerSpaced() @@ -401,6 +394,13 @@ fun GroupMemberInfoLayout( SectionView(title = stringResource(MR.strings.section_title_for_console)) { InfoRow(stringResource(MR.strings.info_row_local_name), member.localDisplayName) InfoRow(stringResource(MR.strings.info_row_database_id), member.groupMemberId.toString()) + val conn = member.activeConn + if (conn != null) { + val connLevelDesc = + if (conn.connLevel == 0) stringResource(MR.strings.conn_level_desc_direct) + else String.format(generalGetString(MR.strings.conn_level_desc_indirect), conn.connLevel) + InfoRow(stringResource(MR.strings.info_row_connection), connLevelDesc) + } SectionItemView({ withBGApi { val info = controller.apiGroupMemberQueueInfo(rhId, groupInfo.apiId, member.groupMemberId) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 3452e6ff60..f3d78eef20 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -324,6 +324,11 @@ Forward Download + Message forwarded + Connection is not established yet, message is forwarded by group admin. + Member inactive + Message is pending delivery when member becomes active. + edited sent @@ -1461,6 +1466,8 @@ Messages from %s will be shown! Blocked by admin blocked + disabled + inactive MEMBER Role Change role diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right_2.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right_2.svg new file mode 100644 index 0000000000..ef2a9e9865 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_chevron_right_2.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg new file mode 100644 index 0000000000..a583950023 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_person_off.svg @@ -0,0 +1 @@ + \ No newline at end of file From c4f8a50f0d60df9ad2556439e468841802762a8f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 10 Jul 2024 00:52:33 +0400 Subject: [PATCH 08/57] ios: show inactive and forwarded group message status; show inactive members (#4423) * ios: differentiate inactive and forwarded group snd statuses; show member connection disabled/inactive * lazy * simplify * Update apps/ios/SimpleXChat/ChatTypes.swift * export localizations, update strings --------- Co-authored-by: Evgeny Poberezkin --- .../Shared/Views/Chat/ChatItemInfoView.swift | 32 ++++----- .../Views/Chat/Group/GroupChatInfoView.swift | 13 +++- .../Chat/Group/GroupMemberInfoView.swift | 10 ++- .../bg.xcloc/Localized Contents/bg.xliff | 20 ++++++ .../cs.xcloc/Localized Contents/cs.xliff | 20 ++++++ .../de.xcloc/Localized Contents/de.xliff | 20 ++++++ .../en.xcloc/Localized Contents/en.xliff | 25 +++++++ .../es.xcloc/Localized Contents/es.xliff | 20 ++++++ .../fi.xcloc/Localized Contents/fi.xliff | 20 ++++++ .../fr.xcloc/Localized Contents/fr.xliff | 20 ++++++ .../hu.xcloc/Localized Contents/hu.xliff | 20 ++++++ .../it.xcloc/Localized Contents/it.xliff | 20 ++++++ .../ja.xcloc/Localized Contents/ja.xliff | 20 ++++++ .../nl.xcloc/Localized Contents/nl.xliff | 20 ++++++ .../pl.xcloc/Localized Contents/pl.xliff | 20 ++++++ .../ru.xcloc/Localized Contents/ru.xliff | 20 ++++++ .../th.xcloc/Localized Contents/th.xliff | 20 ++++++ .../tr.xcloc/Localized Contents/tr.xliff | 20 ++++++ .../uk.xcloc/Localized Contents/uk.xliff | 20 ++++++ .../Localized Contents/zh-Hans.xliff | 20 ++++++ apps/ios/SimpleXChat/ChatTypes.swift | 68 ++++++++++++++++++- .../commonMain/resources/MR/base/strings.xml | 4 +- 22 files changed, 441 insertions(+), 31 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift index 3312833ccc..a46cf03bdf 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemInfoView.swift @@ -415,7 +415,7 @@ struct ChatItemInfoView: View { } @ViewBuilder private func memberDeliveryStatusesView(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> some View { - VStack(alignment: .leading, spacing: 12) { + LazyVStack(alignment: .leading, spacing: 12) { let mss = membersStatuses(memberDeliveryStatuses) if !mss.isEmpty { ForEach(mss, id: \.0.groupMemberId) { memberStatus in @@ -428,7 +428,7 @@ struct ChatItemInfoView: View { } } - private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, CIStatus, Bool?)] { + private func membersStatuses(_ memberDeliveryStatuses: [MemberDeliveryStatus]) -> [(GroupMember, GroupSndStatus, Bool?)] { memberDeliveryStatuses.compactMap({ mds in if let mem = chatModel.getGroupMember(mds.groupMemberId) { return (mem.wrapped, mds.memberDeliveryStatus, mds.sentViaProxy) @@ -438,7 +438,7 @@ struct ChatItemInfoView: View { }) } - private func memberDeliveryStatusView(_ member: GroupMember, _ status: CIStatus, _ sentViaProxy: Bool?) -> some View { + private func memberDeliveryStatusView(_ member: GroupMember, _ status: GroupSndStatus, _ sentViaProxy: Bool?) -> some View { HStack{ ProfileImage(imageStr: member.image, size: 30) .padding(.trailing, 2) @@ -450,23 +450,19 @@ struct ChatItemInfoView: View { .foregroundColor(theme.colors.secondary).opacity(0.67) } let v = Group { - if let (icon, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) { - switch status { - case .sndRcvd: - ZStack(alignment: .trailing) { - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - .padding(.trailing, 6) - Image(systemName: icon) - .foregroundColor(statusColor.opacity(0.67)) - } - default: + let (icon, statusColor) = status.statusIcon(theme.colors.secondary, theme.colors.primary) + switch status { + case .rcvd: + ZStack(alignment: .trailing) { Image(systemName: icon) - .foregroundColor(statusColor) + .foregroundColor(statusColor.opacity(0.67)) + .padding(.trailing, 6) + Image(systemName: icon) + .foregroundColor(statusColor.opacity(0.67)) } - } else { - Image(systemName: "ellipsis") - .foregroundColor(Color.secondary) + default: + Image(systemName: icon) + .foregroundColor(statusColor) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index 8fff94871e..eabf7ad8c9 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -232,8 +232,7 @@ struct GroupChatInfoView: View { let t = Text(member.chatViewName).foregroundColor(member.memberIncognito ? .indigo : theme.colors.onBackground) (member.verified ? memberVerifiedShield + t : t) .lineLimit(1) - let s = Text(member.memberStatus.shortText) - (user ? Text ("you: ") + s : s) + (user ? Text ("you: ") + Text(member.memberStatus.shortText) : Text(memberConnStatus(member))) .lineLimit(1) .font(.caption) .foregroundColor(theme.colors.secondary) @@ -266,6 +265,16 @@ struct GroupChatInfoView: View { } } + private func memberConnStatus(_ member: GroupMember) -> LocalizedStringKey { + if member.activeConn?.connDisabled ?? false { + return "disabled" + } else if member.activeConn?.connInactive ?? false { + return "inactive" + } else { + return member.memberStatus.shortText + } + } + @ViewBuilder private func memberInfo(_ member: GroupMember) -> some View { if member.blocked { Text("blocked") diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index deabf1137e..c04ac6ead9 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -141,12 +141,6 @@ struct GroupMemberInfoView: View { } else { infoRow("Role", member.memberRole.text) } - - // TODO invited by - need to get contact by contact id - if let conn = member.activeConn { - let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) - infoRow("Connection", connLevelDesc) - } } if let connStats = connectionStats { @@ -183,6 +177,10 @@ struct GroupMemberInfoView: View { Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { infoRow("Local name", member.localDisplayName) infoRow("Database ID", "\(member.groupMemberId)") + if let conn = member.activeConn { + let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) + infoRow("Connection", connLevelDesc) + } Button ("Debug delivery") { Task { do { diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 3d2a5ffb59..7ac1db1557 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -3857,6 +3857,10 @@ This is your link for group %@! Член No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Ролята на члена ще бъде променена на "%@". Всички членове на групата ще бъдат уведомени. @@ -3895,6 +3899,14 @@ This is your link for group %@! Чернова на съобщение No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4206,6 +4218,10 @@ This is your link for group %@! Няма токен за устройство! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Няма филтрирани чатове @@ -7762,6 +7778,10 @@ SimpleX сървърите не могат да видят вашия профи iOS Keychain ще се използва за сигурно съхраняване на паролата, след като рестартирате приложението или промените паролата - това ще позволи получаването на push известия. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link инкогнито чрез линк с адрес за контакт diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 7feb1a7b27..392b9cf70d 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -3714,6 +3714,10 @@ This is your link for group %@! Člen No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Role člena se změní na "%@". Všichni členové skupiny budou upozorněni. @@ -3752,6 +3756,14 @@ This is your link for group %@! Návrh zprávy No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4047,6 +4059,10 @@ This is your link for group %@! Žádný token zařízení! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Žádné filtrované chaty @@ -7477,6 +7493,10 @@ Servery SimpleX nevidí váš profil. Klíčenka pro iOS bude použita k bezpečnému uložení přístupové fráze po restartování aplikace nebo změně přístupové fráze – umožní příjem oznámení push. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link inkognito přes odkaz na kontaktní adresu diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index a33fb400c7..cc2773d5e4 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -3869,6 +3869,10 @@ Das ist Ihr Link für die Gruppe %@! Mitglied No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Die Mitgliederrolle wird auf "%@" geändert. Alle Mitglieder der Gruppe werden benachrichtigt. @@ -3908,6 +3912,14 @@ Das ist Ihr Link für die Gruppe %@! Nachrichtenentwurf No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ Das ist Ihr Link für die Gruppe %@! Kein Geräte-Token! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Keine gefilterten Chats @@ -7799,6 +7815,10 @@ SimpleX-Server können Ihr Profil nicht einsehen. Für die sichere Speicherung des Passworts nach dem Neustart der App und dem Wechsel des Passworts wird der iOS Schlüsselbund verwendet - dies erlaubt den Empfang von Push-Benachrichtigungen. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link Inkognito über einen Kontaktadressen-Link diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 30ee4a179a..08e1255199 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -3923,6 +3923,11 @@ This is your link for group %@! Member No comment provided by engineer. + + Member inactive + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Member role will be changed to "%@". All group members will be notified. @@ -3963,6 +3968,16 @@ This is your link for group %@! Message draft No comment provided by engineer. + + Message forwarded + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + Message may be delivered later if member becomes active. + item status description + Message queue info Message queue info @@ -4283,6 +4298,11 @@ This is your link for group %@! No device token! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats No filtered chats @@ -7927,6 +7947,11 @@ SimpleX servers cannot see your profile. iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications. No comment provided by engineer. + + inactive + inactive + No comment provided by engineer. + incognito via contact address link incognito via contact address link diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 7a2e73ae39..3e37e6c6d5 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -3869,6 +3869,10 @@ This is your link for group %@! Miembro No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. El rol del miembro cambiará a "%@" y se notificará al grupo. @@ -3908,6 +3912,14 @@ This is your link for group %@! Borrador de mensaje No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ This is your link for group %@! ¡Sin dispositivo token! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Sin chats filtrados @@ -7800,6 +7816,10 @@ Los servidores de SimpleX no pueden ver tu perfil. iOS Keychain se usará para almacenar la contraseña de forma segura después de reiniciar la aplicación o cambiar la contraseña. Esto permitirá recibir notificaciones automáticas. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link en modo incógnito mediante enlace de dirección del contacto diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 552af207a7..71e0f0796a 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -3704,6 +3704,10 @@ This is your link for group %@! Jäsen No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Jäsenen rooli muuttuu muotoon "%@". Kaikille ryhmän jäsenille ilmoitetaan asiasta. @@ -3742,6 +3746,14 @@ This is your link for group %@! Viestiluonnos No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4036,6 +4048,10 @@ This is your link for group %@! Ei laitetunnusta! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Ei suodatettuja keskusteluja @@ -7462,6 +7478,10 @@ SimpleX-palvelimet eivät näe profiiliasi. iOS-Avainnippua käytetään tunnuslauseen turvalliseen tallentamiseen sen muuttamisen tai sovelluksen uudelleen käynnistämisen jälkeen - se mahdollistaa push-ilmoitusten vastaanottamisen. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link incognito kontaktilinkin kautta diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 39bb04a814..4f618f26b0 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -3869,6 +3869,10 @@ Voici votre lien pour le groupe %@ ! Membre No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Le rôle du membre sera changé pour "%@". Tous les membres du groupe en seront informés. @@ -3908,6 +3912,14 @@ Voici votre lien pour le groupe %@ ! Brouillon de message No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ Voici votre lien pour le groupe %@ ! Pas de token d'appareil ! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Aucune discussion filtrés @@ -7799,6 +7815,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. La keychain d'iOS sera utilisée pour stocker en toute sécurité la phrase secrète après le redémarrage de l'app ou la modification de la phrase secrète - il permettra de recevoir les notifications push. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link mode incognito via le lien d'adresse du contact diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index f3374f2efb..ecc0610115 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -3869,6 +3869,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Tag No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. A tag szerepköre meg fog változni erre: "%@". A csoport minden tagja értesítést kap róla. @@ -3908,6 +3912,14 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Üzenetvázlat No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Nincs eszköztoken! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Nincsenek szűrt csevegések @@ -7799,6 +7815,10 @@ A SimpleX kiszolgálók nem látjhatják profilját. Az iOS kulcstár az alkalmazás újraindítása, vagy a jelmondat módosítása után a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link inkognitó a kapcsolattartási hivatkozáson keresztül diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 2929cc41dc..8d311a803c 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -3869,6 +3869,10 @@ Questo è il tuo link per il gruppo %@! Membro No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Il ruolo del membro verrà cambiato in "%@". Tutti i membri del gruppo verranno avvisati. @@ -3908,6 +3912,14 @@ Questo è il tuo link per il gruppo %@! Bozza dei messaggi No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ Questo è il tuo link per il gruppo %@! Nessun token del dispositivo! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Nessuna chat filtrata @@ -7799,6 +7815,10 @@ I server di SimpleX non possono vedere il tuo profilo. Il portachiavi di iOS verrà usato per archiviare in modo sicuro la password dopo il riavvio dell'app o la modifica della password; consentirà di ricevere notifiche push. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link incognito via link indirizzo del contatto diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 929a6692bc..43f0f8f6cd 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -3729,6 +3729,10 @@ This is your link for group %@! メンバー No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. メンバーの役割が "%@" に変更されます。 グループメンバー全員に通知されます。 @@ -3766,6 +3770,14 @@ This is your link for group %@! メッセージの下書き No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4061,6 +4073,10 @@ This is your link for group %@! デバイストークンがありません! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats フィルタされたチャットはありません @@ -7480,6 +7496,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 iOS キーチェーンは、アプリを再起動するかパスフレーズを変更した後にパスフレーズを安全に保存するために使用され、プッシュ通知を受信できるようになります。 No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link 連絡先リンク経由でシークレットモード diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 129dda6187..fdfd49c3d3 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -3869,6 +3869,10 @@ Dit is jouw link voor groep %@! Lid No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. De rol van lid wordt gewijzigd in "%@". Alle groepsleden worden op de hoogte gebracht. @@ -3908,6 +3912,14 @@ Dit is jouw link voor groep %@! Concept bericht No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ Dit is jouw link voor groep %@! Geen apparaattoken! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Geen gefilterde gesprekken @@ -7799,6 +7815,10 @@ SimpleX servers kunnen uw profiel niet zien. iOS-keychain wordt gebruikt om het wachtwoord veilig op te slaan nadat u de app opnieuw hebt opgestart of het wachtwoord hebt gewijzigd, hiermee kunt u push meldingen ontvangen. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link incognito via contact adres link diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 31a6874e77..9da2b6bd82 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -3869,6 +3869,10 @@ To jest twój link do grupy %@! Członek No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Rola członka grupy zostanie zmieniona na "%@". Wszyscy członkowie grupy zostaną powiadomieni. @@ -3908,6 +3912,14 @@ To jest twój link do grupy %@! Wersja robocza wiadomości No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ To jest twój link do grupy %@! Brak tokenu urządzenia! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Brak filtrowanych czatów @@ -7799,6 +7815,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. iOS Keychain będzie używany do bezpiecznego przechowywania hasła po ponownym uruchomieniu aplikacji lub zmianie hasła - pozwoli to na otrzymywanie powiadomień push. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link incognito poprzez link adresu kontaktowego diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index 0ae6e7ae24..db7f5bfb9e 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -3869,6 +3869,10 @@ This is your link for group %@! Член группы No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Роль члена группы будет изменена на "%@". Все члены группы получат сообщение. @@ -3908,6 +3912,14 @@ This is your link for group %@! Черновик сообщения No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ This is your link for group %@! Отсутствует токен устройства! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Нет отфильтрованных разговоров @@ -7799,6 +7815,10 @@ SimpleX серверы не могут получить доступ к Ваше Пароль базы данных будет безопасно сохранен в iOS Keychain после запуска чата или изменения пароля - это позволит получать мгновенные уведомления. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link инкогнито через ссылку-контакт diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index bf2d6b8bed..a71061d578 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -3687,6 +3687,10 @@ This is your link for group %@! สมาชิก No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. บทบาทของสมาชิกจะถูกเปลี่ยนเป็น "%@" สมาชิกกลุ่มทั้งหมดจะได้รับแจ้ง @@ -3725,6 +3729,14 @@ This is your link for group %@! ร่างข้อความ No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4017,6 +4029,10 @@ This is your link for group %@! ไม่มีโทเค็นอุปกรณ์! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats ไม่มีการกรองการแชท @@ -7430,6 +7446,10 @@ SimpleX servers cannot see your profile. iOS Keychain จะใช้เพื่อจัดเก็บรหัสผ่านอย่างปลอดภัยหลังจากที่คุณรีสตาร์ทแอปหรือเปลี่ยนรหัสผ่าน ซึ่งจะช่วยให้รับการแจ้งเตือนแบบทันทีได้ No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link ไม่ระบุตัวตนผ่านลิงค์ที่อยู่ติดต่อ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 482c3a038f..66f24d5bb7 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -3869,6 +3869,10 @@ Bu senin grup için bağlantın %@! Kişi No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Üye rolü "%@" olarak değiştirilecektir. Ve tüm grup üyeleri bilgilendirilecektir. @@ -3908,6 +3912,14 @@ Bu senin grup için bağlantın %@! Mesaj taslağı No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ Bu senin grup için bağlantın %@! Cihaz tokeni yok! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Filtrelenmiş sohbetler yok @@ -7799,6 +7815,10 @@ SimpleX sunucuları profilinizi göremez. iOS Anahtar Zinciri, uygulamayı yeniden başlattıktan veya parolayı değiştirdikten sonra parolayı güvenli bir şekilde saklamak için kullanılacaktır - anlık bildirimlerin alınmasına izin verecektir. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link kişi bağlantı linki aracılığıyla gizli diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 7e19da7cbd..bf0b5718f7 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -3869,6 +3869,10 @@ This is your link for group %@! Учасник No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. Роль учасника буде змінено на "%@". Всі учасники групи будуть повідомлені про це. @@ -3908,6 +3912,14 @@ This is your link for group %@! Чернетка повідомлення No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4222,6 +4234,10 @@ This is your link for group %@! Токен пристрою відсутній! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats Немає фільтрованих чатів @@ -7799,6 +7815,10 @@ SimpleX servers cannot see your profile. Пароль бази даних буде безпечно збережено в iOS Keychain після запуску чату або зміни пароля - це дасть змогу отримувати миттєві повідомлення. No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link інкогніто за посиланням на контактну адресу diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 2ba735b08d..5d1852b0b4 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -3819,6 +3819,10 @@ This is your link for group %@! 成员 No comment provided by engineer. + + Member inactive + item status text + Member role will be changed to "%@". All group members will be notified. 成员角色将更改为 "%@"。所有群成员将收到通知。 @@ -3857,6 +3861,14 @@ This is your link for group %@! 消息草稿 No comment provided by engineer. + + Message forwarded + item status text + + + Message may be delivered later if member becomes active. + item status description + Message queue info No comment provided by engineer. @@ -4165,6 +4177,10 @@ This is your link for group %@! 无设备令牌! No comment provided by engineer. + + No direct connection yet, message is forwarded by admin. + item status description + No filtered chats 无过滤聊天 @@ -7696,6 +7712,10 @@ SimpleX 服务器无法看到您的资料。 在您重启应用或改变密码后,iOS钥匙串将被用来安全地存储密码——它将允许接收推送通知。 No comment provided by engineer. + + inactive + No comment provided by engineer. + incognito via contact address link 通过联系人地址链接隐身聊天 diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index eb92deb4e9..b84e4bb3a0 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1611,11 +1611,12 @@ public struct Connection: Decodable, Hashable { public var pqSndEnabled: Bool? public var pqRcvEnabled: Bool? public var authErrCounter: Int + public var quotaErrCounter: Int public var connectionStats: ConnectionStats? = nil private enum CodingKeys: String, CodingKey { - case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter + case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, quotaErrCounter } public var id: ChatId { get { ":\(connId)" } } @@ -1624,6 +1625,10 @@ public struct Connection: Decodable, Hashable { authErrCounter >= 10 // authErrDisableCount in core } + public var connInactive: Bool { + quotaErrCounter >= 5 // quotaErrInactiveCount in core + } + public var connPQEnabled: Bool { pqSndEnabled == true && pqRcvEnabled == true } @@ -1637,7 +1642,8 @@ public struct Connection: Decodable, Hashable { viaGroupLink: false, pqSupport: false, pqEncryption: false, - authErrCounter: 0 + authErrCounter: 0, + quotaErrCounter: 0 ) } @@ -2846,6 +2852,62 @@ public enum SndCIStatusProgress: String, Decodable, Hashable { case complete } +public enum GroupSndStatus: Decodable, Hashable { + case new + case forwarded + case inactive + case sent + case rcvd(msgRcptStatus: MsgReceiptStatus) + case error(agentError: SndError) + case warning(agentError: SndError) + case invalid(text: String) + + public func statusIcon(_ metaColor: Color/* = .secondary*/, _ primaryColor: Color = .accentColor) -> (String, Color) { + switch self { + case .new: return ("ellipsis", metaColor) + case .forwarded: return ("chevron.forward.2", metaColor) + case .inactive: return ("person.badge.minus", metaColor) + case .sent: return ("checkmark", metaColor) + case let .rcvd(msgRcptStatus): + switch msgRcptStatus { + case .ok: return ("checkmark", metaColor) + case .badMsgHash: return ("checkmark", .red) + } + case .error: return ("multiply", .red) + case .warning: return ("exclamationmark.triangle.fill", .orange) + case .invalid: return ("questionmark", metaColor) + } + } + + public var statusInfo: (String, String)? { + switch self { + case .new: return nil + case .forwarded: return ( + NSLocalizedString("Message forwarded", comment: "item status text"), + NSLocalizedString("No direct connection yet, message is forwarded by admin.", comment: "item status description") + ) + case .inactive: return ( + NSLocalizedString("Member inactive", comment: "item status text"), + NSLocalizedString("Message may be delivered later if member becomes active.", comment: "item status description") + ) + case .sent: return nil + case .rcvd: return nil + case let .error(agentError): return ( + NSLocalizedString("Message delivery error", comment: "item status text"), + agentError.errorInfo + ) + case let .warning(agentError): return ( + NSLocalizedString("Message delivery warning", comment: "item status text"), + agentError.errorInfo + ) + case let .invalid(text): return ( + NSLocalizedString("Invalid status", comment: "item status text"), + text + ) + } + } +} + public enum CIDeleted: Decodable, Hashable { case deleted(deletedTs: Date?) case blocked(deletedTs: Date?) @@ -4012,6 +4074,6 @@ public struct ChatItemVersion: Decodable, Hashable { public struct MemberDeliveryStatus: Decodable, Hashable { public var groupMemberId: Int64 - public var memberDeliveryStatus: CIStatus + public var memberDeliveryStatus: GroupSndStatus public var sentViaProxy: Bool? } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index f3d78eef20..e2d10ae5ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -325,9 +325,9 @@ Download Message forwarded - Connection is not established yet, message is forwarded by group admin. + No direct connection yet, message is forwarded by admin. Member inactive - Message is pending delivery when member becomes active. + Message may be delivered later if member becomes active. edited From 64a0f509f72979f5797119cc9bf34d6e1dd91d7e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 9 Jul 2024 22:38:08 +0100 Subject: [PATCH 09/57] 6.0-beta.0: ios 227, android 225, desktop 56 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 72 ++++++++++++---------- apps/multiplatform/gradle.properties | 8 +-- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 1c5c032e11..9bd72ff5a9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -203,11 +203,12 @@ D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; - E52FF8DA2C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E52FF8D52C34676600BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7-ghc9.6.3.a */; }; - E52FF8DB2C34676700BF81EB /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E52FF8D62C34676600BF81EB /* libgmpxx.a */; }; - E52FF8DC2C34676700BF81EB /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E52FF8D72C34676700BF81EB /* libffi.a */; }; - E52FF8DD2C34676700BF81EB /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E52FF8D82C34676700BF81EB /* libgmp.a */; }; - E52FF8DE2C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E52FF8D92C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7.a */; }; + E50581002C3DDD7F009C3F71 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FB2C3DDD7F009C3F71 /* libffi.a */; }; + E50581012C3DDD7F009C3F71 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FC2C3DDD7F009C3F71 /* libgmp.a */; }; + E50581022C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FD2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a */; }; + E50581032C3DDD7F009C3F71 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FE2C3DDD7F009C3F71 /* libgmpxx.a */; }; + E50581042C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FF2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a */; }; + E50581062C3DDD9D009C3F71 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = E50581052C3DDD9D009C3F71 /* Yams */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -508,11 +509,11 @@ D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - E52FF8D52C34676600BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7-ghc9.6.3.a"; sourceTree = ""; }; - E52FF8D62C34676600BF81EB /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E52FF8D72C34676700BF81EB /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E52FF8D82C34676700BF81EB /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E52FF8D92C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7.a"; sourceTree = ""; }; + E50580FB2C3DDD7F009C3F71 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E50580FC2C3DDD7F009C3F71 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + E50580FD2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a"; sourceTree = ""; }; + E50580FE2C3DDD7F009C3F71 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E50580FF2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -551,13 +552,14 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E52FF8DE2C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7.a in Frameworks */, - E52FF8DB2C34676700BF81EB /* libgmpxx.a in Frameworks */, + E50581032C3DDD7F009C3F71 /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + E50581002C3DDD7F009C3F71 /* libffi.a in Frameworks */, + E50581012C3DDD7F009C3F71 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E52FF8DC2C34676700BF81EB /* libffi.a in Frameworks */, - E52FF8DD2C34676700BF81EB /* libgmp.a in Frameworks */, - E52FF8DA2C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7-ghc9.6.3.a in Frameworks */, + E50581022C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a in Frameworks */, + E50581062C3DDD9D009C3F71 /* Yams in Frameworks */, + E50581042C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -624,11 +626,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E52FF8D72C34676700BF81EB /* libffi.a */, - E52FF8D82C34676700BF81EB /* libgmp.a */, - E52FF8D62C34676600BF81EB /* libgmpxx.a */, - E52FF8D52C34676600BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7-ghc9.6.3.a */, - E52FF8D92C34676700BF81EB /* libHSsimplex-chat-5.8.2.0-D50x9PQRTdqAAO9AQK2dA7.a */, + E50580FB2C3DDD7F009C3F71 /* libffi.a */, + E50580FC2C3DDD7F009C3F71 /* libgmp.a */, + E50580FE2C3DDD7F009C3F71 /* libgmpxx.a */, + E50580FD2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a */, + E50580FF2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a */, ); path = Libraries; sourceTree = ""; @@ -1065,6 +1067,7 @@ ); name = SimpleXChat; packageProductDependencies = ( + E50581052C3DDD9D009C3F71 /* Yams */, ); productName = SimpleXChat; productReference = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; @@ -1612,7 +1615,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 226; + CURRENT_PROJECT_VERSION = 227; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1637,7 +1640,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES_THIN; - MARKETING_VERSION = 5.8.2; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1661,7 +1664,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 226; + CURRENT_PROJECT_VERSION = 227; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1686,7 +1689,7 @@ "@executable_path/Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.2; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1747,7 +1750,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 226; + CURRENT_PROJECT_VERSION = 227; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -1762,7 +1765,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.2; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1784,7 +1787,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 226; + CURRENT_PROJECT_VERSION = 227; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -1799,7 +1802,7 @@ "@executable_path/../../Frameworks", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.2; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1821,7 +1824,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 226; + CURRENT_PROJECT_VERSION = 227; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1847,7 +1850,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.2; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -1872,7 +1875,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 226; + CURRENT_PROJECT_VERSION = 227; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1898,7 +1901,7 @@ "$(PROJECT_DIR)/Libraries/sim", ); LLVM_LTO = YES; - MARKETING_VERSION = 5.8.2; + MARKETING_VERSION = 6.0; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -2035,6 +2038,11 @@ package = D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */; productName = LZString; }; + E50581052C3DDD9D009C3F71 /* Yams */ = { + isa = XCSwiftPackageProductDependency; + package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */; + productName = Yams; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5CA059BE279559F40002BEB4 /* Project object */; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index d87b70cc56..2923c39742 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=5.8.2 -android.version_code=223 +android.version_name=6.0-beta.0 +android.version_code=225 -desktop.version_name=5.8.2 -desktop.version_code=55 +desktop.version_name=6.0-beta.0 +desktop.version_code=56 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From a8da9b9cd999e99361ad056ba0bc466efd1a9e4f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:52:04 +0400 Subject: [PATCH 10/57] core: fix invitee sending redundant pending messages whey they should be forwarded (#4430) --- src/Simplex/Chat.hs | 54 ++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 786f065ae0..9198a4c9d9 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -5993,14 +5993,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateGroupMemberStatusById db userId groupMemberId GSMemIntroInvited xGrpMemInv :: GroupInfo -> GroupMember -> MemberId -> IntroInvitation -> CM () - xGrpMemInv gInfo@GroupInfo {groupId} m memId introInv = do + xGrpMemInv gInfo m memId introInv = do case memberCategory m of GCInviteeMember -> withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Left _ -> messageError "x.grp.mem.inv error: referenced member does not exist" Right reMember -> do GroupMemberIntro {introId} <- withStore $ \db -> saveIntroInvitation db reMember m introInv - sendGroupMemberMessage user reMember (XGrpMemFwd (memberInfo m) introInv) groupId (Just introId) $ + sendGroupMemberMessage user gInfo reMember (XGrpMemFwd (memberInfo m) introInv) (Just introId) $ withStore' $ \db -> updateIntroStatus db introId GMIntroInvForwarded _ -> messageError "x.grp.mem.inv can be only sent by invitee member" @@ -6812,7 +6812,7 @@ data GroupSndResult = GroupSndResult } sendGroupMessage' :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResult) -sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do +sendGroupMessage' user gInfo@GroupInfo {groupId} members chatMsgEvent = do msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId) recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) let msgFlags = MsgFlags {notification = hasNotification $ toCMEventTag chatMsgEvent} @@ -6838,7 +6838,7 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do liftM2 (<>) (shuffle adminMs) (shuffle otherMs) where isAdmin GroupMember {memberRole} = memberRole >= GRAdmin - addMember m acc@(toSend, pending, forwarded, !mIds, !dups) = case memberSendAction chatMsgEvent members m of + addMember m acc@(toSend, pending, forwarded, !mIds, !dups) = case memberSendAction gInfo chatMsgEvent members m of Just a | mId `S.member` mIds -> (toSend, pending, forwarded, mIds, dups + 1) | otherwise -> case a of @@ -6854,8 +6854,8 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do data MemberSendAction = MSASend Connection | MSAPending | MSAForwarded -memberSendAction :: ChatMsgEvent e -> [GroupMember] -> GroupMember -> Maybe MemberSendAction -memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = case memberConn m of +memberSendAction :: GroupInfo -> ChatMsgEvent e -> [GroupMember] -> GroupMember -> Maybe MemberSendAction +memberSendAction gInfo chatMsgEvent members m = case memberConn m of Nothing -> pendingOrForwarded Just conn@Connection {connStatus} | connDisabled conn || connStatus == ConnDeleted -> Nothing @@ -6863,30 +6863,38 @@ memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = c | connStatus == ConnSndReady || connStatus == ConnReady -> Just (MSASend conn) | otherwise -> pendingOrForwarded where - pendingOrForwarded - | forwardSupported && isForwardedGroupMsg chatMsgEvent = Just MSAForwarded - | isXGrpMsgForward chatMsgEvent = Nothing - | otherwise = Just MSAPending + pendingOrForwarded = case memberCategory m of + GCUserMember -> Nothing -- shouldn't happen + GCInviteeMember -> Just MSAPending + GCHostMember -> Just MSAPending + GCPreMember -> forwardSupportedOrPending (invitedByGroupMemberId $ membership gInfo) + GCPostMember -> forwardSupportedOrPending (invitedByGroupMemberId m) where - forwardSupported = m `supportsVersion` groupForwardVersion && invitingMemberSupportsForward - invitingMemberSupportsForward = case invitedByGroupMemberId of - Just invMemberId -> - -- can be optimized for large groups by replacing [GroupMember] with Map GroupMemberId GroupMember - case find (\m' -> groupMemberId' m' == invMemberId) members of - Just invitingMember -> invitingMember `supportsVersion` groupForwardVersion + forwardSupportedOrPending invitingMemberId_ + | membersSupport && isForwardedGroupMsg chatMsgEvent = Just MSAForwarded + | isXGrpMsgForward = Nothing + | otherwise = Just MSAPending + where + membersSupport = + m `supportsVersion` groupForwardVersion && invitingMemberSupportsForward + invitingMemberSupportsForward = case invitingMemberId_ of + Just invMemberId -> + -- can be optimized for large groups by replacing [GroupMember] with Map GroupMemberId GroupMember + case find (\m' -> groupMemberId' m' == invMemberId) members of + Just invitingMember -> invitingMember `supportsVersion` groupForwardVersion + Nothing -> False Nothing -> False - Nothing -> False - isXGrpMsgForward ev = case ev of - XGrpMsgForward {} -> True - _ -> False + isXGrpMsgForward = case chatMsgEvent of + XGrpMsgForward {} -> True + _ -> False -sendGroupMemberMessage :: MsgEncodingI e => User -> GroupMember -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> CM () -> CM () -sendGroupMemberMessage user m@GroupMember {groupMemberId} chatMsgEvent groupId introId_ postDeliver = do +sendGroupMemberMessage :: MsgEncodingI e => User -> GroupInfo -> GroupMember -> ChatMsgEvent e -> Maybe Int64 -> CM () -> CM () +sendGroupMemberMessage user gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent introId_ postDeliver = do msg <- createSndMessage chatMsgEvent (GroupId groupId) messageMember msg `catchChatError` (\e -> toView (CRChatError (Just user) e)) where messageMember :: SndMessage -> CM () - messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction chatMsgEvent [m] m) $ \case + messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction gInfo chatMsgEvent [m] m) $ \case MSASend conn -> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId >> postDeliver MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ MSAForwarded -> pure () From f41c04735ba84d573e5d13ed849a567fea064dfa Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:15:14 +0400 Subject: [PATCH 11/57] ios: lookup group members via map (#4431) --- apps/ios/Shared/Model/ChatModel.swift | 20 ++++++++++++++++--- apps/ios/Shared/Views/Chat/ChatView.swift | 2 ++ .../Views/Chat/Group/GroupChatInfoView.swift | 1 + .../Shared/Views/NewChat/AddGroupView.swift | 1 + 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 141e6d5c42..262c4a4b48 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -73,6 +73,7 @@ final class ChatModel: ObservableObject { var chatItemStatuses: Dictionary = [:] @Published var chatToTop: String? @Published var groupMembers: [GMember] = [] + @Published var groupMembersIndexes: Dictionary = [:] // groupMemberId to index in groupMembers list // items in the terminal view @Published var showingTerminal = false @Published var terminalItems: [TerminalItem] = [] @@ -180,8 +181,18 @@ final class ChatModel: ObservableObject { } } + func populateGroupMembersIndexes() { + groupMembersIndexes.removeAll() + for (i, member) in groupMembers.enumerated() { + groupMembersIndexes[member.groupMemberId] = i + } + } + func getGroupMember(_ groupMemberId: Int64) -> GMember? { - groupMembers.first { $0.groupMemberId == groupMemberId } + if let i = groupMembersIndexes[groupMemberId] { + return groupMembers[i] + } + return nil } private func getChatIndex(_ id: String) -> Int? { @@ -667,14 +678,17 @@ final class ChatModel: ObservableObject { } // update current chat if chatId == groupInfo.id { - if let i = groupMembers.firstIndex(where: { $0.groupMemberId == member.groupMemberId }) { + if let i = groupMembersIndexes[member.groupMemberId] { withAnimation(.default) { self.groupMembers[i].wrapped = member self.groupMembers[i].created = Date.now } return false } else { - withAnimation { groupMembers.append(GMember(member)) } + withAnimation { + groupMembers.append(GMember(member)) + groupMembersIndexes[member.groupMemberId] = groupMembers.count - 1 + } return true } } else { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index d0e73adccd..7e302d67d1 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -121,6 +121,7 @@ struct ChatView: View { chatModel.chatItemStatuses = [:] chatModel.reversedChatItems = [] chatModel.groupMembers = [] + chatModel.groupMembersIndexes.removeAll() membersLoaded = false } } @@ -255,6 +256,7 @@ struct ChatView: View { await MainActor.run { if chatModel.chatId == groupInfo.id { chatModel.groupMembers = groupMembers.map { GMember.init($0) } + chatModel.populateGroupMembersIndexes() membersLoaded = true updateView() } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index eabf7ad8c9..59a21d2330 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -207,6 +207,7 @@ struct GroupChatInfoView: View { let groupMembers = await apiListMembers(groupInfo.groupId) await MainActor.run { chatModel.groupMembers = groupMembers.map { GMember.init($0) } + chatModel.populateGroupMembersIndexes() } } } diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift index 5c8a88bd8f..42428dfb55 100644 --- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift +++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift @@ -194,6 +194,7 @@ struct AddGroupView: View { let groupMembers = await apiListMembers(gInfo.groupId) await MainActor.run { m.groupMembers = groupMembers.map { GMember.init($0) } + m.populateGroupMembersIndexes() } } let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: []) From f84ac713d7538588e9da3f89e67bd2efabfc3ab8 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:50:23 +0400 Subject: [PATCH 12/57] android: lookup group members via map (#4432) --- .../chat/simplex/common/model/ChatModel.kt | 24 ++++++++++++++++--- .../simplex/common/views/chat/ChatView.kt | 1 + .../views/chatlist/ChatListNavLinkView.kt | 2 ++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 795baece0c..5e2cf9481c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -65,6 +65,7 @@ object ChatModel { val deletedChats = mutableStateOf>>(emptyList()) val chatItemStatuses = mutableMapOf() val groupMembers = mutableStateListOf() + val groupMembersIndexes = mutableStateMapOf() val terminalItems = mutableStateOf>(listOf()) val userAddress = mutableStateOf(null) @@ -170,7 +171,23 @@ object ChatModel { fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id } fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId } fun getGroupChat(groupId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Group && it.chatInfo.apiId == groupId } - fun getGroupMember(groupMemberId: Long): GroupMember? = groupMembers.firstOrNull { it.groupMemberId == groupMemberId } + + fun populateGroupMembersIndexes() { + groupMembersIndexes.clear() + groupMembers.forEachIndexed { i, member -> + groupMembersIndexes[member.groupMemberId] = i + } + } + + fun getGroupMember(groupMemberId: Long): GroupMember? { + val memberIndex = groupMembersIndexes[groupMemberId] + return if (memberIndex != null) { + groupMembers[memberIndex] + } else { + null + } + } + private fun getChatIndex(rhId: Long?, id: String): Int = chats.toList().indexOfFirst { it.id == id && it.remoteHostId == rhId } fun addChat(chat: Chat) = chats.add(index = 0, chat) @@ -620,12 +637,13 @@ object ChatModel { } // update current chat return if (chatId.value == groupInfo.id) { - val memberIndex = groupMembers.indexOfFirst { it.groupMemberId == member.groupMemberId } - if (memberIndex >= 0) { + val memberIndex = groupMembersIndexes[member.groupMemberId] + if (memberIndex != null) { groupMembers[memberIndex] = member false } else { groupMembers.add(member) + groupMembersIndexes[member.groupMemberId] = groupMembers.size - 1 true } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index ec34a73d31..0cd33d659b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -159,6 +159,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: AudioPlayer.stop() chatModel.chatId.value = null chatModel.groupMembers.clear() + chatModel.groupMembersIndexes.clear() }, info = { if (ModalManager.end.hasModalsOpen()) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 2324d62ea3..d06e2ae88e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -253,7 +253,9 @@ suspend fun setGroupMembers(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatMo } } chatModel.groupMembers.clear() + chatModel.groupMembersIndexes.clear() chatModel.groupMembers.addAll(newMembers) + chatModel.populateGroupMembersIndexes() } @Composable From 41c4f139393cca55e10deebf46618779b4e5b645 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 11 Jul 2024 10:57:56 +0300 Subject: [PATCH 13/57] ios: optimise mark-as-read performance (#4434) * ios: optimise mark-as-read rendering performance * merge master * minor * make chat observable * make model EnvironmentObject --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 44 +++++++++++++++-------- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- apps/ios/Shared/Views/Chat/ChatView.swift | 41 ++++++--------------- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 262c4a4b48..7dae94dfcd 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -74,6 +74,7 @@ final class ChatModel: ObservableObject { @Published var chatToTop: String? @Published var groupMembers: [GMember] = [] @Published var groupMembersIndexes: Dictionary = [:] // groupMemberId to index in groupMembers list + @Published var membersLoaded = false // items in the terminal view @Published var showingTerminal = false @Published var terminalItems: [TerminalItem] = [] @@ -195,6 +196,18 @@ final class ChatModel: ObservableObject { return nil } + func loadGroupMembers(_ groupInfo: GroupInfo, updateView: @escaping () -> Void = {}) async { + let groupMembers = await apiListMembers(groupInfo.groupId) + await MainActor.run { + if chatId == groupInfo.id { + self.groupMembers = groupMembers.map { GMember.init($0) } + self.populateGroupMembersIndexes() + self.membersLoaded = true + updateView() + } + } + } + private func getChatIndex(_ id: String) -> Int? { chats.firstIndex(where: { $0.id == id }) } @@ -390,8 +403,8 @@ final class ChatModel: ObservableObject { } func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) { - if cItem.isRcvNew { - decreaseUnreadCounter(cInfo) + if cItem.isRcvNew, let chatIndex = getChatIndex(cInfo.id) { + decreaseUnreadCounter(chatIndex) } // update previews if let chat = getChat(cInfo.id) { @@ -536,13 +549,18 @@ final class ChatModel: ObservableObject { } } - func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) { - if chatId == cInfo.id, let i = getChatItemIndex(cItem) { - if reversedChatItems[i].isRcvNew { - // update current chat - markChatItemRead_(i) - // update preview - decreaseUnreadCounter(cInfo) + func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { + if chatId == cInfo.id, + let itemIndex = getChatItemIndex(cItem), + let chatIndex = getChatIndex(cInfo.id), + reversedChatItems[itemIndex].isRcvNew { + await MainActor.run { + withTransaction(Transaction()) { + // update current chat + markChatItemRead_(itemIndex) + // update preview + decreaseUnreadCounter(chatIndex) + } } } } @@ -558,11 +576,9 @@ final class ChatModel: ObservableObject { } } - func decreaseUnreadCounter(_ cInfo: ChatInfo) { - if let i = getChatIndex(cInfo.id) { - chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount - 1 - decreaseUnreadCounter(user: currentUser!) - } + func decreaseUnreadCounter(_ chatIndex: Int) { + chats[chatIndex].chatStats.unreadCount = chats[chatIndex].chatStats.unreadCount - 1 + decreaseUnreadCounter(user: currentUser!) } func increaseUnreadCounter(user: any UserLike) { diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 8c90c4b1ac..f1b413d663 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1207,7 +1207,7 @@ func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { do { logger.debug("apiMarkChatItemRead: \(cItem.id)") try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id)) - await MainActor.run { ChatModel.shared.markChatItemRead(cInfo, cItem) } + await ChatModel.shared.markChatItemRead(cInfo, cItem) } catch { logger.error("apiMarkChatItemRead apiChatRead error: \(responseError(error))") } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 7e302d67d1..f8723fdba8 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -37,7 +37,6 @@ struct ChatView: View { @State private var searchText: String = "" @FocusState private var searchFocussed // opening GroupMemberInfoView on member icon - @State private var membersLoaded = false @State private var selectedMember: GMember? = nil // opening GroupLinkView on link button (incognito) @State private var showGroupLinkSheet: Bool = false @@ -122,7 +121,7 @@ struct ChatView: View { chatModel.reversedChatItems = [] chatModel.groupMembers = [] chatModel.groupMembersIndexes.removeAll() - membersLoaded = false + chatModel.membersLoaded = false } } } @@ -164,7 +163,7 @@ struct ChatView: View { } } else if case let .group(groupInfo) = cInfo { Button { - Task { await loadGroupMembers(groupInfo) { showChatInfoSheet = true } } + Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } } } label: { ChatInfoToolbar(chat: chat) .tint(theme.colors.primary) @@ -250,19 +249,7 @@ struct ChatView: View { } } } - - private func loadGroupMembers(_ groupInfo: GroupInfo, updateView: @escaping () -> Void = {}) async { - let groupMembers = await apiListMembers(groupInfo.groupId) - await MainActor.run { - if chatModel.chatId == groupInfo.id { - chatModel.groupMembers = groupMembers.map { GMember.init($0) } - chatModel.populateGroupMembersIndexes() - membersLoaded = true - updateView() - } - } - } - + private func initChatView() { let cInfo = chat.chatInfo // This check prevents the call to apiContactInfo after the app is suspended, and the database is closed. @@ -477,9 +464,7 @@ struct ChatView: View { .foregroundColor(theme.colors.primary) } .onTapGesture { - if let latestUnreadItem = filtered(chatModel.reversedChatItems).last(where: { $0.isRcvNew }) { - scrollModel.scrollToItem(id: latestUnreadItem.id) - } + scrollModel.scrollToBottom() } } else if !counts.isNearBottom { circleButton { @@ -534,7 +519,7 @@ struct ChatView: View { private func addMembersButton() -> some View { Button { if case let .group(gInfo) = chat.chatInfo { - Task { await loadGroupMembers(gInfo) { showAddMembersSheet = true } } + Task { await chatModel.loadGroupMembers(gInfo) { showAddMembersSheet = true } } } } label: { Image(systemName: "person.crop.circle.badge.plus") @@ -604,11 +589,9 @@ struct ChatView: View { chat: chat, chatItem: ci, maxWidth: maxWidth, - itemWidth: maxWidth, composeState: $composeState, selectedMember: $selectedMember, - revealedChatItem: $revealedChatItem, - chatView: self + revealedChatItem: $revealedChatItem ) } @@ -616,13 +599,11 @@ struct ChatView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme @ObservedObject var chat: Chat - var chatItem: ChatItem - var maxWidth: CGFloat - @State var itemWidth: CGFloat + let chatItem: ChatItem + let maxWidth: CGFloat @Binding var composeState: ComposeState @Binding var selectedMember: GMember? @Binding var revealedChatItem: ChatItem? - var chatView: ChatView @State private var deletingItem: ChatItem? = nil @State private var showDeleteMessage = false @@ -700,11 +681,11 @@ struct ChatView: View { HStack(alignment: .top, spacing: 8) { ProfileImage(imageStr: member.memberProfile.image, size: memberImageSize, backgroundColor: theme.colors.background) .onTapGesture { - if chatView.membersLoaded { + if m.membersLoaded { selectedMember = m.getGroupMember(member.groupMemberId) } else { Task { - await chatView.loadGroupMembers(groupInfo) { + await m.loadGroupMembers(groupInfo) { selectedMember = m.getGroupMember(member.groupMemberId) } } @@ -1099,7 +1080,7 @@ struct ChatView: View { chatItemInfo = ciInfo } if case let .group(gInfo) = chat.chatInfo { - await chatView.loadGroupMembers(gInfo) + await m.loadGroupMembers(gInfo) } } catch let error { logger.error("apiGetChatItemInfo error: \(responseError(error))") From 859fa0bc2296b27c73941beb0195fb8f2e03c360 Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 11 Jul 2024 15:56:55 +0300 Subject: [PATCH 14/57] ios: make image utilities accessible from extensions (#4438) * ios: move image utils in to framework * Rename UIImage convenience init --- apps/ios/Shared/Theme/Theme.swift | 3 +- .../Views/Chat/ChatItem/CILinkView.swift | 3 +- .../Views/Chat/ChatItem/FramedItemView.swift | 6 +-- apps/ios/Shared/Views/Chat/ChatItemView.swift | 4 +- .../ComposeMessage/ComposeImageView.swift | 5 +- .../Chat/ComposeMessage/ComposeLinkView.swift | 3 +- .../Chat/ComposeMessage/ComposeView.swift | 1 + .../Shared/Views/Helpers/ProfileImage.swift | 4 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 14 +++-- .../Model => SimpleXChat}/ImageUtils.swift | 51 +++++++++++-------- 10 files changed, 49 insertions(+), 45 deletions(-) rename apps/ios/{Shared/Model => SimpleXChat}/ImageUtils.swift (88%) diff --git a/apps/ios/Shared/Theme/Theme.swift b/apps/ios/Shared/Theme/Theme.swift index 3f7013bad7..d4dcc8097f 100644 --- a/apps/ios/Shared/Theme/Theme.swift +++ b/apps/ios/Shared/Theme/Theme.swift @@ -102,8 +102,7 @@ extension ThemeWallpaper { public func importFromString() -> ThemeWallpaper { if preset == nil, let image { // Need to save image from string and to save its path - if let data = Data(base64Encoded: dropImagePrefix(image)), - let parsed = UIImage(data: data), + if let parsed = UIImage(base64Encoded: image), let filename = saveWallpaperFile(image: parsed) { var copy = self copy.image = nil diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 0f7ea9a716..3e6ef4abff 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -15,8 +15,7 @@ struct CILinkView: View { var body: some View { VStack(alignment: .center, spacing: 6) { - if let data = Data(base64Encoded: dropImagePrefix(linkPreview.image)), - let uiImage = UIImage(data: data) { + if let uiImage = UIImage(base64Encoded: linkPreview.image) { Image(uiImage: uiImage) .resizable() .scaledToFit() diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 5d6327139a..595d9bf2fc 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -188,8 +188,7 @@ struct FramedItemView: View { let v = ZStack(alignment: .topTrailing) { switch (qi.content) { case let .image(_, image): - if let data = Data(base64Encoded: dropImagePrefix(image)), - let uiImage = UIImage(data: data) { + if let uiImage = UIImage(base64Encoded: image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) @@ -201,8 +200,7 @@ struct FramedItemView: View { ciQuotedMsgView(qi) } case let .video(_, image, _): - if let data = Data(base64Encoded: dropImagePrefix(image)), - let uiImage = UIImage(data: data) { + if let uiImage = UIImage(base64Encoded: image) { ciQuotedMsgView(qi) .padding(.trailing, 70).frame(minWidth: msgWidth, alignment: .leading) Image(uiImage: uiImage) diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 4cb97112ca..d06e67e2e6 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -68,9 +68,7 @@ struct ChatItemView: View { default: nil } } - .map { dropImagePrefix($0) } - .flatMap { Data(base64Encoded: $0) } - .flatMap { UIImage(data: $0) } + .flatMap { UIImage(base64Encoded: $0) } let adjustedMaxWidth = { if let preview, preview.size.width <= preview.size.height { maxWidth * 0.75 diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift index 52655f1c6a..d9f63f6d33 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift @@ -18,10 +18,7 @@ struct ComposeImageView: View { var body: some View { HStack(alignment: .center, spacing: 8) { let imgs: [UIImage] = images.compactMap { image in - if let data = Data(base64Encoded: dropImagePrefix(image)) { - return UIImage(data: data) - } - return nil + UIImage(base64Encoded: image) } if imgs.count == 0 { ProgressView() diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index 4137370e3f..7df0a3037a 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -69,8 +69,7 @@ struct ComposeLinkView: View { private func linkPreviewView(_ linkPreview: LinkPreview) -> some View { HStack(alignment: .center, spacing: 8) { - if let data = Data(base64Encoded: dropImagePrefix(linkPreview.image)), - let uiImage = UIImage(data: data) { + if let uiImage = UIImage(base64Encoded: linkPreview.image) { Image(uiImage: uiImage) .resizable() .aspectRatio(contentMode: .fit) diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index 819b337a73..c1b709fea5 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -849,6 +849,7 @@ struct ComposeView: View { func sendVideo(_ imageData: (String, UploadContent?), text: String = "", quoted: Int64? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? { let (image, data) = imageData if case let .video(_, url, duration) = data, let savedFile = moveTempFileFromURL(url) { + ChatModel.shared.filesToDelete.remove(url) return await send(.video(text: text, image: image, duration: duration), quoted: quoted, file: savedFile, live: live, ttl: ttl) } return nil diff --git a/apps/ios/Shared/Views/Helpers/ProfileImage.swift b/apps/ios/Shared/Views/Helpers/ProfileImage.swift index 0a0aab6253..e7145711af 100644 --- a/apps/ios/Shared/Views/Helpers/ProfileImage.swift +++ b/apps/ios/Shared/Views/Helpers/ProfileImage.swift @@ -21,9 +21,7 @@ struct ProfileImage: View { @AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner var body: some View { - if let image = imageStr, - let data = Data(base64Encoded: dropImagePrefix(image)), - let uiImage = UIImage(data: data) { + if let uiImage = UIImage(base64Encoded: imageStr) { clipProfileImage(Image(uiImage: uiImage), size: size, radius: radius) } else { let c = color.asAnotherColorFromSecondaryVariant(theme) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 9bd72ff5a9..dba8db792a 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -100,7 +100,6 @@ 5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; }; 5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; }; 5CB9250D27A9432000ACCCDD /* ChatListNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB9250C27A9432000ACCCDD /* ChatListNavLink.swift */; }; - 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; }; 5CBD285C29575B8E00EC2CF4 /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */; }; 5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBE6C11294487F7002D9531 /* VerifyCodeView.swift */; }; 5CBE6C142944CC12002D9531 /* ScanCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBE6C132944CC12002D9531 /* ScanCodeView.swift */; }; @@ -195,6 +194,8 @@ 8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C9BC2642C240D5100875A27 /* ThemeModeEditor.swift */; }; 8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC4ED8F2BD7B8530078AEE8 /* CallAudioDeviceManager.swift */; }; 8CC956EE2BC0041000412A11 /* NetworkObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */; }; + CE38A29A2C3FCA54005ED185 /* ImageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */; }; + CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = CE38A29B2C3FCD72005ED185 /* SwiftyGif */; }; CE984D4B2C36C5D500E3AEFF /* ChatItemClipShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */; }; CEEA861D2C2ABCB50084E1EA /* ReverseList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEA861C2C2ABCB50084E1EA /* ReverseList.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; @@ -560,6 +561,7 @@ E50581022C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a in Frameworks */, E50581062C3DDD9D009C3F71 /* Yams in Frameworks */, E50581042C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a in Frameworks */, + CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -658,7 +660,6 @@ 5CF937212B25034A00E1D781 /* NSESubscriber.swift */, 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */, 5C93293E2928E0FD0090FFF9 /* AudioRecPlay.swift */, - 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */, 8CC956ED2BC0041000412A11 /* NetworkObserver.swift */, ); path = Model; @@ -858,6 +859,7 @@ 5C9FD96A27A56D4D0075386C /* JSON.swift */, 5CDCAD7D2818941F00503DA2 /* API.swift */, 5CDCAD80281A7E2700503DA2 /* Notifications.swift */, + 5CBD2859295711D700EC2CF4 /* ImageUtils.swift */, 64DAE1502809D9F5000DA960 /* FileUtils.swift */, 5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */, 5C00168028C4FE760094D739 /* KeyChain.swift */, @@ -1068,6 +1070,7 @@ name = SimpleXChat; packageProductDependencies = ( E50581052C3DDD9D009C3F71 /* Yams */, + CE38A29B2C3FCD72005ED185 /* SwiftyGif */, ); productName = SimpleXChat; productReference = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; @@ -1205,7 +1208,6 @@ 6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */, 5C93293129239BED0090FFF9 /* ProtocolServerView.swift in Sources */, 5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */, - 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */, 8C74C3EC2C1B92A900039E77 /* Theme.swift in Sources */, 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */, 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, @@ -1376,6 +1378,7 @@ 5CE2BA90284533A300EC33A6 /* JSON.swift in Sources */, 5CE2BA8B284533A300EC33A6 /* ChatTypes.swift in Sources */, 5CE2BA8F284533A300EC33A6 /* APITypes.swift in Sources */, + CE38A29A2C3FCA54005ED185 /* ImageUtils.swift in Sources */, 5C9D811A2AA8727A001D49FD /* CryptoFile.swift in Sources */, 5CE2BA8C284533A300EC33A6 /* AppGroup.swift in Sources */, 8C74C3E52C1B900600039E77 /* ThemeTypes.swift in Sources */, @@ -2023,6 +2026,11 @@ package = 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */; productName = Yams; }; + CE38A29B2C3FCD72005ED185 /* SwiftyGif */ = { + isa = XCSwiftPackageProductDependency; + package = D77B92DA2952372200A5A1CC /* XCRemoteSwiftPackageReference "SwiftyGif" */; + productName = SwiftyGif; + }; D7197A1729AE89660055C05A /* WebRTC */ = { isa = XCSwiftPackageProductDependency; package = D7197A1629AE89660055C05A /* XCRemoteSwiftPackageReference "WebRTC" */; diff --git a/apps/ios/Shared/Model/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift similarity index 88% rename from apps/ios/Shared/Model/ImageUtils.swift rename to apps/ios/SimpleXChat/ImageUtils.swift index 073621caa4..fd6d951f48 100644 --- a/apps/ios/Shared/Model/ImageUtils.swift +++ b/apps/ios/SimpleXChat/ImageUtils.swift @@ -7,18 +7,18 @@ // import Foundation -import SimpleXChat import SwiftUI import AVKit +import SwiftyGif -func getLoadedFileSource(_ file: CIFile?) -> CryptoFile? { +public func getLoadedFileSource(_ file: CIFile?) -> CryptoFile? { if let file = file, file.loaded { return file.fileSource } return nil } -func getLoadedImage(_ file: CIFile?) -> UIImage? { +public func getLoadedImage(_ file: CIFile?) -> UIImage? { if let fileSource = getLoadedFileSource(file) { let filePath = getAppFilePath(fileSource.filePath) do { @@ -37,7 +37,7 @@ func getLoadedImage(_ file: CIFile?) -> UIImage? { return nil } -func getFileData(_ path: URL, _ cfArgs: CryptoFileArgs?) throws -> Data { +public func getFileData(_ path: URL, _ cfArgs: CryptoFileArgs?) throws -> Data { if let cfArgs = cfArgs { return try readCryptoFile(path: path.path, cryptoArgs: cfArgs) } else { @@ -45,7 +45,7 @@ func getFileData(_ path: URL, _ cfArgs: CryptoFileArgs?) throws -> Data { } } -func getLoadedVideo(_ file: CIFile?) -> URL? { +public func getLoadedVideo(_ file: CIFile?) -> URL? { if let fileSource = getLoadedFileSource(file) { let filePath = getAppFilePath(fileSource.filePath) if FileManager.default.fileExists(atPath: filePath.path) { @@ -55,13 +55,13 @@ func getLoadedVideo(_ file: CIFile?) -> URL? { return nil } -func saveAnimImage(_ image: UIImage) -> CryptoFile? { +public func saveAnimImage(_ image: UIImage) -> CryptoFile? { let fileName = generateNewFileName("IMG", "gif") guard let imageData = image.imageData else { return nil } return saveFile(imageData, fileName, encrypted: privacyEncryptLocalFilesGroupDefault.get()) } -func saveImage(_ uiImage: UIImage) -> CryptoFile? { +public func saveImage(_ uiImage: UIImage) -> CryptoFile? { let hasAlpha = imageHasAlpha(uiImage) let ext = hasAlpha ? "png" : "jpg" if let imageDataResized = resizeImageToDataSize(uiImage, maxDataSize: MAX_IMAGE_SIZE, hasAlpha: hasAlpha) { @@ -71,7 +71,7 @@ func saveImage(_ uiImage: UIImage) -> CryptoFile? { return nil } -func cropToSquare(_ image: UIImage) -> UIImage { +public func cropToSquare(_ image: UIImage) -> UIImage { let size = image.size let side = min(size.width, size.height) let newSize = CGSize(width: side, height: side) @@ -84,7 +84,7 @@ func cropToSquare(_ image: UIImage) -> UIImage { return resizeImage(image, newBounds: CGRect(origin: .zero, size: newSize), drawIn: CGRect(origin: origin, size: size), hasAlpha: imageHasAlpha(image)) } -func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64, hasAlpha: Bool) -> Data? { +public func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64, hasAlpha: Bool) -> Data? { var img = image var data = hasAlpha ? img.pngData() : img.jpegData(compressionQuality: 0.85) var dataSize = data?.count ?? 0 @@ -99,7 +99,7 @@ func resizeImageToDataSize(_ image: UIImage, maxDataSize: Int64, hasAlpha: Bool) return data } -func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) -> String? { +public func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) -> String? { var img = image let hasAlpha = imageHasAlpha(image) var str = compressImageStr(img, hasAlpha: hasAlpha) @@ -115,7 +115,7 @@ func resizeImageToStrSize(_ image: UIImage, maxDataSize: Int64) -> String? { return str } -func compressImageStr(_ image: UIImage, _ compressionQuality: CGFloat = 0.85, hasAlpha: Bool) -> String? { +public func compressImageStr(_ image: UIImage, _ compressionQuality: CGFloat = 0.85, hasAlpha: Bool) -> String? { let ext = hasAlpha ? "png" : "jpg" if let data = hasAlpha ? image.pngData() : image.jpegData(compressionQuality: compressionQuality) { return "data:image/\(ext);base64,\(data.base64EncodedString())" @@ -138,7 +138,7 @@ private func resizeImage(_ image: UIImage, newBounds: CGRect, drawIn: CGRect, ha } } -func imageHasAlpha(_ img: UIImage) -> Bool { +public func imageHasAlpha(_ img: UIImage) -> Bool { if let cgImage = img.cgImage { let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) @@ -158,7 +158,7 @@ func imageHasAlpha(_ img: UIImage) -> Bool { return false } -func saveFileFromURL(_ url: URL) -> CryptoFile? { +public func saveFileFromURL(_ url: URL) -> CryptoFile? { let encrypted = privacyEncryptLocalFilesGroupDefault.get() let savedFile: CryptoFile? if url.startAccessingSecurityScopedResource() { @@ -184,7 +184,7 @@ func saveFileFromURL(_ url: URL) -> CryptoFile? { return savedFile } -func moveTempFileFromURL(_ url: URL) -> CryptoFile? { +public func moveTempFileFromURL(_ url: URL) -> CryptoFile? { do { let encrypted = privacyEncryptLocalFilesGroupDefault.get() let fileName = uniqueCombine(url.lastPathComponent) @@ -197,7 +197,6 @@ func moveTempFileFromURL(_ url: URL) -> CryptoFile? { try FileManager.default.moveItem(at: url, to: getAppFilePath(fileName)) savedFile = CryptoFile.plain(fileName) } - ChatModel.shared.filesToDelete.remove(url) return savedFile } catch { logger.error("ImageUtils.moveTempFileFromURL error: \(error.localizedDescription)") @@ -205,7 +204,7 @@ func moveTempFileFromURL(_ url: URL) -> CryptoFile? { } } -func saveWallpaperFile(url: URL) -> String? { +public func saveWallpaperFile(url: URL) -> String? { let destFile = URL(fileURLWithPath: generateNewFileName(getWallpaperDirectory().path + "/" + "wallpaper", "jpg", fullPath: true)) do { try FileManager.default.copyItem(atPath: url.path, toPath: destFile.path) @@ -216,7 +215,7 @@ func saveWallpaperFile(url: URL) -> String? { } } -func saveWallpaperFile(image: UIImage) -> String? { +public func saveWallpaperFile(image: UIImage) -> String? { let hasAlpha = imageHasAlpha(image) let destFile = URL(fileURLWithPath: generateNewFileName(getWallpaperDirectory().path + "/" + "wallpaper", hasAlpha ? "png" : "jpg", fullPath: true)) let dataResized = resizeImageToDataSize(image, maxDataSize: 5_000_000, hasAlpha: hasAlpha) @@ -229,7 +228,7 @@ func saveWallpaperFile(image: UIImage) -> String? { } } -func removeWallpaperFile(fileName: String? = nil) { +public func removeWallpaperFile(fileName: String? = nil) { do { try FileManager.default.contentsOfDirectory(atPath: getWallpaperDirectory().path).forEach { if URL(fileURLWithPath: $0).lastPathComponent == fileName { try FileManager.default.removeItem(atPath: $0) } @@ -242,7 +241,7 @@ func removeWallpaperFile(fileName: String? = nil) { } } -func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String { +public func generateNewFileName(_ prefix: String, _ ext: String, fullPath: Bool = false) -> String { uniqueCombine("\(prefix)_\(getTimestamp()).\(ext)", fullPath: fullPath) } @@ -274,7 +273,7 @@ private func getTimestamp() -> String { return df.string(from: Date()) } -func dropImagePrefix(_ s: String) -> String { +public func dropImagePrefix(_ s: String) -> String { dropPrefix(dropPrefix(s, "data:image/png;base64,"), "data:image/jpg;base64,") } @@ -283,7 +282,7 @@ private func dropPrefix(_ s: String, _ prefix: String) -> String { } extension AVAsset { - func generatePreview() -> (UIImage, Int)? { + public func generatePreview() -> (UIImage, Int)? { let generator = AVAssetImageGenerator(asset: self) generator.appliesPreferredTrackTransform = true var actualTime = CMTimeMake(value: 0, timescale: 0) @@ -295,7 +294,7 @@ extension AVAsset { } extension UIImage { - func replaceColor(_ from: UIColor, _ to: UIColor) -> UIImage { + public func replaceColor(_ from: UIColor, _ to: UIColor) -> UIImage { if let cgImage = cgImage { let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue) @@ -340,4 +339,12 @@ extension UIImage { } return self } + + public convenience init?(base64Encoded: String?) { + if let base64Encoded, let data = Data(base64Encoded: dropImagePrefix(base64Encoded)) { + self.init(data: data) + } else { + return nil + } + } } From a73abfe6420ad60aef0f8c638778d61472fe1108 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:29:56 +0400 Subject: [PATCH 15/57] core: update simplexmq (proxy err encoding) (#4441) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index dbf6fd8556..c60cbe2a76 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 017469b2de65c7e3ef1d680c2da466b320d1b061 + tag: ff2b00a0299343e39bd8d56fc43d97f7a8fe214c source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 126ed9af03..097fba1b00 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."017469b2de65c7e3ef1d680c2da466b320d1b061" = "1c6aap31sa4wdv7x771q2l8bs499qg58rrnx80blj33y2zl877c9"; + "https://github.com/simplex-chat/simplexmq.git"."ff2b00a0299343e39bd8d56fc43d97f7a8fe214c" = "05h3mclw2qvdngvvjm0xsb39v1sycia69xhxrfmg6yj3a3240byn"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; From 2dff94cbb498f6a0e61c2340406c519f0b7f7883 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:49:28 +0400 Subject: [PATCH 16/57] ios: check temporary proxy error (#4440) * ios: check temporary proxy error * update * more errs * more errs * refactor * update * refactor --- apps/ios/Shared/Model/SimpleXAPI.swift | 50 +++++++++++++++---- .../Views/ChatList/ChatListNavLink.swift | 15 ++---- apps/ios/SimpleXChat/APITypes.swift | 18 +++++++ 3 files changed, 63 insertions(+), 20 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index f1b413d663..dd0610e48c 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1091,23 +1091,55 @@ func deleteRemoteCtrl(_ rcId: Int64) async throws { try await sendCommandOkResp(.deleteRemoteCtrl(remoteCtrlId: rcId)) } -func networkErrorAlert(_ r: ChatResponse) -> Alert? { +struct ErrorAlert { + var title: LocalizedStringKey + var message: LocalizedStringKey +} + +func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? { switch r { case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))): - return mkAlert( - title: "Connection timeout", - message: "Please check your network connection with \(serverHostname(addr)) and try again." - ) + return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))): - return mkAlert( - title: "Connection error", - message: "Please check your network connection with \(serverHostname(addr)) and try again." - ) + return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") + case let .chatCmdError(_, .errorAgent(.BROKER(addr, .HOST))): + return ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).") + case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))): + return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") + case let .chatCmdError(_, .errorAgent(.SMP(.PROXY(proxyErr)))): + return proxyErrorAlert(proxyErr) + case let .chatCmdError(_, .errorAgent(.PROXY(_, _, .protocolError(.PROXY(proxyErr))))): + return proxyErrorAlert(proxyErr) default: return nil } } +private func proxyErrorAlert(_ proxyErr: ProxyError) -> ErrorAlert? { + switch proxyErr { + case .BROKER(brokerErr: .TIMEOUT): + return ErrorAlert(title: "Private routing error", message: "Please try later.") + case .BROKER(brokerErr: .NETWORK): + return ErrorAlert(title: "Private routing error", message: "Please try later.") + case .NO_SESSION: + return ErrorAlert(title: "Private routing error", message: "Please try later.") + case .BROKER(brokerErr: .HOST): + return ErrorAlert(title: "Private routing error", message: "Server address is incompatible with network settings.") + case .BROKER(brokerErr: .TRANSPORT(.version)): + return ErrorAlert(title: "Private routing error", message: "Server version is incompatible with network settings.") + default: + return nil + } +} + +func networkErrorAlert(_ r: ChatResponse) -> Alert? { + if let alert = getNetworkErrorAlert(r) { + return mkAlert(title: alert.title, message: alert.message) + } else { + return nil + } +} + func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async { if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) { let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: []) diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index c8a6efd282..c0cc42bd8e 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -564,18 +564,11 @@ func joinGroup(_ groupId: Int64, _ onComplete: @escaping () async -> Void) { } } -struct ErrorAlert { - var title: LocalizedStringKey - var message: LocalizedStringKey -} - func getErrorAlert(_ error: Error, _ title: LocalizedStringKey) -> ErrorAlert { - switch error as? ChatResponse { - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))): - return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.") - case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))): - return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.") - default: + if let r = error as? ChatResponse, + let alert = getNetworkErrorAlert(r) { + return alert + } else { return ErrorAlert(title: title, message: "Error: \(responseError(error))") } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 2d3663b9f8..2129d38fcf 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1906,6 +1906,7 @@ public enum AgentErrorType: Decodable, Hashable { case SMP(smpErr: ProtocolErrorType) case NTF(ntfErr: ProtocolErrorType) case XFTP(xftpErr: XFTPErrorType) + case PROXY(proxyServer: String, relayServer: String, proxyErr: ProxyClientError) case RCP(rcpErr: RCErrorType) case BROKER(brokerAddress: String, brokerErr: BrokerErrorType) case AGENT(agentErr: SMPAgentError) @@ -1943,13 +1944,23 @@ public enum ProtocolErrorType: Decodable, Hashable { case BLOCK case SESSION case CMD(cmdErr: ProtocolCommandError) + indirect case PROXY(proxyErr: ProxyError) case AUTH + case CRYPTO case QUOTA case NO_MSG case LARGE_MSG + case EXPIRED case INTERNAL } +public enum ProxyError: Decodable, Hashable { + case PROTOCOL(protocolErr: ProtocolErrorType) + case BROKER(brokerErr: BrokerErrorType) + case BASIC_AUTH + case NO_SESSION +} + public enum XFTPErrorType: Decodable, Hashable { case BLOCK case SESSION @@ -1967,6 +1978,12 @@ public enum XFTPErrorType: Decodable, Hashable { case INTERNAL } +public enum ProxyClientError: Decodable, Hashable { + case protocolError(protocolErr: ProtocolErrorType) + case unexpectedResponse(responseStr: String) + case responseError(responseErr: ProtocolErrorType) +} + public enum RCErrorType: Decodable, Hashable { case `internal`(internalErr: String) case identity @@ -1996,6 +2013,7 @@ public enum ProtocolCommandError: Decodable, Hashable { public enum ProtocolTransportError: Decodable, Hashable { case badBlock + case version case largeMsg case badSession case noServerAuth From 71ad8f2fd1eb4c8cd80313c686739f5dfacee57b Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:44:31 +0400 Subject: [PATCH 17/57] multiplatform: check temporary proxy error (#4442) * android: check temporary proxy error * version error * errors --- .../chat/simplex/common/model/SimpleXAPI.kt | 110 ++++++++++++++++++ .../commonMain/resources/MR/base/strings.xml | 4 + 2 files changed, 114 insertions(+) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7633b0c808..07d3451d86 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1809,6 +1809,80 @@ object ChatController { ) true } + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent + && r.chatError.agentError is AgentErrorType.BROKER + && r.chatError.agentError.brokerErr is BrokerErrorType.HOST -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.connection_error), + String.format(generalGetString(MR.strings.network_error_broker_host_desc), serverHostname(r.chatError.agentError.brokerAddress)) + ) + true + } + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent + && r.chatError.agentError is AgentErrorType.BROKER + && r.chatError.agentError.brokerErr is BrokerErrorType.TRANSPORT + && r.chatError.agentError.brokerErr.transportErr is SMPTransportError.Version -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.connection_error), + String.format(generalGetString(MR.strings.network_error_broker_version_desc), serverHostname(r.chatError.agentError.brokerAddress)) + ) + true + } + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent + && r.chatError.agentError is AgentErrorType.SMP + && r.chatError.agentError.smpErr is SMPErrorType.PROXY -> + proxyErrorAlert(r.chatError.agentError.smpErr.proxyErr) + r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent + && r.chatError.agentError is AgentErrorType.PROXY + && r.chatError.agentError.proxyErr is ProxyClientError.ProxyProtocolError + && r.chatError.agentError.proxyErr.protocolErr is SMPErrorType.PROXY -> + proxyErrorAlert(r.chatError.agentError.proxyErr.protocolErr.proxyErr) + else -> false + } + } + + private fun proxyErrorAlert(pe: ProxyError): Boolean { + return when { + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.TIMEOUT -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + generalGetString(MR.strings.please_try_later) + ) + true + } + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.NETWORK -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + generalGetString(MR.strings.please_try_later) + ) + true + } + pe is ProxyError.NO_SESSION -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + generalGetString(MR.strings.please_try_later) + ) + true + } + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.HOST -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + generalGetString(MR.strings.srv_error_host) + ) + true + } + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.TRANSPORT + && pe.brokerErr.transportErr is SMPTransportError.Version -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + generalGetString(MR.strings.srv_error_version) + ) + true + } else -> false } } @@ -5500,6 +5574,7 @@ sealed class AgentErrorType { is SMP -> "SMP ${smpErr.string}" // is NTF -> "NTF ${ntfErr.string}" is XFTP -> "XFTP ${xftpErr.string}" + is PROXY -> "PROXY $proxyServer $relayServer ${proxyErr.string}" is RCP -> "RCP ${rcpErr.string}" is BROKER -> "BROKER ${brokerErr.string}" is AGENT -> "AGENT ${agentErr.string}" @@ -5512,6 +5587,7 @@ sealed class AgentErrorType { @Serializable @SerialName("SMP") class SMP(val smpErr: SMPErrorType): AgentErrorType() // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() @Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType() + @Serializable @SerialName("PROXY") class PROXY(val proxyServer: String, val relayServer: String, val proxyErr: ProxyClientError): AgentErrorType() @Serializable @SerialName("RCP") class RCP(val rcpErr: RCErrorType): AgentErrorType() @Serializable @SerialName("BROKER") class BROKER(val brokerAddress: String, val brokerErr: BrokerErrorType): AgentErrorType() @Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType() @@ -5576,22 +5652,42 @@ sealed class SMPErrorType { is BLOCK -> "BLOCK" is SESSION -> "SESSION" is CMD -> "CMD ${cmdErr.string}" + is PROXY -> "PROXY ${proxyErr.string}" is AUTH -> "AUTH" + is CRYPTO -> "CRYPTO" is QUOTA -> "QUOTA" is NO_MSG -> "NO_MSG" is LARGE_MSG -> "LARGE_MSG" + is EXPIRED -> "EXPIRED" is INTERNAL -> "INTERNAL" } @Serializable @SerialName("BLOCK") class BLOCK: SMPErrorType() @Serializable @SerialName("SESSION") class SESSION: SMPErrorType() @Serializable @SerialName("CMD") class CMD(val cmdErr: ProtocolCommandError): SMPErrorType() + @Serializable @SerialName("PROXY") class PROXY(val proxyErr: ProxyError): SMPErrorType() @Serializable @SerialName("AUTH") class AUTH: SMPErrorType() + @Serializable @SerialName("CRYPTO") class CRYPTO: SMPErrorType() @Serializable @SerialName("QUOTA") class QUOTA: SMPErrorType() @Serializable @SerialName("NO_MSG") class NO_MSG: SMPErrorType() @Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType() + @Serializable @SerialName("EXPIRED") class EXPIRED: SMPErrorType() @Serializable @SerialName("INTERNAL") class INTERNAL: SMPErrorType() } +@Serializable +sealed class ProxyError { + val string: String get() = when (this) { + is PROTOCOL -> "PROTOCOL ${protocolErr.string}" + is BROKER -> "BROKER ${brokerErr.string}" + is BASIC_AUTH -> "BASIC_AUTH" + is NO_SESSION -> "NO_SESSION" + } + @Serializable @SerialName("PROTOCOL") class PROTOCOL(val protocolErr: SMPErrorType): ProxyError() + @Serializable @SerialName("BROKER") class BROKER(val brokerErr: BrokerErrorType): ProxyError() + @Serializable @SerialName("BASIC_AUTH") class BASIC_AUTH: ProxyError() + @Serializable @SerialName("NO_SESSION") class NO_SESSION: ProxyError() +} + @Serializable sealed class ProtocolCommandError { val string: String get() = when (this) { @@ -5614,12 +5710,14 @@ sealed class ProtocolCommandError { sealed class SMPTransportError { val string: String get() = when (this) { is BadBlock -> "badBlock" + is Version -> "version" is LargeMsg -> "largeMsg" is BadSession -> "badSession" is NoServerAuth -> "noServerAuth" is Handshake -> "handshake ${handshakeErr.string}" } @Serializable @SerialName("badBlock") class BadBlock: SMPTransportError() + @Serializable @SerialName("version") class Version: SMPTransportError() @Serializable @SerialName("largeMsg") class LargeMsg: SMPTransportError() @Serializable @SerialName("badSession") class BadSession: SMPTransportError() @Serializable @SerialName("noServerAuth") class NoServerAuth: SMPTransportError() @@ -5692,6 +5790,18 @@ sealed class XFTPErrorType { @Serializable @SerialName("INTERNAL") object INTERNAL: XFTPErrorType() } +@Serializable +sealed class ProxyClientError { + val string: String get() = when (this) { + is ProxyProtocolError -> "ProxyProtocolError $protocolErr" + is ProxyUnexpectedResponse -> "ProxyUnexpectedResponse $responseStr" + is ProxyResponseError -> "ProxyResponseError $responseErr" + } + @Serializable @SerialName("protocolError") class ProxyProtocolError(val protocolErr: SMPErrorType): ProxyClientError() + @Serializable @SerialName("unexpectedResponse") class ProxyUnexpectedResponse(val responseStr: String): ProxyClientError() + @Serializable @SerialName("responseError") class ProxyResponseError(val responseErr: SMPErrorType): ProxyClientError() +} + @Serializable sealed class RCErrorType { val string: String get() = when (this) { diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index e2d10ae5ae..8b6e4d8e06 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -112,6 +112,10 @@ Connection timeout Connection error Please check your network connection with %1$s and try again. + Server address is incompatible with network settings: %1$s. + Server version is incompatible with your app: %1$s. + Private routing error + Please try later. Error sending message Error creating message Error loading details From b348979b32a190a013719a3e12772a374139bfb1 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 12 Jul 2024 14:12:20 +0100 Subject: [PATCH 18/57] core: use disabled servers as known for private message routing (#4444) * core: use disabled servers as known for private message routing * send all servers to agent, add all preset servers * update nix hashes * refactor * check that some servers are enabled, refactor * update simplexmq * remove functions --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 128 ++++++++++++++--------------- src/Simplex/Chat/Controller.hs | 12 +-- src/Simplex/Chat/Stats.hs | 3 +- src/Simplex/Chat/Store/Profiles.hs | 8 +- src/Simplex/Chat/Terminal.hs | 15 ++-- src/Simplex/Chat/Types.hs | 50 ----------- src/Simplex/Chat/View.hs | 12 +-- 9 files changed, 93 insertions(+), 139 deletions(-) diff --git a/cabal.project b/cabal.project index c60cbe2a76..bcaf9f2abd 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: ff2b00a0299343e39bd8d56fc43d97f7a8fe214c + tag: e56bd0b47b22781f9b5936e3b8a9d51cc3f9e8d5 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 097fba1b00..195589ae83 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."ff2b00a0299343e39bd8d56fc43d97f7a8fe214c" = "05h3mclw2qvdngvvjm0xsb39v1sycia69xhxrfmg6yj3a3240byn"; + "https://github.com/simplex-chat/simplexmq.git"."e56bd0b47b22781f9b5936e3b8a9d51cc3f9e8d5" = "11ghsddgyrlnddxqvvb0xlqvnnhnnsbixz1kg01x3gd6vpdqkz47"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 9198a4c9d9..1a4811c2a3 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -95,7 +95,7 @@ import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.FileTransfer.Types (FileErrorType (..), RcvFileId, SndFileId) import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getNetworkConfig', ipAddressProtected, withLockMap) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), createAgentStore, defaultAgentConfig) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), createAgentStore, defaultAgentConfig, enabledServerCfg, presetServerCfg) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..)) @@ -112,7 +112,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth (..), ProtocolServer, ProtocolType (..), ProtocolTypeI (..), SProtocolType (..), SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM @@ -148,7 +148,7 @@ defaultChatConfig = DefaultAgentServers { smp = _defaultSMPServers, ntf = _defaultNtfServers, - xftp = defaultXFTPServers, + xftp = L.map (presetServerCfg True) defaultXFTPServers, netCfg = defaultNetworkConfig }, tbqSize = 1024, @@ -172,15 +172,29 @@ defaultChatConfig = chatHooks = defaultChatHooks } -_defaultSMPServers :: NonEmpty SMPServerWithAuth +_defaultSMPServers :: NonEmpty (ServerCfg 'PSMP) _defaultSMPServers = - L.fromList - [ "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", - "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", - "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", - "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", - "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" - ] + L.fromList $ + map + (presetServerCfg True) + [ "smp://h--vW7ZSkXPeOUpfxlFGgauQmXNFOzGoizak7Ult7cw=@smp15.simplex.im,oauu4bgijybyhczbnxtlggo6hiubahmeutaqineuyy23aojpih3dajad.onion", + "smp://hejn2gVIqNU6xjtGM3OwQeuk8ZEbDXVJXAlnSBJBWUA=@smp16.simplex.im,p3ktngodzi6qrf7w64mmde3syuzrv57y55hxabqcq3l5p6oi7yzze6qd.onion", + "smp://ZKe4uxF4Z_aLJJOEsC-Y6hSkXgQS5-oc442JQGkyP8M=@smp17.simplex.im,ogtwfxyi3h2h5weftjjpjmxclhb5ugufa5rcyrmg7j4xlch7qsr5nuqd.onion", + "smp://PtsqghzQKU83kYTlQ1VKg996dW4Cw4x_bvpKmiv8uns=@smp18.simplex.im,lyqpnwbs2zqfr45jqkncwpywpbtq7jrhxnib5qddtr6npjyezuwd3nqd.onion", + "smp://N_McQS3F9TGoh4ER0QstUf55kGnNSd-wXfNPZ7HukcM=@smp19.simplex.im,i53bbtoqhlc365k6kxzwdp5w3cdt433s7bwh3y32rcbml2vztiyyz5id.onion" + ] + <> map + (presetServerCfg False) + [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", + "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", + "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion", + "smp://0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU=@smp8.simplex.im,beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion", + "smp://SkIkI6EPd2D63F4xFKfHk7I1UGZVNn6k1QWZ5rcyr6w=@smp9.simplex.im,jssqzccmrcws6bhmn77vgmhfjmhwlyr3u7puw4erkyoosywgl67slqqd.onion", + "smp://6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE=@smp10.simplex.im,rb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion", + "smp://1OwYGt-yqOfe2IyVHhxz3ohqo3aCCMjtB-8wn4X_aoY=@smp11.simplex.im,6ioorbm6i3yxmuoezrhjk6f6qgkc4syabh7m3so74xunb5nzr4pwgfqd.onion", + "smp://UkMFNAXLXeAAe0beCa4w6X_zp18PwxSaSjY17BKUGXQ=@smp12.simplex.im,ie42b5weq7zdkghocs3mgxdjeuycheeqqmksntj57rmejagmg4eor5yd.onion", + "smp://enEkec4hlR3UtKx2NMpOUK_K4ZuDxjWBO1d9Y4YXVaA=@smp14.simplex.im,aspkyu2sopsnizbyfabtsicikr2s4r3ti35jogbcekhm3fsoeyjvgrid.onion" + ] _defaultNtfServers :: [NtfServer] _defaultNtfServers = @@ -301,8 +315,8 @@ newChatController configServers :: DefaultAgentServers configServers = let DefaultAgentServers {smp = defSmp, xftp = defXftp} = defaultServers - smp' = fromMaybe defSmp (nonEmpty smpServers) - xftp' = fromMaybe defXftp (nonEmpty xftpServers) + smp' = maybe defSmp (L.map enabledServerCfg) (nonEmpty smpServers) + xftp' = maybe defXftp (L.map enabledServerCfg) (nonEmpty xftpServers) in defaultServers {smp = smp', xftp = xftp', netCfg = updateNetworkConfig defaultNetworkConfig simpleNetCfg} agentServers :: ChatConfig -> IO InitialAgentServers agentServers config@ChatConfig {defaultServers = defServers@DefaultAgentServers {ntf, netCfg}} = do @@ -311,15 +325,15 @@ newChatController xftp' <- getUserServers users SPXFTP pure InitialAgentServers {smp = smp', xftp = xftp', ntf, netCfg} where - getUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => [User] -> SProtocolType p -> IO (Map UserId (NonEmpty (ProtoServerWithAuth p))) + getUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => [User] -> SProtocolType p -> IO (Map UserId (NonEmpty (ServerCfg p))) getUserServers users protocol = case users of [] -> pure $ M.fromList [(1, cfgServers protocol defServers)] _ -> M.fromList <$> initialServers where - initialServers :: IO [(UserId, NonEmpty (ProtoServerWithAuth p))] + initialServers :: IO [(UserId, NonEmpty (ServerCfg p))] initialServers = mapM (\u -> (aUserId u,) <$> userServers u) users - userServers :: User -> IO (NonEmpty (ProtoServerWithAuth p)) - userServers user' = activeAgentServers config protocol <$> withTransaction chatStore (`getProtocolServers` user') + userServers :: User -> IO (NonEmpty (ServerCfg p)) + userServers user' = useServers config protocol <$> withTransaction chatStore (`getProtocolServers` user') updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig updateNetworkConfig cfg SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} = @@ -362,14 +376,10 @@ withFileLock :: String -> Int64 -> CM a -> CM a withFileLock name = withEntityLock name . CLFile {-# INLINE withFileLock #-} -activeAgentServers :: UserProtocol p => ChatConfig -> SProtocolType p -> [ServerCfg p] -> NonEmpty (ProtoServerWithAuth p) -activeAgentServers ChatConfig {defaultServers} p = - fromMaybe (cfgServers p defaultServers) - . nonEmpty - . map (\ServerCfg {server} -> server) - . filter (\ServerCfg {enabled} -> enabled == SEEnabled) +useServers :: UserProtocol p => ChatConfig -> SProtocolType p -> [ServerCfg p] -> NonEmpty (ServerCfg p) +useServers ChatConfig {defaultServers} p = fromMaybe (cfgServers p defaultServers) . nonEmpty -cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ProtoServerWithAuth p)) +cfgServers :: UserProtocol p => SProtocolType p -> (DefaultAgentServers -> NonEmpty (ServerCfg p)) cfgServers p DefaultAgentServers {smp, xftp} = case p of SPSMP -> smp SPXFTP -> xftp @@ -529,18 +539,17 @@ processChatCommand' vr = \case atomically . writeTVar u $ Just user pure $ CRActiveUser user where - chooseServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> CM (NonEmpty (ProtoServerWithAuth p), [ServerCfg p]) + chooseServers :: (ProtocolTypeI p, UserProtocol p) => SProtocolType p -> CM (NonEmpty (ServerCfg p), [ServerCfg p]) chooseServers protocol | sameServers = asks currentUser >>= readTVarIO >>= \case Nothing -> throwChatError CENoActiveUser - Just user -> do - servers <- withStore' (`getProtocolServers` user) - cfg <- asks config - pure (activeAgentServers cfg protocol servers, servers) - | otherwise = do - defServers <- asks $ defaultServers . config - pure (cfgServers protocol defServers, []) + Just user -> chosenServers =<< withStore' (`getProtocolServers` user) + | otherwise = chosenServers [] + where + chosenServers servers = do + cfg <- asks config + pure (useServers cfg protocol servers, servers) storeServers user servers = unless (null servers) . withStore $ \db -> overwriteProtocolServers db user servers @@ -1307,20 +1316,18 @@ processChatCommand' vr = \case withStore (\db -> Just <$> getConnectionEntity db vr user agentConnId) `catchChatError` (\e -> toView (CRChatError (Just user) e) $> Nothing) pure CRNtfMessages {user_, connEntity_, msgTs = msgTs', ntfMessages = map ntfMsgInfo msgs} APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do - ChatConfig {defaultServers} <- asks config + cfg@ChatConfig {defaultServers} <- asks config servers <- withStore' (`getProtocolServers` user) - let defServers = cfgServers p defaultServers - servers' = fromMaybe (L.map toServerCfg defServers) $ nonEmpty servers - pure $ CRUserProtoServers user $ AUPS $ UserProtoServers p servers' defServers - where - toServerCfg server = ServerCfg {server, preset = True, tested = Nothing, enabled = SEEnabled} + pure $ CRUserProtoServers user $ AUPS $ UserProtoServers p (useServers cfg p servers) (cfgServers p defaultServers) GetUserProtoServers aProtocol -> withUser $ \User {userId} -> processChatCommand $ APIGetUserProtoServers userId aProtocol - APISetUserProtoServers userId (APSC p (ProtoServersConfig servers)) -> withUserId userId $ \user -> withServerProtocol p $ do - withStore $ \db -> overwriteProtocolServers db user servers - cfg <- asks config - lift $ withAgent' $ \a -> setProtocolServers a (aUserId user) $ activeAgentServers cfg p servers - ok user + APISetUserProtoServers userId (APSC p (ProtoServersConfig servers)) + | null servers || any (\ServerCfg {enabled} -> enabled) servers -> withUserId userId $ \user -> withServerProtocol p $ do + withStore $ \db -> overwriteProtocolServers db user servers + cfg <- asks config + lift $ withAgent' $ \a -> setProtocolServers a (aUserId user) $ useServers cfg p servers + ok user + | otherwise -> withUserId userId $ \user -> pure $ chatCmdError (Just user) "all servers are disabled" SetUserProtoServers serversConfig -> withUser $ \User {userId} -> processChatCommand $ APISetUserProtoServers userId serversConfig APITestProtoServer userId srv@(AProtoServerWithAuth _ server) -> withUserId userId $ \user -> @@ -2254,18 +2261,10 @@ processChatCommand' vr = \case GetAgentServersSummary userId -> withUserId userId $ \user -> do agentServersSummary <- lift $ withAgent' getAgentServersSummary users <- withStore' getUsers - smpServers <- getUserServers user SPSMP - xftpServers <- getUserServers user SPXFTP + smpServers <- getUserProtocolServers user SPSMP + xftpServers <- getUserProtocolServers user SPXFTP let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers pure $ CRAgentServersSummary user presentedServersSummary - where - getUserServers :: forall p. (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM [ProtocolServer p] - getUserServers users protocol = do - ChatConfig {defaultServers} <- asks config - let defServers = cfgServers protocol defaultServers - servers <- map (\ServerCfg {server} -> server) <$> withStore' (`getProtocolServers` users) - let srvs = if null servers then L.toList defServers else servers - pure $ map protoServer srvs ResetAgentServersStats -> withAgent resetAgentServersStats >> ok_ GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails @@ -3224,10 +3223,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} S.toList $ S.fromList $ concatMap (\FD.FileChunk {replicas} -> map (\FD.FileChunkReplica {server} -> server) replicas) chunks getUnknownSrvs :: [XFTPServer] -> CM [XFTPServer] getUnknownSrvs srvs = do - ChatConfig {defaultServers = DefaultAgentServers {xftp = defXftp}} <- asks config - storedSrvs <- map (\ServerCfg {server} -> protoServer server) <$> withStore' (`getProtocolServers` user) - let defXftp' = L.map protoServer defXftp - knownSrvs = fromMaybe defXftp' $ nonEmpty storedSrvs + knownSrvs <- getUserProtocolServers user SPXFTP pure $ filter (`notElem` knownSrvs) srvs ipProtectedForSrvs :: [XFTPServer] -> CM Bool ipProtectedForSrvs srvs = do @@ -3239,6 +3235,11 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs +getUserProtocolServers :: (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM (NonEmpty (ProtocolServer p)) +getUserProtocolServers user p = do + cfg <- asks config + L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> withStore' (`getProtocolServers` user) + getNetworkConfig :: CM' NetworkConfig getNetworkConfig = withAgent' $ liftIO . getNetworkConfig' @@ -6301,7 +6302,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = updateDirectItemsStatus ct conn msgIds newStatus = do cis_ <- withStore' $ \db -> forM msgIds $ \msgId -> runExceptT $ updateDirectItemStatus' db ct conn msgId newStatus -- only send the last expired item event to view - case catMaybes $ rights $ reverse cis_ of + case reverse $ catMaybes $ rights cis_ of ci : _ -> toView $ CRChatItemStatusUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) _ -> pure () @@ -6786,7 +6787,7 @@ deliverMessagesB msgReqs = do sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, GroupSndResult) sendGroupMessage user gInfo members chatMsgEvent = do when shouldSendProfileUpdate $ - sendProfileUpdate `catchChatError` (\e -> toView (CRChatError (Just user) e)) + sendProfileUpdate `catchChatError` (toView . CRChatError (Just user)) sendGroupMessage' user gInfo members chatMsgEvent where User {profile = p, userMemberProfileUpdatedAt} = user @@ -6891,7 +6892,7 @@ memberSendAction gInfo chatMsgEvent members m = case memberConn m of sendGroupMemberMessage :: MsgEncodingI e => User -> GroupInfo -> GroupMember -> ChatMsgEvent e -> Maybe Int64 -> CM () -> CM () sendGroupMemberMessage user gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId} chatMsgEvent introId_ postDeliver = do msg <- createSndMessage chatMsgEvent (GroupId groupId) - messageMember msg `catchChatError` (\e -> toView (CRChatError (Just user) e)) + messageMember msg `catchChatError` (toView . CRChatError (Just user)) where messageMember :: SndMessage -> CM () messageMember SndMessage {msgId, msgBody} = forM_ (memberSendAction gInfo chatMsgEvent [m] m) $ \case @@ -7124,7 +7125,7 @@ agentXFTPDeleteSndFilesRemote user sndFiles = do (sfsNoDescr, sfsWithDescr) <- partitionSndDescr sndFilesAll' [] [] withAgent' $ \a -> xftpDeleteSndFilesInternal a sfsNoDescr withAgent' $ \a -> xftpDeleteSndFilesRemote a (aUserId user) sfsWithDescr - void . withStoreBatch' $ \db -> map (setSndFTAgentDeleted db user . (\(_, fId) -> fId)) sndFilesAll' + void . withStoreBatch' $ \db -> map (setSndFTAgentDeleted db user . snd) sndFilesAll' where mapRedirectMeta :: FileTransferMeta -> Maybe (XFTPSndFile, FileTransferId) mapRedirectMeta FileTransferMeta {fileId = fileId, xftpSndFile = Just sndFileRedirect} = Just (sndFileRedirect, fileId) @@ -7444,9 +7445,9 @@ chatCommandP = "/xftp test " *> (TestProtoServer . AProtoServerWithAuth SPXFTP <$> strP), "/ntf test " *> (TestProtoServer . AProtoServerWithAuth SPNTF <$> strP), "/_servers " *> (APISetUserProtoServers <$> A.decimal <* A.space <*> srvCfgP), - "/smp " *> (SetUserProtoServers . APSC SPSMP . ProtoServersConfig . map toServerCfg <$> protocolServersP), + "/smp " *> (SetUserProtoServers . APSC SPSMP . ProtoServersConfig . map enabledServerCfg <$> protocolServersP), "/smp default" $> SetUserProtoServers (APSC SPSMP $ ProtoServersConfig []), - "/xftp " *> (SetUserProtoServers . APSC SPXFTP . ProtoServersConfig . map toServerCfg <$> protocolServersP), + "/xftp " *> (SetUserProtoServers . APSC SPXFTP . ProtoServersConfig . map enabledServerCfg <$> protocolServersP), "/xftp default" $> SetUserProtoServers (APSC SPXFTP $ ProtoServersConfig []), "/_servers " *> (APIGetUserProtoServers <$> A.decimal <* A.space <*> strP), "/smp" $> GetUserProtoServers (AProtocolType SPSMP), @@ -7785,7 +7786,6 @@ chatCommandP = (Just <$> (AutoAccept <$> (" incognito=" *> onOffP <|> pure False) <*> optional (A.space *> msgContentP))) (pure Nothing) srvCfgP = strP >>= \case AProtocolType p -> APSC p <$> (A.space *> jsonP) - toServerCfg server = ServerCfg {server, preset = False, tested = Nothing, enabled = SEEnabled} rcCtrlAddressP = RCCtrlAddress <$> ("addr=" *> strP) <*> (" iface=" *> (jsonP <|> text1P)) text1P = safeDecodeUtf8 <$> A.takeTill (== ' ') char_ = optional . A.char diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index fdb07b0bd9..6c127264d2 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -69,8 +69,8 @@ import Simplex.Chat.Types.UITheme import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) -import Simplex.Messaging.Agent.Client (AgentLocks, ServerQueueInfo, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, UserNetworkInfo) -import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) +import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, ServerQueueInfo, UserNetworkInfo) +import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction) @@ -84,7 +84,7 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, XFTPServerWithAuth, userProtocol) +import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, NtfServer, ProtocolType (..), ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, userProtocol) import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) import Simplex.Messaging.Transport.Client (SocksProxy, TransportHost) @@ -173,9 +173,9 @@ defaultChatHooks = } data DefaultAgentServers = DefaultAgentServers - { smp :: NonEmpty SMPServerWithAuth, + { smp :: NonEmpty (ServerCfg 'PSMP), ntf :: [NtfServer], - xftp :: NonEmpty XFTPServerWithAuth, + xftp :: NonEmpty (ServerCfg 'PXFTP), netCfg :: NetworkConfig } @@ -930,7 +930,7 @@ deriving instance Show AProtoServersConfig data UserProtoServers p = UserProtoServers { serverProtocol :: SProtocolType p, protoServers :: NonEmpty (ServerCfg p), - presetServers :: NonEmpty (ProtoServerWithAuth p) + presetServers :: NonEmpty (ServerCfg p) } deriving (Show) diff --git a/src/Simplex/Chat/Stats.hs b/src/Simplex/Chat/Stats.hs index f14353f0e0..334fbb2c11 100644 --- a/src/Simplex/Chat/Stats.hs +++ b/src/Simplex/Chat/Stats.hs @@ -6,6 +6,7 @@ module Simplex.Chat.Stats where import qualified Data.Aeson.TH as J +import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe, isJust) @@ -106,7 +107,7 @@ data XFTPServerSummary = XFTPServerSummary -- - users are passed to exclude hidden users from totalServersSummary; -- - if currentUser is hidden, it should be accounted in totalServersSummary; -- - known is set only in user level summaries based on passed userSMPSrvs and userXFTPSrvs -toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> [SMPServer] -> [XFTPServer] -> PresentedServersSummary +toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> NonEmpty SMPServer -> NonEmpty XFTPServer -> PresentedServersSummary toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrvs = do let (userSMPSrvsSumms, allSMPSrvsSumms) = accSMPSrvsSummaries (userSMPTotals, allSMPTotals) = (accSMPTotals userSMPSrvsSumms, accSMPTotals allSMPSrvsSumms) diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index a740389c6a..cad06448e6 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -84,6 +84,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme +import Simplex.Messaging.Agent.Env.SQLite (ServerCfg (..)) import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB @@ -523,10 +524,9 @@ getProtocolServers db User {userId} = (userId, decodeLatin1 $ strEncode protocol) where protocol = protocolTypeI @p - toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Int) -> ServerCfg p - toServerCfg (host, port, keyHash, auth_, preset, tested, enabledInt) = + toServerCfg :: (NonEmpty TransportHost, String, C.KeyHash, Maybe Text, Bool, Maybe Bool, Bool) -> ServerCfg p + toServerCfg (host, port, keyHash, auth_, preset, tested, enabled) = let server = ProtoServerWithAuth (ProtocolServer protocol host port keyHash) (BasicAuth . encodeUtf8 <$> auth_) - enabled = toServerEnabled enabledInt in ServerCfg {server, preset, tested, enabled} overwriteProtocolServers :: forall p. ProtocolTypeI p => DB.Connection -> User -> [ServerCfg p] -> ExceptT StoreError IO () @@ -543,7 +543,7 @@ overwriteProtocolServers db User {userId} servers = (protocol, host, port, key_hash, basic_auth, preset, tested, enabled, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?) |] - ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, fromServerEnabled enabled, userId, currentTs, currentTs)) + ((protocol, host, port, keyHash, safeDecodeUtf8 . unBasicAuth <$> auth_) :. (preset, tested, enabled, userId, currentTs, currentTs)) pure $ Right () where protocol = decodeLatin1 $ strEncode $ protocolTypeI @p diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index 2060e529eb..ca9b2edb65 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -21,6 +21,7 @@ import Simplex.Chat.Options import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) +import Simplex.Messaging.Agent.Env.SQLite (presetServerCfg) import Simplex.Messaging.Client (defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) import System.IO (hFlush, hSetEcho, stdin, stdout) @@ -31,13 +32,15 @@ terminalChatConfig = { defaultServers = DefaultAgentServers { smp = - L.fromList - [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", - "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", - "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" - ], + L.fromList $ + map + (presetServerCfg True) + [ "smp://u2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU=@smp4.simplex.im,o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion", + "smp://hpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg=@smp5.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion", + "smp://PQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo=@smp6.simplex.im,bylepyau3ty4czmn77q4fglvperknl4bi2eb2fdy2bh4jxtf32kf73yd.onion" + ], ntf = ["ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,ntg7jdjy2i3qbib3sykiho3enekwiaqg3icctliqhtqcg6jmoh6cxiad.onion"], - xftp = defaultXFTPServers, + xftp = L.map (presetServerCfg True) defaultXFTPServers, netCfg = defaultNetworkConfig }, deviceNameForRemote = "SimpleX CLI" diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 07c9afa2e2..04cc2bcc6f 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -53,7 +53,6 @@ import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON) -import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI) import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version import Simplex.Messaging.Version.Internal @@ -1628,46 +1627,6 @@ data NoteFolder = NoteFolder type NoteFolderId = Int64 -data ServerCfg p = ServerCfg - { server :: ProtoServerWithAuth p, - preset :: Bool, - tested :: Maybe Bool, - enabled :: ServerEnabled - } - deriving (Show) - -data ServerEnabled - = SEDisabled - | SEEnabled - | -- server is marked as known, but it's not in the list of configured servers; - -- e.g., it may be added via an unknown server dialogue and user didn't manually configure it, - -- meaning server wasn't tested (or at least such option wasn't presented in UI) - -- and it may be inoperable for user due to server password - SEKnown - deriving (Eq, Show) - -pattern DBSEDisabled :: Int -pattern DBSEDisabled = 0 - -pattern DBSEEnabled :: Int -pattern DBSEEnabled = 1 - -pattern DBSEKnown :: Int -pattern DBSEKnown = 2 - -toServerEnabled :: Int -> ServerEnabled -toServerEnabled = \case - DBSEDisabled -> SEDisabled - DBSEEnabled -> SEEnabled - DBSEKnown -> SEKnown - _ -> SEDisabled - -fromServerEnabled :: ServerEnabled -> Int -fromServerEnabled = \case - SEDisabled -> DBSEDisabled - SEEnabled -> DBSEEnabled - SEKnown -> DBSEKnown - data ChatVersion instance VersionScope ChatVersion @@ -1795,12 +1754,3 @@ $(JQ.deriveJSON defaultJSON ''Contact) $(JQ.deriveJSON defaultJSON ''ContactRef) $(JQ.deriveJSON defaultJSON ''NoteFolder) - -$(JQ.deriveJSON (enumJSON $ dropPrefix "SE") ''ServerEnabled) - -instance ProtocolTypeI p => ToJSON (ServerCfg p) where - toEncoding = $(JQ.mkToEncoding defaultJSON ''ServerCfg) - toJSON = $(JQ.mkToJSON defaultJSON ''ServerCfg) - -instance ProtocolTypeI p => FromJSON (ServerCfg p) where - parseJSON = $(JQ.mkParseJSON defaultJSON ''ServerCfg) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 04c06103ea..4dffa41dea 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -52,7 +52,7 @@ import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) -import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) +import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..), ServerCfg (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..)) @@ -62,7 +62,7 @@ import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON) -import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtoServerWithAuth, ProtocolServer (..), ProtocolTypeI, SProtocolType (..)) +import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtocolServer (..), ProtocolTypeI, SProtocolType (..)) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Util (safeDecodeUtf8, tshow) @@ -1183,8 +1183,8 @@ viewUserServers (AUPS UserProtoServers {serverProtocol = p, protoServers, preset pName = protocolName p customServers = if null protoServers - then ("no " <> pName <> " servers saved, using presets: ") : viewServers id presetServers - else viewServers (\ServerCfg {server} -> server) protoServers + then ("no " <> pName <> " servers saved, using presets: ") : viewServers presetServers + else viewServers protoServers protocolName :: ProtocolTypeI p => SProtocolType p -> StyledString protocolName = plain . map toUpper . T.unpack . decodeLatin1 . strEncode @@ -1281,8 +1281,8 @@ viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} = ["receiving messages via: " <> viewRcvQueuesInfo rcvQueuesInfo | not $ null rcvQueuesInfo] <> ["sending messages via: " <> viewSndQueuesInfo sndQueuesInfo | not $ null sndQueuesInfo] -viewServers :: ProtocolTypeI p => (a -> ProtoServerWithAuth p) -> NonEmpty a -> [StyledString] -viewServers f = map (plain . B.unpack . strEncode . f) . L.toList +viewServers :: ProtocolTypeI p => NonEmpty (ServerCfg p) -> [StyledString] +viewServers = map (plain . B.unpack . strEncode . (\ServerCfg {server} -> server)) . L.toList viewRcvQueuesInfo :: [RcvQueueInfo] -> StyledString viewRcvQueuesInfo = plain . intercalate ", " . map showQueueInfo From 70204e071deccd65763f77cbabbce6a079b5939b Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 12 Jul 2024 21:03:30 +0400 Subject: [PATCH 19/57] ui: split servers into lists; allow to enable only tested servers (#4445) --- .../UserSettings/ProtocolServerView.swift | 12 +- .../UserSettings/ProtocolServersView.swift | 164 +++++++++++------- .../UserSettings/ScanProtocolServer.swift | 2 +- .../ar.xcloc/Localized Contents/ar.xliff | 4 +- .../bg.xcloc/Localized Contents/bg.xliff | 6 +- .../bn.xcloc/Localized Contents/bn.xliff | 4 +- .../cs.xcloc/Localized Contents/cs.xliff | 6 +- .../de.xcloc/Localized Contents/de.xliff | 6 +- .../el.xcloc/Localized Contents/el.xliff | 4 +- .../en.xcloc/Localized Contents/en.xliff | 6 +- .../es.xcloc/Localized Contents/es.xliff | 6 +- .../fi.xcloc/Localized Contents/fi.xliff | 6 +- .../fr.xcloc/Localized Contents/fr.xliff | 6 +- .../he.xcloc/Localized Contents/he.xliff | 6 +- .../hi.xcloc/Localized Contents/hi.xliff | 4 +- .../hr.xcloc/Localized Contents/hr.xliff | 4 +- .../hu.xcloc/Localized Contents/hu.xliff | 6 +- .../it.xcloc/Localized Contents/it.xliff | 6 +- .../ja.xcloc/Localized Contents/ja.xliff | 6 +- .../ko.xcloc/Localized Contents/ko.xliff | 4 +- .../lt.xcloc/Localized Contents/lt.xliff | 6 +- .../ml.xcloc/Localized Contents/ml.xliff | 4 +- .../nl.xcloc/Localized Contents/nl.xliff | 6 +- .../pl.xcloc/Localized Contents/pl.xliff | 6 +- .../Localized Contents/pt-BR.xliff | 6 +- .../pt.xcloc/Localized Contents/pt.xliff | 4 +- .../ru.xcloc/Localized Contents/ru.xliff | 6 +- .../th.xcloc/Localized Contents/th.xliff | 6 +- .../tr.xcloc/Localized Contents/tr.xliff | 6 +- .../uk.xcloc/Localized Contents/uk.xliff | 6 +- .../Localized Contents/zh-Hans.xliff | 6 +- .../Localized Contents/zh-Hant.xliff | 6 +- apps/ios/SimpleXChat/APITypes.swift | 22 +-- apps/ios/bg.lproj/Localizable.strings | 2 +- apps/ios/cs.lproj/Localizable.strings | 2 +- apps/ios/de.lproj/Localizable.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 2 +- apps/ios/fi.lproj/Localizable.strings | 2 +- apps/ios/fr.lproj/Localizable.strings | 2 +- apps/ios/hu.lproj/Localizable.strings | 2 +- apps/ios/it.lproj/Localizable.strings | 2 +- apps/ios/ja.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 2 +- apps/ios/pl.lproj/Localizable.strings | 2 +- apps/ios/ru.lproj/Localizable.strings | 2 +- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 2 +- apps/ios/uk.lproj/Localizable.strings | 2 +- apps/ios/zh-Hans.lproj/Localizable.strings | 2 +- .../chat/simplex/common/model/SimpleXAPI.kt | 19 +- .../views/onboarding/LinkAMobileView.kt | 2 +- .../common/views/remote/ConnectDesktopView.kt | 6 +- .../common/views/remote/ConnectMobileView.kt | 2 +- .../views/usersettings/ProtocolServerView.kt | 12 +- .../views/usersettings/ProtocolServersView.kt | 74 +++++--- .../views/usersettings/ScanProtocolServer.kt | 3 +- .../common/views/usersettings/SettingsView.kt | 4 +- .../commonMain/resources/MR/ar/strings.xml | 2 +- .../commonMain/resources/MR/base/strings.xml | 6 +- .../commonMain/resources/MR/bg/strings.xml | 2 +- .../commonMain/resources/MR/bn/strings.xml | 2 +- .../commonMain/resources/MR/cs/strings.xml | 2 +- .../commonMain/resources/MR/de/strings.xml | 2 +- .../commonMain/resources/MR/el/strings.xml | 2 +- .../commonMain/resources/MR/es/strings.xml | 2 +- .../commonMain/resources/MR/fa/strings.xml | 2 +- .../commonMain/resources/MR/fi/strings.xml | 2 +- .../commonMain/resources/MR/fr/strings.xml | 2 +- .../commonMain/resources/MR/hi/strings.xml | 2 +- .../commonMain/resources/MR/hu/strings.xml | 2 +- .../commonMain/resources/MR/it/strings.xml | 2 +- .../commonMain/resources/MR/iw/strings.xml | 2 +- .../commonMain/resources/MR/ja/strings.xml | 2 +- .../commonMain/resources/MR/ko/strings.xml | 2 +- .../commonMain/resources/MR/lt/strings.xml | 2 +- .../commonMain/resources/MR/ml/strings.xml | 2 +- .../commonMain/resources/MR/nl/strings.xml | 2 +- .../commonMain/resources/MR/pl/strings.xml | 2 +- .../resources/MR/pt-rBR/strings.xml | 2 +- .../commonMain/resources/MR/pt/strings.xml | 2 +- .../commonMain/resources/MR/ro/strings.xml | 2 +- .../commonMain/resources/MR/ru/strings.xml | 2 +- .../commonMain/resources/MR/th/strings.xml | 2 +- .../commonMain/resources/MR/tr/strings.xml | 2 +- .../commonMain/resources/MR/uk/strings.xml | 2 +- .../commonMain/resources/MR/vi/strings.xml | 2 +- .../resources/MR/zh-rCN/strings.xml | 2 +- .../resources/MR/zh-rTW/strings.xml | 2 +- 88 files changed, 320 insertions(+), 258 deletions(-) diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift index f4e5459613..6433168810 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift @@ -15,7 +15,6 @@ struct ProtocolServerView: View { let serverProtocol: ServerProtocol @Binding var server: ServerCfg @State var serverToEdit: ServerCfg - @State var serverEnabled: Bool @State private var showTestFailure = false @State private var testing = false @State private var testFailure: ProtocolTestFailure? @@ -113,10 +112,10 @@ struct ProtocolServerView: View { Spacer() showTestStatus(server: serverToEdit) } - Toggle("Use for new connections", isOn: $serverEnabled) - .onChange(of: serverEnabled) { enabled in - serverToEdit.enabled = enabled ? .enabled : .disabled - } + let useForNewDisabled = serverToEdit.tested != true && !serverToEdit.preset + Toggle("Use for new connections", isOn: $serverToEdit.enabled) + .disabled(useForNewDisabled) + .foregroundColor(useForNewDisabled ? theme.colors.secondary : theme.colors.onBackground) } } } @@ -185,8 +184,7 @@ struct ProtocolServerView_Previews: PreviewProvider { ProtocolServerView( serverProtocol: .smp, server: Binding.constant(ServerCfg.sampleData.custom), - serverToEdit: ServerCfg.sampleData.custom, - serverEnabled: true + serverToEdit: ServerCfg.sampleData.custom ) } } diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift index 857bab69fb..ea1953e4ac 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift @@ -18,8 +18,9 @@ struct ProtocolServersView: View { @Environment(\.editMode) private var editMode let serverProtocol: ServerProtocol @State private var currServers: [ServerCfg] = [] - @State private var presetServers: [String] = [] - @State private var servers: [ServerCfg] = [] + @State private var presetServers: [ServerCfg] = [] + @State private var configuredServers: [ServerCfg] = [] + @State private var otherServers: [ServerCfg] = [] @State private var selectedServer: String? = nil @State private var showAddServer = false @State private var showScanProtoServer = false @@ -53,31 +54,53 @@ struct ProtocolServersView: View { private func protocolServersView() -> some View { List { - Section { - ForEach($servers) { srv in - protocolServerView(srv) + if !configuredServers.isEmpty { + Section { + ForEach($configuredServers) { srv in + protocolServerView(srv) + } + .onMove { indexSet, offset in + configuredServers.move(fromOffsets: indexSet, toOffset: offset) + } + .onDelete { indexSet in + configuredServers.remove(atOffsets: indexSet) + } + } header: { + Text("Configured \(proto) servers") + .foregroundColor(theme.colors.secondary) + } footer: { + Text("The servers for new connections of your current chat profile **\(m.currentUser?.displayName ?? "")**.") + .foregroundColor(theme.colors.secondary) + .lineLimit(10) } - .onMove { indexSet, offset in - servers.move(fromOffsets: indexSet, toOffset: offset) + } + + if !otherServers.isEmpty { + Section { + ForEach($otherServers) { srv in + protocolServerView(srv) + } + .onMove { indexSet, offset in + otherServers.move(fromOffsets: indexSet, toOffset: offset) + } + .onDelete { indexSet in + otherServers.remove(atOffsets: indexSet) + } + } header: { + Text("Other \(proto) servers") + .foregroundColor(theme.colors.secondary) } - .onDelete { indexSet in - servers.remove(atOffsets: indexSet) - } - Button("Add server…") { - showAddServer = true - } - } header: { - Text("\(proto) servers") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("The servers for new connections of your current chat profile **\(m.currentUser?.displayName ?? "")**.") - .foregroundColor(theme.colors.secondary) - .lineLimit(10) } Section { - Button("Reset") { servers = currServers } - .disabled(servers == currServers || testing) + Button("Add server") { + showAddServer = true + } + } + + Section { + Button("Reset") { partitionServers(currServers) } + .disabled(Set(allServers) == Set(currServers) || testing) Button("Test servers", action: testServers) .disabled(testing || allServersDisabled) Button("Save servers", action: saveServers) @@ -86,17 +109,17 @@ struct ProtocolServersView: View { } } .toolbar { EditButton() } - .confirmationDialog("Add server…", isPresented: $showAddServer, titleVisibility: .hidden) { + .confirmationDialog("Add server", isPresented: $showAddServer, titleVisibility: .hidden) { Button("Enter server manually") { - servers.append(ServerCfg.empty) - selectedServer = servers.last?.id + otherServers.append(ServerCfg.empty) + selectedServer = allServers.last?.id } Button("Scan server QR code") { showScanProtoServer = true } Button("Add preset servers", action: addAllPresets) .disabled(hasAllPresets()) } .sheet(isPresented: $showScanProtoServer) { - ScanProtocolServer(servers: $servers) + ScanProtocolServer(servers: $otherServers) .modifier(ThemedBackground(grouped: true)) } .modifier(BackButton(disabled: Binding.constant(false)) { @@ -133,27 +156,39 @@ struct ProtocolServersView: View { } .onAppear { // this condition is needed to prevent re-setting the servers when exiting single server view - if !justOpened { return } - do { - let r = try getUserProtoServers(serverProtocol) - currServers = r.protoServers - presetServers = r.presetServers - servers = currServers - } catch let error { - alert = .error( - title: "Error loading \(proto) servers", - error: "Error: \(responseError(error))" - ) + if justOpened { + do { + let r = try getUserProtoServers(serverProtocol) + currServers = r.protoServers + presetServers = r.presetServers + partitionServers(currServers) + } catch let error { + alert = .error( + title: "Error loading \(proto) servers", + error: "Error: \(responseError(error))" + ) + } + justOpened = false + } else { + partitionServers(allServers) } - justOpened = false } } + private func partitionServers(_ servers: [ServerCfg]) { + configuredServers = servers.filter { $0.preset || $0.enabled } + otherServers = servers.filter { !($0.preset || $0.enabled) } + } + + private var allServers: [ServerCfg] { + configuredServers + otherServers + } + private var saveDisabled: Bool { - servers.isEmpty || - servers == currServers || + allServers.isEmpty || + Set(allServers) == Set(currServers) || testing || - !servers.allSatisfy { srv in + !allServers.allSatisfy { srv in if let address = parseServerAddress(srv.server) { return uniqueAddress(srv, address) } @@ -163,7 +198,7 @@ struct ProtocolServersView: View { } private var allServersDisabled: Bool { - servers.allSatisfy { $0.enabled != .enabled } + allServers.allSatisfy { !$0.enabled } } private func protocolServerView(_ server: Binding) -> some View { @@ -172,8 +207,7 @@ struct ProtocolServersView: View { ProtocolServerView( serverProtocol: serverProtocol, server: server, - serverToEdit: srv, - serverEnabled: srv.enabled == .enabled + serverToEdit: srv ) .navigationBarTitle(srv.preset ? "Preset server" : "Your server") .modifier(ThemedBackground(grouped: true)) @@ -187,7 +221,7 @@ struct ProtocolServersView: View { invalidServer() } else if !uniqueAddress(srv, address) { Image(systemName: "exclamationmark.circle").foregroundColor(.red) - } else if srv.enabled != .enabled { + } else if !srv.enabled { Image(systemName: "slash.circle").foregroundColor(theme.colors.secondary) } else { showTestStatus(server: srv) @@ -200,7 +234,7 @@ struct ProtocolServersView: View { .padding(.trailing, 4) let v = Text(address?.hostnames.first ?? srv.server).lineLimit(1) - if srv.enabled == .enabled { + if srv.enabled { v } else { v.foregroundColor(theme.colors.secondary) @@ -227,7 +261,7 @@ struct ProtocolServersView: View { } private func uniqueAddress(_ s: ServerCfg, _ address: ServerAddress) -> Bool { - servers.allSatisfy { srv in + allServers.allSatisfy { srv in address.hostnames.allSatisfy { host in srv.id == s.id || !srv.server.contains(host) } @@ -241,13 +275,13 @@ struct ProtocolServersView: View { private func addAllPresets() { for srv in presetServers { if !hasPreset(srv) { - servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: .enabled)) + configuredServers.append(srv) } } } - private func hasPreset(_ srv: String) -> Bool { - servers.contains(where: { $0.server == srv }) + private func hasPreset(_ srv: ServerCfg) -> Bool { + allServers.contains(where: { $0.server == srv.server }) } private func testServers() { @@ -265,19 +299,31 @@ struct ProtocolServersView: View { } private func resetTestStatus() { - for i in 0.. [String: ProtocolTestFailure] { var fs: [String: ProtocolTestFailure] = [:] - for i in 0..Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 7ac1db1557..f3a70390da 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -615,9 +615,9 @@ Добави сървъри чрез сканиране на QR кодове. No comment provided by engineer. - - Add server… - Добави сървър… + + Add server + Добави сървър No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff index f599f9c300..b92196b78b 100644 --- a/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff +++ b/apps/ios/SimpleX Localizations/bn.xcloc/Localized Contents/bn.xliff @@ -386,8 +386,8 @@ Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 392b9cf70d..46a890f5ba 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -596,9 +596,9 @@ Přidejte servery skenováním QR kódů. No comment provided by engineer. - - Add server… - Přidat server… + + Add server + Přidat server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index cc2773d5e4..03b9ae1d5e 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -615,9 +615,9 @@ Fügen Sie Server durch Scannen der QR Codes hinzu. No comment provided by engineer. - - Add server… - Füge Server hinzu… + + Add server + Füge Server hinzu No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff index 18051ae350..b8432a33b6 100644 --- a/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff +++ b/apps/ios/SimpleX Localizations/el.xcloc/Localized Contents/el.xliff @@ -336,8 +336,8 @@ Available in v5.1 Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 08e1255199..29f3f30d89 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -618,9 +618,9 @@ Add servers by scanning QR codes. No comment provided by engineer. - - Add server… - Add server… + + Add server + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 3e37e6c6d5..4c4e416a7f 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -615,9 +615,9 @@ Añadir servidores mediante el escaneo de códigos QR. No comment provided by engineer. - - Add server… - Añadir servidor… + + Add server + Añadir servidor No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 71e0f0796a..e132584d0e 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -591,9 +591,9 @@ Lisää palvelimia skannaamalla QR-koodeja. No comment provided by engineer. - - Add server… - Lisää palvelin… + + Add server + Lisää palvelin No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 4f618f26b0..aa26193e2a 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -615,9 +615,9 @@ Ajoutez des serveurs en scannant des codes QR. No comment provided by engineer. - - Add server… - Ajouter un serveur… + + Add server + Ajouter un serveur No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff index 45cfe0c468..ee628c7935 100644 --- a/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff +++ b/apps/ios/SimpleX Localizations/he.xcloc/Localized Contents/he.xliff @@ -403,9 +403,9 @@ Available in v5.1 הוספת שרתים על ידי סריקת קוד QR. No comment provided by engineer. - - Add server… - הוסף שרת… + + Add server + הוסף שרת No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hi.xcloc/Localized Contents/hi.xliff b/apps/ios/SimpleX Localizations/hi.xcloc/Localized Contents/hi.xliff index 31746eccd9..a4ed3b39d8 100644 --- a/apps/ios/SimpleX Localizations/hi.xcloc/Localized Contents/hi.xliff +++ b/apps/ios/SimpleX Localizations/hi.xcloc/Localized Contents/hi.xliff @@ -300,8 +300,8 @@ Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff index abf15ee42d..50f5536e5e 100644 --- a/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff +++ b/apps/ios/SimpleX Localizations/hr.xcloc/Localized Contents/hr.xliff @@ -367,8 +367,8 @@ Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index ecc0610115..e70d844e4e 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -615,9 +615,9 @@ Kiszolgáló hozzáadása QR-kód beolvasásával. No comment provided by engineer. - - Add server… - Kiszolgáló hozzáadása… + + Add server + Kiszolgáló hozzáadása No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 8d311a803c..21acc23c9b 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -615,9 +615,9 @@ Aggiungi server scansionando codici QR. No comment provided by engineer. - - Add server… - Aggiungi server… + + Add server + Aggiungi server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 43f0f8f6cd..589973d1c0 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -608,9 +608,9 @@ QRコードでサーバを追加する。 No comment provided by engineer. - - Add server… - サーバを追加… + + Add server + サーバを追加 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index cc7b5522e6..555d40285c 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -300,8 +300,8 @@ Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff index feb1e177f1..6df24149e9 100644 --- a/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff +++ b/apps/ios/SimpleX Localizations/lt.xcloc/Localized Contents/lt.xliff @@ -329,9 +329,9 @@ Pridėti serverius skenuojant QR kodus. No comment provided by engineer. - - Add server… - Pridėti serverį… + + Add server + Pridėti serverį No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ml.xcloc/Localized Contents/ml.xliff b/apps/ios/SimpleX Localizations/ml.xcloc/Localized Contents/ml.xliff index f4a1a815ea..1726f2ea77 100644 --- a/apps/ios/SimpleX Localizations/ml.xcloc/Localized Contents/ml.xliff +++ b/apps/ios/SimpleX Localizations/ml.xcloc/Localized Contents/ml.xliff @@ -367,8 +367,8 @@ Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index fdfd49c3d3..186c3a7e32 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -615,9 +615,9 @@ Servers toevoegen door QR-codes te scannen. No comment provided by engineer. - - Add server… - Server toevoegen… + + Add server + Server toevoegen No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 9da2b6bd82..667a725e93 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -615,9 +615,9 @@ Dodaj serwery, skanując kody QR. No comment provided by engineer. - - Add server… - Dodaj serwer… + + Add server + Dodaj serwer No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index b4fa69449f..0abab468fd 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -374,9 +374,9 @@ Adicione servidores escaneando o QR code. No comment provided by engineer. - - Add server… - Adicionar servidor… + + Add server + Adicionar servidor No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index c9c9707c39..5942971eac 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -359,8 +359,8 @@ Available in v5.1 Add servers by scanning QR codes. No comment provided by engineer. - - Add server… + + Add server No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index db7f5bfb9e..f4ad23262c 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -615,9 +615,9 @@ Добавить серверы через QR код. No comment provided by engineer. - - Add server… - Добавить сервер… + + Add server + Добавить сервер No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index a71061d578..7e3298ed58 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -583,9 +583,9 @@ เพิ่มเซิร์ฟเวอร์โดยการสแกนรหัสคิวอาร์โค้ด No comment provided by engineer. - - Add server… - เพิ่มเซิร์ฟเวอร์… + + Add server + เพิ่มเซิร์ฟเวอร์ No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 66f24d5bb7..3ce0b3ec75 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -615,9 +615,9 @@ Karekod taratarak sunucuları ekleyin. No comment provided by engineer. - - Add server… - Sunucu ekle… + + Add server + Sunucu ekle No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index bf0b5718f7..7234888bf1 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -615,9 +615,9 @@ Додайте сервери, відсканувавши QR-код. No comment provided by engineer. - - Add server… - Додати сервер… + + Add server + Додати сервер No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 5d1852b0b4..3823d6e6a9 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -603,9 +603,9 @@ 扫描二维码来添加服务器。 No comment provided by engineer. - - Add server… - 添加服务器… + + Add server + 添加服务器 No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff index 03a108d112..8bc5a3b25d 100644 --- a/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hant.xcloc/Localized Contents/zh-Hant.xliff @@ -358,9 +358,9 @@ 使用二維碼掃描以新增伺服器。 No comment provided by engineer. - - Add server… - 新增伺服器… + + Add server + 新增伺服器 No comment provided by engineer. diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 2129d38fcf..d5cd9c8f3b 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1122,20 +1122,20 @@ public struct ProtoServersConfig: Codable { public struct UserProtoServers: Decodable { public var serverProtocol: ServerProtocol public var protoServers: [ServerCfg] - public var presetServers: [String] + public var presetServers: [ServerCfg] } -public struct ServerCfg: Identifiable, Equatable, Codable { +public struct ServerCfg: Identifiable, Equatable, Codable, Hashable { public var server: String public var preset: Bool public var tested: Bool? - public var enabled: ServerEnabled + public var enabled: Bool var createdAt = Date() // public var sendEnabled: Bool // can we potentially want to prevent sending on the servers we use to receive? // Even if we don't see the use case, it's probably better to allow it in the model // In any case, "trusted/known" servers are out of scope of this change - public init(server: String, preset: Bool, tested: Bool?, enabled: ServerEnabled) { + public init(server: String, preset: Bool, tested: Bool?, enabled: Bool) { self.server = server self.preset = preset self.tested = tested @@ -1148,7 +1148,7 @@ public struct ServerCfg: Identifiable, Equatable, Codable { public var id: String { "\(server) \(createdAt)" } - public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: .enabled) + public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: false) public var isEmpty: Bool { server.trimmingCharacters(in: .whitespaces) == "" @@ -1165,19 +1165,19 @@ public struct ServerCfg: Identifiable, Equatable, Codable { server: "smp://abcd@smp8.simplex.im", preset: true, tested: true, - enabled: .enabled + enabled: true ), custom: ServerCfg( server: "smp://abcd@smp9.simplex.im", preset: false, tested: false, - enabled: .disabled + enabled: false ), untested: ServerCfg( server: "smp://abcd@smp10.simplex.im", preset: false, tested: nil, - enabled: .enabled + enabled: true ) ) @@ -1189,12 +1189,6 @@ public struct ServerCfg: Identifiable, Equatable, Codable { } } -public enum ServerEnabled: String, Codable { - case disabled - case enabled - case known -} - public enum ProtocolTestStep: String, Decodable, Equatable { case connect case disconnect diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 5fe8fe909c..a15ceb805c 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Добави профил"; /* No comment provided by engineer. */ -"Add server…" = "Добави сървър…"; +"Add server" = "Добави сървър"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Добави сървъри чрез сканиране на QR кодове."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index bbe46b225f..cf92e47d21 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -318,7 +318,7 @@ "Add profile" = "Přidat profil"; /* No comment provided by engineer. */ -"Add server…" = "Přidat server…"; +"Add server" = "Přidat server"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Přidejte servery skenováním QR kódů."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 01a760721f..40c48745df 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Profil hinzufügen"; /* No comment provided by engineer. */ -"Add server…" = "Füge Server hinzu…"; +"Add server" = "Füge Server hinzu"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Fügen Sie Server durch Scannen der QR Codes hinzu."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 01924b1903..19e75bcf67 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Añadir perfil"; /* No comment provided by engineer. */ -"Add server…" = "Añadir servidor…"; +"Add server" = "Añadir servidor"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Añadir servidores mediante el escaneo de códigos QR."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 12d17aac36..58e4cf4a5a 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -309,7 +309,7 @@ "Add profile" = "Lisää profiili"; /* No comment provided by engineer. */ -"Add server…" = "Lisää palvelin…"; +"Add server" = "Lisää palvelin"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Lisää palvelimia skannaamalla QR-koodeja."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 91b8d7c866..1c8396bdfe 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Ajouter un profil"; /* No comment provided by engineer. */ -"Add server…" = "Ajouter un serveur…"; +"Add server" = "Ajouter un serveur"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Ajoutez des serveurs en scannant des codes QR."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 9d04ebd4fb..b0ccd9e28c 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Profil hozzáadása"; /* No comment provided by engineer. */ -"Add server…" = "Kiszolgáló hozzáadása…"; +"Add server" = "Kiszolgáló hozzáadása"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Kiszolgáló hozzáadása QR-kód beolvasásával."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 7039ebc4d0..8bfd8aff77 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Aggiungi profilo"; /* No comment provided by engineer. */ -"Add server…" = "Aggiungi server…"; +"Add server" = "Aggiungi server"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Aggiungi server scansionando codici QR."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index bfb6ef4a3a..6c7ff469ad 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -360,7 +360,7 @@ "Add profile" = "プロフィールを追加"; /* No comment provided by engineer. */ -"Add server…" = "サーバを追加…"; +"Add server" = "サーバを追加"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "QRコードでサーバを追加する。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 119cbc6378..46eb5ccf2b 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Profiel toevoegen"; /* No comment provided by engineer. */ -"Add server…" = "Server toevoegen…"; +"Add server" = "Server toevoegen"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Servers toevoegen door QR-codes te scannen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 3189c74cf2..e29fcf7d9e 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Dodaj profil"; /* No comment provided by engineer. */ -"Add server…" = "Dodaj serwer…"; +"Add server" = "Dodaj serwer"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Dodaj serwery, skanując kody QR."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index c0ad6e23cf..df4b9d7009 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Добавить профиль"; /* No comment provided by engineer. */ -"Add server…" = "Добавить сервер…"; +"Add server" = "Добавить сервер"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Добавить серверы через QR код."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index fa31dccf96..4eeb03bd9b 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -285,7 +285,7 @@ "Add profile" = "เพิ่มโปรไฟล์"; /* No comment provided by engineer. */ -"Add server…" = "เพิ่มเซิร์ฟเวอร์…"; +"Add server" = "เพิ่มเซิร์ฟเวอร์"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "เพิ่มเซิร์ฟเวอร์โดยการสแกนรหัสคิวอาร์โค้ด"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 696d5a3f21..7af1054f56 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Profil ekle"; /* No comment provided by engineer. */ -"Add server…" = "Sunucu ekle…"; +"Add server" = "Sunucu ekle"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Karekod taratarak sunucuları ekleyin."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 7cb4270d5b..3483caca88 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -369,7 +369,7 @@ "Add profile" = "Додати профіль"; /* No comment provided by engineer. */ -"Add server…" = "Додати сервер…"; +"Add server" = "Додати сервер"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "Додайте сервери, відсканувавши QR-код."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 74d9970b93..55d59f554c 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -333,7 +333,7 @@ "Add profile" = "添加个人资料"; /* No comment provided by engineer. */ -"Add server…" = "添加服务器…"; +"Add server" = "添加服务器"; /* No comment provided by engineer. */ "Add servers by scanning QR codes." = "扫描二维码来添加服务器。"; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 07d3451d86..70b18fb379 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3138,7 +3138,7 @@ data class ProtoServersConfig( data class UserProtocolServers( val serverProtocol: ServerProtocol, val protoServers: List, - val presetServers: List, + val presetServers: List, ) @Serializable @@ -3147,7 +3147,7 @@ data class ServerCfg( val server: String, val preset: Boolean, val tested: Boolean? = null, - val enabled: ServerEnabled + val enabled: Boolean ) { @Transient private val createdAt: Date = Date() @@ -3161,7 +3161,7 @@ data class ServerCfg( get() = server.isBlank() companion object { - val empty = ServerCfg(remoteHostId = null, server = "", preset = false, tested = null, enabled = ServerEnabled.Enabled) + val empty = ServerCfg(remoteHostId = null, server = "", preset = false, tested = null, enabled = false) class SampleData( val preset: ServerCfg, @@ -3175,33 +3175,26 @@ data class ServerCfg( server = "smp://abcd@smp8.simplex.im", preset = true, tested = true, - enabled = ServerEnabled.Enabled + enabled = true ), custom = ServerCfg( remoteHostId = null, server = "smp://abcd@smp9.simplex.im", preset = false, tested = false, - enabled = ServerEnabled.Disabled + enabled = false ), untested = ServerCfg( remoteHostId = null, server = "smp://abcd@smp10.simplex.im", preset = false, tested = null, - enabled = ServerEnabled.Enabled + enabled = true ) ) } } -@Serializable -enum class ServerEnabled { - @SerialName("disabled") Disabled, - @SerialName("enabled") Enabled, - @SerialName("known") Known; -} - @Serializable enum class ProtocolTestStep { @SerialName("connect") Connect, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt index 4e3b70405d..0dae98559c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/LinkAMobileView.kt @@ -66,7 +66,7 @@ private fun LinkAMobileLayout( SectionView(generalGetString(MR.strings.this_device_name).uppercase()) { DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) } SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile)) - PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), remember { ChatModel.controller.appPrefs.offerRemoteMulticast.state }.value) { + PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { ChatModel.controller.appPrefs.offerRemoteMulticast.state }.value) { ChatModel.controller.appPrefs.offerRemoteMulticast.set(it) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index d201ac482a..b5349e826d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -432,14 +432,14 @@ private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList) { SectionDividerSpaced() SectionView(stringResource(MR.strings.linked_desktop_options).uppercase()) { - PreferenceToggle(stringResource(MR.strings.verify_connections), remember { controller.appPrefs.confirmRemoteSessions.state }.value) { + PreferenceToggle(stringResource(MR.strings.verify_connections), checked = remember { controller.appPrefs.confirmRemoteSessions.state }.value) { controller.appPrefs.confirmRemoteSessions.set(it) } - PreferenceToggle(stringResource(MR.strings.discover_on_network), remember { controller.appPrefs.connectRemoteViaMulticast.state }.value) { + PreferenceToggle(stringResource(MR.strings.discover_on_network), checked = remember { controller.appPrefs.connectRemoteViaMulticast.state }.value) { controller.appPrefs.connectRemoteViaMulticast.set(it) } if (remember { controller.appPrefs.connectRemoteViaMulticast.state }.value) { - PreferenceToggle(stringResource(MR.strings.multicast_connect_automatically), remember { controller.appPrefs.connectRemoteViaMulticastAuto.state }.value) { + PreferenceToggle(stringResource(MR.strings.multicast_connect_automatically), checked = remember { controller.appPrefs.connectRemoteViaMulticastAuto.state }.value) { controller.appPrefs.connectRemoteViaMulticastAuto.set(it) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index e13b86258d..d8c9557863 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -97,7 +97,7 @@ fun ConnectMobileLayout( SectionView(generalGetString(MR.strings.this_device_name).uppercase()) { DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) } SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile)) - PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), remember { controller.appPrefs.offerRemoteMulticast.state }.value) { + PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), checked = remember { controller.appPrefs.offerRemoteMulticast.state }.value) { controller.appPrefs.offerRemoteMulticast.set(it) } SectionDividerSpaced(maxBottomPadding = false) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index a7b3896ce2..88018dd1c9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -175,10 +175,16 @@ private fun UseServerSection( Text(stringResource(MR.strings.smp_servers_test_server), color = if (valid && !testing) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) ShowTestStatus(server) } - val enabled = rememberUpdatedState(server.enabled == ServerEnabled.Enabled) - PreferenceToggle(stringResource(MR.strings.smp_servers_use_server_for_new_conn), enabled.value) { enable -> - onUpdate(server.copy(enabled = if (enable) ServerEnabled.Enabled else ServerEnabled.Disabled)) + + val enabled = rememberUpdatedState(server.enabled) + PreferenceToggle( + stringResource(MR.strings.smp_servers_use_server_for_new_conn), + disabled = server.tested != true && !server.preset, + checked = enabled.value + ) { + onUpdate(server.copy(enabled = it)) } + SectionItemView(onDelete, disabled = testing) { Text(stringResource(MR.strings.smp_servers_delete_server), color = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.error) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index bbf3e07c93..5d5f1d039a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -28,13 +28,13 @@ import chat.simplex.res.MR @Composable fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtocol, close: () -> Unit) { - var presetServers by remember(rhId) { mutableStateOf(emptyList()) } + var presetServers by remember(rhId) { mutableStateOf(emptyList()) } var servers by remember { stateGetOrPut("servers") { emptyList() } } var serversAlreadyLoaded by remember { stateGetOrPut("serversAlreadyLoaded") { false } } val currServers = remember(rhId) { mutableStateOf(servers) } val testing = rememberSaveable(rhId) { mutableStateOf(false) } val serversUnchanged = remember(servers) { derivedStateOf { servers == currServers.value || testing.value } } - val allServersDisabled = remember { derivedStateOf { servers.none { it.enabled == ServerEnabled.Enabled } } } + val allServersDisabled = remember { derivedStateOf { servers.none { it.enabled } } } val saveDisabled = remember(servers) { derivedStateOf { servers.isEmpty() || @@ -198,12 +198,42 @@ private fun ProtocolServersLayout( ) { AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.your_SMP_servers else MR.strings.your_XFTP_servers)) - SectionView(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.smp_servers else MR.strings.xftp_servers).uppercase()) { - for (srv in servers) { - SectionItemView({ showServer(srv) }, disabled = testing) { - ProtocolServerView(serverProtocol, srv, servers, testing) + val configuredServers = servers.filter { it.preset || it.enabled } + val otherServers = servers.filter { !(it.preset || it.enabled) } + + if (configuredServers.isNotEmpty()) { + SectionView(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.smp_servers_configured else MR.strings.xftp_servers_configured).uppercase()) { + for (srv in configuredServers) { + SectionItemView({ showServer(srv) }, disabled = testing) { + ProtocolServerView(serverProtocol, srv, servers, testing) + } } } + SectionTextFooter( + remember(currentUser?.displayName) { + buildAnnotatedString { + append(generalGetString(MR.strings.smp_servers_per_user) + " ") + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(currentUser?.displayName ?: "") + } + append(".") + } + } + ) + SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + } + + if (otherServers.isNotEmpty()) { + SectionView(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.smp_servers_other else MR.strings.xftp_servers_other).uppercase()) { + for (srv in otherServers.filter { !(it.preset || it.enabled) }) { + SectionItemView({ showServer(srv) }, disabled = testing) { + ProtocolServerView(serverProtocol, srv, servers, testing) + } + } + } + } + + SectionView { SettingsActionItem( painterResource(MR.images.ic_add), stringResource(MR.strings.smp_servers_add), @@ -212,19 +242,9 @@ private fun ProtocolServersLayout( textColor = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary, iconColor = if (testing) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) + SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) } - SectionTextFooter( - remember(currentUser?.displayName) { - buildAnnotatedString { - append(generalGetString(MR.strings.smp_servers_per_user) + " ") - withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { - append(currentUser?.displayName ?: "") - } - append(".") - } - } - ) - SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false) + SectionView { SectionItemView(resetServers, disabled = serversUnchanged) { Text(stringResource(MR.strings.reset_verb), color = if (!serversUnchanged) MaterialTheme.colors.onBackground else MaterialTheme.colors.secondary) @@ -250,12 +270,12 @@ private fun ProtocolServerView(serverProtocol: ServerProtocol, srv: ServerCfg, s val address = parseServerAddress(srv.server) when { address == null || !address.valid || address.serverProtocol != serverProtocol || !uniqueAddress(srv, address, servers) -> InvalidServer() - srv.enabled != ServerEnabled.Enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) + !srv.enabled -> Icon(painterResource(MR.images.ic_do_not_disturb_on), null, tint = MaterialTheme.colors.secondary) else -> ShowTestStatus(srv) } Spacer(Modifier.padding(horizontal = 4.dp)) val text = address?.hostnames?.firstOrNull() ?: srv.server - if (srv.enabled == ServerEnabled.Enabled) { + if (srv.enabled) { Text(text, color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground, maxLines = 1) } else { Text(text, maxLines = 1, color = MaterialTheme.colors.secondary) @@ -285,21 +305,21 @@ private fun uniqueAddress(s: ServerCfg, address: ServerAddress, servers: List, servers: List, m: ChatModel): Boolean = +private fun hasAllPresets(presetServers: List, servers: List, m: ChatModel): Boolean = presetServers.all { hasPreset(it, servers) } ?: true -private fun addAllPresets(rhId: Long?, presetServers: List, servers: List, m: ChatModel): List { +private fun addAllPresets(rhId: Long?, presetServers: List, servers: List, m: ChatModel): List { val toAdd = ArrayList() for (srv in presetServers) { if (!hasPreset(srv, servers)) { - toAdd.add(ServerCfg(remoteHostId = rhId, srv, preset = true, tested = null, enabled = ServerEnabled.Enabled)) + toAdd.add(srv) } } return toAdd } -private fun hasPreset(srv: String, servers: List): Boolean = - servers.any { it.server == srv } +private fun hasPreset(srv: ServerCfg, servers: List): Boolean = + servers.any { it.server == srv.server } private suspend fun testServers(testing: MutableState, servers: List, m: ChatModel, onUpdated: (List) -> Unit) { val resetStatus = resetTestStatus(servers) @@ -319,7 +339,7 @@ private suspend fun testServers(testing: MutableState, servers: List): List { val copy = ArrayList(servers) for ((index, server) in servers.withIndex()) { - if (server.enabled == ServerEnabled.Enabled) { + if (server.enabled) { copy.removeAt(index) copy.add(index, server.copy(tested = null)) } @@ -331,7 +351,7 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd val fs: MutableMap = mutableMapOf() val updatedServers = ArrayList(servers) for ((index, server) in servers.withIndex()) { - if (server.enabled == ServerEnabled.Enabled) { + if (server.enabled) { interruptIfCancelled() val (updatedServer, f) = testServerConnection(server, m) updatedServers.removeAt(index) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt index c1951341a4..7c2c578d6a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ScanProtocolServer.kt @@ -7,7 +7,6 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress import chat.simplex.common.model.ServerCfg -import chat.simplex.common.model.ServerEnabled import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCodeScanner @@ -26,7 +25,7 @@ fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) { QRCodeScanner { text -> val res = parseServerAddress(text) if (res != null) { - onNext(ServerCfg(remoteHostId = rhId, text, false, null, ServerEnabled.Enabled)) + onNext(ServerCfg(remoteHostId = rhId, text, false, null, false)) } else { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.smp_servers_invalid_address), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index cef457de2e..a78b7663ab 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -416,13 +416,15 @@ fun SettingsPreferenceItem( @Composable fun PreferenceToggle( text: String, + disabled: Boolean = false, checked: Boolean, onChange: (Boolean) -> Unit = {}, ) { - SettingsActionItemWithContent(null, text, extraPadding = true,) { + SettingsActionItemWithContent(null, text, disabled = disabled, extraPadding = true,) { DefaultSwitch( checked = checked, onCheckedChange = onChange, + enabled = !disabled ) } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index ef4edcf0cd..e6abfca2c6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -31,7 +31,7 @@ أضِف إلى جهاز آخر سيتم حذف جميع الدردشات والرسائل - لا يمكن التراجع عن هذا! الوصول إلى الخوادم عبر بروكسي SOCKS على المنفذ %d؟ يجب بدء تشغيل الوكيل قبل تمكين هذا الخيار. - أضِف خادم… + أضِف خادم إعدادات الشبكة المتقدمة سيبقى جميع أعضاء المجموعة على اتصال. السماح باختفاء الرسائل فقط إذا سمحت جهة اتصالك بذلك. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 8b6e4d8e06..a447c9d8e5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -673,9 +673,11 @@ SimpleX Lock Chat console SMP servers + Configured SMP servers + Other SMP servers Preset server address Add preset servers - Add server… + Add server Test server Test servers Save servers @@ -695,6 +697,8 @@ The servers for new connections of your current chat profile Save servers? XFTP servers + Configured XFTP servers + Other XFTP servers Subscription percentage Install SimpleX Chat for terminal Star on GitHub diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index ca9070cbd6..81f537133c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -4,7 +4,7 @@ %1$s иска да се свърже с вас чрез 1 минута Добави сървъри чрез сканиране на QR кодове. - Добави сървър… + Добави сървър админ Добави съобщение при посрещане Всички данни се изтриват при въвеждане. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bn/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bn/strings.xml index 13e322f078..bb448339bd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bn/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bn/strings.xml @@ -28,7 +28,7 @@ আনুষঙ্গিক রং অ্যাডমিনরা গ্রুপে যোগদানের সংযোগ-সূত্র তৈরি করতে পারবেন। QR কোড স্ক্যান করে সার্ভার যুক্ত করুন। - সার্ভার যুক্ত করুন… + সার্ভার যুক্ত করুন ঠিকানা ঠিকানা পরিবর্তন বাতিল করা হবে। বার্তা গ্রহণের পুরনো ঠিকানা ব্যবহার করা হবে। অ্যাপের সকল তথ্য মুছে ফেলা হয়েছে। diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index e80b2d88ff..5c7844889c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -8,7 +8,7 @@ Přidat přednastavené servery Pokročilá nastavení sítě Přijmout - Přidat server… + Přidat server Přistupovat k serverům přes SOCKS proxy na portu %d\? Před povolením této možnosti musí být spuštěna proxy. Přijmout Povolit svým kontaktům odesílat mizící zprávy. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index df264384a5..2f8db4cba3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -355,7 +355,7 @@ SMP-Server Voreingestellte Serveradresse Füge voreingestellte Server hinzu - Füge Server hinzu… + Füge Server hinzu Teste Server Teste alle Server Alle Server speichern diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml index 811257ff39..baa85f07f4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/el/strings.xml @@ -42,7 +42,7 @@ Αποδοχή αιτήματος σύνδεσης; αποδεκτή κλήση Πρόσβαση στους διακομιστές μέσω SOCKS proxy στην πόρτα %d; Ο διακομιστής μεσολάβησης (proxy server) πρέπει να είναι ενεργός πριν ενεργοποιηθεί αυτή η ρύθμιση. - Προσθήκη διακομιστή… + Προσθήκη διακομιστή Προχωρημένες ρυθμίσεις δικτύου Προσθήκη διακομιστών μέσω σάρωσης QR κωδικών. Οι διαχειριστές μπορούν να δημιουργήσουν τους συνδέσμους συμμετοχής σε ομάδες. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 0c013f0bbc..17cf2a40e5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -30,7 +30,7 @@ ¿Aceptar solicitud de conexión\? Aceptar incógnito Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse! - Añadir servidor… + Añadir servidor ¿Acceder a los servidores a través del proxy SOCKS en el puerto %d\? El proxy debe iniciarse antes de activar esta opción. Todos tus contactos permanecerán conectados. Apariencia diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 10daae4c52..9cf12231ab 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -539,7 +539,7 @@ ایده‌ها و سوالات را ارسال کنید به ما ایمیل بفرستید قفل SimpleX - افزودن سرور… + افزودن سرور آزمایش سرورها ذخیره سرورها عدم موفقیت آزمایش سرور! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 62a01fdd64..4b6cc724cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -16,7 +16,7 @@ 30 sekuntia 5 minuuttia Salli katoavat viestit vain, jos kontaktisi sallii ne. - Lisää palvelin… + Lisää palvelin Lisää tervetuloviesti Salli ääniviestien lähettäminen. Kertakäyttölinkki diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index a9d9f0188b..fcf82960f4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -313,7 +313,7 @@ Scannez le code de sécurité depuis l\'application de votre contact. Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils. scanner un code QR lors d\'un appel vidéo, ou votre contact peut partager un lien d\'invitation.]]> - Ajouter un serveur… + Ajouter un serveur Markdown dans les messages Ajouter des serveurs prédéfinis Utiliser les serveurs SimpleX Chat \? diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml index 61b9ad8de6..9c283a98e9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml @@ -36,7 +36,7 @@ गुप्त स्वीकार करें पूर्वनिर्धारित सर्वर जोड़ें प्रोफ़ाइल जोड़ें - सर्वर जोड़े… + सर्वर जोड़े हमेशा बने रहें संलग्न करना उन्नत संजाल समायोजन diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 94db4f4618..2739b346c4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -165,7 +165,7 @@ Minden %s által írt új üzenet elrejtésre kerül! Alkalmazás verzió: v%s A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. - Kiszolgáló hozzáadása… + Kiszolgáló hozzáadása Hang bekapcsolva hanghívás (nem e2e titkosított) letiltva diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 2ea3c53514..25b7dc28d2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -249,7 +249,7 @@ Accetta in incognito Tutti i messaggi verranno eliminati, non è reversibile! I messaggi verranno eliminati SOLO per te. Aggiungi server preimpostati - Aggiungi server… + Aggiungi server Impostazioni di rete avanzate Riguardo SimpleX chiamata accettata diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index e335c199d0..52a863d1f2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -18,7 +18,7 @@ כל ההודעות יימחקו – לא ניתן לבטל זאת! ההודעות יימחקו רק עבורך. אשר זהות נסתרת הוסף שרתים מוגדרים מראש - הוסף שרת… + הוסף שרת לגשת לשרתים דרך פרוקסי SOCKS בפורט %d\? הפרוקסי חייב לפעול לפני הפעלת אפשרות זו. הגדרות רשת מתקדמות מראה diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 41f1d0d6a8..98a80931fa 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -21,7 +21,7 @@ QRコードでサーバを追加 別の端末に追加 プロフィールを追加 - サーバを追加… + サーバを追加 SOCKSプロキシ(ポート%d)経由で接続しますか?(※設定する前にプロキシ起動が必要※) 全チャットとメッセージが削除されます(※元に戻せません※)! 送信相手からの音声メッセージを許可する。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index 3caa99bc59..e1e84a4ebf 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -43,7 +43,7 @@ 연결 요청이 전송되었습니다! 링크로 연결 프리셋 서버 추가 - 서버 추가… + 서버 추가 채팅 콘솔 서버 주소를 확인 후 다시 시도해 주세요. ICE 서버 설정 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index b5191f342d..d4538299d0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -6,7 +6,7 @@ 1 diena a + b Apie SimpleX - Pridėti serverį… + Pridėti serverį Pridėti serverius nuskanuojant QR kodus. Išvaizda Programėlės versija diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index 455f9d53bd..b807789c08 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -109,7 +109,7 @@ തിരികെ നിശബ്ദമാക്കുക സഹായം - സെർവർ ചേർക്കുക… + സെർവർ ചേർക്കുക മറ്റൊരു ഉപകരണത്തിലേക്ക് ചേർക്കുക സ്വയമേവ സ്വീകരിക്കുക സ്ഥിരീകരണത്തിനായി കാത്തിരിക്കുന്നു… diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 4fa3ba4f51..f3434f2efe 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -39,7 +39,7 @@ Accepteer incognito Vooraf ingestelde servers toevoegen Profiel toevoegen - Server toevoegen… + Server toevoegen Toevoegen aan een ander apparaat Beheerders kunnen de uitnodiging links naar groepen aanmaken. Servers toevoegen door QR-codes te scannen. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 4853fdf127..4e49c7235d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -290,7 +290,7 @@ Zostaniesz połączony do grupy, gdy urządzenie gospodarza grupy będzie online, proszę czekać lub sprawdzić później! Zostaniesz połączony, gdy urządzenie Twojego kontaktu będzie online, proszę czekać lub sprawdzić później! Dodaj gotowe serwery - Dodaj serwer… + Dodaj serwer Dodaj do innego urządzenia Konsola czatu Sprawdź adres serwera i spróbuj ponownie. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index 7d4570e561..a12c0c3992 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -110,7 +110,7 @@ Conectar via link/QR code Todas as mensagens serão excluídas - isso não pode ser desfeito! As mensagens serão excluídas APENAS para você. Adicionar servidores pré-definidos - Adicionar servidor… + Adicionar servidor Crie seu perfil Ícone de contexto Contato e todas as mensagens serão excluídas - isso não pode ser desfeito! diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 4bb442faf8..65ffeef271 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -122,7 +122,7 @@ 1 semana Aceitar aceitar chamada - Adicionar servidor… + Adicionar servidor Aceitar Aceitar pedido de ligação\? Aceitar modo anónimo diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index ae03ebd1ae..b2ff82dcdd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -5,7 +5,7 @@ 30 secunde Acceptă Acceptă incognito - Adaugă server… + Adaugă server Setări de rețea avansate %1$s dorește să se conecteze cu tine prin Acceptă diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 88dd4c2783..0e5b329669 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -354,7 +354,7 @@ SMP серверы Адрес сервера по умолчанию Добавить серверы по умолчанию - Добавить сервер… + Добавить сервер Тестировать сервер Тестировать серверы Сохранить серверы diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index f6988ca367..ef020efb54 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -46,7 +46,7 @@ ขอรับภาพ ขอรับวิดีโอ ข้อความทั้งหมดจะถูกลบ - การดำเนินการนี้ไม่สามารถยกเลิกได้! ข้อความจะถูกลบสำหรับคุณเท่านั้น - เพิ่มเซิร์ฟเวอร์… + เพิ่มเซิร์ฟเวอร์ เวอร์ชันแอป เวอร์ชันแอป: v%s ผู้ติดต่อทั้งหมดของคุณจะยังคงเชื่อมต่ออยู่. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index f25811fef7..36386e00ea 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -38,7 +38,7 @@ Adres değişikliği iptal edilecek. Eski alıcı adresi kullanılacaktır. 1 dakika Tüm mesajlar silinecektir. Bu, geri alınamaz! Mesajlar, YALNIZCA senin için silinecektir. - Sunucu ekle… + Sunucu ekle Veri tabanı ayarları tek kullanımlık bağlantı Gelişmiş ağ ayarları diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index b8d802450d..06aedf5fbb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -55,7 +55,7 @@ Після перезапуску додатка або зміни ключової фрази буде використано сховище ключів Android для безпечного збереження ключової фрази - це дозволить отримувати сповіщення. Дозвольте вашим контактам надсилати голосові повідомлення. Прийняти інкогніто - Додати сервер… + Додати сервер адміністратор Додати привітання Всі члени групи залишаться підключеними. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 735ef63237..f819aaf01c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -10,7 +10,7 @@ liên kết dùng một lần Thêm liên hệ Thông tin về SimpleX Chat - Thêm máy chủ… + Thêm máy chủ Thông tin về SimpleX quản trị viên Thêm lời chào diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index bd9c8fa5c2..4c541d1bff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -15,7 +15,7 @@ 已接受通话 接受 通过在 %d 端口的 SOCKS 代理访问服务器?启用该选项前必须先启动代理。 - 添加服务器…… + 添加服务器 添加另一设备 管理员 扫描二维码来添加服务器。 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index b6434c39db..21298376d9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -15,7 +15,7 @@ 管理員 然後,選按: 新增預設伺服器 - 新增伺服器… + 新增伺服器 接受 認證無效 允許 From 17526fa385eb5089b8a62439811a669e498e0ea0 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 12 Jul 2024 20:31:41 +0100 Subject: [PATCH 20/57] ui: translations (#4433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * blog: future of privacy (#4396) * blog: future of privacy * update * Translated using Weblate (French) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/fr/ * Translated using Weblate (Italian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (1823 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/uk/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/uk/ * Translated using Weblate (Polish) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1823 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Romanian) Currently translated at 18.7% (341 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 11.6% (213 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (German) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1823 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/tr/ * Translated using Weblate (Turkish) Currently translated at 100.0% (1823 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/tr/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1823 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/nl/ * Translated using Weblate (Vietnamese) Currently translated at 12.2% (223 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 89.1% (1625 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pt_BR/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1823 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Romanian) Currently translated at 19.4% (354 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Romanian) Currently translated at 20.3% (371 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Romanian) Currently translated at 20.6% (376 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Vietnamese) Currently translated at 12.3% (225 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 12.5% (228 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 12.6% (230 of 1823 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 12.6% (232 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (German) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 13.2% (243 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (French) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Arabic) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1832 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Vietnamese) Currently translated at 13.8% (254 of 1832 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Vietnamese) Currently translated at 14.4% (266 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (French) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Italian) Currently translated at 99.9% (1834 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Arabic) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (German) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Vietnamese) Currently translated at 15.0% (277 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Polish) Currently translated at 99.9% (1834 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Vietnamese) Currently translated at 15.8% (291 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 16.6% (305 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Dutch) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Czech) Currently translated at 93.9% (1724 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Korean) Currently translated at 0.8% (13 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ko/ * Translated using Weblate (Persian) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fa/ * Translated using Weblate (Vietnamese) Currently translated at 18.0% (332 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 18.7% (344 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Korean) Currently translated at 1.2% (20 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/ko/ * Translated using Weblate (Japanese) Currently translated at 96.7% (1775 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (German) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Vietnamese) Currently translated at 19.2% (354 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Lithuanian) Currently translated at 95.8% (1758 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/lt/ * Translated using Weblate (Italian) Currently translated at 100.0% (1835 of 1835 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (German) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Spanish) Currently translated at 99.8% (1835 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Arabic) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Korean) Currently translated at 51.1% (940 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ko/ * Translated using Weblate (Polish) Currently translated at 99.9% (1836 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/pl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Added translation using Weblate (Indonesian) * Translated using Weblate (Dutch) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Japanese) Currently translated at 98.1% (1803 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/es/ * Translated using Weblate (Spanish) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/es/ * Translated using Weblate (Japanese) Currently translated at 98.2% (1804 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Portuguese) Currently translated at 10.3% (160 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/pt/ * Translated using Weblate (Japanese) Currently translated at 98.2% (1805 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Romanian) Currently translated at 33.5% (616 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Romanian) Currently translated at 33.5% (616 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1552 of 1552 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Vietnamese) Currently translated at 19.9% (367 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Vietnamese) Currently translated at 21.6% (398 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Hebrew) Currently translated at 98.1% (1803 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/he/ * Translated using Weblate (Romanian) Currently translated at 36.5% (672 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ro/ * Translated using Weblate (Vietnamese) Currently translated at 22.8% (419 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Finnish) Currently translated at 80.9% (1487 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fi/ * Translated using Weblate (Vietnamese) Currently translated at 24.4% (449 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Indonesian) Currently translated at 1.1% (22 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Indonesian) Currently translated at 1.2% (23 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (Vietnamese) Currently translated at 25.1% (462 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (French) Currently translated at 100.0% (1837 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/fr/ * Translated using Weblate (Czech) Currently translated at 95.9% (1763 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Vietnamese) Currently translated at 26.2% (483 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/vi/ * Translated using Weblate (Czech) Currently translated at 96.6% (1775 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/cs/ * Translated using Weblate (Indonesian) Currently translated at 2.8% (52 of 1837 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * process localizations --------- Co-authored-by: khalidbelk Co-authored-by: Random Co-authored-by: Maksym Lukashenko Co-authored-by: B.O.S.S Co-authored-by: summoner001 Co-authored-by: Flavian <3zorro.1@gmail.com> Co-authored-by: tuananh-ng <158744840+tuananh-ng@users.noreply.github.com> Co-authored-by: mlanp Co-authored-by: No name Co-authored-by: z0rden Co-authored-by: M1K4 Co-authored-by: jorge2000x Co-authored-by: 大王叫我来巡山 Co-authored-by: Ghost of Sparta Co-authored-by: jonnysemon Co-authored-by: zenobit Co-authored-by: Songun Lee Co-authored-by: sor sor Co-authored-by: Miyu Sakatsuki Co-authored-by: Anonimas Co-authored-by: robbie.med <161779148+robbie-med@users.noreply.github.com> Co-authored-by: Antonio Oliveira Co-authored-by: PaulDotSH <84643540+PaulDotSH@users.noreply.github.com> Co-authored-by: WebTrans Co-authored-by: Jiri Grönroos Co-authored-by: billy appetie Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com> --- .../de.xcloc/Localized Contents/de.xliff | 9 +- .../es.xcloc/Localized Contents/es.xliff | 64 ++-- .../fr.xcloc/Localized Contents/fr.xliff | 5 + .../hu.xcloc/Localized Contents/hu.xliff | 133 +++---- .../it.xcloc/Localized Contents/it.xliff | 7 +- .../ko.xcloc/Localized Contents/ko.xliff | 77 +++- .../nl.xcloc/Localized Contents/nl.xliff | 5 + .../pl.xcloc/Localized Contents/pl.xliff | 5 + .../pt.xcloc/Localized Contents/pt.xliff | 62 ++- .../tr.xcloc/Localized Contents/tr.xliff | 5 + .../uk.xcloc/Localized Contents/uk.xliff | 5 + apps/ios/bg.lproj/Localizable.strings | 16 +- apps/ios/cs.lproj/Localizable.strings | 14 +- apps/ios/de.lproj/Localizable.strings | 29 +- apps/ios/es.lproj/Localizable.strings | 79 ++-- apps/ios/fi.lproj/Localizable.strings | 14 +- apps/ios/fr.lproj/Localizable.strings | 25 +- apps/ios/hu.lproj/Localizable.strings | 153 ++++---- apps/ios/it.lproj/Localizable.strings | 27 +- apps/ios/ja.lproj/Localizable.strings | 14 +- apps/ios/nl.lproj/Localizable.strings | 25 +- apps/ios/pl.lproj/Localizable.strings | 25 +- apps/ios/ru.lproj/Localizable.strings | 16 +- apps/ios/th.lproj/Localizable.strings | 14 +- apps/ios/tr.lproj/Localizable.strings | 25 +- apps/ios/uk.lproj/Localizable.strings | 25 +- apps/ios/zh-Hans.lproj/Localizable.strings | 16 +- .../commonMain/resources/MR/ar/strings.xml | 15 + .../commonMain/resources/MR/cs/strings.xml | 83 ++++ .../commonMain/resources/MR/de/strings.xml | 17 +- .../commonMain/resources/MR/es/strings.xml | 73 ++-- .../commonMain/resources/MR/fa/strings.xml | 21 +- .../commonMain/resources/MR/fi/strings.xml | 147 +++++++- .../commonMain/resources/MR/fr/strings.xml | 15 + .../commonMain/resources/MR/hu/strings.xml | 159 ++++---- .../commonMain/resources/MR/in/strings.xml | 49 +++ .../commonMain/resources/MR/it/strings.xml | 17 +- .../commonMain/resources/MR/iw/strings.xml | 125 ++++++ .../commonMain/resources/MR/ja/strings.xml | 48 ++- .../commonMain/resources/MR/ko/strings.xml | 29 ++ .../commonMain/resources/MR/lt/strings.xml | 22 ++ .../commonMain/resources/MR/nl/strings.xml | 15 + .../commonMain/resources/MR/pl/strings.xml | 15 + .../resources/MR/pt-rBR/strings.xml | 21 ++ .../commonMain/resources/MR/ro/strings.xml | 357 +++++++++++++++++- .../commonMain/resources/MR/tr/strings.xml | 6 + .../commonMain/resources/MR/uk/strings.xml | 6 + .../commonMain/resources/MR/vi/strings.xml | 291 +++++++++++++- .../resources/MR/zh-rCN/strings.xml | 15 + scripts/ios/import-localizations.sh | 1 - 50 files changed, 1907 insertions(+), 534 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 03b9ae1d5e..656cc6c70e 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -1152,7 +1152,7 @@ Cellular - Zellulär + Mobilfunknetz No comment provided by engineer. @@ -1837,6 +1837,7 @@ Das ist Ihr eigener Einmal-Link! Debug delivery + Debugging-Zustellung No comment provided by engineer. @@ -3922,6 +3923,7 @@ Das ist Ihr Link für die Gruppe %@! Message queue info + Nachrichten-Warteschlangen-Information No comment provided by engineer. @@ -4817,7 +4819,7 @@ Fehler: %@ Protect your IP address from the messaging relays chosen by your contacts. Enable in *Network & servers* settings. - Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais , die Ihre Kontakte ausgewählt haben. + Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais, die Ihre Kontakte ausgewählt haben. Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. No comment provided by engineer. @@ -8103,6 +8105,9 @@ SimpleX-Server können Ihr Profil nicht einsehen. server queue info: %1$@ last received msg: %2$@ + Server-Warteschlangen-Information: %1$@ + +Zuletzt empfangene Nachricht: %2$@ queue info diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 4c4e416a7f..a4cc2d2e68 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -1507,7 +1507,7 @@ This is your own one-time link! Connection error (AUTH) - Error conexión (Autenticación) + Error de conexión (Autenticación) No comment provided by engineer. @@ -1522,7 +1522,7 @@ This is your own one-time link! Connection timeout - Tiempo de conexión expirado + Tiempo de conexión agotado No comment provided by engineer. @@ -1837,6 +1837,7 @@ This is your own one-time link! Debug delivery + Informe debug No comment provided by engineer. @@ -3333,7 +3334,7 @@ Error: %2$@ If you can't meet in person, show QR code in a video call, or share the link. - Si no puedes reunirte en persona, muestra el código QR por videollamada, o comparte el enlace. + Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace. No comment provided by engineer. @@ -3922,6 +3923,7 @@ This is your link for group %@! Message queue info + Información cola de mensajes No comment provided by engineer. @@ -4323,7 +4325,7 @@ This is your link for group %@! One-time invitation link - Enlace único de invitación de un uso + Enlace de invitación de un solo uso No comment provided by engineer. @@ -4467,7 +4469,7 @@ This is your link for group %@! Or scan QR code - O escanear código QR + O escanea el código QR No comment provided by engineer. @@ -4477,7 +4479,7 @@ This is your link for group %@! Or show this code - O mostrar este código + O muestra este código QR No comment provided by engineer. @@ -4547,7 +4549,7 @@ This is your link for group %@! Paste the link you received - Pegar el enlace recibido + Pega el enlace recibido No comment provided by engineer. @@ -4561,7 +4563,7 @@ This is your link for group %@! Periodically - Periódico + Periódicamente No comment provided by engineer. @@ -4828,12 +4830,12 @@ Actívalo en ajustes de *Servidores y Redes*. Protocol timeout - Tiempo de espera del protocolo + Timeout protocolo No comment provided by engineer. Protocol timeout per KB - Límite de espera del protocolo por KB + Timeout protocolo por KB No comment provided by engineer. @@ -4876,32 +4878,32 @@ Actívalo en ajustes de *Servidores y Redes*. Read more - Saber más + Conoce más No comment provided by engineer. Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). - Saber más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). + Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). No comment provided by engineer. Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). - Saber más en [Guía de Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). No comment provided by engineer. Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). - Saber más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends). + Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends). No comment provided by engineer. Read more in our GitHub repository. - Saber más en nuestro repositorio GitHub. + Conoce más en nuestro repositorio GitHub. No comment provided by engineer. Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). - Saber más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme). + Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme). No comment provided by engineer. @@ -5755,7 +5757,7 @@ Actívalo en ajustes de *Servidores y Redes*. Share this 1-time invite link - Compartir este enlace de un uso + Comparte este enlace de un solo uso No comment provided by engineer. @@ -6037,7 +6039,7 @@ Actívalo en ajustes de *Servidores y Redes*. TCP connection timeout - Tiempo de espera de la conexión TCP agotado + Timeout de la conexión TCP No comment provided by engineer. @@ -6087,7 +6089,7 @@ Actívalo en ajustes de *Servidores y Redes*. Tap to paste link - Pulsa para pegar enlace + Pulsa para pegar el enlacePulsa para pegar enlace No comment provided by engineer. @@ -6355,7 +6357,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. To protect your IP address, private routing uses your SMP servers to deliver messages. - Para proteger tu dirección IP, el enrutamiento privado usa tus servidores SMP para enviar mensajes. + Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes. No comment provided by engineer. @@ -6516,9 +6518,8 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. To connect, please ask your contact to create another connection link and check that you have a stable network connection. - A menos que tu contacto haya eliminado la conexión o -que este enlace ya se haya usado, podría ser un error. Por favor, notifícalo. -Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueba que tienes buena conexión de red. + A menos que tu contacto haya eliminado la conexión o el enlace haya sido usado, podría ser un error. Por favor, notifícalo. +Para conectarte pide a tu contacto que cree otro enlace y comprueba la conexión de red. No comment provided by engineer. @@ -6670,12 +6671,12 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Use private routing with unknown servers when IP address is not protected. - Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida. + Usar enrutamiento privado con servidores desconocidos cuando tu dirección IP no está protegida. No comment provided by engineer. Use private routing with unknown servers. - Usar enrutamiento privado con servidores desconocidos. + Usar enrutamiento privado con servidores de retransmisión desconocidos. No comment provided by engineer. @@ -7218,7 +7219,7 @@ Repeat connection request? You will be required to authenticate when you start or resume the app after 30 seconds in background. - Se te pedirá identificarte cuándo inicies o continues usando la aplicación tras 30 segundos en segundo plano. + Se te pedirá autenticarte cuando inicies la aplicación o sigas usándola tras 30 segundos en segundo plano. No comment provided by engineer. @@ -7351,8 +7352,8 @@ Puedes cancelarla y eliminar el contacto (e intentarlo más tarde con un enlace Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile. - Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos. -Los servidores de SimpleX no pueden ver tu perfil. + Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. +Los servidores SimpleX no pueden ver tu perfil. No comment provided by engineer. @@ -8104,6 +8105,9 @@ Los servidores de SimpleX no pueden ver tu perfil. server queue info: %1$@ last received msg: %2$@ + información cola del servidor: %1$@ + +último mensaje recibido: %2$@ queue info @@ -8148,7 +8152,7 @@ last received msg: %2$@ unknown relays - servidor de retransmisión desconocido + con servidores desconocidos No comment provided by engineer. @@ -8158,7 +8162,7 @@ last received msg: %2$@ unprotected - desprotegido + con IP desprotegida No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index aa26193e2a..5416324e89 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -1837,6 +1837,7 @@ Il s'agit de votre propre lien unique ! Debug delivery + Livraison de débogage No comment provided by engineer. @@ -3922,6 +3923,7 @@ Voici votre lien pour le groupe %@ ! Message queue info + Informations sur la file d'attente des messages No comment provided by engineer. @@ -8103,6 +8105,9 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. server queue info: %1$@ last received msg: %2$@ + info sur la file d'attente du serveur : %1$@ + +dernier message reçu : %2$@ queue info diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index e70d844e4e..0264f5ffc4 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -497,7 +497,7 @@ <p>Hi!</p> <p><a href="%@">Connect to me via SimpleX Chat</a></p> <p>Üdvözlöm!</p> -<p><a href="%@">Csatlakozzon hozzám a SimpleX Chaten</a></p> +<p><a href=„%@”>Csatlakozzon hozzám a SimpleX Chaten</a></p> email text @@ -544,17 +544,17 @@ About SimpleX - A SimpleX névjegye + A SimpleX-ről No comment provided by engineer. About SimpleX Chat - A SimpleX Chat névjegye + A SimpleX Chat-ről No comment provided by engineer. About SimpleX address - A SimpleX azonosítóról + A SimpleX címről No comment provided by engineer. @@ -592,7 +592,7 @@ Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. - Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára. + Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára. No comment provided by engineer. @@ -766,7 +766,7 @@ Allow sending direct messages to members. - Közvetlen üzenetek küldésének engedélyezése a tagok számára. + A közvetlen üzenetek küldése a tagok között engedélyezve van. No comment provided by engineer. @@ -999,7 +999,7 @@ Bad desktop address - Hibás számítógép azonosító + Hibás számítógép cím No comment provided by engineer. @@ -1033,7 +1033,7 @@ Block for all - Mindenki számára letiltva + Letiltás mindenki számára No comment provided by engineer. @@ -1108,7 +1108,7 @@ Camera not available - A fényképező nem elérhető + A kamera nem elérhető No comment provided by engineer. @@ -1428,7 +1428,7 @@ Connect to yourself? This is your own SimpleX address! Kapcsolódás saját magához? -Ez a SimpleX azonosítója! +Ez az ön SimpleX címe! No comment provided by engineer. @@ -1440,7 +1440,7 @@ Ez az egyszer használatos hivatkozása! Connect via contact address - Kapcsolódás a kapcsolattartási azonosítón keresztül + Kapcsolódás a kapcsolattartási címen keresztül No comment provided by engineer. @@ -1613,7 +1613,7 @@ Ez az egyszer használatos hivatkozása! Create SimpleX address - SimpleX azonosító létrehozása + SimpleX cím létrehozása No comment provided by engineer. @@ -1623,7 +1623,7 @@ Ez az egyszer használatos hivatkozása! Create an address to let people connect with you. - Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel. + Cím létrehozása, hogy az emberek kapcsolatba léphessenek önnel. No comment provided by engineer. @@ -1837,6 +1837,7 @@ Ez az egyszer használatos hivatkozása! Debug delivery + Kézbesítési hibák felderítése No comment provided by engineer. @@ -1866,12 +1867,12 @@ Ez az egyszer használatos hivatkozása! Delete address - Azonosító törlése + Cím törlése No comment provided by engineer. Delete address? - Azonosító törlése? + Cím törlése? No comment provided by engineer. @@ -2081,7 +2082,7 @@ Ez a művelet nem vonható vissza! Desktop address - Számítógép azonosítója + Számítógép címe No comment provided by engineer. @@ -2239,7 +2240,7 @@ Ez a művelet nem vonható vissza! Don't create address - Ne hozzon létre azonosítót + Ne hozzon létre címet No comment provided by engineer. @@ -2331,7 +2332,7 @@ Ez a művelet nem vonható vissza! Enable TCP keep-alive - TCP életben tartásának engedélyezése + TCP életben tartása No comment provided by engineer. @@ -2521,7 +2522,7 @@ Ez a művelet nem vonható vissza! Error aborting address change - Hiba az azonosító megváltoztatásának megszakításakor + Hiba a cím megváltoztatásának megszakításakor No comment provided by engineer. @@ -2541,7 +2542,7 @@ Ez a művelet nem vonható vissza! Error changing address - Hiba az azonosító megváltoztatásakor + Hiba a cím megváltoztatásakor No comment provided by engineer. @@ -2556,7 +2557,7 @@ Ez a művelet nem vonható vissza! Error creating address - Hiba az azonosító létrehozásakor + Hiba a cím létrehozásakor No comment provided by engineer. @@ -2712,7 +2713,7 @@ Ez a művelet nem vonható vissza! Error saving group profile - Hiba a csoport profil mentésekor + Hiba a csoportprofil mentésekor No comment provided by engineer. @@ -3223,7 +3224,7 @@ Hiba: %2$@ Group profile - Csoport profil + Csoportprofil No comment provided by engineer. @@ -3826,12 +3827,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). - Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nem duplikáltak (%@). + Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@). No comment provided by engineer. Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. - Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. + Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. No comment provided by engineer. @@ -3875,12 +3876,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Member role will be changed to "%@". All group members will be notified. - A tag szerepköre meg fog változni erre: "%@". A csoport minden tagja értesítést kap róla. + A tag szerepköre meg fog változni erre: „%@”. A csoport minden tagja értesítést kap róla. No comment provided by engineer. Member role will be changed to "%@". The member will receive a new invitation. - A tag szerepköre meg fog változni erre: "%@". A tag új meghívást fog kapni. + A tag szerepköre meg fog változni erre: „%@”. A tag új meghívást fog kapni. No comment provided by engineer. @@ -3922,6 +3923,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message queue info + Üzenet-várakoztatási információ No comment provided by engineer. @@ -4293,7 +4295,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! - disable members ("observer" role) Most már az adminok is: - törölhetik a tagok üzeneteit. -- letilthatnak tagokat ("megfigyelő" szerepkör) +- letilthatnak tagokat („megfigyelő” szerepkör) No comment provided by engineer. @@ -4527,12 +4529,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Past member %@ - Korábbi csoport tag %@ + Már nem tag - %@ past/unknown group member Paste desktop address - Számítógép azonosítójának beillesztése + Számítógép címének beillesztése No comment provided by engineer. @@ -4786,7 +4788,7 @@ Hiba: %@ Prohibit sending direct messages to members. - A közvetlen üzenetek küldése le van tiltva a tagok között. + A közvetlen üzenetek küldése a tagok között le van tiltva. No comment provided by engineer. @@ -4806,7 +4808,7 @@ Hiba: %@ Protect IP address - Az IP-cím védelme + IP-cím védelem No comment provided by engineer. @@ -5037,12 +5039,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Relay server is only used if necessary. Another party can observe your IP address. - Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét. + Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. No comment provided by engineer. Relay server protects your IP address, but it can observe the duration of the call. - Az átjátszó kiszolgáló megvédi IP-címét, de megfigyelheti a hívás időtartamát. + Az átjátszó kiszolgáló megvédi az IP-címet, de megfigyelheti a hívás időtartamát. No comment provided by engineer. @@ -5192,7 +5194,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Revert - Visszaállít + Visszaállítás No comment provided by engineer. @@ -5226,7 +5228,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< SMP servers - Üzenetküldő (SMP) kiszolgálók + SMP kiszolgálók No comment provided by engineer. @@ -5261,7 +5263,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Save and update group profile - Mentés és a csoport profil frissítése + Mentés és csoportprofil frissítése No comment provided by engineer. @@ -5276,7 +5278,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Save group profile - Csoport profil elmentése + Csoportprofil elmentése No comment provided by engineer. @@ -5740,12 +5742,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Share address - Azonosító megosztása + Cím megosztása No comment provided by engineer. Share address with contacts? - Megosztja az azonosítót az ismerőseivel? + Megosztja a címet az ismerőseivel? No comment provided by engineer. @@ -5809,7 +5811,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< SimpleX Address - SimpleX azonosító + SimpleX cím No comment provided by engineer. @@ -5839,12 +5841,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< SimpleX address - SimpleX azonosító + SimpleX cím No comment provided by engineer. SimpleX contact address - SimpleX kapcsolattartási azonosító + SimpleX kapcsolattartási cím simplex link type @@ -6302,7 +6304,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This is your own SimpleX address! - Ez a SimpleX azonosítója! + Ez az ön SimpleX címe! No comment provided by engineer. @@ -6355,7 +6357,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. To protect your IP address, private routing uses your SMP servers to deliver messages. - Az IP-címe védelme érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. + Az IP-cím védelmének érdekében a privát útválasztás az SMP kiszolgálókat használja az üzenetek kézbesítéséhez. No comment provided by engineer. @@ -6974,7 +6976,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol You accepted connection - Kapcsolódás elfogadva + Kapcsolat létrehozása No comment provided by engineer. @@ -7095,12 +7097,12 @@ Csatlakozási kérés megismétlése? You can share this address with your contacts to let them connect with **%@**. - Megoszthatja ezt az azonosítót az ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül. + Megoszthatja ezt a címet az ismerőseivel, hogy kapcsolatba léphessenek önnel a(z) **%@** nevű profilján keresztül. No comment provided by engineer. You can share your address as a link or QR code - anybody can connect to you. - Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki kapcsolódhat önhöz. + Megoszthatja a címét egy hivatkozásként vagy QR-kódként – így bárki kapcsolódhat önhöz. No comment provided by engineer. @@ -7140,7 +7142,7 @@ Csatlakozási kérés megismétlése? You have already requested connection via this address! - Már kért egy kapcsolódási kérelmet ezen az azonosítón keresztül! + Már kért egy kapcsolódási kérelmet ezen a címen keresztül! No comment provided by engineer. @@ -7237,7 +7239,7 @@ Kapcsolódási kérés megismétlése? You won't lose your contacts if you later delete your address. - Nem veszíti el az ismerőseit, ha később törli az azonosítóját. + Nem veszíti el az ismerőseit, ha később törli a címét. No comment provided by engineer. @@ -7267,7 +7269,7 @@ Kapcsolódási kérés megismétlése? Your SimpleX address - SimpleX azonosítója + Az ön SimpleX címe No comment provided by engineer. @@ -7446,7 +7448,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. and %lld other events - és %lld további esemény + és további %lld esemény No comment provided by engineer. @@ -7480,7 +7482,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. blocked %@ - %@ letiltva + letiltotta őt: %@ rcv group event chat item @@ -7530,12 +7532,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. changing address for %@… - cím módosítása %@ számára… + cím megváltoztatása nála: %@… chat item text changing address… - azonosító megváltoztatása… + cím megváltoztatása… chat item text @@ -7694,7 +7696,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. duplicate message - duplikálódott üzenet + duplikált üzenet integrity error chat item @@ -7797,7 +7799,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. group profile updated - csoport profil frissítve + csoportprofil frissítve snd group event chat item @@ -7976,7 +7978,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. off - ki + kikapcsolva enabled status group pref value time to disappear @@ -8051,7 +8053,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. removed contact address - törölt kapcsolattartási azonosító + törölt kapcsolattartási cím profile update event chat item @@ -8103,16 +8105,19 @@ A SimpleX kiszolgálók nem látjhatják profilját. server queue info: %1$@ last received msg: %2$@ + kiszolgáló üzenet-várakotatási információ: %1$@ + +utoljára fogadott üzenet: %2$@ queue info set new contact address - új kapcsolattartási azonosító beállítása + új kapcsolattartási cím beállítása profile update event chat item set new profile picture - új profilkép beállítása + új profilképet állított be profile update event chat item @@ -8182,7 +8187,7 @@ last received msg: %2$@ via contact address link - kapcsolattartási azonosító-hivatkozáson keresztül + kapcsolattartási cím-hivatkozáson keresztül chat list item description @@ -8257,12 +8262,12 @@ last received msg: %2$@ you changed address - azonosítója megváltoztatva + cím megváltoztatva chat item text you changed address for %@ - %@ azonosítója megváltoztatva + cím megváltoztatva nála: %@ chat item text diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 21acc23c9b..024950730f 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -1837,6 +1837,7 @@ Questo è il tuo link una tantum! Debug delivery + Debug della consegna No comment provided by engineer. @@ -3922,6 +3923,7 @@ Questo è il tuo link per il gruppo %@! Message queue info + Info coda messaggi No comment provided by engineer. @@ -4702,7 +4704,7 @@ Errore: %@ Private message routing - Instradamento privato messaggi + Instradamento privato dei messaggi No comment provided by engineer. @@ -8103,6 +8105,9 @@ I server di SimpleX non possono vedere il tuo profilo. server queue info: %1$@ last received msg: %2$@ + info coda server: %1$@ + +ultimo msg ricevuto: %2$@ queue info diff --git a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff index 555d40285c..511536427d 100644 --- a/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff +++ b/apps/ios/SimpleX Localizations/ko.xcloc/Localized Contents/ko.xliff @@ -5,9 +5,11 @@ - + + + No comment provided by engineer. @@ -484,53 +486,62 @@ Can't delete user profile! No comment provided by engineer. - + Can't invite contact! + 주소를 초대할 수 없습니다. No comment provided by engineer. Can't invite contacts! No comment provided by engineer. - + Cancel + 취소 No comment provided by engineer. - + Cannot access keychain to save database password + 데이터베이스 암호를 저장하는 키체인에 접근 할 수 없습니다 No comment provided by engineer. - + Cannot receive file + 파일을 받을 수 없습니다 No comment provided by engineer. - + Change + 변경 No comment provided by engineer. Change database passphrase? No comment provided by engineer. - + Change member role? + 멤버 역할을 변경하시겠습니까? No comment provided by engineer. - + Change receiving address + 수신 주소 변경 No comment provided by engineer. Change receiving address? - 修改接收地址? + 수신 주소를 변경하시겠습니까? No comment provided by engineer. - + Change role + 역할 변경 No comment provided by engineer. - + Chat archive + 채팅 기록 보관함 No comment provided by engineer. @@ -545,8 +556,9 @@ Chat database deleted No comment provided by engineer. - + Chat database imported + 채팅 데이터베이스를 가져옴 No comment provided by engineer. @@ -2397,24 +2409,29 @@ We will be adding server redundancy to prevent lost messages. Send live message No comment provided by engineer. - + Send notifications + 알림 전송 No comment provided by engineer. - + Send notifications: + 알림 전송: No comment provided by engineer. - + Send questions and ideas + 질문이나 아이디어 보내기 No comment provided by engineer. - + Send them from gallery or custom keyboards. + 갤러리 또는 사용자 정의 키보드에서 그들을 보내십시오. No comment provided by engineer. - + Sender cancelled file transfer. + 상대방이 파일 전송을 취소했습니다. No comment provided by engineer. @@ -3755,6 +3772,26 @@ SimpleX servers cannot see your profile. \~strike~ No comment provided by engineer. + + Change passcode + 패스코드 변경 + authentication reason + + + Cellular + 셀룰러 + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + 이 서버 또는 도착 서버가 비밀 라우팅을 지원하지 않을 때 직통 메시지 보내기. + No comment provided by engineer. + + + Send up to 100 last messages to new members. + 새로운 멤버에게 최대 100개의 마지막 메시지 보내기. + No comment provided by engineer. + @@ -3778,8 +3815,9 @@ SimpleX servers cannot see your profile. SimpleX needs microphone access for audio and video calls, and to record voice messages. Privacy - Microphone Usage Description - + SimpleX needs access to Photo Library for saving captured and received media + SimpleX는 캡처 및 수신 된 미디어를 저장하기 위해 사진 라이브러리에 접근이 필요합니다 Privacy - Photo Library Additions Usage Description @@ -3793,8 +3831,9 @@ SimpleX servers cannot see your profile. SimpleX NSE Bundle display name - + SimpleX NSE + SimpleX NSE Bundle name diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 186c3a7e32..44fda15333 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -1837,6 +1837,7 @@ Dit is uw eigen eenmalige link! Debug delivery + Foutopsporing bezorging No comment provided by engineer. @@ -3922,6 +3923,7 @@ Dit is jouw link voor groep %@! Message queue info + Informatie over berichtenwachtrij No comment provided by engineer. @@ -8103,6 +8105,9 @@ SimpleX servers kunnen uw profiel niet zien. server queue info: %1$@ last received msg: %2$@ + informatie over serverwachtrij: %1$@ + +laatst ontvangen bericht: %2$@ queue info diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 667a725e93..dd4aecf428 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -1837,6 +1837,7 @@ To jest twój jednorazowy link! Debug delivery + Dostarczenie debugowania No comment provided by engineer. @@ -3922,6 +3923,7 @@ To jest twój link do grupy %@! Message queue info + Informacje kolejki wiadomości No comment provided by engineer. @@ -8103,6 +8105,9 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. server queue info: %1$@ last received msg: %2$@ + Informacje kolejki serwera: %1$@ + +ostatnia otrzymana wiadomość: %2$@ queue info diff --git a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff index 5942971eac..a9bf86e778 100644 --- a/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff +++ b/apps/ios/SimpleX Localizations/pt.xcloc/Localized Contents/pt.xliff @@ -5,9 +5,11 @@ - + + + No comment provided by engineer. @@ -50,16 +52,19 @@ Available in v5.1 #secreto# No comment provided by engineer. - + %@ + %@ No comment provided by engineer. - + %@ %@ + %@ %@ No comment provided by engineer. - + %@ / %@ + %@ / %@ No comment provided by engineer. @@ -117,12 +122,14 @@ Available in v5.1 %d mensagem(s) ignorada(s) integrity error chat item - + %lld + %lld No comment provided by engineer. - + %lld %@ + %lld %@ No comment provided by engineer. @@ -155,24 +162,29 @@ Available in v5.1 %lld segundos No comment provided by engineer. - + %lldd + %lldd No comment provided by engineer. - + %lldh + %lldh No comment provided by engineer. - + %lldk + %lldk No comment provided by engineer. - + %lldm + %lldm No comment provided by engineer. - + %lldmth + %lldmth No comment provided by engineer. @@ -193,8 +205,9 @@ Available in v5.1 %u mensagens ignoradas. No comment provided by engineer. - + ( + ( No comment provided by engineer. @@ -4540,6 +4553,31 @@ SimpleX servers cannot see your profile. Confirmar envio No comment provided by engineer. + + %@ downloaded + %@ baixado + No comment provided by engineer. + + + # %@ + # %@ + copied message info title, # <title> + + + %@: + %@: + copied message info + + + %@ (current) + %@(atual) + No comment provided by engineer. + + + %@ (current): + %@ (atual): + copied message info + diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 3ce0b3ec75..15ea09d060 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -1837,6 +1837,7 @@ Bu senin kendi tek kullanımlık bağlantın! Debug delivery + Hata ayıklama teslimatı No comment provided by engineer. @@ -3922,6 +3923,7 @@ Bu senin grup için bağlantın %@! Message queue info + Mesaj kuyruğu bilgisi No comment provided by engineer. @@ -8103,6 +8105,9 @@ SimpleX sunucuları profilinizi göremez. server queue info: %1$@ last received msg: %2$@ + sunucu kuyruk bilgisi: %1$@ + +son alınan msj: %2$@ queue info diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 7234888bf1..aa069d444b 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -1837,6 +1837,7 @@ This is your own one-time link! Debug delivery + Доставка налагодження No comment provided by engineer. @@ -3922,6 +3923,7 @@ This is your link for group %@! Message queue info + Інформація про чергу повідомлень No comment provided by engineer. @@ -8103,6 +8105,9 @@ SimpleX servers cannot see your profile. server queue info: %1$@ last received msg: %2$@ + інформація про чергу на сервері: %1$@ + +останнє отримане повідомлення: %2$@ queue info diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index a15ceb805c..9748a26730 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "по-горе, след това избери:"; -/* No comment provided by engineer. */ -"Accent color" = "Основен цвят"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Приеми"; @@ -647,7 +644,7 @@ /* rcv group event chat item */ "blocked %@" = "блокиран %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "блокиран от админ"; /* No comment provided by engineer. */ @@ -831,9 +828,6 @@ /* No comment provided by engineer. */ "colored" = "цветен"; -/* No comment provided by engineer. */ -"Colors" = "Цветове"; - /* server test step */ "Compare file" = "Сравни файл"; @@ -1011,7 +1005,7 @@ /* No comment provided by engineer. */ "Continue" = "Продължи"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Копирай"; /* No comment provided by engineer. */ @@ -1779,7 +1773,8 @@ /* No comment provided by engineer. */ "Error: " = "Грешка: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Грешка: %@"; /* No comment provided by engineer. */ @@ -3791,9 +3786,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Текстът, който поставихте, не е SimpleX линк за връзка."; -/* No comment provided by engineer. */ -"Theme" = "Тема"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Тези настройки са за текущия ви профил **%@**."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index cf92e47d21..95c79efd8b 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -289,9 +289,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "výše, pak vyberte:"; -/* No comment provided by engineer. */ -"Accent color" = "Zbarvení"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Přijmout"; @@ -678,9 +675,6 @@ /* No comment provided by engineer. */ "colored" = "barevné"; -/* No comment provided by engineer. */ -"Colors" = "Barvy"; - /* server test step */ "Compare file" = "Porovnat soubor"; @@ -813,7 +807,7 @@ /* No comment provided by engineer. */ "Continue" = "Pokračovat"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Kopírovat"; /* No comment provided by engineer. */ @@ -1461,7 +1455,8 @@ /* No comment provided by engineer. */ "Error: " = "Chyba: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Chyba: %@"; /* No comment provided by engineer. */ @@ -3095,9 +3090,6 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Servery pro nová připojení vašeho aktuálního chat profilu **%@**."; -/* No comment provided by engineer. */ -"Theme" = "Téma"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 40c48745df..7464e096c4 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "Danach die gewünschte Aktion auswählen:"; -/* No comment provided by engineer. */ -"Accent color" = "Akzentfarbe"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Annehmen"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "%@ wurde blockiert"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "wurde vom Administrator blockiert"; /* No comment provided by engineer. */ @@ -726,7 +723,7 @@ "Capacity exceeded - recipient did not receive previously sent messages." = "Kapazität überschritten - der Empfänger hat die zuvor gesendeten Nachrichten nicht empfangen."; /* No comment provided by engineer. */ -"Cellular" = "Zellulär"; +"Cellular" = "Mobilfunknetz"; /* No comment provided by engineer. */ "Change" = "Ändern"; @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "farbig"; -/* No comment provided by engineer. */ -"Colors" = "Farben"; - /* server test step */ "Compare file" = "Datei vergleichen"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Weiter"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Kopieren"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "Tage"; +/* No comment provided by engineer. */ +"Debug delivery" = "Debugging-Zustellung"; + /* No comment provided by engineer. */ "Decentralized" = "Dezentral"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Fehler: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Fehler: %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Nachrichtenentwurf"; +/* No comment provided by engineer. */ +"Message queue info" = "Nachrichten-Warteschlangen-Information"; + /* chat feature */ "Message reactions" = "Reaktionen auf Nachrichten"; @@ -3060,7 +3061,7 @@ "Protect your chat profiles with a password!" = "Ihre Chat-Profile mit einem Passwort schützen!"; /* No comment provided by engineer. */ -"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais , die Ihre Kontakte ausgewählt haben.\nAktivieren Sie es in den *Netzwerk & Server* Einstellungen."; +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais, die Ihre Kontakte ausgewählt haben.\nAktivieren Sie es in den *Netzwerk & Server* Einstellungen."; /* No comment provided by engineer. */ "Protocol timeout" = "Protokollzeitüberschreitung"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "Server-Warteschlangen-Information: %1$@\n\nZuletzt empfangene Nachricht: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; -/* No comment provided by engineer. */ -"Theme" = "Design"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Diese Einstellungen betreffen Ihr aktuelles Profil **%@**."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 19e75bcf67..54316b092f 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "y después elige:"; -/* No comment provided by engineer. */ -"Accent color" = "Color"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Aceptar"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "ha bloqueado a %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "bloqueado por administrador"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "coloreado"; -/* No comment provided by engineer. */ -"Colors" = "Colores"; - /* server test step */ "Compare file" = "Comparar archivo"; @@ -967,7 +961,7 @@ "Connection error" = "Error conexión"; /* No comment provided by engineer. */ -"Connection error (AUTH)" = "Error conexión (Autenticación)"; +"Connection error (AUTH)" = "Error de conexión (Autenticación)"; /* chat list item title (it should not be shown */ "connection established" = "conexión establecida"; @@ -979,7 +973,7 @@ "Connection terminated" = "Conexión finalizada"; /* No comment provided by engineer. */ -"Connection timeout" = "Tiempo de conexión expirado"; +"Connection timeout" = "Tiempo de conexión agotado"; /* connection information */ "connection:%@" = "conexión: % @"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Continuar"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Copiar"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "días"; +/* No comment provided by engineer. */ +"Debug delivery" = "Informe debug"; + /* No comment provided by engineer. */ "Decentralized" = "Descentralizada"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Error: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Error: %@"; /* No comment provided by engineer. */ @@ -2101,7 +2099,7 @@ "ICE servers (one per line)" = "Servidores ICE (uno por línea)"; /* No comment provided by engineer. */ -"If you can't meet in person, show QR code in a video call, or share the link." = "Si no puedes reunirte en persona, muestra el código QR por videollamada, o comparte el enlace."; +"If you can't meet in person, show QR code in a video call, or share the link." = "Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace."; /* No comment provided by engineer. */ "If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "¡Si introduces este código al abrir la aplicación, todos los datos de la misma se eliminarán de forma irreversible!"; @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Borrador de mensaje"; +/* No comment provided by engineer. */ +"Message queue info" = "Información cola de mensajes"; + /* chat feature */ "Message reactions" = "Reacciones a mensajes"; @@ -2766,7 +2767,7 @@ "on" = "Activado"; /* No comment provided by engineer. */ -"One-time invitation link" = "Enlace único de invitación de un uso"; +"One-time invitation link" = "Enlace de invitación de un solo uso"; /* No comment provided by engineer. */ "Onion hosts will be required for connection. Requires enabling VPN." = "Se requieren hosts .onion para la conexión. Requiere activación de la VPN."; @@ -2850,13 +2851,13 @@ "Or paste archive link" = "O pegar enlace del archivo"; /* No comment provided by engineer. */ -"Or scan QR code" = "O escanear código QR"; +"Or scan QR code" = "O escanea el código QR"; /* No comment provided by engineer. */ "Or securely share this file link" = "O comparte de forma segura este enlace al archivo"; /* No comment provided by engineer. */ -"Or show this code" = "O mostrar este código"; +"Or show this code" = "O muestra este código QR"; /* No comment provided by engineer. */ "Other" = "Otro"; @@ -2898,7 +2899,7 @@ "Paste link to connect!" = "Pegar enlace para conectar!"; /* No comment provided by engineer. */ -"Paste the link you received" = "Pegar el enlace recibido"; +"Paste the link you received" = "Pega el enlace recibido"; /* No comment provided by engineer. */ "peer-to-peer" = "p2p"; @@ -2907,7 +2908,7 @@ "People can connect to you only via the links you share." = "Las personas pueden conectarse contigo solo mediante los enlaces que compartes."; /* No comment provided by engineer. */ -"Periodically" = "Periódico"; +"Periodically" = "Periódicamente"; /* message decrypt error item */ "Permanent decryption error" = "Error permanente descifrado"; @@ -3063,10 +3064,10 @@ "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Protege tu dirección IP de los servidores de retransmisión elegidos por tus contactos.\nActívalo en ajustes de *Servidores y Redes*."; /* No comment provided by engineer. */ -"Protocol timeout" = "Tiempo de espera del protocolo"; +"Protocol timeout" = "Timeout protocolo"; /* No comment provided by engineer. */ -"Protocol timeout per KB" = "Límite de espera del protocolo por KB"; +"Protocol timeout per KB" = "Timeout protocolo por KB"; /* No comment provided by engineer. */ "Push notifications" = "Notificaciones automáticas"; @@ -3090,22 +3091,22 @@ "Read" = "Leer"; /* No comment provided by engineer. */ -"Read more" = "Saber más"; +"Read more" = "Conoce más"; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Saber más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Saber más en [Guía de Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Conoce más en la [Guía del Usuario](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; /* No comment provided by engineer. */ -"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Saber más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; +"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Conoce más en el [Manual del Usuario](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; /* No comment provided by engineer. */ -"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Saber más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; +"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Conoce más en nuestro [repositorio GitHub](https://github.com/simplex-chat/simplex-chat#readme)."; /* No comment provided by engineer. */ -"Read more in our GitHub repository." = "Saber más en nuestro repositorio GitHub."; +"Read more in our GitHub repository." = "Conoce más en nuestro repositorio GitHub."; /* No comment provided by engineer. */ "Receipts are disabled" = "Las confirmaciones están desactivadas"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "La dirección del servidor es incompatible con la configuración de la red."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "información cola del servidor: %1$@\n\núltimo mensaje recibido: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "El servidor requiere autorización para crear colas, comprueba la contraseña"; @@ -3591,7 +3595,7 @@ "Share link" = "Compartir enlace"; /* No comment provided by engineer. */ -"Share this 1-time invite link" = "Compartir este enlace de un uso"; +"Share this 1-time invite link" = "Comparte este enlace de un solo uso"; /* No comment provided by engineer. */ "Share with contacts" = "Compartir con contactos"; @@ -3771,7 +3775,7 @@ "Tap to join incognito" = "Pulsa para unirte en modo incógnito"; /* No comment provided by engineer. */ -"Tap to paste link" = "Pulsa para pegar enlace"; +"Tap to paste link" = "Pulsa para pegar el enlacePulsa para pegar enlace"; /* No comment provided by engineer. */ "Tap to scan" = "Pulsa para escanear"; @@ -3780,7 +3784,7 @@ "Tap to start a new chat" = "Pulsa para iniciar chat nuevo"; /* No comment provided by engineer. */ -"TCP connection timeout" = "Tiempo de espera de la conexión TCP agotado"; +"TCP connection timeout" = "Timeout de la conexión TCP"; /* No comment provided by engineer. */ "TCP_KEEPCNT" = "TCP_KEEPCNT"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "El texto pegado no es un enlace SimpleX."; -/* No comment provided by engineer. */ -"Theme" = "Tema"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Esta configuración afecta a tu perfil actual **%@**."; @@ -3942,7 +3943,7 @@ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Para proteger tu información, activa el Bloqueo SimpleX.\nSe te pedirá que completes la autenticación antes de activar esta función."; /* No comment provided by engineer. */ -"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tus servidores SMP para enviar mensajes."; +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes."; /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Para grabar el mensaje de voz concede permiso para usar el micrófono."; @@ -4029,7 +4030,7 @@ "Unknown error" = "Error desconocido"; /* No comment provided by engineer. */ -"unknown relays" = "servidor de retransmisión desconocido"; +"unknown relays" = "con servidores desconocidos"; /* No comment provided by engineer. */ "Unknown servers!" = "¡Servidores desconocidos!"; @@ -4041,7 +4042,7 @@ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "A menos que utilices la interfaz de llamadas de iOS, activa el modo No molestar para evitar interrupciones."; /* No comment provided by engineer. */ -"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "A menos que tu contacto haya eliminado la conexión o\nque este enlace ya se haya usado, podría ser un error. Por favor, notifícalo.\nPara conectarte, pide a tu contacto que cree otro enlace de conexión y comprueba que tienes buena conexión de red."; +"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "A menos que tu contacto haya eliminado la conexión o el enlace haya sido usado, podría ser un error. Por favor, notifícalo.\nPara conectarte pide a tu contacto que cree otro enlace y comprueba la conexión de red."; /* No comment provided by engineer. */ "Unlink" = "Desenlazar"; @@ -4059,7 +4060,7 @@ "Unmute" = "Activar audio"; /* No comment provided by engineer. */ -"unprotected" = "desprotegido"; +"unprotected" = "con IP desprotegida"; /* No comment provided by engineer. */ "Unread" = "No leído"; @@ -4131,10 +4132,10 @@ "Use only local notifications?" = "¿Usar sólo notificaciones locales?"; /* No comment provided by engineer. */ -"Use private routing with unknown servers when IP address is not protected." = "Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida."; +"Use private routing with unknown servers when IP address is not protected." = "Usar enrutamiento privado con servidores desconocidos cuando tu dirección IP no está protegida."; /* No comment provided by engineer. */ -"Use private routing with unknown servers." = "Usar enrutamiento privado con servidores desconocidos."; +"Use private routing with unknown servers." = "Usar enrutamiento privado con servidores de retransmisión desconocidos."; /* No comment provided by engineer. */ "Use server" = "Usar servidor"; @@ -4524,7 +4525,7 @@ "You will be connected when your contact's device is online, please wait or check later!" = "Te conectarás cuando el dispositivo del contacto esté en línea, por favor espera o compruébalo más tarde."; /* No comment provided by engineer. */ -"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Se te pedirá identificarte cuándo inicies o continues usando la aplicación tras 30 segundos en segundo plano."; +"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Se te pedirá autenticarte cuando inicies la aplicación o sigas usándola tras 30 segundos en segundo plano."; /* No comment provided by engineer. */ "You will connect to all group members." = "Te conectarás con todos los miembros del grupo."; @@ -4596,7 +4597,7 @@ "Your profile **%@** will be shared." = "Tu perfil **%@** será compartido."; /* No comment provided by engineer. */ -"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos.\nLos servidores de SimpleX no pueden ver tu perfil."; +"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos.\nLos servidores SimpleX no pueden ver tu perfil."; /* No comment provided by engineer. */ "Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 58e4cf4a5a..1405f14694 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -280,9 +280,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "edellä, valitse sitten:"; -/* No comment provided by engineer. */ -"Accent color" = "Korostusväri"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Hyväksy"; @@ -663,9 +660,6 @@ /* No comment provided by engineer. */ "colored" = "värillinen"; -/* No comment provided by engineer. */ -"Colors" = "Värit"; - /* server test step */ "Compare file" = "Vertaa tiedostoa"; @@ -795,7 +789,7 @@ /* No comment provided by engineer. */ "Continue" = "Jatka"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Kopioi"; /* No comment provided by engineer. */ @@ -1434,7 +1428,8 @@ /* No comment provided by engineer. */ "Error: " = "Virhe: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Virhe: %@"; /* No comment provided by engineer. */ @@ -3056,9 +3051,6 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "Palvelimet nykyisen keskusteluprofiilisi uusille yhteyksille **%@**."; -/* No comment provided by engineer. */ -"Theme" = "Teema"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Nämä asetukset koskevat nykyistä profiiliasi **%@**."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 1c8396bdfe..aa9bfef3e5 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "ci-dessus, puis choisissez :"; -/* No comment provided by engineer. */ -"Accent color" = "Couleur principale"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Accepter"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "%@ bloqué"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "bloqué par l'administrateur"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "coloré"; -/* No comment provided by engineer. */ -"Colors" = "Couleurs"; - /* server test step */ "Compare file" = "Comparer le fichier"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Continuer"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Copier"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "jours"; +/* No comment provided by engineer. */ +"Debug delivery" = "Livraison de débogage"; + /* No comment provided by engineer. */ "Decentralized" = "Décentralisé"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Erreur : "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Erreur : %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Brouillon de message"; +/* No comment provided by engineer. */ +"Message queue info" = "Informations sur la file d'attente des messages"; + /* chat feature */ "Message reactions" = "Réactions aux messages"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "L'adresse du serveur est incompatible avec les paramètres du réseau."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "info sur la file d'attente du serveur : %1$@\n\ndernier message reçu : %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Le texte collé n'est pas un lien SimpleX."; -/* No comment provided by engineer. */ -"Theme" = "Thème"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ces paramètres s'appliquent à votre profil actuel **%@**."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index b0ccd9e28c..44b3447ef8 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -266,7 +266,7 @@ "`a + b`" = "a + b"; /* email text */ -"

Hi!

\n

Connect to me via SimpleX Chat

" = "

Üdvözlöm!

\n

Csatlakozzon hozzám a SimpleX Chaten

"; +"

Hi!

\n

Connect to me via SimpleX Chat

" = "

Üdvözlöm!

\n

Csatlakozzon hozzám a SimpleX Chaten

"; /* No comment provided by engineer. */ "~strike~" = "\\~áthúzott~"; @@ -326,20 +326,17 @@ "Abort changing address?" = "Címváltoztatás megszakítása??"; /* No comment provided by engineer. */ -"About SimpleX" = "A SimpleX névjegye"; +"About SimpleX" = "A SimpleX-ről"; /* No comment provided by engineer. */ -"About SimpleX address" = "A SimpleX azonosítóról"; +"About SimpleX address" = "A SimpleX címről"; /* No comment provided by engineer. */ -"About SimpleX Chat" = "A SimpleX Chat névjegye"; +"About SimpleX Chat" = "A SimpleX Chat-ről"; /* No comment provided by engineer. */ "above, then choose:" = "gombra fent, majd válassza ki:"; -/* No comment provided by engineer. */ -"Accent color" = "Kiemelő szín"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Elfogadás"; @@ -357,7 +354,7 @@ "accepted call" = "elfogadott hívás"; /* No comment provided by engineer. */ -"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára."; +"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára."; /* No comment provided by engineer. */ "Add contact" = "Ismerős hozzáadása"; @@ -462,7 +459,7 @@ "Allow message reactions." = "Üzenetreakciók engedélyezése."; /* No comment provided by engineer. */ -"Allow sending direct messages to members." = "Közvetlen üzenetek küldésének engedélyezése a tagok számára."; +"Allow sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között engedélyezve van."; /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Az eltűnő üzenetek küldése engedélyezve van."; @@ -522,7 +519,7 @@ "An empty chat profile with the provided name is created, and the app opens as usual." = "Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik."; /* No comment provided by engineer. */ -"and %lld other events" = "és %lld további esemény"; +"and %lld other events" = "és további %lld esemény"; /* No comment provided by engineer. */ "Answer call" = "Hívás fogadása"; @@ -609,7 +606,7 @@ "Back" = "Vissza"; /* No comment provided by engineer. */ -"Bad desktop address" = "Hibás számítógép azonosító"; +"Bad desktop address" = "Hibás számítógép cím"; /* integrity error chat item */ "bad message hash" = "téves üzenet hash"; @@ -633,7 +630,7 @@ "Block" = "Blokkolás"; /* No comment provided by engineer. */ -"Block for all" = "Mindenki számára letiltva"; +"Block for all" = "Letiltás mindenki számára"; /* No comment provided by engineer. */ "Block group members" = "Csoporttagok blokkolása"; @@ -651,9 +648,9 @@ "blocked" = "blokkolva"; /* rcv group event chat item */ -"blocked %@" = "%@ letiltva"; +"blocked %@" = "letiltotta őt: %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "letiltva az admin által"; /* No comment provided by engineer. */ @@ -699,7 +696,7 @@ "Calls" = "Hívások"; /* No comment provided by engineer. */ -"Camera not available" = "A fényképező nem elérhető"; +"Camera not available" = "A kamera nem elérhető"; /* No comment provided by engineer. */ "Can't invite contact!" = "Ismerősök meghívása le van tiltva!"; @@ -769,10 +766,10 @@ "changed your role to %@" = "megváltoztatta a szerepkörét erre: %@"; /* chat item text */ -"changing address for %@…" = "cím módosítása %@ számára…"; +"changing address for %@…" = "cím megváltoztatása nála: %@…"; /* chat item text */ -"changing address…" = "azonosító megváltoztatása…"; +"changing address…" = "cím megváltoztatása…"; /* No comment provided by engineer. */ "Chat archive" = "Csevegési archívum"; @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "színes"; -/* No comment provided by engineer. */ -"Colors" = "Színek"; - /* server test step */ "Compare file" = "Fájl összehasonlítás"; @@ -904,10 +898,10 @@ "Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az egyszer használatos hivatkozása!"; /* No comment provided by engineer. */ -"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz a SimpleX azonosítója!"; +"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz az ön SimpleX címe!"; /* No comment provided by engineer. */ -"Connect via contact address" = "Kapcsolódás a kapcsolattartási azonosítón keresztül"; +"Connect via contact address" = "Kapcsolódás a kapcsolattartási címen keresztül"; /* No comment provided by engineer. */ "Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Folytatás"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Másolás"; /* No comment provided by engineer. */ @@ -1039,7 +1033,7 @@ "Create a group using a random profile." = "Csoport létrehozása véletlenszerűen létrehozott profillal."; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel."; +"Create an address to let people connect with you." = "Cím létrehozása, hogy az emberek kapcsolatba léphessenek önnel."; /* server test step */ "Create file" = "Fájl létrehozása"; @@ -1066,7 +1060,7 @@ "Create secret group" = "Titkos csoport létrehozása"; /* No comment provided by engineer. */ -"Create SimpleX address" = "SimpleX azonosító létrehozása"; +"Create SimpleX address" = "SimpleX cím létrehozása"; /* No comment provided by engineer. */ "Create your profile" = "Saját profil létrehozása"; @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "nap"; +/* No comment provided by engineer. */ +"Debug delivery" = "Kézbesítési hibák felderítése"; + /* No comment provided by engineer. */ "Decentralized" = "Decentralizált"; @@ -1189,10 +1186,10 @@ "Delete %lld messages?" = "Töröl %lld üzenetet?"; /* No comment provided by engineer. */ -"Delete address" = "Azonosító törlése"; +"Delete address" = "Cím törlése"; /* No comment provided by engineer. */ -"Delete address?" = "Azonosító törlése?"; +"Delete address?" = "Cím törlése?"; /* No comment provided by engineer. */ "Delete after" = "Törlés ennyi idő után"; @@ -1324,7 +1321,7 @@ "Description" = "Leírás"; /* No comment provided by engineer. */ -"Desktop address" = "Számítógép azonosítója"; +"Desktop address" = "Számítógép címe"; /* No comment provided by engineer. */ "Desktop app version %@ is not compatible with this app." = "Az asztali kliens verziója %@ nem kompatibilis ezzel az alkalmazással."; @@ -1423,7 +1420,7 @@ "Do NOT use SimpleX for emergency calls." = "NE használja a SimpleX-et segélyhívásokhoz."; /* No comment provided by engineer. */ -"Don't create address" = "Ne hozzon létre azonosítót"; +"Don't create address" = "Ne hozzon létre címet"; /* No comment provided by engineer. */ "Don't enable" = "Ne engedélyezze"; @@ -1453,7 +1450,7 @@ "Duplicate display name!" = "Duplikált megjelenítési név!"; /* integrity error chat item */ -"duplicate message" = "duplikálódott üzenet"; +"duplicate message" = "duplikált üzenet"; /* No comment provided by engineer. */ "Duration" = "Időtartam"; @@ -1507,7 +1504,7 @@ "Enable SimpleX Lock" = "SimpleX zárolás engedélyezése"; /* No comment provided by engineer. */ -"Enable TCP keep-alive" = "TCP életben tartásának engedélyezése"; +"Enable TCP keep-alive" = "TCP életben tartása"; /* enabled status */ "enabled" = "engedélyezve"; @@ -1633,7 +1630,7 @@ "Error" = "Hiba"; /* No comment provided by engineer. */ -"Error aborting address change" = "Hiba az azonosító megváltoztatásának megszakításakor"; +"Error aborting address change" = "Hiba a cím megváltoztatásának megszakításakor"; /* No comment provided by engineer. */ "Error accepting contact request" = "Hiba történt a kapcsolatfelvételi kérelem elfogadásakor"; @@ -1645,7 +1642,7 @@ "Error adding member(s)" = "Hiba a tag(-ok) hozzáadásakor"; /* No comment provided by engineer. */ -"Error changing address" = "Hiba az azonosító megváltoztatásakor"; +"Error changing address" = "Hiba a cím megváltoztatásakor"; /* No comment provided by engineer. */ "Error changing role" = "Hiba a szerepkör megváltoztatásakor"; @@ -1654,7 +1651,7 @@ "Error changing setting" = "Hiba a beállítás megváltoztatásakor"; /* No comment provided by engineer. */ -"Error creating address" = "Hiba az azonosító létrehozásakor"; +"Error creating address" = "Hiba a cím létrehozásakor"; /* No comment provided by engineer. */ "Error creating group" = "Hiba a csoport létrehozásakor"; @@ -1735,7 +1732,7 @@ "Error saving %@ servers" = "Hiba történt a %@ kiszolgálók mentése közben"; /* No comment provided by engineer. */ -"Error saving group profile" = "Hiba a csoport profil mentésekor"; +"Error saving group profile" = "Hiba a csoportprofil mentésekor"; /* No comment provided by engineer. */ "Error saving ICE servers" = "Hiba az ICE kiszolgálók mentésekor"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Hiba: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Hiba: %@"; /* No comment provided by engineer. */ @@ -2029,13 +2027,13 @@ "Group preferences" = "Csoport beállítások"; /* No comment provided by engineer. */ -"Group profile" = "Csoport profil"; +"Group profile" = "Csoportprofil"; /* No comment provided by engineer. */ "Group profile is stored on members' devices, not on the servers." = "A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon."; /* snd group event chat item */ -"group profile updated" = "csoport profil frissítve"; +"group profile updated" = "csoportprofil frissítve"; /* No comment provided by engineer. */ "Group welcome message" = "Csoport üdvözlő üzenete"; @@ -2437,10 +2435,10 @@ "Make profile private!" = "Tegye priváttá a profilját!"; /* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nem duplikáltak (%@)."; +"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva (%@)."; /* No comment provided by engineer. */ -"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak."; +"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva."; /* No comment provided by engineer. */ "Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" = "Sokan kérdezték: *ha a SimpleX-nek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*"; @@ -2476,10 +2474,10 @@ "member connected" = "kapcsolódott"; /* No comment provided by engineer. */ -"Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: \"%@\". A csoport minden tagja értesítést kap róla."; +"Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: „%@”. A csoport minden tagja értesítést kap róla."; /* No comment provided by engineer. */ -"Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre meg fog változni erre: \"%@\". A tag új meghívást fog kapni."; +"Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre meg fog változni erre: „%@”. A tag új meghívást fog kapni."; /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "A tag eltávolítása a csoportból - ez a művelet nem vonható vissza!"; @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Üzenetvázlat"; +/* No comment provided by engineer. */ +"Message queue info" = "Üzenet-várakoztatási információ"; + /* chat feature */ "Message reactions" = "Üzenetreakciók"; @@ -2731,7 +2732,7 @@ "Notifications are disabled!" = "Az értesítések le vannak tiltva!"; /* No comment provided by engineer. */ -"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Most már az adminok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (\"megfigyelő\" szerepkör)"; +"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Most már az adminok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat („megfigyelő” szerepkör)"; /* member role */ "observer" = "megfigyelő"; @@ -2739,7 +2740,7 @@ /* enabled status group pref value time to disappear */ -"off" = "ki"; +"off" = "kikapcsolva"; /* No comment provided by engineer. */ "Off" = "Ki"; @@ -2886,10 +2887,10 @@ "Password to show" = "Jelszó megjelenítése"; /* past/unknown group member */ -"Past member %@" = "Korábbi csoport tag %@"; +"Past member %@" = "Már nem tag - %@"; /* No comment provided by engineer. */ -"Paste desktop address" = "Számítógép azonosítójának beillesztése"; +"Paste desktop address" = "Számítógép címének beillesztése"; /* No comment provided by engineer. */ "Paste image" = "Kép beillesztése"; @@ -3036,7 +3037,7 @@ "Prohibit messages reactions." = "Az üzenetreakciók tiltása."; /* No comment provided by engineer. */ -"Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése le van tiltva a tagok között."; +"Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között le van tiltva."; /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldése le van tiltva."; @@ -3054,7 +3055,7 @@ "Protect app screen" = "Alkalmazás képernyőjének védelme"; /* No comment provided by engineer. */ -"Protect IP address" = "Az IP-cím védelme"; +"Protect IP address" = "IP-cím védelem"; /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Csevegési profiljok védelme jelszóval!"; @@ -3174,10 +3175,10 @@ "rejected call" = "elutasított hívás"; /* No comment provided by engineer. */ -"Relay server is only used if necessary. Another party can observe your IP address." = "Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét."; +"Relay server is only used if necessary. Another party can observe your IP address." = "Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet."; /* No comment provided by engineer. */ -"Relay server protects your IP address, but it can observe the duration of the call." = "Az átjátszó kiszolgáló megvédi IP-címét, de megfigyelheti a hívás időtartamát."; +"Relay server protects your IP address, but it can observe the duration of the call." = "Az átjátszó kiszolgáló megvédi az IP-címet, de megfigyelheti a hívás időtartamát."; /* No comment provided by engineer. */ "Remove" = "Eltávolítás"; @@ -3198,7 +3199,7 @@ "removed %@" = "%@ eltávolítva"; /* profile update event chat item */ -"removed contact address" = "törölt kapcsolattartási azonosító"; +"removed contact address" = "törölt kapcsolattartási cím"; /* profile update event chat item */ "removed profile picture" = "törölt profilkép"; @@ -3270,7 +3271,7 @@ "Reveal" = "Felfedés"; /* No comment provided by engineer. */ -"Revert" = "Visszaállít"; +"Revert" = "Visszaállítás"; /* No comment provided by engineer. */ "Revoke" = "Visszavonás"; @@ -3306,7 +3307,7 @@ "Save and notify group members" = "Mentés és a csoporttagok értesítése"; /* No comment provided by engineer. */ -"Save and update group profile" = "Mentés és a csoport profil frissítése"; +"Save and update group profile" = "Mentés és csoportprofil frissítése"; /* No comment provided by engineer. */ "Save archive" = "Archívum mentése"; @@ -3315,7 +3316,7 @@ "Save auto-accept settings" = "Automatikus elfogadási beállítások mentése"; /* No comment provided by engineer. */ -"Save group profile" = "Csoport profil elmentése"; +"Save group profile" = "Csoportprofil elmentése"; /* No comment provided by engineer. */ "Save passphrase and open chat" = "Jelmondat elmentése és csevegés megnyitása"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "A kiszolgáló címe nem kompatibilis a hálózati beállításokkal."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "kiszolgáló üzenet-várakotatási információ: %1$@\n\nutoljára fogadott üzenet: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát"; @@ -3549,10 +3553,10 @@ "Set it instead of system authentication." = "Rendszerhitelesítés helyetti beállítás."; /* profile update event chat item */ -"set new contact address" = "új kapcsolattartási azonosító beállítása"; +"set new contact address" = "új kapcsolattartási cím beállítása"; /* profile update event chat item */ -"set new profile picture" = "új profilkép beállítása"; +"set new profile picture" = "új profilképet állított be"; /* No comment provided by engineer. */ "Set passcode" = "Jelkód beállítása"; @@ -3582,10 +3586,10 @@ "Share 1-time link" = "Egyszer használatos hivatkozás megosztása"; /* No comment provided by engineer. */ -"Share address" = "Azonosító megosztása"; +"Share address" = "Cím megosztása"; /* No comment provided by engineer. */ -"Share address with contacts?" = "Megosztja az azonosítót az ismerőseivel?"; +"Share address with contacts?" = "Megosztja a címet az ismerőseivel?"; /* No comment provided by engineer. */ "Share link" = "Hivatkozás megosztása"; @@ -3621,16 +3625,16 @@ "Show:" = "Megjelenítés:"; /* No comment provided by engineer. */ -"SimpleX address" = "SimpleX azonosító"; +"SimpleX address" = "SimpleX cím"; /* No comment provided by engineer. */ -"SimpleX Address" = "SimpleX azonosító"; +"SimpleX Address" = "SimpleX cím"; /* No comment provided by engineer. */ "SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva."; /* simplex link type */ -"SimpleX contact address" = "SimpleX kapcsolattartási azonosító"; +"SimpleX contact address" = "SimpleX kapcsolattartási cím"; /* notification */ "SimpleX encrypted message or connection event" = "SimpleX titkosított üzenet vagy kapcsolati esemény"; @@ -3675,7 +3679,7 @@ "Small groups (max 20)" = "Kis csoportok (max. 20 tag)"; /* No comment provided by engineer. */ -"SMP servers" = "Üzenetküldő (SMP) kiszolgálók"; +"SMP servers" = "SMP kiszolgálók"; /* No comment provided by engineer. */ "Some non-fatal errors occurred during import - you may see Chat console for more details." = "Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat."; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "A beillesztett szöveg nem egy SimpleX hivatkozás."; -/* No comment provided by engineer. */ -"Theme" = "Téma"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak."; @@ -3915,7 +3916,7 @@ "This is your own one-time link!" = "Ez az egyszer használatos hivatkozása!"; /* No comment provided by engineer. */ -"This is your own SimpleX address!" = "Ez a SimpleX azonosítója!"; +"This is your own SimpleX address!" = "Ez az ön SimpleX címe!"; /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes."; @@ -3942,7 +3943,7 @@ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Az adatavédelem érdekében kapcsolja be a SimpleX zárolás funkciót.\nA funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befejezésére."; /* No comment provided by engineer. */ -"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-címe védelme érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez."; +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-cím védelmének érdekében a privát útválasztás az SMP kiszolgálókat használja az üzenetek kézbesítéséhez."; /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Hangüzenet rögzítéséhez adjon engedélyt a mikrofon használathoz."; @@ -4185,7 +4186,7 @@ "Via browser" = "Böngészőn keresztül"; /* chat list item description */ -"via contact address link" = "kapcsolattartási azonosító-hivatkozáson keresztül"; +"via contact address link" = "kapcsolattartási cím-hivatkozáson keresztül"; /* chat list item description */ "via group link" = "csoport hivatkozáson keresztül"; @@ -4347,7 +4348,7 @@ "You **must not** use the same database on two devices." = "**Nem szabad** ugyanazt az adatbázist használni egyszerre két eszközön."; /* No comment provided by engineer. */ -"You accepted connection" = "Kapcsolódás elfogadva"; +"You accepted connection" = "Kapcsolat létrehozása"; /* No comment provided by engineer. */ "You allow" = "Engedélyezte"; @@ -4425,10 +4426,10 @@ "You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait."; /* No comment provided by engineer. */ -"You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt az azonosítót az ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül."; +"You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a címet az ismerőseivel, hogy kapcsolatba léphessenek önnel a(z) **%@** nevű profilján keresztül."; /* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki kapcsolódhat önhöz."; +"You can share your address as a link or QR code - anybody can connect to you." = "Megoszthatja a címét egy hivatkozásként vagy QR-kódként – így bárki kapcsolódhat önhöz."; /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás Beállítások / Adatbázis menü segítségével vagy az alkalmazás újraindításával indíthatja el"; @@ -4446,10 +4447,10 @@ "You can't send messages!" = "Nem lehet üzeneteket küldeni!"; /* chat item text */ -"you changed address" = "azonosítója megváltoztatva"; +"you changed address" = "cím megváltoztatva"; /* chat item text */ -"you changed address for %@" = "%@ azonosítója megváltoztatva"; +"you changed address for %@" = "cím megváltoztatva nála: %@"; /* snd group event chat item */ "you changed role for yourself to %@" = "saját szerepkör megváltoztatva erre: %@"; @@ -4464,7 +4465,7 @@ "You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbálja meg újra."; /* No comment provided by engineer. */ -"You have already requested connection via this address!" = "Már kért egy kapcsolódási kérelmet ezen az azonosítón keresztül!"; +"You have already requested connection via this address!" = "Már kért egy kapcsolódási kérelmet ezen a címen keresztül!"; /* No comment provided by engineer. */ "You have already requested connection!\nRepeat connection request?" = "Már kért egy kapcsolódási kérelmet!\nKapcsolódási kérés megismétlése?"; @@ -4536,7 +4537,7 @@ "You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak."; /* No comment provided by engineer. */ -"You won't lose your contacts if you later delete your address." = "Nem veszíti el az ismerőseit, ha később törli az azonosítóját."; +"You won't lose your contacts if you later delete your address." = "Nem veszíti el az ismerőseit, ha később törli a címét."; /* No comment provided by engineer. */ "you: " = "ön: "; @@ -4614,7 +4615,7 @@ "Your settings" = "Beállítások"; /* No comment provided by engineer. */ -"Your SimpleX address" = "SimpleX azonosítója"; +"Your SimpleX address" = "Az ön SimpleX címe"; /* No comment provided by engineer. */ "Your SMP servers" = "SMP kiszolgálók"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 8bfd8aff77..addb026ab7 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "sopra, quindi scegli:"; -/* No comment provided by engineer. */ -"Accent color" = "Colore principale"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Accetta"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "ha bloccato %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "bloccato dall'amministratore"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "colorato"; -/* No comment provided by engineer. */ -"Colors" = "Colori"; - /* server test step */ "Compare file" = "Confronta file"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Continua"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Copia"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "giorni"; +/* No comment provided by engineer. */ +"Debug delivery" = "Debug della consegna"; + /* No comment provided by engineer. */ "Decentralized" = "Decentralizzato"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Errore: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Errore: %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Bozza dei messaggi"; +/* No comment provided by engineer. */ +"Message queue info" = "Info coda messaggi"; + /* chat feature */ "Message reactions" = "Reazioni ai messaggi"; @@ -2991,7 +2992,7 @@ "Private filenames" = "Nomi di file privati"; /* No comment provided by engineer. */ -"Private message routing" = "Instradamento privato messaggi"; +"Private message routing" = "Instradamento privato dei messaggi"; /* No comment provided by engineer. */ "Private message routing 🚀" = "Instradamento privato dei messaggi 🚀"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "L'indirizzo del server non è compatibile con le impostazioni di rete."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "info coda server: %1$@\n\nultimo msg ricevuto: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Il server richiede l'autorizzazione di creare code, controlla la password"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Il testo che hai incollato non è un link SimpleX."; -/* No comment provided by engineer. */ -"Theme" = "Tema"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Queste impostazioni sono per il tuo profilo attuale **%@**."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index 6c7ff469ad..d66f5bee3d 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -331,9 +331,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "上で選んでください:"; -/* No comment provided by engineer. */ -"Accent color" = "アクセントカラー"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "承諾"; @@ -735,9 +732,6 @@ /* No comment provided by engineer. */ "colored" = "色付き"; -/* No comment provided by engineer. */ -"Colors" = "色"; - /* server test step */ "Compare file" = "ファイルを比較"; @@ -867,7 +861,7 @@ /* No comment provided by engineer. */ "Continue" = "続ける"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "コピー"; /* No comment provided by engineer. */ @@ -1509,7 +1503,8 @@ /* No comment provided by engineer. */ "Error: " = "エラー : "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "エラー : %@"; /* No comment provided by engineer. */ @@ -3113,9 +3108,6 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "現在のチャットプロフィールの新しい接続のサーバ **%@**。"; -/* No comment provided by engineer. */ -"Theme" = "テーマ"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "これらの設定は現在のプロファイル **%@** 用です。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 46eb5ccf2b..c361c2c64e 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "hier boven, kies dan:"; -/* No comment provided by engineer. */ -"Accent color" = "Accent kleur"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Accepteer"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "geblokkeerd %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "geblokkeerd door beheerder"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "gekleurd"; -/* No comment provided by engineer. */ -"Colors" = "Kleuren"; - /* server test step */ "Compare file" = "Bestand vergelijken"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Doorgaan"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Kopiëren"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "dagen"; +/* No comment provided by engineer. */ +"Debug delivery" = "Foutopsporing bezorging"; + /* No comment provided by engineer. */ "Decentralized" = "Gedecentraliseerd"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Fout: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Fout: %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Concept bericht"; +/* No comment provided by engineer. */ +"Message queue info" = "Informatie over berichtenwachtrij"; + /* chat feature */ "Message reactions" = "Reacties op berichten"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Serveradres is niet compatibel met netwerkinstellingen."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "informatie over serverwachtrij: %1$@\n\nlaatst ontvangen bericht: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "De tekst die u hebt geplakt is geen SimpleX link."; -/* No comment provided by engineer. */ -"Theme" = "Thema"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Deze instellingen zijn voor uw huidige profiel **%@**."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index e29fcf7d9e..5860ed1868 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "powyżej, a następnie wybierz:"; -/* No comment provided by engineer. */ -"Accent color" = "Kolor akcentu"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Akceptuj"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "zablokowany %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "zablokowany przez admina"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "kolorowy"; -/* No comment provided by engineer. */ -"Colors" = "Kolory"; - /* server test step */ "Compare file" = "Porównaj plik"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Kontynuuj"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Kopiuj"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "dni"; +/* No comment provided by engineer. */ +"Debug delivery" = "Dostarczenie debugowania"; + /* No comment provided by engineer. */ "Decentralized" = "Zdecentralizowane"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Błąd: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Błąd: %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Wersja robocza wiadomości"; +/* No comment provided by engineer. */ +"Message queue info" = "Informacje kolejki wiadomości"; + /* chat feature */ "Message reactions" = "Reakcje wiadomości"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Adres serwera jest niekompatybilny z ustawieniami sieciowymi."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "Informacje kolejki serwera: %1$@\n\nostatnia otrzymana wiadomość: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Tekst, który wkleiłeś nie jest linkiem SimpleX."; -/* No comment provided by engineer. */ -"Theme" = "Motyw"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Te ustawienia dotyczą Twojego bieżącego profilu **%@**."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index df4b9d7009..450a6147b7 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "наверху, затем выберите:"; -/* No comment provided by engineer. */ -"Accent color" = "Основной цвет"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Принять"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "%@ заблокирован"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "заблокировано администратором"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "цвет"; -/* No comment provided by engineer. */ -"Colors" = "Цвета"; - /* server test step */ "Compare file" = "Сравнение файла"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Продолжить"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Скопировать"; /* No comment provided by engineer. */ @@ -1800,7 +1794,8 @@ /* No comment provided by engineer. */ "Error: " = "Ошибка: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Ошибка: %@"; /* No comment provided by engineer. */ @@ -3872,9 +3867,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Вставленный текст не является SimpleX-ссылкой."; -/* No comment provided by engineer. */ -"Theme" = "Тема"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Установки для Вашего активного профиля **%@**."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 4eeb03bd9b..5ce2e40e14 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -259,9 +259,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "ด้านบน จากนั้นเลือก:"; -/* No comment provided by engineer. */ -"Accent color" = "สีเน้น"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "รับ"; @@ -639,9 +636,6 @@ /* No comment provided by engineer. */ "colored" = "มีสี"; -/* No comment provided by engineer. */ -"Colors" = "สี"; - /* server test step */ "Compare file" = "เปรียบเทียบไฟล์"; @@ -765,7 +759,7 @@ /* No comment provided by engineer. */ "Continue" = "ดำเนินการต่อ"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "คัดลอก"; /* No comment provided by engineer. */ @@ -1386,7 +1380,8 @@ /* No comment provided by engineer. */ "Error: " = "ผิดพลาด: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "ข้อผิดพลาด: % @"; /* No comment provided by engineer. */ @@ -2975,9 +2970,6 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "เซิร์ฟเวอร์สำหรับการเชื่อมต่อใหม่ของโปรไฟล์การแชทปัจจุบันของคุณ **%@**"; -/* No comment provided by engineer. */ -"Theme" = "ธีม"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "การตั้งค่าเหล่านี้ใช้สำหรับโปรไฟล์ปัจจุบันของคุณ **%@**"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 7af1054f56..65496a82bf 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "yukarı çıkın, ardından seçin:"; -/* No comment provided by engineer. */ -"Accent color" = "Vurgu rengi"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Kabul et"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "engellendi %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "yönetici tarafından engellendi"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "renklendirilmiş"; -/* No comment provided by engineer. */ -"Colors" = "Renkler"; - /* server test step */ "Compare file" = "Dosya karşılaştır"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Devam et"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Kopyala"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "gün"; +/* No comment provided by engineer. */ +"Debug delivery" = "Hata ayıklama teslimatı"; + /* No comment provided by engineer. */ "Decentralized" = "Merkezi Olmayan"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Hata: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Hata: %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Mesaj taslağı"; +/* No comment provided by engineer. */ +"Message queue info" = "Mesaj kuyruğu bilgisi"; + /* chat feature */ "Message reactions" = "Mesaj tepkileri"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Sunucu adresi ağ ayarlarıyla uyumlu değil."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "sunucu kuyruk bilgisi: %1$@\n\nson alınan msj: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Yapıştırdığın metin bir SimpleX bağlantısı değildir."; -/* No comment provided by engineer. */ -"Theme" = "Tema"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Bu ayarlar mevcut profiliniz **%@** içindir."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 3483caca88..69aac64ab5 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -337,9 +337,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "вище, а потім обирайте:"; -/* No comment provided by engineer. */ -"Accent color" = "Акцентний колір"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "Прийняти"; @@ -653,7 +650,7 @@ /* rcv group event chat item */ "blocked %@" = "заблоковано %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "заблоковано адміністратором"; /* No comment provided by engineer. */ @@ -840,9 +837,6 @@ /* No comment provided by engineer. */ "colored" = "кольоровий"; -/* No comment provided by engineer. */ -"Colors" = "Кольори"; - /* server test step */ "Compare file" = "Порівняти файл"; @@ -1023,7 +1017,7 @@ /* No comment provided by engineer. */ "Continue" = "Продовжуйте"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "Копіювати"; /* No comment provided by engineer. */ @@ -1167,6 +1161,9 @@ /* time unit */ "days" = "днів"; +/* No comment provided by engineer. */ +"Debug delivery" = "Доставка налагодження"; + /* No comment provided by engineer. */ "Decentralized" = "Децентралізований"; @@ -1800,7 +1797,8 @@ /* No comment provided by engineer. */ "Error: " = "Помилка: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "Помилка: %@"; /* No comment provided by engineer. */ @@ -2496,6 +2494,9 @@ /* No comment provided by engineer. */ "Message draft" = "Чернетка повідомлення"; +/* No comment provided by engineer. */ +"Message queue info" = "Інформація про чергу повідомлень"; + /* chat feature */ "Message reactions" = "Реакції на повідомлення"; @@ -3518,6 +3519,9 @@ /* srv error text. */ "Server address is incompatible with network settings." = "Адреса сервера несумісна з налаштуваннями мережі."; +/* queue info */ +"server queue info: %@\n\nlast received msg: %@" = "інформація про чергу на сервері: %1$@\n\nостаннє отримане повідомлення: %2$@"; + /* server test error */ "Server requires authorization to create queues, check password" = "Сервер вимагає авторизації для створення черг, перевірте пароль"; @@ -3872,9 +3876,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Текст, який ви вставили, не є посиланням SimpleX."; -/* No comment provided by engineer. */ -"Theme" = "Тема"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ці налаштування стосуються вашого поточного профілю **%@**."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 55d59f554c..94588646c8 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -301,9 +301,6 @@ /* No comment provided by engineer. */ "above, then choose:" = "上面,然后选择:"; -/* No comment provided by engineer. */ -"Accent color" = "色调"; - /* accept contact request via notification accept incoming call via notification */ "Accept" = "接受"; @@ -605,7 +602,7 @@ /* rcv group event chat item */ "blocked %@" = "已封禁 %@"; -/* marked deleted chat item preview text */ +/* blocked chat item */ "blocked by admin" = "由管理员封禁"; /* No comment provided by engineer. */ @@ -786,9 +783,6 @@ /* No comment provided by engineer. */ "colored" = "彩色"; -/* No comment provided by engineer. */ -"Colors" = "颜色"; - /* server test step */ "Compare file" = "对比文件"; @@ -951,7 +945,7 @@ /* No comment provided by engineer. */ "Continue" = "继续"; -/* chat item action */ +/* No comment provided by engineer. */ "Copy" = "复制"; /* No comment provided by engineer. */ @@ -1689,7 +1683,8 @@ /* No comment provided by engineer. */ "Error: " = "错误: "; -/* snd error text */ +/* file error text + snd error text */ "Error: %@" = "错误: %@"; /* No comment provided by engineer. */ @@ -3644,9 +3639,6 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "您粘贴的文本不是 SimpleX 链接。"; -/* No comment provided by engineer. */ -"Theme" = "主题"; - /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "这些设置适用于您当前的配置文件 **%@**。"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index e6abfca2c6..f7c6e03811 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -1856,4 +1856,19 @@ \nError: %s تحسين تسليم الرسائل مع انخفاض استخدام البطارية. + مفتاح خاطئ أو عنوان مجموعة الملف غير معروف - على الأرجح حُذف الملف. + خطأ في خادم الملفات: %1$s + خطأ في الملف + خطأ في الملف مؤقت + حالة الرسالة + لم يتم العثور على الملف - على الأرجح حُذف الملف أو إلغاؤه. + حالة الملف + حالة الملف: %s + حالة الرسالة: %s + خطأ في النسخ + تم استخدام هذا الرابط مع جهاز محمول آخر، يُرجى إنشاء رابط جديد على سطح المكتب. + يُرجى التحقق من اتصال الهاتف المحمول وسطح المكتب بنفس الشبكة المحلية، وأن جدار حماية سطح المكتب يسمح بالاتصال. +\nيُرجى مشاركة أي مشاكل أُخرى مع المطورين. + لا يمكن إرسال الرسالة + تفضيلات الدردشة المحدّدة تحظر هذه الرسالة. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 5c7844889c..5a73805093 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -1771,4 +1771,87 @@ koncovým šifrováním s dokonalým dopředným utajením, odmítnutím a obnovením po vloupání.]]> Pokročilé nastavení Všechny barevné režimy + Překročená kapacita - příjemci neobdrží dříve poslané zprávy. + Vždy + Použít na + Potvrdit soubory z neznámých serverů. + Tmavý mód + Téma aplikace + Vždy užít soukromé směrování. + Povolit downgrade + Kopírovat chybu + Barvy chatu + Téma chatu + Další zbarvení 2 + Černé + Mód barvy + Tmavé + Mód tmavých barev + Informace o frontě zpráv + Světlý mód + Upravtesi svůj chat, aby vypadal jinak! + Chyba cílového serveru: %1$s + Chyba: %1$s + Předávací server: %1$s +\nChyba cílového serveru: %2$s + Předávací server: %1$s +\nChyba: %2$s + Upozornění doručování zpráv + Problémy se sítí - zpráva vypršela po mnoha pokusech o odeslání. + Soukromé směrování + Soubor nebyl nalezen - s největší pravděpodobností byl soubor odstraněn nebo zrušen. + Chyba souboru serveru: %1$s + Nelze odeslat zprávu + Chyba souboru + NEpoužívat soukromé směrování. + Záložní směrování zpráv + Režim přeposílání zpráv + Nikdy + Chyba inicializace WebView. Aktualizujte systém na novou verzi. Prosím kontaktujte vývojáře. +\nChyba: %s + Ochrana IP adresy + Status souboru + Status zprávy + Status souboru: %s + Stav zprávy: %s + žádné + Vyplnit + Opakovat + Obnovit uživatelské téma + Obnovit téma aplikace + Bezpečné přijímání souborů + Nové motivy chatu + Soukromé směrování zpráv 🚀 + Chraňte vaši IP adresu před relé zpráv, které jste si vybrali. +\nPovolit v nastavení *Síť & servery*. + Vylepšené doručování zpráv + Perské UI + Prosím zkontrolujte, že mobil a desktop jsou připojeny ke stejné místní síti, a že stolní firewall umožňuje připojení. +\nProsím sdělte jakékoli další problémy vývojářům. + Ne + NEposílejte zprávy přímo, i když váš nebo cílový server nepodporuje soukromé směrování. + SOUBORY + SOUKROMÉ SMĚROVÁNÍ ZPRÁV + Téma profilu + Přijata odpověď + Obnovit barvu + Dobré odpoledne! + Odebrat obrázek + Dobré ráno! + Světlé + Špatný klíč nebo neznámé spojení - pravděpodobně je spojení smazáno. + Špatný klíč nebo neznámá adresa části souboru - soubor je pravděpodobně odstraněn. + Nechráněno + Použít soukromé směrování s neznámými servery. + Použit soukromé směrování s neznámými servery, když IP adresa není chráněna. + Bez Tor nebo VPN bude vaše IP adresa viditelná souborovým serverům. + Ladit doručování + Snížena spotřeba baterie. + Neznámé servery! + Bez Tor nebo VPN bude vaše IP adresa viditelná pro tyto XFTP relé: +\n%1$s. + Když je IP adresa skryta + Ano + Zbarvení tapety + Pozadí tapety \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 2f8db4cba3..cdc5720e37 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -1807,7 +1807,7 @@ weitergeleitet Netzwerkverbindung Keine Netzwerkverbindung - Zellulär + Mobilfunknetz Andere WiFi Kabelgebundenes Netzwerk @@ -1939,4 +1939,19 @@ Server-Warteschlangen-Information: %1$s \n \nZuletzt empfangene Nachricht: %2$s + Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen. + Datei-Server-Fehler: %1$s + Falscher Schlüssel oder unbekannte Datei-Chunk-Adresse - höchstwahrscheinlich wurde die Datei gelöscht. + Datei-Fehler + Nachrichten-Status + Nachrichten-Status: %s + Datei-Status + Datei-Status: %s + Temporärer Datei-Fehler + Fehlermeldung kopieren + Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App. + Bitte überprüfen Sie, ob sich das Mobiltelefon und die Desktop-App im gleichen lokalen Netzwerk befinden, und die Desktop-Firewall die Verbindung erlaubt. +\nBitte teilen Sie weitere mögliche Probleme den Entwicklern mit. + Nachricht wurde nicht gesendet + Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index 17cf2a40e5..dcf6b25181 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -153,7 +153,7 @@ Eliminar en %d seg El contácto ya existe - Error conexión (Autenticación) + Error de conexión (Autenticación) Eliminar para mí Desconectado Conectado @@ -328,7 +328,7 @@ Error al crear dirección Error al eliminar perfil Activar Bloqueo SimpleX - Enlace de invitación de un uso + Enlace de invitación de un solo uso Servidores SMP Características experimentales Error al importar base de datos @@ -355,8 +355,8 @@ Si has recibido un enlace de invitación a SimpleX Chat puedes abrirlo en tu navegador: Si seleccionas rechazar, el remitente NO será notificado. ¡Enlace no válido! - escanear el código QR en la videollamada, o tu contacto puede compartir un enlace de invitación.]]> - muestra el código QR en la videollamada, o comparte el enlace.]]> + escanear el código QR por videollamada, o tu contacto puede compartir un enlace de invitación.]]> + muestra el código QR por videollamada, o comparte el enlace.]]> Cómo Ignorar Error al eliminar base de datos @@ -616,7 +616,7 @@ imagen del perfil No se permiten mensajes de voz. Proteger la pantalla de la aplicación - repositorio GitHub .]]> + repositorio GitHub .]]> Grabar mensaje de voz ha expulsado a %1$s Enviar previsualizacion de enlaces @@ -660,7 +660,7 @@ confirmación recibida… Periódico Privacidad redefinida - Saber más en nuestro repositorio GitHub. + Conoce más en nuestro repositorio GitHub. Rechazar Abrir Llamada pendiente @@ -670,7 +670,7 @@ Introduce la contraseña anterior después de restaurar la copia de seguridad de la base de datos. Esta acción no se puede deshacer. te ha expulsado Recibiendo vía - Tiempo de espera del protocolo + Timeout protocolo seg Datos del perfil y conexiones No se permiten mensajes temporales. @@ -707,8 +707,8 @@ Guardar y notificar contacto ¿Guardar preferencias\? Guardar y notificar grupo - A menos que tu contacto haya eliminado la conexión o que este enlace ya se haya usado, podría ser un error. Por favor, notifícalo. -\nPara conectarte, pide a tu contacto que cree otro enlace de conexión y comprueba que tienes buena conexión de red. + A menos que tu contacto haya eliminado la conexión o el enlace haya sido usado, podría ser un error. Por favor, notifícalo. +\nPara conectarte pide a tu contacto que cree otro enlace y comprueba la conexión de red. La aplicación recoge nuevos mensajes periódicamente lo que consume un pequeño porcentaje de batería al día. La aplicación no usa notificaciones push por tanto los datos de tu dispositivo no se envían a los servidores push. Bloqueo SimpleX Desbloquear @@ -747,7 +747,7 @@ Altavoz activado Para habilitar las acciones sobre la base de datos, debes parar Chat ¡La conexión que has aceptado se cancelará! - La base de datos no funciona correctamente. Pulsa para saber más + La base de datos no funciona correctamente. Pulsa para conocer más El mensaje será marcado como moderado para todos los miembros. La nueva generación de mensajería privada Esta acción es irreversible. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán. @@ -763,7 +763,7 @@ Para proteger tu privacidad, en lugar de los identificadores de usuario que usan el resto de plataformas, SimpleX dispone de identificadores para las colas de mensajes, independientes para cada uno de tus contactos. Para proteger tu información, activa el Bloqueo SimpleX. \nSe te pedirá que completes la autenticación antes de activar esta función. - Al actualizar la configuración, el cliente se reconectará a todos los servidores. + Al actualizar la configuración el cliente se reconectará a todos los servidores. ¿Usar servidores SimpleX Chat\? Enlace de grupo SimpleX Invitación única SimpleX @@ -809,7 +809,7 @@ %s segundo(s) Pulsa para unirte ha actualizado el perfil del grupo - Tiempo de espera de la conexión TCP agotado + Timeout de la conexión TCP Tema Establece preferencias de grupo SOPORTE SIMPLEX CHAT @@ -883,7 +883,7 @@ Tu perfil aleatorio Te conectarás cuando tu solicitud se acepte, por favor espera o compruébalo más tarde. Te conectarás cuando el dispositivo de tu contacto esté en línea, por favor espera o compruébalo más tarde. - Se te pedirá identificarte cuándo inicies o continues usando la aplicación tras 30 segundos en segundo plano. + Se te pedirá autenticarte cuando inicies la aplicación o sigas usándola tras 30 segundos en segundo plano. Estás intentando invitar a un contacto con el que compartes un perfil incógnito a un grupo en el que usas tu perfil principal Mediante navegador mediante %1$s @@ -955,7 +955,7 @@ Tu servidor Dirección de tu servidor Tu perfil actual - Tu perfil se almacena en tu dispositivo y sólo se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. + Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil. Sistema Añadir mensaje de bienvenida Llamadas y videollamadas @@ -1043,7 +1043,7 @@ Servidores XFTP Puerto puerto %d - Usar hosts .onion a No si el proxy SOCKS no los admite.]]> + Usar hosts .onion como No si el proxy SOCKS no los admite.]]> Descargar archivo Usar proxy SOCKS Host @@ -1116,14 +1116,14 @@ ¡Rápido y sin necesidad de esperar a que el remitente esté en línea! Abrir perfiles Más información - Si no puedes reunirte en persona, **muestra el código QR por videollamada**, o comparte el enlace. + Si no puedes reunirte en persona, muestra el código QR por videollamada o comparte el enlace. Para conectarse, tu contacto puede escanear el código QR o usar el enlace en la aplicación. Crear dirección SimpleX Auto aceptar Vista previa Abriendo base de datos… Error al introducir dirección - Guía de Usuario.]]> + Guía de Usuario.]]> Enlace un uso Dirección SimpleX Cuando alguien solicite conectarse podrás aceptar o rechazar su solicitud. @@ -1243,7 +1243,7 @@ Personalizar y compartir temas de color. ¡Por fin los tenemos! 🚀 Reacciones a los mensajes - Saber más + Conoce más Interfaz en japonés y portugués sin texto Algunos errores no críticos ocurrieron durante la importación - para más detalles puedes ver la consola de Chat. @@ -1268,7 +1268,7 @@ Se permite enviar archivos y multimedia Favorito Sólo los propietarios del grupo pueden activar los archivos y multimedia. - Timeout de protocolo por KB + Timeout protocolo por KB renegociación de cifrado permitida para %s cifrado acordado cifrado ok @@ -1533,26 +1533,26 @@ Enviar hasta 100 últimos mensajes a los miembros nuevos. Añadir contacto: crea un enlace de invitación nuevo o usa un enlace recibido.]]> No enviar historial a miembros nuevos. - O mostrar este código + O muestra este código QR Hasta 100 últimos mensajes son enviados a los miembros nuevos. El código QR escaneado no es un enlace SimpleX. El texto pegado no es un enlace SimpleX. Permitir acceso a la cámara Podrás ver el enlace de invitación en detalles de conexión. ¿Guardar invitación no usada? - Compartir este enlace de un uso + Comparte este enlace de un solo uso Crear grupo: crea un grupo nuevo.]]> Historial visible Código acceso app Nuevo chat Cargando chats… Creando enlace… - O escanear código QR + O escanea el código QR Código QR no válido Añadir contacto Pulsa para escanear Guardar - Pulsa para pegar enlace + Pulsa para pegar el enlace Buscar o pegar enlace SimpleX Con uso reducido de batería. bloqueado por administrador @@ -1779,17 +1779,17 @@ Problema en la red - el mensaje ha expirado tras muchos intentos de envío. La versión del servidor es incompatible con la configuración de red. Enrutamiento privado - Servidor de retransmisión desconocido + Con servidores desconocidos NO usar enrutamiento privado. Enrutamiento de mensajes alternativo Modo de enrutamiento de mensajes No Estado del mensaje - Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida. + Usar enrutamiento privado con servidores desconocidos cuando tu dirección IP no está protegida. Con IP oculta Si Enviar mensajes directamente cuando tu servidor o el de destino no admitan enrutamiento privado. - Para proteger tu dirección IP, el enrutamiento privado usa tus servidores SMP para enviar mensajes. + Para proteger tu dirección IP, el enrutamiento privado usa tu lista de servidores SMP para enviar mensajes. NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado. Siempre Permitir versión anterior @@ -1798,9 +1798,9 @@ Nunca ENRUTAMIENTO PRIVADO DE MENSAJES La dirección del servidor es incompatible con la configuración de la red. - Desprotegido + Con IP desprotegida Clave incorrecta o conexión desconocida - probablemente esta conexión fue eliminada - Usar enrutamiento privado con servidores desconocidos. + Usar enrutamiento privado con servidores de retransmisión desconocidos. Enviar mensajes directamente cuando tu dirección IP está protegida y tu servidor o el de destino no admitan enrutamiento privado. ¡Servidores desconocidos! Sin Tor o VPN, tu dirección IP será visible para estos relés XFTP: @@ -1848,7 +1848,7 @@ Con uso reducido de la batería. Tema de la app Confirma archivos de servidores desconocidos. - Entrega de debug + Informe debug ¡Cambia el aspecto de tus chats! Nuevos temas de chat Información cola de mensajes @@ -1859,4 +1859,19 @@ Error al inicializar WebView. Actualiza tu sistema a la última versión. Por favor, ponte en contacto con los desarrolladores. \nError: %s Interfaz en persa + Clave incorrecta o dirección de bloque de datos del archivo desconocida - lo más probable es que el archivo se haya borrado. + Archivo no encontrado - el archivo probablemente ha sido borrado o cancelado. + Error del servidor de archivos: %1$s + Error de archivo + Error de archivo temporal + Estado del mensaje: %s + Estado del mensaje + Estado del archivo: %s + Estado del archivo + Comprueba que el móvil y el ordenador están conectados a la misma red local y que el firewall del ordenador permite la conexión. +\nPor favor, comparte cualquier otro problema con los desarrolladores. + Error al copiar + Este enlace ha sido usado en otro dispositivo móvil, por favor crea un enlace nuevo en el ordenador. + No se puede enviar el mensaje + Las preferencias seleccionadas no permiten este mensaje. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 9cf12231ab..c18c163483 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -716,7 +716,7 @@ حذف تمام پرونده‌ها اصلاح نام به %s؟ بعدا از طریق تنظیمات قابل تغییر است. - تماس بی‌پاسخ + تماس پذیرفته نشده هش پیام ناصحیح هش پیام قبلی متفاوت است. شناسه پیام ناصحیح @@ -753,7 +753,7 @@ صدا خاموش بلندگو خاموش بلندگو روشن - در حال اتصال + در حال اتصال تماس فعال کردن قفل کد عبور تعیین شد! کد عبور تغییر کرد! @@ -1351,11 +1351,11 @@ اجازه ارسال پرونده‌ها و رسانه را می‌دهید. اعضای گروه می‌توانند پیام‌های ارسالی را به صورت غیرقابل برگشت حذف کنند. (۲۴ ساعت) حذف بعد از - تنظیمات گپ + تنظیمات گفت‌و‌گو پرونده‌ها و رسانه تماس‌های صوتی/تصویری " -\nموجود در v5.1" +\nموجود در نسخه 5.1" به مخاطبان خود اجازه ارسال پیام‌های صوتی می‌دهید. فقط زمانی اجازه حذف پیام‌ها به صورت غیرقابل برگشت را می‌دهید که مخاطب شما این اجازه را به شما بدهد. (۲۴ ساعت) پیام‌های ناپدید شونده در این گروه ممنوع هستند. @@ -1853,4 +1853,17 @@ از نشانی IP خود در برابر واسطه‌های پیام‌رسانی انتخاب شده توسط مخاطبانتان محافظت کنید. \nدر تنظیمات «شبکه و سرورها» فعال کنید. دریافت امن پرونده‌ها + پرونده یافت نشد - احتمالا حذف یا لغو شده. + کلید اشتباه یا نشانی پرونده ناشناخته - به احتمال زیاد پرونده حذف شده است. + خطای پرونده + خطای پرونده موقت + وضعیت پرونده + وضعیت پیام + وضعیت پرونده: %s + وضعیت پیام: %s + خطای کپی + لطفا بررسی کنید که تلفن همراه و کامپیوتر به شبکه محلی یکسانی متصل هستند، و فایروال کامپیوتر شما اجازه اتصال را میدهد. +\nلطفا هر مشکل دیگری را با توسعه‌دهندگان به اشتراک بگذارید. + این لینک توسط موبایل دیگری استفاده شده است، لطفا لینک جدیدی در کامپیوتر بسازید. + خطای سرور پرونده:%1$s \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 4b6cc724cb..b89f0ddb62 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -94,7 +94,7 @@ Puhelu on jo päättynyt! Tumma teema Yhdistetäänkö kontaktilinkin kautta\? - Yhdistetäänkö ryhmälinkin kautta\? + Liitytäänkö ryhmään? Yhdistetäänkö kutsulinkin kautta\? Yhdistä yhdistää @@ -456,7 +456,7 @@ Virhe roolin vaihdossa %dw Ryhmän moderointi - Näyttönimi: + Profiilin nimi: Virhe käynnistettäessä keskustelua Virhe keskustelun lopettamisessa Virhe asetuksen muuttamisessa @@ -558,7 +558,7 @@ Virheellinen QR-koodi näytä QR-koodi videopuhelussa tai jaa linkki.]]> Virheellinen palvelimen osoite! - Näyttönimi + Kirjoita nimesi: Käännä kamera Se voi tapahtua, kun sinä tai kontaktisi käytitte vanhaa varmuuskopiota tietokannasta. Väärä pääsykoodi @@ -589,7 +589,7 @@ Vanhentunut kutsu! kutsuttu epäsuora (%1$s) - Ryhmän näyttönimi: + Kirjoita ryhmän nimi: Syötä salasana hakuun Ryhmän asetukset Ryhmän jäsenet voivat lisätä viestireaktioita. @@ -956,7 +956,7 @@ Järjestelmä Vain sinä voit soittaa puheluita. Varmista, että WebRTC ICE -palvelinosoitteet ovat oikeassa muodossa, rivieroteltuina ja että ne eivät ole päällekkäisiä. - Verkko &; palvelimet + Verkko & palvelimet QR-koodi Skannaa koodi alkaa… @@ -1132,7 +1132,7 @@ SMP-palvelimesi XFTP-palvelimesi Käytä SimpleX Chat palvelimia\? - TEEMAN VÄRIT + KÄYTTÖLIITTYMÄN VÄRIT Päivitä kuljetuksen eristystila\? Voit luoda sen myöhemmin Voit paljastaa piilotetun profiilisi kirjoittamalla koko salasanan Keskusteluprofiilit-sivun hakukenttään. @@ -1382,4 +1382,139 @@ 6 uutta käyttöliittymän kieltä Löydä ryhmiä ja liity niihin Luo uusi profiili työpöytäsovelluksessa. 💻 + Virheellinen linkki + Virheellinen QR-koodi + Ei verkkoyhteyttä + %s ladattu + Päivitä + %s ja %s + Istuntokoodi + Vahvista tietokannan tunnuslause + %1$s!]]> + Tämä on oma SimpleX-osoitteesi! + Vahvista tunnuslause + %1$s.]]> + Salli SimpleX-linkkien lähettäminen. + Lisää kontakti + Tai näytä tämä koodi + Lataus epäonnistui + Historiaa ei lähetetä uusille jäsenille. + Arkistoidaan tietokanta + Varoitus: arkisto poistetaan.]]> + Kamera ja mikrofoni + Tarkista internetyhteys ja yritä uudelleen + Yhdistä automaattisesti + Poista tietokanta tältä laitteelta + Virhe selainta avatessa + Tiedoston tila: %s + Laajenna + Hyvää huomenta! + Lisäasetukset + Aseta oletusteema + Vaalea tila + Virhe: %1$s + kaikki jäsenet + Aseta tunnuslause + Älä lähetä historiaa uusille jäsenille. + Kyllä + Tiedosto poistettiin tai linkki on virheellinen + Äänipuhelu + Lopeta puhelu + Videopuhelu + Napauta skannataksesi + Kehittäjävalinnat + Lataa + Tiedostovirhe + Näytä viestin tila + Bluetooth + Kaiutin + Kuulokkeet + Vahvista yhteydet + Sovelluksen teema + Kaikki väritilat + Ryhmäjäsenet voivat lähettää SimpleX-linkkejä. + (uusi)]]> + VIestiä ei voi lähettää + SimpleX-linkit eivät ole sallittuja + Ladataan tiedostoa + Kaikki viestit poistetaan - tätä ei voi perua! + Virheellinen nimi! + Profiilin teema + %s yhdistetty + %s, %s ja %d jäsentä + tuntematon tila + Virhe kutsua lähettäessä + Tiedoston tila + Viestin tila + Viestin tila: %s + Poistetaanko jäsen? + Luo ryhmä + Musta + Väritila + Tumma + Tumman tilan värit + Tumma tila + Hyvää iltapäivää! + Laitteet + Kirjoita tunnuslause + Virhe tietokantaa poistaessa + Viimeistele migraatio toisella laitteella. + tallennettu + Tallennettu + Toteuta + Tuonti epäonnistui + WiFi + Kiinteä ethernet + Sisäinen virhe + Vahvista verkkoasetukset + Viimeistele migraatio + Virheellinen näyttönimi! + Napauta liittääksesi linkin + Virheellinen tiedostopolku + Verkkoyhteys + Katkaise yhteys + Kamera ei saatavilla + Luodaan linkki… + Tai skannaa QR-koodi + Aina + Ei koskaan + Ei + Kamera + Avaa asetukset + Suojaa IP-osoite + TIEDOSTOT + Profiilikuvat + tuntematon + Poista jäsen + SimpleX-linkit + Kvanttiturvallinen salaus + Verkon hallinta + Kirjoita tämän laitteen nimi… + Vahvista yhteys + Työpöytälaitteet + Ei yhteensopiva! + Satunnainen + Ryhmä on jo olemassa! + Kriittinen virhe + Virhe asetuksia tallentaessa + Virhe + Tietokannan migraatio on käynnissä. +\nTämä saattaa kestää muutaman minuutin. + Yritä uudelleen + Näytä sisäiset virheet + Näytä hitaat API-kutsut + Luo profiili + Tämä on oma kertakäyttöinen linkkisi! + Virhe + Hidas funktio + Napauta yhdistääksesi + Viesti on liian pitkä + Virhe viestiä luotaessa + Mikrofoni + Vaalea + Järjestelmä + Poista kuva + Yhteensopimaton versio + Uusi mobiililaite + Tämä laite \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index fcf82960f4..92091ef126 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -1858,4 +1858,19 @@ Système Erreur d\'initialisation de WebView. Mettez votre système à jour avec la nouvelle version. Veuillez contacter les développeurs. \nErreur : %s + Fichier introuvable - le fichier a probablement été supprimé ou annulé. + Mauvaise clé ou adresse inconnue du bloc de données du fichier - le fichier est probablement supprimé. + Erreur du serveur de fichiers : %1$s + Erreur de fichier + Erreur de fichier temporaire + Statut du fichier + Statut du fichier: %s + Statut du message + Statut du message: %s + Erreur de copie + Veuillez vérifier que le mobile et l\'ordinateur sont connectés au même réseau local et que le pare-feu de l\'ordinateur autorise la connexion. +\nVeuillez faire part de tout autre problème aux développeurs. + Ce lien a été utilisé avec un autre appareil mobile, veuillez créer un nouveau lien sur le desktop. + Impossible d\'envoyer le message + Les paramètres de chat sélectionnés ne permettent pas l\'envoi de ce message. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 2739b346c4..66ea969b09 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -9,16 +9,16 @@ 6 új kezelőfelületi nyelv 5 perc 1 perc - A SimpleX azonosítóról + A SimpleX címről Címváltoztatás megszakítása? Megszakítás 30 másodperc Egyszer használatos hivatkozás %1$s szeretne kapcsolatba lépni önnel ezen keresztül: - A SimpleX Chat névjegye + A SimpleX Chat-ről 1 nap Címváltoztatás megszakítása - A SimpleX névjegye + A SimpleX-ről Kiemelés fogadott hívás Hozzáférés a kiszolgálókhoz SOCKS proxy segítségével a %d porton? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót. @@ -29,7 +29,7 @@ Kapcsolódási kérelem elfogadása? Elfogadás Elfogadás - Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősök számára. + Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősök számára. További kiemelés hiba a hívásban Csoporttagok letiltása @@ -74,7 +74,7 @@ Minden üzenet törlésre kerül - ez a művelet nem vonható vissza! Az üzenetek CSAK az ön számára törlődnek. Hívás befejeződött HÍVÁSOK - és %d további esemény + és további %d esemény Cím Csatlakozás folyamatban! Automatikus elfogadás @@ -141,12 +141,12 @@ Jobb üzenetek A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. Engedélyezés - Hibás számítógép-azonosító + Hibás számítógép cím Profil hozzáadása Csatolás Alkalmazás jelkód Felkérték a kép fogadására - Fényképező + Kamera A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó mentése végett hívás folyamatban Fotók automatikus elfogadása @@ -156,7 +156,7 @@ Az eltűnő üzenetek küldése engedélyezve van. Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Hang kikapcsolva - A közvetlen üzenetek küldése a tagok számára engedélyezve van. + A közvetlen üzenetek küldése a tagok között engedélyezve van. Alkalmazás Hívás folyamatban Mindkét fél küldhet üzenetreakciókat. @@ -205,8 +205,8 @@ Kapcsolódás egy hivatkozás / QR-kód által Kapcsolódási hiba (AUTH) Ismerős neve - Kapcsolódik a kapcsolattartási azonosítón keresztül? - Azonosító létrehozása + Kapcsolódik a kapcsolattartási címen keresztül? + Cím létrehozása Másolás Folytatás Kapcsolódás egy hivatkozáson keresztül? @@ -240,7 +240,7 @@ \n- gyorsabb és stabilabb. Hozzájárulás kapcsolódás (bemutatkozó meghívó) - SimpleX azonosító létrehozása + SimpleX cím létrehozása törölt ismerős Csoporttag üzenet törlése? A csevegés fut @@ -276,7 +276,7 @@ Jelenleg támogatott legnagyobb fájl méret: %1$s. Fájl törlése Hamarosan! - azonosító megváltoztatása erre: %s… + cím megváltoztatása nála: %s … Csevegési adatbázis importálva CSEVEGÉSI ARCHÍVUM Üzenetek törlése @@ -301,7 +301,7 @@ Csoport törlése? Adatbázis frissítés megerősítése Saját profil létrehozása - azonosító megváltoztatása… + cím megváltoztatása… kapcsolódás… Hívás kapcsolása Fájlok és a médiatartalmak törlése? @@ -331,7 +331,7 @@ Várólista törlése Ismerős törlése Létrehozva ekkor: %1$s - címek megváltoztatása… + cím megváltoztatása… Csatlakoztatva a mobilhoz Jelenlegi jelmondat… Fájl kiválasztás @@ -341,7 +341,7 @@ Kiürítés Ismerős törlése? Kiürítés - Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel. + Cím létrehozása, hogy az emberek kapcsolatba léphessenek önnel. Biztonsági kódok összehasonlítása az ismerőseiével. Fájl összehasonlítás Csevegések @@ -367,9 +367,9 @@ Engedélyezés minden csoport számára engedélyezve az ismerős számára Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. - Azonosító törlése + Cím törlése %d hét - Számítógép azonosítója + Számítógép címe %dmp Kézbesítési jelentések! Eszközhitelesítés nem engedélyezett.A SimpleX zárolás bekapcsolható a Beállításokon keresztül, miután az eszköz hitelesítés engedélyezésre került. @@ -379,7 +379,7 @@ Törlés %d óra %d hónap - Azonosító törlése? + Cím törlése? Üzenet kézbesítési jelentések letiltása? Az adatbázis jelmondat eltérő a Keystore-ba elmentettől. Közvetlen üzenetek @@ -396,7 +396,7 @@ Helyi csoportok felfedezése és csatlakozás %1$d üzenet moderálva lett %2$s által Eltűnő üzenet - Ne hozzon létre azonosítót + Ne hozzon létre címet Ne mutasd újra SimpleX zárolás kikapcsolása e2e titkosított @@ -455,7 +455,7 @@ Archívum törlése Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. alap (%s) - duplikálódott üzenet + duplikált üzenet Számítógép leválasztása? Számítógép kliens verziója %s nem kompatibilis ezzel az alkalmazással. Kézbesítés @@ -473,7 +473,7 @@ Hiba a csoport törlésekor Kilépés mentés nélkül Tárolt fájlok és médiatartalmak titkosítása - Hiba az azonosító beállításakor + Hiba a cím beállításakor A csoport meghívó lejárt Hiba az ICE kiszolgálók mentésekor Hiba @@ -481,8 +481,8 @@ Hiba az XFTP kiszolgálók betöltésekor Hiba az SMP kiszolgálók betöltésekor Hiba a hálózat konfigurációjának frissítésekor - TCP életben tartásának engedélyezése - Fényképezőgép megfordítása + TCP életben tartása + Kamera váltás Üdv! \nCsatlakozzon hozzám SimpleX Chat-en keresztül: %s A megjelenített név nem tartalmazhat szóközöket. @@ -526,7 +526,7 @@ A zárolási képernyőn megjelenő hívások engedélyezése a Beállításokban. titkosítás egyeztetve Üzenet kézbesítési jelentések engedélyezése? - Hiba a csoport profil mentésekor + Hiba a csoportprofil mentésekor hiba A fájl törölve lesz a kiszolgálóról. Akkor is, ha le van tiltva a beszélgetésben. @@ -569,7 +569,7 @@ Hiba a beállítás megváltoztatásakor Hiba a csoport hivatkozás frissítésekor a csoport törölve - csoport profil frissítve + csoportprofil frissítve Hiba a függőben lévő ismerős kapcsolatának törlésekor Hiba a csevegési adatbázis importálásakor Hiba a kézbesítési jelentések engedélyezésekor! @@ -590,7 +590,7 @@ segítség Önmegsemmisítő jelkód engedélyezése KÍSÉRLETI - Hiba az azonosító megváltoztatásának megszakításakor + Hiba a cím megváltoztatásának megszakításakor Hiba a fájl fogadásakor titkosítás rendben Hiba az ismerős kérelem törlésekor @@ -608,14 +608,14 @@ Titkosítás javítása az adatmentések helyreállítása után. Hiba a csevegési adatbázis törlésekor Teljes hivatkozás - Hiba az azonosító megváltoztatásakor + Hiba a cím megváltoztatásakor A csoport tagjai küldhetnek hangüzeneteket. Csoport beállítások Hiba: %s Eltűnő üzenetek SimpleX zárolás engedélyezése Hiba a kapcsolat szinkronizálása során - Hiba az azonosító létrehozásakor + Hiba a cím létrehozásakor engedélyezve Hiba a részletek betöltésekor Hiba történt a kapcsolatfelvételi kérelem elfogadásakor @@ -704,7 +704,7 @@ Nincs kézbesítési információ moderált A tag eltávolítása a csoportból - ez a művelet nem vonható vissza! - Győződjön meg róla, hogy az XFTP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. + Győződjön meg róla, hogy az XFTP kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. Nem kerültek ismerősök kiválasztásra Nincsenek fogadott, vagy küldött fájlok Megnyitás mobil alkalmazásban, majd koppintson a Kapcsolódás gombra az alkalmazásban.]]> @@ -730,7 +730,7 @@ Új asztali alkalmazás! Most már az adminok is: \n- törölhetik a tagok üzeneteit. -\n- letilthatnak tagokat (\"megfigyelő\" szerepkör) +\n- letilthatnak tagokat („megfigyelő” szerepkör) meghívta őt: %1$s Az üzenetreakciók küldése le van tiltva ebben a csoportban. Nem @@ -738,7 +738,7 @@ TAG Ez később a beállításokon keresztül módosítható. Új tag szerepköre - Ki + Kikapcsolva Érvénytelen hivatkozás! A kapcsolódáshoz Onion kiszolgálókra lesz szükség. Változások a %s verzióban @@ -747,7 +747,7 @@ k soha (új)]]> - Győződjön meg arról, hogy az SMP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. + Győződjön meg arról, hogy az SMP kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. Onion kiszolgálók nem lesznek használva. perc Tudjon meg többet @@ -762,7 +762,7 @@ A meghívó lejárt! (csak a csoporttagok tárolják) Moderálás - be + bekapcsolva Japán és portugál kezelőfelület Az üzenetek végleges törlése le van tiltva ebben a csoportban. Onion kiszolgálók nem lesznek használva. @@ -776,7 +776,7 @@ elhagyta a csoportot Az üzenetek végleges törlése le van tiltva ebben a csevegésben. Max 40 másodperc, azonnal fogadható. - inkognitó a kapcsolattartási azonosító-hivatkozáson keresztül + inkognitó a kapcsolattartási cím-hivatkozáson keresztül A kapcsolódáshoz Onion kiszolgálókra lesz szükség. \nFigyelem: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz. Olasz kezelőfelület @@ -789,7 +789,7 @@ Rendben Nincsenek szűrt csevegések érvénytelen adat - Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. + Győződjön meg arról, hogy a WebRTC ICE kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva. Csak a csoporttulajdonosok engedélyezhetik a fájlok- és a médiatartalmak küldését. A fájl betöltése Nincs hozzáadandó ismerős @@ -821,7 +821,7 @@ érvénytelen üzenet formátum Csatlakozás Az értesítések az alkalmazás elindításáig nem fognak működni - ki` + kikapcsolva` (ez az eszköz: v%s)]]> ajánlott %s Csoport elhagyása @@ -896,7 +896,7 @@ Kapcsolódási kérés megismétlése? Véglegesen csak ön törölhet üzeneteket (ismerőse csak törlésre jelölheti őket ). (24 óra) Szerepkör - SimpleX kapcsolattartási azonosító + SimpleX kapcsolattartási cím Megállítás Előre beállított kiszolgáló Új csevegés kezdése @@ -911,9 +911,9 @@ PING időköze Eltűnő üzenet küldése Önmegsemmisítési jelkód - Mentés és csoport profil frissítése + Mentés és csoportprofil frissítése Adatvédelem - SimpleX azonosító + Az ön SimpleX címe Jelentse a fejlesztőknek. Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak. Az eltűnő üzenetek küldése le van tiltva. @@ -984,7 +984,7 @@ egyszer használatos hivatkozást osztott meg A hivatkozás megnyitása a böngészőben gyengítheti az adatvédelmet és a biztonságot. A megbízhatatlan SimpleX hivatkozások pirossal vannak kiemelve. ICE kiszolgálók - Kapcsolódás elfogadva + Kapcsolat létrehozása Elutasítás Ismerős és üzenet megjelenítése BEÁLLÍTÁSOK @@ -1019,7 +1019,7 @@ Elküldve ekkor: %s Jelenlegi profil használata Ez az eszköz - Megosztja az azonosítót az ismerőseivel? + Megosztja a címet az ismerőseivel? Profiljelszó Téma Jelmondat eltávolítása a beállításokból? @@ -1085,7 +1085,7 @@ Adatbázis jelmondat beállítása Elküldött üzenet Időszakosan indul - Ez a SimpleX azonosítója! + Ez az ön SimpleX címe! eltávolítva Megosztás SimpleX csapat @@ -1104,7 +1104,7 @@ SOCKS PROXY Mentés Újraindítás - Üzenetküldő (SMP) kiszolgálók + SMP kiszolgálók Videó Automatikus elfogadási beállítások mentése Újraegyzetetés @@ -1135,7 +1135,7 @@ ÖN port %d Kapcsolódás hivatkozáson keresztül - Azonosító megosztása + Cím megosztása A kiszolgáló QR-kódjának beolvasása Megállítás Címmegosztás megállítása? @@ -1146,7 +1146,7 @@ Biztosan eltávolítja? Biztonsági kód ellenőrzése eltávolítottak - SimpleX azonosító + SimpleX cím Megjelenítés: fogadott válasz… Adatbázismentés visszaállítása? @@ -1207,7 +1207,7 @@ videóhívás Kedvenc törlése Üzenet kézbesítési jelentések küldése - SimpleX azonosító + SimpleX cím Koppintson a Mentés és ismerős értesítése Elutasított hívás @@ -1229,7 +1229,7 @@ Jelmondat mentése és csevegés megnyitása Beállítások mentése? Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. - A közvetlen üzenetek küldése le van tiltva a tagok között. + A közvetlen üzenetek küldése a tagok között le van tiltva. SOCKS proxy használata? Hangszóró kikapcsolva hét @@ -1290,7 +1290,7 @@ Csevegési profil felfedése Videók és fájlok 1Gb méretig TCP kapcsolat időtúllépés - A(z) %1$s nevű profiljának SimpleX azonosítója megosztásra fog kerülni. + A(z) %1$s nevű profiljának SimpleX címe megosztásra fog kerülni. Ön már kapcsolódott ehhez: %1$s. Jelenlegi csevegési adatbázis TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! \nEz a művelet nem vonható vissza - profiljai, ismerősei, csevegési üzenetei és fájljai véglegesen törölve lesznek! @@ -1303,17 +1303,17 @@ Várakozás a mobiltelefon társítására: Kapcsolat biztonságának ellenőrzése fájlok küldése egyelőre még nem támogatott - azonosítója erre változott: %s + cím megváltoztatva nála: %s fájlok fogadása egyelőre még nem támogatott - Csoport profil mentése + Csoportprofil mentése Alaphelyzetbe állítás Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, lehet hogy ez egy hiba – jelentse a problémát. \nA kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. videóhívás (nem e2e titkosított) Alkalmazás új kapcsolatokhoz Az új üzenetek rendszeresen letöltésre kerülnek az alkalmazás által – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push értesítéseket – az eszközről származó adatok nem kerülnek elküldésre a kiszolgálóknak. - Számítógép azonosítójának beillesztése - kapcsolattartási azonosító-hivatkozáson keresztül + Számítógép címének beillesztése + kapcsolattartási cím-hivatkozáson keresztül SimpleX háttérszolgáltatást használja - az akkumulátor néhány százalékát használja naponta.]]> Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. \nVisszavonhatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). @@ -1390,7 +1390,7 @@ Akkor lesz kapcsolódva, amikor az ismerősének az eszköze online lesz, várjon, vagy ellenőrizze később! Kéretlen üzenetek elrejtése. Használja az .onion kiszolgálókat NEM értékre, ha a SOCKS proxy nem támogatja őket.]]> - Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki kapcsolódhat önhöz. + Megoszthatja a címét egy hivatkozásként vagy egy QR-kódként – így bárki kapcsolódhat önhöz. Létrehozás később Profilja az eszközön van tárolva és csak az ismerőseivel kerül megosztásra. A SimpleX kiszolgálók nem láthatják a profilját. %s szerepkörét megváltoztatta erre: %s @@ -1411,7 +1411,7 @@ Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősei számára. fogadja az üzeneteket, ismerősöket – a kiszolgálók, amelyeket az üzenetküldéshez használ.]]> %1$s csoport tagja.]]> - azonosítója megváltoztatva + cím megváltoztatva Ismerősei engedélyezhetik a teljes üzenet törlést. A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. Ha engedélyezni szeretné, hogy egy mobilalkalmazás csatlakozzon a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha engedélyezte azt @@ -1420,7 +1420,7 @@ Ez a karakterlánc nem egy meghívó hivatkozás! Új csevegés kezdése A kapcsolódás már folyamatban van ezen az egyszer használatos hivatkozáson keresztül! - Nem veszíti el az ismerőseit, ha később törli az azonosítóját. + Nem veszíti el az ismerőseit, ha később törli a címét. A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. kapcsolatba akar lépni veled! saját szerepköre erre változott: %s @@ -1442,8 +1442,8 @@ Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. A kapcsolódási kérelem elküldésre kerül ezen csoporttag számára Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. - Már kért egy kapcsolódási kérelmet ezen az azonosítón keresztül! - Megoszthatja ezt a SimpleX azonosítót az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. + Már kért egy kapcsolódási kérelmet ezen a címen keresztül! + Megoszthatja ezt a SimpleX címet az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat. Megjelenítendő üzenet beállítása az új tagok számára! Köszönet a felhasználóknak - hozzájárulás a Weblaten! @@ -1472,7 +1472,7 @@ Kézbesítési jelentések le vannak tiltva a(z) %d csoportban Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat. Köszönet a felhasználóknak - hozzájárulás a Weblaten! - Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét. + Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címet. Rendszerhitelesítés helyetti beállítás. A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. A csevegés megállítása a csevegő adatbázis exportálásához, importálásához, vagy törléséhez. A csevegés megállítása alatt nem tud üzeneteket fogadni és küldeni. @@ -1481,12 +1481,12 @@ Jelmondat mentése a beállításokban Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem kerülnek elküldésre. A második jelölés, amit kihagytunk! ✅ - Az átjátszó kiszolgáló megvédi IP-címét, de megfigyelheti a hívás időtartamát. + Az átjátszó kiszolgáló megvédi az IP-címet, de megfigyelheti a hívás időtartamát. További információ a GitHub tárolónkban. Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek. Kézbesítési jelentések engedélyezve vannak a(z) %d csoportban - A szerepkör meg fog változni erre: \"%s\". A csoportban mindenki értesítve lesz. + A szerepkör meg fog változni erre: „%s”. A csoportban mindenki értesítve lesz. Profil és kiszolgálókapcsolatok Egy üzenetküldő- és alkalmazásplatform, amely védi az ön adatait és biztonságát. A profil aktiválásához koppintson az ikonra. @@ -1506,7 +1506,7 @@ A jelenlegi csevegési profilhoz tartozó új kapcsolatok kiszolgálói Fogadás ezen keresztül: Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez. - A tag szerepköre erre fog változni: \"%s\". A tag új meghívót fog kapni. + A tag szerepköre erre fog változni: „%s”. A tag új meghívót fog kapni. profilkép helyőrző A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! Ez a művelet nem vonható vissza - profiljai, ismerősei, üzenetei és fájljai véglegesen törlésre kerülnek. @@ -1536,7 +1536,7 @@ A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt. Az előzmények nem kerülnek elküldésre az új tagok számára. Újrapróbálkozás - A fényképező nem elérhető + A kamera nem elérhető Az utolsó 100 üzenet elküldése az új tagok számára. Az előzmények ne kerüljenek elküldésre az új tagok számára. Vagy mutassa meg ezt a kódot @@ -1585,13 +1585,13 @@ Fejlesztői beállítások A funkció végrehajtása túl sokáig tart: %1$d másodperc: %2$s %s mobil eszköz elfoglalt]]> - Legutóbbi tag %1$s + Már nem tag - %1$s ismeretlen státusz %1$s megváltoztatta a nevét erre: %2$s - törölt kapcsolattartási azonosító + törölt kapcsolattartási cím törölt profilkép - új kapcsolattartási azonosító beállítása - új profilkép beállítása + új kapcsolattartási cím beállítása + új profilképet állított be frissített profil %1$s megváltoztatta a nevét erre: %2$s Privát jegyzetek @@ -1616,8 +1616,8 @@ letiltva letiltva az admin által Letiltva az admin által - %s letiltva - Mindenki számára letiltva + letiltotta őt: %s + Letiltás mindenki számára Mindenki számára letiltja ezt a tagot? %d üzenet letiltva az admin által Letiltás feloldása mindenki számára @@ -1791,14 +1791,14 @@ Üzenet útválasztási mód Közvetlen üzenetküldés, ha az IP-cím védett és az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Közvetlen üzenetküldés, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. - Az IP-címe védelme érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. + Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. Üzenet útválasztási tartalék PRIVÁT ÜZENET ÚTVÁLASZTÁS Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett. Ne küldjön üzeneteket közvetlenül, még akkor sem, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára. FÁJLOK - Az IP-cím védelme + IP-cím védelem Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS proxy engedélyezve van). Ismeretlen kiszolgálók! Tor vagy VPN nélkül az IP-címe látható lesz az XFTP átjátszók számára: @@ -1821,7 +1821,7 @@ Csevegőlista megjelenítése új ablakban Világos Világos mód - Kapott válasz + Fogadott válasz Kép eltávolítása Mozaik Szín visszaállítása @@ -1849,7 +1849,22 @@ Felhasználó által létrehozott téma visszaállítása Üzenet várakoztatási információ nincs - Hibakeresés kézbesítés + Kézbesítési hibák felderítése Kiszolgáló várakoztatási infó: %1$s \nUtoljára kézbesített üzenet: %2$s + Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. + Ideiglenes fájlhiba + Üzenetállapot + Üzenetállapot: %s + Fájlhiba + A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. + Fájlkiszolgáló hiba: %1$s + Fájlállapot + Fájlállapot: %s + Másolási hiba + Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. + Ellenőrizze, hogy a mobil és az asztali számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint az asztali számítógép tűzfalában engedélyezve van-e a kapcsolat. +\nMinden további problémát osszon meg a fejlesztőkkel. + Nem lehet üzenetet küldeni + A kiválasztott csevegési beállítások tiltják ezt az üzenetet. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml new file mode 100644 index 0000000000..3cace47d15 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -0,0 +1,49 @@ + + + %1$s ANGGOTA + Alamat + %1$d pesan dihapus admin %2$s + 1 menit + 30 detik + 5 menit + Semua pesan akan dihapus - ini tidak bisa dikembalikan! Pesan akan HANYA dihapus untukmu. + Terima permintaan relasi? + Tentang alamat SimpleX + Tambah kontak + Tentang SimpleX + %1$d gagal mendekripsi pesan. + Tolong laporkan hal ini ke pengembang. + 1 bulan + 1 minggu + Terima + %1$s.]]> + Panggilan suara + Batal + izinkan pesan suara? + Terima + Versi aplikasi + Versi aplikasi: v%s + panggilan suara + 1 hari + Buat pesan sambutan + Batalkan penggantian alamat + Tambah profil + Corak + Tambahan sekunder + Motif aplikasi + selalu + diizinkan mengirim pesan suara. + semua anggota + Panggilan suara dan video + Hal-hal lain + Sudah menghubungi! + Sudah bergabung dengan grup! + %1$s ingin menghubungimu lewat + Batalkan penggantian alamat? + diatas, lalu: + Tambahkan ke perangkat lain + Boleh + Selalu + APLIKASI + Tampilan + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 25b7dc28d2..690c826549 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1797,7 +1797,7 @@ NON inviare messaggi direttamente, anche se il tuo server o quello di destinazione non supporta l\'instradamento privato. NON usare l\'instradamento privato. No - INSTRADAMENTO PRIVATO MESSAGGI + INSTRADAMENTO PRIVATO DEI MESSAGGI Invia messaggi direttamente quando l\'indirizzo IP è protetto e il tuo server o quello di destinazione non supporta l\'instradamento privato. Per proteggere il tuo indirizzo IP, l\'instradamento privato usa i tuoi server SMP per consegnare i messaggi. Non protetto @@ -1858,4 +1858,19 @@ \n \nultimo msg ricevuto: %2$s Debug della consegna + Stato del messaggio: %s + Stato del file: %s + Chiave sbagliata o indirizzo sconosciuto per frammento del file - probabilmente il file è stato eliminato. + File non trovato - probabilmente è stato eliminato o annullato. + Stato del messaggio + Stato del file + Errore del server del file: %1$s + Errore del file + Errore del file temporaneo + Controlla che mobile e desktop siano collegati alla stessa rete locale e che il firewall del desktop consenta la connessione. +\nSi prega di condividere qualsiasi altro problema con gli sviluppatori. + Questo link è stato usato con un altro dispositivo mobile, creane uno nuovo sul desktop. + Copia errore + Impossibile inviare il messaggio + Le preferenze della chat selezionata vietano questo messaggio. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 52a863d1f2..c35b750c01 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -1715,4 +1715,129 @@ %1$s.]]> הסר משתתף הסתיים פסק הזמן הקצוב להתחברות למחשב השולחני + חיבור רשת יותר אמין. + מתי שהIP מוסתר + השתמש במסלול פרטי עם שרתים לא ידועים + אזהרה על אופן שליחת ההודעה + הראה סטטוס הודעה + מסלול פרטי להודעה 🚀 + סטטוס הודעה:%s + אנא וודא שהמכשיר והמחשב מחוברים לאותה רשת מקומית, ושהפיירוול של המחשב מאפשר את החיבור. +\nאנא תשתף כל בעיות אחרות עם המפתחים. + "אשר קבלת קבצים משרתים לא מוכרים" + שגיאת רשת - ההודעה פגה תוקף לאחר ריבוי ניסיונות שליחה + שגיאה:%1$s + כל החברים + הודעת המקור נשארת פרטית + רינגטון ל שיחה נכנסת + קובץ לא נמצא - ככל הנראה הקובץ נמחק או בוטל + שגיאת שרת קבצים:%1$s + הורדה + לא + ערכת נושא חדשה לצא\'ט + מרובע, עיגול, או כל דבר ביניהם + העבר ושמור הודעות + מתי שמתחבר שחיות קוליות ווידאו. + לא מצליח לשלוח הודעה + הודעות קוליות לא מאופשרות + שגיאת קובץ זמני + קבצים ומדיה לא מאופשרים + שגיאת קובץ + השתמש במסלול פרטי עם שרתים לא ידועים מתי שכתובת הIP לא מוגנת. + שלח הודעות ישירות מתי שכתובת הIP מוגנת ואתה או שרת היעד לא תומך במסלול פרטי. + כדי להגן על כתובת הIP שלך, מסלול פרטי משתמש בשרתי הSMP שלך כדי להעביר את ההודעות. + אל תשתמש במסלול פרטי + תמיד השתמש בנתיב פרטי + תאפשר בהגדרות + תאפשר הרשאות בשביל להתקשר + מצלמה ומיקרופון + פתח הגדרות + מצא את ההרשאה בהגדרות המכשיר ותאפשר אותה ידנית + רמקול + אוזניות + אוזניות + ערכת נושא לפרופיל + צבעי הצא\'ט + הראה רשימת צא\'טים בחלון חדש + מצב הקובץ + סטטוס הודעה + מצב הקובץ:%s + ריק + כהה + מצב צבעוני + שחור + בהיר + אפס צבע + ערכת נושא לאפליקציה + שלח תגובה + התקבל תגובה + טפט רקע + הסר תמונה + חזור + מלא + אפס ערכת נושא לאפליקציה + התאם + צהריים טובים + בוקר טוב! + הגדרות מתקדמות + הגדר ערכת נושא ברירת מחדל + אפס ערכת נושא למשתמש + החל ל + מצב כל הצבעים + חברי הקבוצה יכולים לשלוח קישורי SimpleX + עשה שהצאט\'ים שלך יראו אחרת! + הגדרות רשת + הקישור הזה שומש כבר במכשיר אחר, אנא צור קישור חדש במחשב. + חיבור קווי + סלולרי + נשמר + נשמר מ%s + הועבר + הועבר + מחובר לרשת + Wi-Fi אלחוטי + שגיאה בהעתקה + ערכת נושא כהה + ערכת נושא + צבעי מצב כהה + שגיאה בשרת היעד:%1$s + "אל תשלח הודעות ישירות אפילו אם שרת היעד לא תומך במסלול פרטי" + שיפור בשליחת הודעות + מצלמה + אפשר שליחת קישורי SimpleX + בעלים + מנהלים + מופעל עבור + שרתים לא ידועים! + בלי טור או VPN, כתובת הIP שלך תהיה חשופה למתווכי XFTP האלה: +\n%1$s + הועבר מ + נשמר + נשמר מ + מקבלי ההודעה לא יוכלו לראות מי שלח את ההודעה + הועבר + מעביר הודעה… + קישורי SimpleX לא מאופשרים + מסלול פרטי + לא מוגן + תמיד + מתווכים לא ידועים + לעולם לא + כן + אפשר שינמוך + שלח הודעות ישירות מתי שאתה או שרת היעד לא תומך במסלול פרטי + מיקרופון + הרשאות שניתנו + כתובת IP מוגנת + האפליקציה תשאל כדי לאשר הורדות משרתי קבצים לא ידועים (למעט שרתי טור או מתי שפרוקסי SOCKS מופעל). + בלי טור או VPN, כתובת הIP שלך תהיה גלויה לשרתי קבצים. + קבצים + תמונות פרופיל + מסלול הודעה פרטית + קישורי SimpleX + אין חיבור לרשת + אחר + ערכת נושא בהירה + מערכת + שגיאה בהצגת התראה, צור קשר עם המפתחים \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 98a80931fa..ac3f75780e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -298,7 +298,7 @@ ファイル保存でエラー発生 接続中 確認待ち - 中止 + キャンセル 使い捨ての招待リンク 音声メッセージを録音 ギャラリーから @@ -1034,7 +1034,7 @@ メンバーのメッセージを削除しますか? テーマのインポート ポート - テーマカラー + インターフェースカラー 共有を停止 友人を招待する 後からでも作成できます @@ -1084,7 +1084,7 @@ Bluetoothのサポートおよびその他の機能も改善されました。 中継サーバーは必要な場合にのみ使用されます。 別の当事者があなたの IP アドレスを監視できます。 アドレス共有の停止? - 送信 + 送信メッセージ 受信したメッセージ 以前のメッセージとハッシュ値が異なります。 次のメッセージの ID が正しくありません (前のメッセージより小さいか等しい)。 @@ -1594,8 +1594,8 @@ ステータス不明 プライベートノート メッセージ配信の改善 - エンドツーエンドの暗号化によって保護されます。]]> - 耐量子E2E暗号化によって保護されます。]]> + エンドツーエンドの暗号化によって保護されます。]]> + 耐量子E2E暗号化によって保護されます。]]> このチャットはエンドツーエンド暗号化により保護されています。 このチャットは耐量子エンドツーエンド暗号化により保護されています。 プライベートノート @@ -1796,4 +1796,42 @@ 壁紙の背景 チャットカラー チャットテーマ + ファイルエラー + ファイルサーバーエラー: %1$s + ファイルステータス + ファイルステータス: %s + ペルシャ語UI + ファイルが見つかりません - 削除されたかキャンセルされた可能性があります。 + チャットの見た目を変更できます! + ファイル + ネットワークエラー - 複数回送信が試行されましたが、メッセージが期限切れになりました + 無効 + メッセージステータス + ファイルの安全な受け取り + プロフィールテーマ + メッセージステータス:%s + 色のリセット + 受信した返信元メッセージ + 送信した返信元メッセージ + 画像を削除 + 拡大縮小 + フィットさせる + 画像全体 + アプリのテーマをリセット + ユーザーテーマをリセット + デフォルトのテーマを設定 + プライベートメッセージルーティング 🚀 + 不明なサーバーからのファイルを確認できます + バッテリーの使用量が減少しました + メッセージ配信の改善 + 不明なサーバーです! + プライベートルーティングを使用しない + IPアドレス保護 + コピーエラー + 新しいチャットテーマ + 連絡先が選択したメッセージリレーからあなたのIPアドレスを保護します。 +\n*ネットワークとサーバー*設定から有効にして下さい。 + いいえ + はい + メッセージルーティングモード \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index e1e84a4ebf..9d7088d742 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -938,4 +938,33 @@ 이미지 기다리는 중 음성 메시지 %1$d개의 메시지의 해독에 실패했습니다. + 추가 강조 색상 2 + 백업 복원 후 암호화 수정. + 채팅 더 빠르게 찾기 + 그룹 링크 + 읽지 않은 채팅과 즐겨찾기 채팅 필터링. + 고급 설정 + 연결 유지하기 + 소유자 + %1$d개의 메시지가 %2$s에 의해 삭제되었습니다 + 6개의 새로운 인터페이스 언어 + 연락처 추가 + 관리자 + 관리자는 모든 멤버를 위해 특정 멤버를 차단할 수 있습니다. + 메시지 전달 확인서! + 우리가 놓친 두 번째 체크! ✅ + 주소 변경 중지 + - 최대 5분의 음성 메시지. +\n- 사용자 정의 소멸 시간. +\n- 편집 기록. + 사용자 여러분께 감사드립니다 – Weblate를 통해 기여하세요! + 주소 변경이 중지됩니다. 이전 수신 주소가 사용됩니다. + 일본어 및 포르투갈어 UI + 주소 변경을 중지하시겠습니까? + 모든 멤버 + 위해 활성화됨 + 새로운 기능 + 더 보기 + 보안 평가 + SimpleX Chat 보안은 Trail of Bits에 의해 감사되었습니다. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index d4538299d0..a98aba3ff6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -1763,4 +1763,26 @@ SimpleX nuorodos neleidžiamos Kai jungiami garso ir vaizdo skambučiai. Bus įjungta tiesioginiuose pokalbiuose! + Juodo režimo spalvos + Patvirtinkite failus iš nežinomų serverių. + Paskirties serverio klaida: %1$s + Klaida: %1$s + Tamsu + Spalvos režimas + Programėlės tema + Pridėtinis akcentas 2 + Pažangūs nustatymai + Visi spalvų režimai + Visada + Pokalbio spalvos + Juodas režimas + Juoda + Talpa viršyta – gavėjas negavo anksčiau išsiųstų žinučių. + Pokalbio tema + NESIŲSTI žinučių tiesiogiai, net jei jūsų ar paskirties serveris nepalaiko privataus maršruto. + Visada naudoti privatų maršrutą. + NENAUDOTI privataus maršruto. + Taip + Kopijavimo klaida + Pritaikyti prie \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index f3434f2efe..ef66bf6e10 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -1856,4 +1856,19 @@ informatie over serverwachtrij: %1$s \n \nlaatst ontvangen bericht: %2$s + Bestand niet gevonden - hoogstwaarschijnlijk is het bestand verwijderd of geannuleerd. + Verkeerde sleutel of onbekend bestanddeeladres - hoogstwaarschijnlijk is het bestand verwijderd. + Bestandsserverfout: %1$s + Bestandsfout + Tijdelijke bestandsfout + Kopieerfout + Controleer of mobiel en desktop met hetzelfde lokale netwerk zijn verbonden en of de desktopfirewall de verbinding toestaat. +\nDeel eventuele andere problemen met de ontwikkelaars. + Deze link is gebruikt met een ander mobiel apparaat. Maak een nieuwe link op de desktop. + Bestandsstatus + Berichtstatus: %s + Bestandsstatus: %s + Berichtstatus + Kan bericht niet verzenden + Geselecteerde chatvoorkeuren verbieden dit bericht. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 4e49c7235d..ce2e3751a5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -1858,4 +1858,19 @@ Motyw aplikacji Zresetuj do motywu aplikacji Zresetuj do motywu użytkownika + Nie odnaleziono pliku - najprawdopodobniej plik został usunięty lub anulowany. + Status pliku + Status wiadomości + Status pliku: %s + Status wiadomości: %s + Kopiuj błąd + Ten link dostał użyty z innym urządzeniem mobilnym, proszę stworzyć nowy link na komputerze. + Błąd pliku + Błąd serwera plików: %1$s + Sprawdź, czy telefon i komputer są podłączone do tej samej sieci lokalnej i czy zapora sieciowa komputera umożliwia połączenie. +\nProszę podzielić się innymi problemami z deweloperami. + Tymczasowy błąd pliku + Zły klucz lub nieznany adres fragmentu pliku - najprawdopodobniej plik został usunięty. + Nie można wysłać wiadomości + Wybrane preferencje czatu zabraniają tej wiadomości. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index a12c0c3992..bf14ac248d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -1637,4 +1637,25 @@ Criar novo perfil no aplicativo de desktop. 💻 Criptografar arquivos armazenados & arquivos de mídia Novo aplicativo de desktop! + Arquivando banco de dados + Cancelar migração + Por Favor, note que: usando o mesmo banco de dados em dois dispositivos vai quebrar a descriptografia das mensagens das suas conexões, como proteção de segurança.]]> + Aplicar + Tema do aplicativo + Preto + Bluetooth + Arquivar e enviar + Aplicar para + Administradores podem bloquear um membro para todos. + Migração de dados do aplicativo + Todos os seus contatos,conversas e arquivos irão ser criptografados seguramente e enviados em partes para relays de XFTP configurados. + Aviso: o arquivo irá ser deletado.]]> + Rede móvel + Sempre + Sempre usar roteamento privado. + Câmera + Câmera e microfone + Permitir o envio de links do SimpleX. + Todos os membros + Configurações avançadas \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index b2ff82dcdd..4c8865f464 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -154,7 +154,7 @@ autor Arabă, Bulgară, Finlandeză, Ebraică, Thailandeză și Ucraineană - mulțumită utilizatorilor și Weblate. Apelurile audio/video sunt interzise. - (prezent) + (actual) Poză de profil eliminată Temă întunecată eliminat @@ -320,4 +320,359 @@ Arată ultimul mesaj Trimite mesaj direct Setează tema implicită + SimpleX + SimpleX nu poate rula în fundal. Vei primi notificările doar când aplicația rulează. + Serviciu SimpleX Chat + Adresă SimpleX + Închide + Link-uri SimpleX + Link-urile SimpleX sunt interzise în acest grup. + Securitatea SimpleX Chat a fost verificată de Trail of Bits. + Mesaje SimpleX Chat + Apeluri SimpleX Chat + Adresă SimpleX + simplexmq: v%s (%2s) + Link-uri SimpleX nepermise + Echipa SimpleX + Închizi? + Adresă de contact SimpleX + Link de grup SimpleX + Link-uri SimpleX + Invitație unică SimpleX + Siglă SimpleX + Grupuri mici (max 20) + Mod incognito simplificat + Pătrat, cerc, sau orice între. + %s nu este verificat + %s este verificat + Servere SMP + %s, %s și %d alți membri s-au conectat + %s, %s și %d membri + Difuzor + %s, %s și %s s-au conectat + %s: %s + Niște servere au eșuat testul: + Difuzor oprit + Difuzor pornit + Copie de rezervă a datelor aplicației + Tema aplicației + Accent suplimentar 2 + Începe conversația + Toate modurile de culoare + Folosește mereu releu + Aplică pentru + Începe o nouă conversație + Stea pe GitHub + Criptare de la capăt la capăt standard + Pornește periodic + Mereu + Folosește mereu rutare privată. + Toate contactele vor rămâne conectate. Actualizarea profilului va fi trimisă contactelor tale. + pornire… + %s secunde + Începi conversația? + Setări avansate + Adresă desktop rea + ID de mesaj incorect + Hash de mesaj incorect + ID de mesaj incorect + Hash de mesaj incorect + Schimbă adresa de primire + Conversația este oprită. Dacă ai folosit deja această bază de date pe alt dispozitiv, ar trebui să o transferi înapoi înainte de a porni conversația. + APELURI + ai schimbat rolul pentru tine la %s + Capacitate depășită - destinatarul nu a primit mesajele trimise anterior. + Schimbă codul de acces autodistructibil + Conversația este oprită + se schimbă adresa… + Contact verificat + Creat la + Migrează de pe alt dispozitiv pe dispozitivul nou și scanează codul QR.]]> + Te conectezi prin adresa de contact? + Te conectezi printr-un link unic? + Conectare incognito + Contactul deja există + Schimbă codul de acces + Poți porni Blocare SimpleX din Setări. + Conversații + Alege un fișier + Contactul tău trebuie să fie online pentru a se completa conexiunea. +\nPoți anula această conexiune și elimina contactul (și poți încerca mai târziu cu un nou link). + eroare apel + Apelurile tale + Schimbă + Schimbă rolul + Contactul permite + Grupuri mai bune + Desktop conectat + Preferințe contact + Te conectezi cu %1$s? + Anulează previzualizarea imaginii + Discută cu dezvoltatorii + Trebuie să introduci fraza de acces de fiecare dată când aplicația pornește - nu este stocată pe dispozitiv. + blocat + Preferințe conversație + Mod întunecat + Interfață chineză și spaniolă + Crează un grup folosind un profil aleatoriu. + Blochează membrii grupului + Mobil conectat + Conectat la desktop + Anulează migrarea + Celular + Nu poți trimite mesaje! + Schimbi adresa de primire? + Contactul nu este conectat încă! + Conectare prin link + Poți vedea linkul de invitație din nou în detaliile conexiunii. + Profilurile tale de conversație + Crează profil de conversație + apel terminat %1$s + Crează + Bluetooth + Camera + Apeluri pe ecranul de blocare: + contactul are criptare e2e + contactul nu are criptare e2e + Contacte + CONVERSAȚII + BAZĂ DE DATE CONVERSAȚIE + Baza de date a conversației ștearsă + Conversația rulează + Baza ta de date a conversațiilor + Baza ta de date a conversațiilor nu este criptată - setează frază de acces pentru a o proteja. + Fraza de acces de criptare a bazei de date va fi actualizată și stocată în setări. + Creat pe %1$s + ai schimbat rolul %s la %s + Nu se pot invita contactele! + Te-ai alăturat grupului + Nu se poate invita contactul! + se conectează (acceptat) + Creat la: %s + Blochează + Blochează membru + Conectare directă? + Profilul tău de conversație va fi trimis membrilor grupului + Întunecat + Culori mod întunecat + Și tu și contactul tău puteți face apeluri. + %s anulat + Conectat la mobil + Eroare copiere + Te conectezi prin link? + Nu ai putut fi verificat(ă); te rog încearcă din nou. + Copiază + Anulează mesajul live + Conectare prin link / cod QR + Contactele tale vor rămâne conectate. + Fraza de acces de criptare a bazei de date va fi actualizată. + Contactele tale pot permite ștergerea totală a mesajelor. + Trebuie să permiți contactului tău să trimită mesaje vocale pentru a le putea trimite. + Versiunede bază: v%s + Creează o adresă pentru a permite oamenilor să se conecteze cu tine. + %s blocat + ai schimbat adresa + ai schimbat adresa pentru %s + se schimbă adresa… + se schimbă adresa pentru %s… + Și tu și contactul tău puteți trimite mesaje temporare. + Nu se pot primi fișiere + Poate fi dezactivat în setări – notificările vor fi încă afișate cât timp aplicația rulează.]]> + Verifică mesajele noi la fiecare 10 minute timp de până la 1 minut + Nu ai conversații + Contactul și toate mesajele vor fi șterse - acest lucru nu poate fi anulat! + Copiat în clipboard + Camera + Ai invitat un contact + Profilul tău de conversație va fi trimis +\ncontactului tău + Contribuie + Profil conversație + aldin + Poți folosi markdown pentru a formata mesaje: + Apelând… + Schimbă modul de autodistrugere + Baza de date a conversației importată + Trebuie să folosești cea mai recentă versiune a bazei de date a conversațiilor DOAR pe un singur dispozitiv, altfel se poate să nu mai primești mesajele de la unele contacte. + Nu se poate accesa Keystore pentru a salva parola bazei de date + Conversație migrată! + Continuă + apel în curs + Apel în curs + Și tu și contactul tău puteți trimite mesaje vocale. + Contact ascuns: + Nume contact + Crează adresă + Bază de date criptată! + Schimbi fraza de acces a bazei de date? + Conversația este oprită + Poți porni conversația prin Setările aplicației / Bază de date sau repornind aplicația. + ai ieșit + Blochezi membrul? + Blocat de admin + Și tu și contactul tău puteți adăuga reacții la mesaje. + Mesaje mai bune + blocat + blocat de admin + Nu se poate inițializa baza de date + Contactul tău a trimis un fișier care este mai mare decât dimensiunea maximă suportată în prezent (%1$s). + anulează previzualizarea link-ului + Verifică adresa serverului și încearcă din nou. + Tu îți controlezi conversația! + Apel deja terminat! + Apel terminat + Blochează pentru toți + Ai cerut deja conexiunea prin această adresă! + Te conectezi la tine? + Verifică conexiunea la internet și încearcă din nou + Culori conversație + Temă conversație + Bun pentru baterie. +\nServiciul în fundal verifică mesaje la fiecare 10 minute. Ai putea rata apeluri sau mesaje urgente. + Negru + Blochezi membrul pentru toți? + Și tu și contactul tău puteți șterge ireversibil mesajele trimise. (24 de ore) + Folosește mai multă baterie! +\nServiciul în fundal rulează mereu – notificările sunt afișate imediat ce mesajele sunt disponibile. + Nu se poate trimite mesajul + Ștergeți + Confirmați fișiere de la servere necunoscute. + schimbat adresa pentru dumneavoastră + Confirmați parola nouă… + Conectare + conexiune %1$d + conexiune stabilită + Eroare de conexiune + Conexiune expirată + se conectează + Confirmare + În curând! + se conectează… + Schimbați modul de blocare + Versiunea aplicației: %s + pentru fiecare profil de conversație pe care le aveți în aplicație]]> + Permiteți downgrade-ul + O conexiune separată TCP (și SOCKS credential) va fi folosită pentru fiecare contact și membru de grup +\nVa rugăm considerați că: dacă aveți prea multe conexiuni, consumul dumneavoastră de baterie și trafic de internet pot fi considerabil mai mari, iar unele conexiuni pot eșua. + Conexiune terminată + Se conectează la desktop + Rugat să primească imaginea + Cerere de conexiune trimisă! + Vă rugăm să rețineți: mesajele și releurile pentru fișiere sunt conectate printr-un proxy SOCKS. Apelurile ți trimiterea de previzualizări a link-urilor folosesc o conexiune directă.]]> + conectat + Confirmați codul de access + Confirmare actualizare bază de date + "schimbat rolul lui %s la %s" + conectat + se conectează (introdus) + conectat + se conectează + Schimbați rolul de grup? + Modul color + Prin profil de conversație (implicit) sau prin conexiune (BETA). + Comparați codurile de securitate cu contactele dumneavoastră. + Conexiune oprită + Conexiune oprită + Conectare la desktop + Conexiunea la desktop este într-o stare proastă + Conectare + Conexiune + Ștergeți notițele private? + Conectare automată + se conectează apelul… + se conectează (invetație la introducere) + se conectează… + %s este într-o stare proastă]]> + Confirmați setările de rețea + se conectează… + Eroare de conexiune (AUTENTIFICARE) + Optimizarea pentru baterie este activă, vom opri serviciile din fundal și cererile periodice pentru mesaje noi. Le puteți reactiva din setări. + conectat + Crează un grup: pentru a crea un nou grup.]]> + Ștergeți conversația? + Ștergeți + Ștergeți conversația + Conectare + Consolă conversație + Confirmați parola + colorat + Arhivă conversație + Toate contactele, conversațiile și fișierele dumneavoastră vor fi encriptate într-un mod sigur și încărcate pe bucăți pe releurile XFTP configurate. + Rugat să primească videoclipul + Comparați fișierul + Vă rugăm să rețineți: nu veți putea recupera sau schimba parola dacă o veți pierde.]]> + schimbat rolul dumneavoastră la %s + conectat + se conectează + conectat + Ștergeți + Buton de închidere + Ștergeți verificarea + Configurare servere ICE + conectat direct + se conectează (anunțat) + Cel mai bun pentru baterie. Veți primi notificări doar când aplicația rulează (FĂRĂ servicii de fundal).]]> + Se conectează apelul + complet + Vă rugăm să rețineți: folosind aceeași bază de date pe două dispozitive, va intrerupe decripția mesajelor de la conexiunile dumneavoastră, ca protecție de securitate.]]> + Confirmați încărcarea + Confirmați că țineți minte parola de la baza de date pentru a o migra. + ARHIVĂ CONVERSAȚIE + Conexiune + Ștergi profilul de conversație? + Șterge pentru mine + %dd + șters + Șterge contact + Șters la + Versiunea aplicației desktop %s nu este compatibilă cu această aplicație. + Ștergi mesajul membrului? + Șterge arhiva + Șterge grup + Șterge și notifică contactele + Decentralizat + grup șters + %d contact(e) selectat(e) + Șters la: %s + Ștergi profilul de conversație? + Șterge profil + implicit (%s) + Confirmări de livrare! + Confirmările de livrare sunt dezactivate! + Adresă desktop + Dispozitive desktop + Desktop + Eroare decriptare + Șterge imagine + Șterge după + Șterge pentru toată lumea + Descriere + Șterge fișier + Șterge coadă + Ștergi contactul? + Șterge + Șterge + Ștergi fișiere și media? + Ștergi arhiva conversației? + %d zile + %d zi + Șterge adresa + Șterge mesaje + Ștergi %d mesaje? + Ștergi adresa? + Șterge toate fișierele + Șterge link + Ștergi link? + zile + Șterge + Ștergi mesajul? + Livrare + Ștergi conexiunea în așteptare? + Șterge server + Șterge baza de date + contact șters + Ștergi grupul? + Șterge baza de date de pe acest dispozitiv + Șterge profil de conversație + Șterge fișiere pentru toate profilurile de conversație \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 36386e00ea..1d67d2a0a9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -1853,4 +1853,10 @@ Azaltılmış pil kullanımı ile. Uygulama teması Uygulama temasına sıfırla + Hata ayıklama teslimatı + Mesaj kuyruğu bilgisi + hiçbiri + sunucu kuyruk bilgisi: %1$s +\n +\nson alınan msj: %2$s \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 06aedf5fbb..abdf0dd8e0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -1850,4 +1850,10 @@ Скинути колір Система Без Tor або VPN ваша IP-адреса буде видимою для файлових серверів. + немає + Доставка налагодження + Інформація про чергу повідомлень + інформація про чергу на сервері: %1$s +\n +\nостаннє отримане повідомлення: %2$s \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index f819aaf01c..85a7037cb6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -24,7 +24,7 @@ Chấp nhận Thêm máy chủ bằng cách quét mã QR. Cài đặt mạng nâng cao - Tất cả các cuộc hội thoại và tin nhắn sẽ bị xóa - quá trình này không thể được hoàn tác! + Tất cả các cuộc trò chuyện và tin nhắn sẽ bị xóa - quá trình này không thể được hoàn tác! Hủy bỏ việc đổi địa chỉ? Việc thay đổi địa chỉ sẽ bị hủy bỏ. Địa chỉ nhận cũ tiếp tục được sử dụng. 30 giây @@ -96,12 +96,12 @@ ỨNG DỤNG Di chuyển dữ liệu ứng dụng Sao lưu dữ liệu ứng dụng - Mật mã ứng dụng đã được thay thế bằng mật mã tự hủy. + Mã truy cập ứng dụng đã được thay thế bằng mã tự hủy. Ứng dụng mã hóa các tệp cục bộ mới (trừ video). Áp dụng - Mật mã ứng dụng + Mã truy cập ứng dụng BIỂU TƯỢNG ỨNG DỤNG - Mật mã ứng dụng + Mã truy cập Phiên bản ứng dụng: v%s Phiên bản ứng dụng Tiếng Ả Rập, tiếng Bungari, tiếng Phần Lan, tiếng Do Thái, tiếng Thái và tiếng Ukraina - trân thành gửi lời cảm ơn tới các tình nguyện viên dịch thuật và Weblate. @@ -199,4 +199,287 @@ Cài đặt nâng cao Tất cả chế độ màu Camera và mic + Không thể mời liên hệ! + Không thể truy cập Keystore để lưu mật khẩu cơ sở dữ liệu + hủy bỏ xem trước liên kết + Hủy bỏ di chuyển + Chủ đề ứng dụng + Không thể nhận tệp + Không thể khởi tạo cơ sở dữ liệu + đã hủy bỏ %s + Không thể mời liên hệ! + Hủy bỏ tin nhắn động + đã thay đổi quyền hạn của %s thành %s + Dung lượng lưu trữ vượt quá giới hạn - người nhận không thể nhận được tin nhắn vừa gửi trước đó. + đã thay đổi địa chỉ cho bạn + Thay đổi mật khẩu cơ sở dữ liệu? + đã thay đổi quyền hạn của bạn thành %s + Thay đổi chế độ khóa + Di động + Đổi mật khẩu + Thay đổi quyền hạn của nhóm? + Thay đổi + Thay đổi địa chỉ nhận? + Thay đổi địa chỉ nhận + Thay đổi chế độ tự hủy + Thay đổi mã tự hủy + Thay đổi quyền hạn + đang thay đổi địa chỉ… + đang thay đổi địa chỉ… + KHO LƯU TRỮ SIMPLEX CHAT + Kho lưu trữ SimpleX Chat + Bảng điều khiển trò chuyện + Ứng dụng SimpleX Chat đang hoạt động + Cơ sở dữ liệu SimpleX Chat đã bị xóa + Ứng dụng SimpleX Chat đã được dừng lại. Nếu bạn đã sử dụng cơ sở dữ liệu này trên một thiết bị khác, bạn nên chuyển nó trở lại trước khi bắt đầu ứng dụng. + Cơ sở dữ liệu SimpleX Chat đã được nhập + Ứng dụng SimpleX Chat đã được dừng lại + đang thay đổi địa chỉ cho %s… + Tùy chọn trò chuyện + Màu trò chuyện + CƠ SỞ DỮ LIỆU SIMPLEX CHAT + Ứng dụng SimpleX Chat đã được dừng lại + Cơ sở dữ liệu đã được di chuyển! + Các cuộc trò chuyện + CÁC CUỘC TRÒ CHUYỆN + Kiểm tra tin nhắn mới mỗi 10 phút trong tối đa 1 phút + Giao diện Trung Quốc và Tây Ban Nha + Trò chuyện với nhà phát triển + Kiểm tra lại địa chỉ server và thử lại. + Chọn một tệp + Hồ sơ trò chuyện + Kiểm tra kết nối internet của bạn và thử lại + Chủ đề trò chuyện + Chế độ màu + Xác minh xóa + Xóa + Nút đóng + Xóa ghi chú riêng tư? + có màu + Xóa cuộc trò chuyện + Xóa + Sắp ra mắt! + Di chuyển từ một thiết bị kháctrên thiết bị mới và quét mã QR.]]> + Xóa + Xóa cuộc trò chuyện? + Cài đặt cấu hình cho các máy chủ ICE + Xác nhận các cài đặt mạng + Xác nhận mã truy cập + hoàn thành + Xác nhận mật khẩu + Xác nhận nâng cấp cơ sở dữ liệu + So sánh mã bảo mật với liên hệ của bạn. + Xác nhận + So sánh tệp + Xác nhận các tệp từ những máy chủ không xác định. + Xác nhận mật khẩu mới… + Xác nhận tải lên + Xác nhận rằng bạn nhớ mật khẩu cơ sở dữ liệu để chuyển nó đi. + Kết nối + đã kết nối + Đã kết nối + đã kết nối + Kết nối trực tiếp? + Kết nối + đã kết nối + Tự động kết nối + đã kết nối + Đã kết nối + Xác nhận thông tin đăng nhập của bạn + Kết nối + Máy tính đã được kết nối + đã kết nối trực tiếp + đang kết nối (đã được chấp nhận) + đang kết nối… + Đã kết nối tới điện thoại + Điện thoại đã được kết nối + đang kết nối + đang kết nối… + đang kết nối… + Kết nối ẩn danh + đang kết nối + Đã kết nối tới máy tính + đang kết nối (đã được thông báo) + đang kết nối… + đang kết nối (đã được giới thiệu) + đang kết nối (lời mời giới thiệu) + Kết nối tới máy tính đang ở trong tình trạng không tốt + Kết nối đã bị ngắt + Kết nối thông qua liên kết / mã QR + Kết nối tới chính bạn? + Yêu cầu kết nối đã được gửi! + Kết nối thông qua liên kết + Đang kết nối cuộc gọi + Kết nối đã bị ngắt + Đang kết nối tới máy tính + - kết nối tới dịch vụ thư mục (BETA)! +\n- đánh dấu đã nhận (tối đa 20 thành viên). +\n- nhanh hơn và ổn định hơn. + Kết nối đã bị ngắt + %s đang ở trong tình trạng không tốt]]> + Kết nối tới máy tính + Hết thời gian chờ kết nối + Lỗi kết nối + Kết nối + Kết nối thông qua liên kết? + kết nối %1$d + kết nối đã được tạo lập + Kết nối với %1$s? + Lỗi kết nối (AUTH) + Kết nối + đang kết nối cuộc gọi… + Kết nối thông qua địa chỉ liên lạc? + Kết nối thông qua liên kết dùng một lần? + Liên hệ đã được kiểm tra + Các liên hệ + Liên hệ có cho phép + Liên hệ đã tồn tại + liện hệ %1$s đã thay đổi thành %2$s + Liên hệ này vẫn chưa được kết nối! + Liên hệ ẩn: + Liên hệ và tất cả tin nhắn sẽ bị xóa - quá trình này không thể hoàn tác được! + Tên liên hệ + liên hệ có bảo mật đầu cuối + liên hệ không có bảo mật đầu cuối + Tùy chọn liên hệ + Sao chép + Tạo + Liên hệ có thể đánh dấu tin nhắn để xóa; bạn vẫn sẽ có thể xem được chúng. + Biểu tượng ngữ cảnh + Tiếp tục + Đóng góp + Đã sao chép vào bộ nhớ đệm + Phiên bản lõi: v%s + Sửa tên thành %s? + Sao chép lỗi + Tạo nhóm bằng một hồ sơ ngẫu nhiên. + Tạo hồ sơ mới trong ứng dụng trên máy tính. 💻 + Không thể gửi tin nhắn + Tạo tệp + Tạo một địa chỉ để cho mọi người kết nối với bạn. + Tạo liên kết nhóm + Tạo liên kết + Được tạo ra tại + Tạo địa chỉ + Được tạo ra vào %1$s + Được tạo ra tại: %s + Tạo hồ sơ trò chuyện + Tạo nhóm + Chủ đề tối + Mật khẩu hiện tại… + Cơ sở dữ liệu đã được mã hóa! + Mật khẩu mã hóa cơ sở dữ liệu sẽ đượ cập nhật và lưu trữ trong Keystore. + (hiện tại) + Tối + Các màu chế độ tối + Tối + Lỗi nghiêm trọng + Tạo liên kết lời mời dùng một lần + Đang tạo liên kết… + Tạo hồ sơ + Tùy chỉnh và chia sẻ các chủ đề màu sắc. + Các chủ đề tùy chỉnh + Kích cỡ tệp hiện đang được hỗ trợ tối đa là %1$s. + Tạo hồ sơ của bạn + Người sáng tạo + Tạo nhóm bí mật + Mã truy cập hiện tại + Tạo nhóm bí mật + Mật khẩu mã hóa cơ sở dữ liệu sẽ được cập nhật và lưu trữ trong cài đặt. + Thời lượng tùy chỉnh + Mật khẩu mã hóa cơ sở dữ liệu sẽ được cập nhật. + Tùy chỉnh chủ đề + Tạo địa chỉ SimpleX + Tạo hồ sơ + Tạo hàng đợi + Tạo liên kết lưu trữ + tùy chỉnh + Chủ đề tối + Hạ cấp cơ sở dữ liệu + %d liên hệ đã được chọn + ID cơ sở dữ liệu: %d + Cơ sở dữ liệu sẽ được mã hóa và mật khẩu thì được lưu trữ trong Keystore. + Nâng cấp cơ sở dữ liệu + Phiên bản cơ sở dữ liệu mới hơn so với ứng dụng, nhưng không có hạ cấp cho: %s + ID cơ sở dữ liệu + ngày + Cơ sở dữ liệu được mã hóa bằng một mật khẩu ngẫu nhiên. Vui lòng đổi mật khẩu trước khi xuất dữ liệu. + Cơ sở dữ liệu được mã hóa bằng một mật khẩu ngẫu nhiên, bạn có thể thay đổi nó. + Mật khẩu cơ sở dữ liệu + Cơ sở dữ liệu sẽ được mã hóa và mật khẩu thì được lưu trữ trong phần cài đặt. + %d ngày + Các ID cơ sở dữ liệu và tùy chọn cách ly truyền tải. + %dd + %d ngày + Cơ sở dữ liệu sẽ được mã hóa. + Việc di chuyển cơ sở dữ liệu đang diễn ra. +\nQuá trình này có thể mất một vài phút. + Mật khẩu cơ sở dữ liệu và xuất dữ liệu + Mật khẩu cơ sở dữ liệu khác với mật khẩu được lưu trong Keystore. + Mật khẩu cơ sở dữ liệu là cần thiết để mở ứng dụng SimpleX Chat. + Lỗi cơ sở dữ liệu + Xóa + Xóa địa chỉ? + Xóa hồ sơ trò chuyện? + Xóa %d tin nhắn? + Xóa địa chỉ + Phi tập trung + Xóa + Xóa cơ sở dữ liệu khỏi thiết bị này + Xóa sau + đã xóa liên hệ + Gỡ lỗi truyền tải + Đã xóa vào: %s + Lỗi chuyển đổi + Xóa và thông báo tới liên hệ + Xóa liên hệ + Xóa + Xóa cơ sở dữ liệu + Xóa tất cả các tệp + Xóa tệp và đa phương tiện? + Xóa kho lữu trữ + mặc định (%s) + Xóa kho lữu trữ SimpleX Chat? + đã xóa nhóm + Xóa tệp + Xóa liên hệ? + Đã xóa vào + Lỗi giải mã + đã xóa + Xóa hồ sơ trò chuyện + Xóa hồ sơ trò chuyện? + Xóa tin nhắn thành viên? + Xóa chỉ mình tôi + Xóa nhóm + Xóa kết nối đang chờ? + Xóa tệp cho tất cả các hồ sơ trò chuyện + Xóa liên kết? + Xóa hồ sơ + Xóa cho mọi người + Xóa liên kết + Xóa ảnh + Xóa các tin nhắn + Xóa nhóm? + Xóa tin nhắn? + Phiên bản ứng dụng trên máy tính %s không tương thích với ứng dụng này. + Máy tính đang bận + Máy tính đang không hoạt động + Máy tính đã bị ngắt kết nối + Quét mã QR.]]> + Xóa máy chủ + Xóa hàng đợi + Công cụ nhà phát triển + THIẾT BỊ + Tùy chọn nhà phát triển + Xác thực thiết bị đã bị vô hiệu hóa. Tắt Khóa SimpleX. + Lỗi máy chủ đích: %1$s + Chỉ báo đã nhận! + Chỉ báo đã nhận bị vô hiệu hóa! + Máy tính có mã mời sai + Mô tả + Chuyển gửi + Các thiết bị máy tính + Máy tính + Địa chỉ máy tính + Máy tính có một phiên bản không được hỗ trợ. Vui lòng đảm bảo rằng bạn sử dụng cùng một phiên bản ở cả hai thiết bị. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 4c541d1bff..3bb4d1b72b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -1858,4 +1858,19 @@ \n \n上一则收到的信息:%2$s + 未找到文件 - 最有可能的情况是文件被删或被取消了 + 错误的密钥或未知的文件块地址 - 最可能的情况是文件被删了。 + 文件错误 + 文件服务器错误:%1$s + 文件状态 + 消息状态 + 临时性文件错误 + 文件状态:%s + 消息状态:%s + 请检查移动设备和桌面设备连接到的是同一个本地网络,且桌面防火墙允许连接。 +\n请和开发者分享任何其他问题。 + 此链接用于另一台移动设备,请在桌面上创建新的链接。 + 复制错误 + 无法发送消息 + 选择的聊天首选项禁止此条消息。 \ No newline at end of file diff --git a/scripts/ios/import-localizations.sh b/scripts/ios/import-localizations.sh index e323865f2d..0f04cd0cde 100755 --- a/scripts/ios/import-localizations.sh +++ b/scripts/ios/import-localizations.sh @@ -12,7 +12,6 @@ for lang in "${langs[@]}"; do xcodebuild -importLocalizations \ -project ./apps/ios/SimpleX.xcodeproj \ -localizationPath ./apps/ios/SimpleX\ Localizations/$lang.xcloc \ - -disableAutomaticPackageResolution \ -skipPackageUpdates sleep 10 done From 23f24b1677ce38569da92fe3cb80cf0fbf24c18e Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 12 Jul 2024 20:35:58 +0100 Subject: [PATCH 21/57] website: translations (#4436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (French) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * blog: future of privacy (#4396) * blog: future of privacy * update * Translated using Weblate (French) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/fr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/es/ * Translated using Weblate (Czech) Currently translated at 98.8% (254 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/cs/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/hu/ * Translated using Weblate (Hebrew) Currently translated at 100.0% (257 of 257 strings) Translation: SimpleX Chat/SimpleX Chat website Translate-URL: https://hosted.weblate.org/projects/simplex-chat/website/he/ --------- Co-authored-by: khalidbelk Co-authored-by: zenobit Co-authored-by: summoner001 Co-authored-by: Ghost of Sparta Co-authored-by: 大王叫我来巡山 Co-authored-by: WebTrans --- website/langs/cs.json | 8 +++++--- website/langs/es.json | 8 +++++--- website/langs/fr.json | 2 +- website/langs/he.json | 11 +++++++---- website/langs/hu.json | 10 +++++----- website/langs/zh_Hans.json | 2 +- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/website/langs/cs.json b/website/langs/cs.json index 674351f63a..7e3e9530b9 100644 --- a/website/langs/cs.json +++ b/website/langs/cs.json @@ -28,7 +28,7 @@ "copyright-label": "© 2020-2024 SimpleX | Projekt s otevřeným zdrojovým kódem", "simplex-chat-protocol": "SimpleX Chat protokol", "terminal-cli": "Terminálové rozhraní příkazového řádku", - "terms-and-privacy-policy": "Podmínky a zásady ochrany osobních údajů", + "terms-and-privacy-policy": "Ochrana soukromí", "hero-header": "Nová definice ochrany osobních údajů", "hero-subheader": "První messenger
bez uživatelských ID", "hero-overlay-1-textlink": "Proč jsou uživatelská ID špatná pro soukromí?", @@ -40,7 +40,7 @@ "feature-3-title": "E2E-šifrované decentralizované skupiny — pouze uživatelé vědí, že existují", "feature-4-title": "Hlasové zprávy šifrované E2E", "feature-6-title": "E2E šifrované
hlasové a videohovory", - "feature-7-title": "Přenosné šifrované ůložiště aplikace — přeneste profil do jiného zařízení", + "feature-7-title": "Přenosné šifrované úložiště aplikace — přesuňte profil do jiného zařízení", "feature-8-title": "režim Inkognito —
Unikátní pro SimpleX Chat", "simplex-network-overlay-1-title": "Srovnání s protokoly zpráv P2P", "simplex-private-2-title": "Další vrstva
šifrování serveru", @@ -253,5 +253,7 @@ "hero-overlay-card-3-p-2": "Trail of Bits přezkoumala kryptografii a síťové komponenty SimpleX platformy v listopadu 2022.", "hero-overlay-card-3-p-3": "Přečtěte si více v ohlášení.", "docs-dropdown-9": "Ke stažení", - "docs-dropdown-10": "Transparentnost" + "docs-dropdown-10": "Transparentnost", + "docs-dropdown-11": "FAQ (často kladené dotazy)", + "docs-dropdown-12": "Bezpečnost" } diff --git a/website/langs/es.json b/website/langs/es.json index f91ab5a92a..443f04b2ac 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -12,7 +12,7 @@ "donate": "Donación", "copyright-label": "© 2020-2024 SimpleX | Proyecto de Código Abierto", "simplex-chat-protocol": "Protocolo de SimpleX Chat", - "terms-and-privacy-policy": "Términos y Política de Privacidad", + "terms-and-privacy-policy": "Política de Privacidad", "hero-header": "Privacidad redefinida", "hero-overlay-1-textlink": "¿Por qué los ID de usuario son perjudiciales para la privacidad?", "hero-overlay-2-textlink": "¿Cómo funciona SimpleX?", @@ -242,7 +242,7 @@ "stable-versions-built-by-f-droid-org": "Versión estable compilada por F-Droid.org", "f-droid-page-f-droid-org-repo-section-text": "Los repositorios de SimpleX Chat y F-Droid.org firman con distinto certificado. Para cambiar, por favor exportar la base de datos y reinstala la aplicación.", "signing-key-fingerprint": "Huella digital de la clave de firma (SHA-256)", - "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen 1-2 días más tarde en este repositorio", + "releases-to-this-repo-are-done-1-2-days-later": "Las versiones aparecen varios días más tarde en este repositorio", "comparison-section-list-point-4a": "Los servidores de retransmisión no pueden comprometer la encriptación e2e. Para evitar posibles ataques, verifique el código de seguridad mediante un canal alternativo", "hero-overlay-3-title": "Evaluación de la seguridad", "hero-overlay-card-3-p-2": "Trail of Bits revisó la criptografía y los componentes de red de la plataforma SimpleX en noviembre de 2022.", @@ -253,5 +253,7 @@ "docs-dropdown-9": "Descargas", "please-enable-javascript": "Habilita JavaScript para ver el código QR.", "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil,", - "docs-dropdown-10": "Transparencia" + "docs-dropdown-10": "Transparencia", + "docs-dropdown-11": "FAQ", + "docs-dropdown-12": "Seguridad" } diff --git a/website/langs/fr.json b/website/langs/fr.json index 7a59ead53f..678753d1e6 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -234,7 +234,7 @@ "docs-dropdown-4": "Hébergement d'un serveur SMP", "on-this-page": "Sur cette page", "glossary": "Glossaire", - "releases-to-this-repo-are-done-1-2-days-later": "Les mises à jour de ce dépôt sont faites 1 à 2 jours plus tard", + "releases-to-this-repo-are-done-1-2-days-later": "Les mises à jour de ce dépôt sont faites quelques jours plus tard", "f-droid-page-f-droid-org-repo-section-text": "Les dépôts SimpleX Chat et F-Droid.org signent les builds avec des clés différentes. Pour changer, veuillez exporter la base de données des chats et réinstaller l'application.", "docs-dropdown-8": "Service de répertoire SimpleX", "simplex-chat-via-f-droid": "SimpleX Chat via F-Droid", diff --git a/website/langs/he.json b/website/langs/he.json index 9effb8474b..72411c8cd1 100644 --- a/website/langs/he.json +++ b/website/langs/he.json @@ -12,7 +12,7 @@ "simplex-explained-tab-2-text": "2. איך זה עובד", "simplex-chat-protocol": "פרוטוקול SimpleX Chat", "terminal-cli": "ממשק שורת פקודה", - "terms-and-privacy-policy": "תנאים ומדיניות פרטיות", + "terms-and-privacy-policy": "מדיניות הפרטיות", "hero-header": "פרטיות מוגדרת מחדש", "hero-subheader": "מערכת העברת ההודעות הראשונה
ללא מזהי שתמש", "hero-overlay-1-textlink": "מדוע מזהי משתמש מזיקים לפרטיות?", @@ -53,7 +53,7 @@ "smp-protocol": "פרוטוקול SMP", "chat-protocol": "פרוטוקול צ'אט", "donate": "תרומה", - "copyright-label": "© 2020-2023 SimpleX | פרויקט קוד פתוח", + "copyright-label": "© 2020-2024 SimpleX | פרויקט קוד פתוח", "hero-p-1": "לאפליקציות אחרות יש מזהי משתמש: Signal, Matrix, Session, Briar, Jami, Cwtch וכו'.
ל-SimpleX אין, אפילו לא מספרים אקראיים.
זה משפר באופן קיצוני את הפרטיות שלך.", "hero-overlay-2-title": "מדוע מזהי משתמש מזיקים לפרטיות?", "feature-6-title": "שיחות שמע ווידאו
מוצפנות מקצה לקצה", @@ -242,7 +242,7 @@ "simplex-chat-via-f-droid": "SimpleX Chat דרך F-Droid", "glossary": "מילון מונחים", "jobs": "הצטרפו לצוות", - "releases-to-this-repo-are-done-1-2-days-later": "גרסאות למאגר זה משוחררות לאחר יום או יומיים", + "releases-to-this-repo-are-done-1-2-days-later": "גרסאות למאגר זה משוחררות לאחר כמה ימים", "f-droid-org-repo": "מאגר F-Droid.org", "stable-versions-built-by-f-droid-org": "גרסאות יציבות שנבנו על ידי F-Droid.org", "back-to-top": "חזרה למעלה", @@ -252,5 +252,8 @@ "docs-dropdown-8": "שירות מדריך כתובות SimpleX", "f-droid-page-f-droid-org-repo-section-text": "מאגרי SimpleX Chat ו-F-Droid.org חותמים על גרסאות עם מפתחות שונים. כדי לעבור, אנא ייצא את מסד הנתונים של הצ'אט והתקן מחדש את האפליקציה.", "please-enable-javascript": "אנא הפעל JavaScript כדי לראות את קוד ה-QR.", - "please-use-link-in-mobile-app": "אנא השתמש בקישור באפליקציה במכשיר נייד" + "please-use-link-in-mobile-app": "אנא השתמש בקישור באפליקציה במכשיר נייד", + "docs-dropdown-10": "שקיפות", + "docs-dropdown-11": "שאלות ותשובות", + "docs-dropdown-12": "אבטחה" } diff --git a/website/langs/hu.json b/website/langs/hu.json index b4aa44f12e..7b6b856065 100644 --- a/website/langs/hu.json +++ b/website/langs/hu.json @@ -26,7 +26,7 @@ "terms-and-privacy-policy": "Adatvédelmi irányelvek", "hero-header": "Újradefiniált adatvédelem", "hero-subheader": "Az első üzenetküldő
felhasználói azonosítók nélkül", - "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
A SimpleX nem, még véletlenszerű számok sem.
Ez radikálisan javítja az adatvédelmet.", + "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
A SimpleX nem, még véletlenszerű számokkal sem.
Ez radikálisan javítja az adatvédelmet.", "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", "hero-overlay-3-textlink": "A biztonság értékelése", @@ -93,7 +93,7 @@ "hero-overlay-card-2-p-1": "Ha a felhasználók állandó azonosítóval rendelkeznek, még akkor is, ha ez csak egy véletlenszerű szám, például egy munkamenet-azonosító, fennáll annak a veszélye, hogy a szolgáltató vagy egy támadó megfigyelheti, hogyan kapcsolódnak a felhasználók, és hány üzenetet küldenek.", "hero-overlay-card-2-p-2": "Ezt az információt aztán összefüggésbe hozhatják a meglévő nyilvános közösségi hálózatokkal, és meghatározhatnak néhány valódi személyazonosságot.", "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", - "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezek ellen a támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", + "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezen támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX platform kriptográfiai és hálózati komponenseit.", "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", @@ -127,15 +127,15 @@ "simplex-unique-card-1-p-1": "A SimpleX védi az ön profiljához tartozó kapcsolatait és metaadatait, elrejtve azokat a SimpleX platform kiszolgálói és a megfigyelők elől.", "simplex-unique-card-1-p-2": "Minden más létező üzenetküldő platformtól eltérően a SimpleX nem rendelkezik a felhasználókhoz rendelt azonosítókkal — még véletlenszerű számokkal sem.", "simplex-unique-card-2-p-1": "Mivel a SimpleX platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", - "simplex-unique-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag az klienseken tárolja egy hordozható titkosított adatbázis-formátumban —, amely exportálható és átvihető bármely más támogatott eszközre.", - "simplex-unique-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX átjátszó-kiszolgálókon tartózkodnak, amíg be nem érkeznek a címzetthez, majd végleges törlésre kerülnek.", + "simplex-unique-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban —, amely exportálható és átvihető bármely más támogatott eszközre.", + "simplex-unique-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX átjátszó-kiszolgálókon tartózkodnak, míg meg nem érkeznek a címzetthez, majd ezt követően végleges törlésre kerülnek.", "simplex-unique-card-4-p-1": "A SimpleX hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más platformtól, kivéve az internetet.", "simplex-unique-card-4-p-2": "Használhatja a SimpleX-et saját kiszolgálóival vagy az általunk biztosított kiszolgálókkal, és továbbra is kapcsolódhat bármely felhasználóhoz.", "join": "Csatlakozás", "we-invite-you-to-join-the-conversation": "Meghívjuk önt, hogy csatlakozzon a beszélgetéshez", "join-the-REDDIT-community": "Csatlakozzon a REDDIT közösséghez", "join-us-on-GitHub": "Csatlakozzon hozzánk a GitHubon", - "donate-here-to-help-us": "Adományozzon itt, hogy segítsen nekünk", + "donate-here-to-help-us": "Adományozzon és segítsen nekünk", "sign-up-to-receive-our-updates": "Regisztráljon az oldalra, hogy megkapja frissítéseinket", "enter-your-email-address": "Adja meg az e-mail címét", "get-simplex": "SimpleX Desktop alkalmazás letöltése", diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index 01f6149669..03da9db140 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -57,7 +57,7 @@ "simplex-chat-protocol": "SimpleX 聊天协议", "smp-protocol": "SMP协议", "chat-protocol": "聊天协议", - "copyright-label": "© 2020-2023 SimpleX | 开源项目", + "copyright-label": "© 2020-2024 SimpleX | 开源项目", "terminal-cli": "命令行程式", "simplex-explained-tab-1-p-1": "您可以创建联系人和群组,并进行双向对话,就像是任何其他即时通讯软件一样。", "hero-p-1": "其他应用——如Signal、Matrix、Session、Briar、Jami、Cwtch 等——都需要用户 ID。
而SimpleX 不需要用户ID,连随机生成的也不需要。
这从根本上改善了您的隐私。", From 3e873fcb32151b0b570a8710ebee34797a243c69 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 13 Jul 2024 08:44:51 +0100 Subject: [PATCH 22/57] core: faster tracking of active subscriptions; ui: only track in foreground (#4446) * core: faster tracking of active subscriptions * combine db transaction * optimizations of queries from UI * ios: track when active * ios: disable log --------- Co-authored-by: Avently <7953703+avently@users.noreply.github.com> Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../Shared/Views/ChatList/ChatListView.swift | 35 +++++------------- .../Views/ChatList/ServersSummaryView.swift | 35 ++++-------------- .../common/platform/AppCommon.android.kt | 4 +- .../chat/simplex/common/platform/AppCommon.kt | 3 +- .../common/views/chatlist/ChatListView.kt | 37 ++++++------------- .../common/platform/AppCommon.desktop.kt | 3 ++ src/Simplex/Chat.hs | 17 ++++----- src/Simplex/Chat/Stats.hs | 8 ++-- 8 files changed, 47 insertions(+), 95 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 886b7465f3..16f87f0c13 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -266,23 +266,19 @@ struct ChatListView: View { } struct SubsStatusIndicator: View { - @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs - @State private var sess: ServerSessions = ServerSessions.newServerSessions + @State private var serversSummary: PresentedServersSummary? @State private var timer: Timer? = nil @State private var timerCounter = 0 @State private var showServersSummary = false @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false - // Constants for the intervals - let initialInterval: TimeInterval = 1.0 - let regularInterval: TimeInterval = 3.0 - let initialPhaseDuration: TimeInterval = 10.0 // Duration for initial phase in seconds - var body: some View { Button { showServersSummary = true } label: { + let subs = serversSummary?.allUsersSMP.smpTotals.subs ?? SMPServerSubs.newSMPServerSubs + let sess = serversSummary?.allUsersSMP.smpTotals.sessions ?? ServerSessions.newServerSessions HStack(spacing: 4) { SubscriptionStatusIndicatorView(subs: subs, sess: sess) if showSubscriptionPercentage { @@ -291,34 +287,24 @@ struct SubsStatusIndicator: View { } } .onAppear { - startInitialTimer() + startTimer() } .onDisappear { stopTimer() } .sheet(isPresented: $showServersSummary) { - ServersSummaryView() + ServersSummaryView(serversSummary: $serversSummary) } } - private func startInitialTimer() { - timer = Timer.scheduledTimer(withTimeInterval: initialInterval, repeats: true) { _ in - getServersSummary() - timerCounter += 1 - // Switch to the regular timer after the initial phase - if timerCounter * Int(initialInterval) >= Int(initialPhaseDuration) { - switchToRegularTimer() + private func startTimer() { + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if AppChatState.shared.value == .active { + getServersSummary() } } } - func switchToRegularTimer() { - timer?.invalidate() - timer = Timer.scheduledTimer(withTimeInterval: regularInterval, repeats: true) { _ in - getServersSummary() - } - } - func stopTimer() { timer?.invalidate() timer = nil @@ -326,8 +312,7 @@ struct SubsStatusIndicator: View { private func getServersSummary() { do { - let summ = try getAgentServersSummary() - (subs, sess) = (summ.allUsersSMP.smpTotals.subs, summ.allUsersSMP.smpTotals.sessions) + serversSummary = try getAgentServersSummary() } catch let error { logger.error("getAgentServersSummary error: \(responseError(error))") } diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 1fb889f863..3021a7ceb2 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -11,12 +11,12 @@ import SimpleXChat struct ServersSummaryView: View { @EnvironmentObject var m: ChatModel - @State private var serversSummary: PresentedServersSummary? = nil + @EnvironmentObject var theme: AppTheme + @Binding var serversSummary: PresentedServersSummary? @State private var selectedUserCategory: PresentedUserCategory = .allUsers @State private var selectedServerType: PresentedServerType = .smp @State private var selectedSMPServer: String? = nil @State private var selectedXFTPServer: String? = nil - @State private var timer: Timer? = nil @State private var alert: SomeAlert? @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -47,26 +47,10 @@ struct ServersSummaryView: View { if m.users.filter({ u in u.user.activeUser || !u.user.hidden }).count == 1 { selectedUserCategory = .currentUser } - getServersSummary() - startTimer() - } - .onDisappear { - stopTimer() } .alert(item: $alert) { $0.alert } } - private func startTimer() { - timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in - getServersSummary() - } - } - - func stopTimer() { - timer?.invalidate() - timer = nil - } - private func shareButton() -> some View { Button { if let serversSummary = serversSummary { @@ -183,6 +167,8 @@ struct ServersSummaryView: View { } } else { Text("No info, try to reload") + .foregroundColor(theme.colors.secondary) + .background(theme.colors.background) } } @@ -369,7 +355,6 @@ struct ServersSummaryView: View { Task { do { try await resetAgentServersStats() - getServersSummary() } catch let error { alert = SomeAlert( alert: mkAlert( @@ -389,14 +374,6 @@ struct ServersSummaryView: View { Text("Reset all statistics") } } - - private func getServersSummary() { - do { - serversSummary = try getAgentServersSummary() - } catch let error { - logger.error("getAgentServersSummary error: \(responseError(error))") - } - } } struct SubscriptionStatusIndicatorView: View { @@ -734,5 +711,7 @@ struct DetailedXFTPStatsView: View { } #Preview { - ServersSummaryView() + ServersSummaryView( + serversSummary: Binding.constant(nil) + ) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 547db51bad..d739a033f9 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -6,8 +6,6 @@ import android.net.LocalServerSocket import android.util.Log import androidx.activity.ComponentActivity import androidx.fragment.app.FragmentActivity -import chat.simplex.common.* -import chat.simplex.common.platform.* import java.io.* import java.lang.ref.WeakReference import java.util.* @@ -24,6 +22,8 @@ var isAppOnForeground: Boolean = false @Suppress("ConstantLocale") val defaultLocale: Locale = Locale.getDefault() +actual fun isAppVisibleAndFocused(): Boolean = isAppOnForeground + @SuppressLint("StaticFieldLeak") lateinit var androidAppContext: Context var mainActivity: WeakReference = WeakReference(null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index 10cb17df1d..110e878c44 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -3,7 +3,6 @@ package chat.simplex.common.platform import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.ui.theme.DefaultTheme -import java.io.File import java.util.* enum class AppPlatform { @@ -20,6 +19,8 @@ expect val appPlatform: AppPlatform expect val deviceName: String +expect fun isAppVisibleAndFocused(): Boolean + val appVersionInfo: Pair = if (appPlatform == AppPlatform.ANDROID) BuildConfigCommon.ANDROID_VERSION_NAME to BuildConfigCommon.ANDROID_VERSION_CODE else diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 13e88c3b2f..45e851055e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -266,44 +266,29 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt fun SubscriptionStatusIndicator(serversSummary: MutableState, click: (() -> Unit)) { var subs by remember { mutableStateOf(SMPServerSubs.newSMPServerSubs) } var sess by remember { mutableStateOf(ServerSessions.newServerSessions) } - var timer: Job? by remember { mutableStateOf(null) } - - val fetchInterval: Duration = 1.seconds - val scope = rememberCoroutineScope() - fun setServersSummary() { - withBGApi { - serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) + suspend fun setServersSummary() { + serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) - serversSummary.value?.let { - subs = it.allUsersSMP.smpTotals.subs - sess = it.allUsersSMP.smpTotals.sessions - } + serversSummary.value?.let { + subs = it.allUsersSMP.smpTotals.subs + sess = it.allUsersSMP.smpTotals.sessions } } LaunchedEffect(Unit) { setServersSummary() - timer = timer ?: scope.launch { - while (true) { - delay(fetchInterval.inWholeMilliseconds) - setServersSummary() + scope.launch { + while (isActive) { + delay(1.seconds) + if ((appPlatform.isDesktop || chatModel.chatId.value == null) && !ModalManager.start.hasModalsOpen() && !ModalManager.fullscreen.hasModalsOpen() && isAppVisibleAndFocused()) { + setServersSummary() + } } } } - fun stopTimer() { - timer?.cancel() - timer = null - } - - DisposableEffect(Unit) { - onDispose { - stopTimer() - } - } - SimpleButtonFrame(click = click) { SubscriptionStatusIndicatorView(subs = subs, sess = sess) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 905e25566b..9fc84dc86f 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -1,6 +1,7 @@ package chat.simplex.common.platform import chat.simplex.common.model.* +import chat.simplex.common.simplexWindowState import chat.simplex.common.views.call.RcvCallInvitation import chat.simplex.common.views.helpers.* import java.util.* @@ -10,6 +11,8 @@ actual val appPlatform = AppPlatform.DESKTOP actual val deviceName = generalGetString(MR.strings.desktop_device) +actual fun isAppVisibleAndFocused() = simplexWindowState.windowFocused.value + @Suppress("ConstantLocale") val defaultLocale: Locale = Locale.getDefault() diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 1a4811c2a3..8f5295705b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2260,11 +2260,14 @@ processChatCommand' vr = \case DebugEvent event -> toView event >> ok_ GetAgentServersSummary userId -> withUserId userId $ \user -> do agentServersSummary <- lift $ withAgent' getAgentServersSummary - users <- withStore' getUsers - smpServers <- getUserProtocolServers user SPSMP - xftpServers <- getUserProtocolServers user SPXFTP + cfg <- asks config + (users, smpServers, xftpServers) <- + withStore' $ \db -> (,,) <$> getUsers db <*> getServers db cfg user SPSMP <*> getServers db cfg user SPXFTP let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers pure $ CRAgentServersSummary user presentedServersSummary + where + getServers :: (ProtocolTypeI p, UserProtocol p) => DB.Connection -> ChatConfig -> User -> SProtocolType p -> IO (NonEmpty (ProtocolServer p)) + getServers db cfg user p = L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> getProtocolServers db user ResetAgentServersStats -> withAgent resetAgentServersStats >> ok_ GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails @@ -3223,7 +3226,8 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} S.toList $ S.fromList $ concatMap (\FD.FileChunk {replicas} -> map (\FD.FileChunkReplica {server} -> server) replicas) chunks getUnknownSrvs :: [XFTPServer] -> CM [XFTPServer] getUnknownSrvs srvs = do - knownSrvs <- getUserProtocolServers user SPXFTP + cfg <- asks config + knownSrvs <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPXFTP <$> withStore' (`getProtocolServers` user) pure $ filter (`notElem` knownSrvs) srvs ipProtectedForSrvs :: [XFTPServer] -> CM Bool ipProtectedForSrvs srvs = do @@ -3235,11 +3239,6 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs -getUserProtocolServers :: (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM (NonEmpty (ProtocolServer p)) -getUserProtocolServers user p = do - cfg <- asks config - L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> withStore' (`getProtocolServers` user) - getNetworkConfig :: CM' NetworkConfig getNetworkConfig = withAgent' $ liftIO . getNetworkConfig' diff --git a/src/Simplex/Chat/Stats.hs b/src/Simplex/Chat/Stats.hs index 334fbb2c11..e1d0372080 100644 --- a/src/Simplex/Chat/Stats.hs +++ b/src/Simplex/Chat/Stats.hs @@ -150,7 +150,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress} = agentSummary countUserInAll auId = countUserInAllStats (AgentUserId auId) currentUser users accSMPTotals :: Map SMPServer SMPServerSummary -> SMPTotals - accSMPTotals = M.foldr addTotals initialTotals + accSMPTotals = M.foldr' addTotals initialTotals where initialTotals = SMPTotals {sessions = ServerSessions 0 0 0, subs = SMPServerSubs 0 0, stats = newAgentSMPServerStatsData} addTotals SMPServerSummary {sessions, subs, stats} SMPTotals {sessions = accSess, subs = accSubs, stats = accStats} = @@ -160,7 +160,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv stats = maybe accStats (accStats `addSMPStatsData`) stats } accXFTPTotals :: Map XFTPServer XFTPServerSummary -> XFTPTotals - accXFTPTotals = M.foldr addTotals initialTotals + accXFTPTotals = M.foldr' addTotals initialTotals where initialTotals = XFTPTotals {sessions = ServerSessions 0 0 0, stats = newAgentXFTPServerStatsData} addTotals XFTPServerSummary {sessions, stats} XFTPTotals {sessions = accSess, stats = accStats} = @@ -169,7 +169,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv stats = maybe accStats (accStats `addXFTPStatsData`) stats } smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary]) - smpSummsIntoCategories = foldr partitionSummary ([], [], []) + smpSummsIntoCategories = M.foldr' partitionSummary ([], [], []) where partitionSummary srvSumm (curr, prev, prox) | isCurrentlyUsed srvSumm = (srvSumm : curr, prev, prox) @@ -183,7 +183,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv Just AgentSMPServerStatsData {_sentDirect, _sentProxied, _sentDirectAttempts, _sentProxiedAttempts, _recvMsgs, _connCreated, _connSecured, _connSubscribed, _connSubAttempts} -> _sentDirect > 0 || _sentProxied > 0 || _sentDirectAttempts > 0 || _sentProxiedAttempts > 0 || _recvMsgs > 0 || _connCreated > 0 || _connSecured > 0 || _connSubscribed > 0 || _connSubAttempts > 0 xftpSummsIntoCategories :: Map XFTPServer XFTPServerSummary -> ([XFTPServerSummary], [XFTPServerSummary]) - xftpSummsIntoCategories = foldr partitionSummary ([], []) + xftpSummsIntoCategories = M.foldr' partitionSummary ([], []) where partitionSummary srvSumm (curr, prev) | isCurrentlyUsed srvSumm = (srvSumm : curr, prev) From 670bf34ff5fd2436c9a7835015ac8b277c9a5000 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Sun, 14 Jul 2024 02:06:04 +0700 Subject: [PATCH 23/57] desktop: in-app update functionality (#4443) * desktop: in-app update functionality * without Android * refactor * working windows * tabs vs spaces * better working mac * changes * repo * undo manifest changes * changes * changes * unneeded changes * revert * new line * fix update notice * different way * changes to mac logic * changes to mac logic * more * update strings --------- Co-authored-by: Evgeny Poberezkin --- apps/multiplatform/common/build.gradle.kts | 1 + .../simplex/common/platform/Files.android.kt | 2 + .../kotlin/chat/simplex/common/App.kt | 1 + .../chat/simplex/common/model/ChatModel.kt | 4 + .../chat/simplex/common/model/SimpleXAPI.kt | 6 + .../chat/simplex/common/platform/AppCommon.kt | 15 + .../chat/simplex/common/platform/Files.kt | 2 + .../chat/simplex/common/platform/Platform.kt | 1 + .../common/views/chat/item/CIFileView.kt | 60 +-- .../common/views/chatlist/ChatListView.kt | 24 +- .../common/views/helpers/AlertManager.kt | 23 +- .../common/views/onboarding/WhatsNewView.kt | 17 +- .../common/views/usersettings/SettingsView.kt | 3 + .../commonMain/resources/MR/base/strings.xml | 19 + .../common/platform/AppCommon.desktop.kt | 1 + .../simplex/common/platform/Files.desktop.kt | 6 +- .../common/platform/Platform.desktop.kt | 12 +- .../common/views/helpers/AppUpdater.kt | 394 ++++++++++++++++++ .../views/helpers/OkHttpProgressListener.kt | 46 ++ .../usersettings/SettingsView.desktop.kt | 17 +- .../kotlin/chat/simplex/desktop/Main.kt | 28 ++ 21 files changed, 632 insertions(+), 50 deletions(-) create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt create mode 100644 apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/OkHttpProgressListener.kt diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index ac131c9748..7e97ea3414 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -105,6 +105,7 @@ kotlin { implementation("uk.co.caprica:vlcj:4.8.2") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd:efb2ebf85a") implementation("com.github.NanoHttpd.nanohttpd:nanohttpd-websocket:efb2ebf85a") + implementation("com.squareup.okhttp3:okhttp:4.12.0") } } val desktopTest by getting diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index ea092453ee..bfe961a512 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -29,6 +29,8 @@ actual val remoteHostsDir: File = File(tmpDir.absolutePath + File.separator + "r actual fun desktopOpenDatabaseDir() {} +actual fun desktopOpenDir(dir: File) {} + @Composable actual fun rememberFileChooserLauncher(getContent: Boolean, rememberedValue: Any?, onResult: (URI?) -> Unit): FileChooserLauncher { val launcher = rememberLauncherForActivityResult( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index bd35594ac0..59f5307a19 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -80,6 +80,7 @@ fun MainScreen() { laUnavailableInstructionAlert() } } + platform.desktopShowAppUpdateNotice() LaunchedEffect(chatModel.clearOverlays.value) { if (chatModel.clearOverlays.value) { ModalManager.closeAllModalsEverywhere() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 5e2cf9481c..5fd813f09f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -28,6 +28,7 @@ import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.* +import java.io.Closeable import java.io.File import java.net.URI import java.time.format.DateTimeFormatter @@ -122,6 +123,9 @@ object ChatModel { val clipboardHasText = mutableStateOf(false) val networkInfo = mutableStateOf(UserNetworkInfo(networkType = UserNetworkType.OTHER, online = true)) + val updatingProgress = mutableStateOf(null as Float?) + var updatingRequest: Closeable? = null + val updatingChatsMutex: Mutex = Mutex() val changingActiveUserMutex: Mutex = Mutex() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 70b18fb379..d259aa65c8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -160,6 +160,9 @@ class AppPreferences { val showHiddenProfilesNotice = mkBoolPreference(SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE, true) val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true) val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null) + val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } } + val appSkippedUpdate = mkStrPreference(SHARED_PREFS_APP_SKIPPED_UPDATE, "") + val appUpdateNoticeShown = mkBoolPreference(SHARED_PREFS_APP_UPDATE_NOTICE_SHOWN, false) val onboardingStage = mkEnumPreference(SHARED_PREFS_ONBOARDING_STAGE, OnboardingStage.OnboardingComplete) { OnboardingStage.values().firstOrNull { it.name == this } } val migrationToStage = mkStrPreference(SHARED_PREFS_MIGRATION_TO_STAGE, null) @@ -331,6 +334,9 @@ class AppPreferences { private const val SHARED_PREFS_CHAT_ARCHIVE_NAME = "ChatArchiveName" private const val SHARED_PREFS_CHAT_ARCHIVE_TIME = "ChatArchiveTime" private const val SHARED_PREFS_APP_LANGUAGE = "AppLanguage" + private const val SHARED_PREFS_APP_UPDATE_CHANNEL = "AppUpdateChannel" + private const val SHARED_PREFS_APP_SKIPPED_UPDATE = "AppSkippedUpdate" + private const val SHARED_PREFS_APP_UPDATE_NOTICE_SHOWN = "AppUpdateNoticeShown" private const val SHARED_PREFS_ONBOARDING_STAGE = "OnboardingStage" const val SHARED_PREFS_MIGRATION_TO_STAGE = "MigrationToStage" const val SHARED_PREFS_MIGRATION_FROM_STAGE = "MigrationFromStage" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index 110e878c44..60a65eaac6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -3,6 +3,8 @@ package chat.simplex.common.platform import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.ui.theme.DefaultTheme +import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.res.MR import java.util.* enum class AppPlatform { @@ -56,3 +58,16 @@ fun runMigrations() { } } } + +enum class AppUpdatesChannel { + DISABLED, + STABLE, + BETA; + + val text: String + get() = when (this) { + DISABLED -> generalGetString(MR.strings.app_check_for_updates_disabled) + STABLE -> generalGetString(MR.strings.app_check_for_updates_stable) + BETA -> generalGetString(MR.strings.app_check_for_updates_beta) + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index 250afe03c4..9110987190 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -34,6 +34,8 @@ expect val remoteHostsDir: File expect fun desktopOpenDatabaseDir() +expect fun desktopOpenDir(dir: File) + fun createURIFromPath(absolutePath: String): URI = URI.create(URLEncoder.encode(absolutePath, "UTF-8")) fun URI.toFile(): File = File(URLDecoder.decode(rawPath, "UTF-8").removePrefix("file:")) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index f61c5bc83e..7020a42c1e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -29,6 +29,7 @@ interface PlatformInterface { @Composable fun desktopScrollBarComponents(): Triple, Modifier, MutableState> = remember { Triple(Animatable(0f), Modifier, mutableStateOf(Job())) } @Composable fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) {} @Composable fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) {} + @Composable fun desktopShowAppUpdateNotice() {} } /** * Multiplatform project has separate directories per platform + common directory that contains directories per platform + common for all of them. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index 89751dd140..f181126b33 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -128,30 +128,6 @@ fun CIFileView( } } - @Composable - fun progressIndicator() { - CircularProgressIndicator( - Modifier.size(32.dp), - color = if (isInDarkTheme()) FileDark else FileLight, - strokeWidth = 3.dp - ) - } - - @Composable - fun progressCircle(progress: Long, total: Long) { - val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat() - val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() } - val strokeColor = if (isInDarkTheme()) FileDark else FileLight - Surface( - Modifier.drawRingModifier(angle, strokeColor, strokeWidth), - color = Color.Transparent, - shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)), - contentColor = LocalContentColor.current - ) { - Box(Modifier.size(32.dp)) - } - } - @Composable fun fileIndicator() { Box( @@ -164,14 +140,14 @@ fun CIFileView( when (file.fileStatus) { is CIFileStatus.SndStored -> when (file.fileProtocol) { - FileProtocol.XFTP -> progressIndicator() + FileProtocol.XFTP -> CIFileViewScope.progressIndicator() FileProtocol.SMP -> fileIcon() FileProtocol.LOCAL -> fileIcon() } is CIFileStatus.SndTransfer -> when (file.fileProtocol) { - FileProtocol.XFTP -> progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal) - FileProtocol.SMP -> progressIndicator() + FileProtocol.XFTP -> CIFileViewScope.progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal) + FileProtocol.SMP -> CIFileViewScope.progressIndicator() FileProtocol.LOCAL -> {} } is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled)) @@ -186,9 +162,9 @@ fun CIFileView( is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = painterResource(MR.images.ic_more_horiz)) is CIFileStatus.RcvTransfer -> if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) { - progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal) + CIFileViewScope.progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal) } else { - progressIndicator() + CIFileViewScope.progressIndicator() } is CIFileStatus.RcvAborted -> fileIcon(innerIcon = painterResource(MR.images.ic_sync_problem), color = MaterialTheme.colors.primary) @@ -265,6 +241,32 @@ fun rememberSaveFileLauncher(ciFile: CIFile?): FileChooserLauncher = } } +object CIFileViewScope { + @Composable + fun progressIndicator() { + CircularProgressIndicator( + Modifier.size(32.dp), + color = if (isInDarkTheme()) FileDark else FileLight, + strokeWidth = 3.dp + ) + } + + @Composable + fun progressCircle(progress: Long, total: Long) { + val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat() + val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() } + val strokeColor = if (isInDarkTheme()) FileDark else FileLight + Surface( + Modifier.drawRingModifier(angle, strokeColor, strokeWidth), + color = Color.Transparent, + shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)), + contentColor = LocalContentColor.current + ) { + Box(Modifier.size(32.dp)) + } + } +} + /* class ChatItemProvider: PreviewParameterProvider { private val sentFile = ChatItem( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 45e851055e..5483b3f041 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -1,6 +1,8 @@ package chat.simplex.common.views.chatlist import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* import androidx.compose.foundation.shape.CircleShape @@ -30,6 +32,8 @@ import chat.simplex.common.views.onboarding.shouldShowWhatsNew import chat.simplex.common.views.usersettings.SettingsView import chat.simplex.common.platform.* import chat.simplex.common.views.call.Call +import chat.simplex.common.views.chat.group.ProgressIndicator +import chat.simplex.common.views.chat.item.CIFileViewScope import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.* @@ -187,8 +191,24 @@ private fun ConnectButton(text: String, onClick: () -> Unit) { private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableStateFlow, stopped: Boolean) { val serversSummary: MutableState = remember { mutableStateOf(null) } val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() - - if (stopped) { + val updatingProgress = remember { chatModel.updatingProgress }.value + if (updatingProgress != null) { + barButtons.add { + val interactionSource = remember { MutableInteractionSource() } + val hovered = interactionSource.collectIsHoveredAsState().value + IconButton(onClick = { + chatModel.updatingRequest?.close() + }, Modifier.hoverable(interactionSource)) { + if (hovered) { + Icon(painterResource(MR.images.ic_close), null, tint = WarningOrange) + } else if (updatingProgress == -1f) { + CIFileViewScope.progressIndicator() + } else { + CIFileViewScope.progressCircle((updatingProgress * 100).toLong(), 100) + } + } + } + } else if (stopped) { barButtons.add { IconButton(onClick = { AlertManager.shared.showAlertMsg( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt index 98f3921059..6bfcf2809f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/AlertManager.kt @@ -69,16 +69,19 @@ class AlertManager { fun showAlertDialogButtonsColumn( title: String, text: String? = null, + textAlign: TextAlign = TextAlign.Center, + dismissible: Boolean = true, onDismissRequest: (() -> Unit)? = null, hostDevice: Pair? = null, + belowTextContent: @Composable (() -> Unit) = {}, buttons: @Composable () -> Unit, ) { showAlert { AlertDialog( - onDismissRequest = { onDismissRequest?.invoke(); hideAlert() }, + onDismissRequest = { onDismissRequest?.invoke(); if (dismissible) hideAlert() }, title = alertTitle(title), buttons = { - AlertContent(text, hostDevice, extraPadding = true) { + AlertContent(text, hostDevice, extraPadding = true, textAlign = textAlign, belowTextContent = belowTextContent) { buttons() } }, @@ -286,7 +289,14 @@ private fun alertTitle(title: String): (@Composable () -> Unit)? { } @Composable -private fun AlertContent(text: String?, hostDevice: Pair?, extraPadding: Boolean = false, content: @Composable (() -> Unit)) { +private fun AlertContent( + text: String?, + hostDevice: Pair?, + extraPadding: Boolean = false, + textAlign: TextAlign = TextAlign.Center, + belowTextContent: @Composable (() -> Unit) = {}, + content: @Composable (() -> Unit) +) { BoxWithConstraints { Column( Modifier @@ -300,17 +310,20 @@ private fun AlertContent(text: String?, hostDevice: Pair?, extraP CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) { if (text != null) { Column(Modifier.heightIn(max = this@BoxWithConstraints.maxHeight * 0.7f) + .padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING) .verticalScroll(rememberScrollState()) ) { SelectionContainer { Text( escapedHtmlToAnnotatedString(text, LocalDensity.current), - Modifier.fillMaxWidth().padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 1.5f), + Modifier.fillMaxWidth(), fontSize = 16.sp, - textAlign = TextAlign.Center, + textAlign = textAlign, color = MaterialTheme.colors.secondary ) } + belowTextContent() + Spacer(Modifier.height(DEFAULT_PADDING * 1.5f)) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index caf021a381..340346f5b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -118,13 +118,8 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { featureDescription(painterResource(feature.icon), feature.titleId, feature.descrId, feature.link) } - val uriHandler = LocalUriHandler.current if (v.post != null) { - Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = DEFAULT_PADDING.div(4))) { - Text(stringResource(MR.strings.whats_new_read_more), color = MaterialTheme.colors.primary, - modifier = Modifier.clickable { uriHandler.openUriCatching(v.post) }) - Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.whats_new_read_more), tint = MaterialTheme.colors.primary) - } + ReadMoreButton(v.post) } if (!viaSettings) { @@ -149,6 +144,16 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { } } +@Composable +fun ReadMoreButton(url: String) { + val uriHandler = LocalUriHandler.current + Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(top = DEFAULT_PADDING.div(4))) { + Text(stringResource(MR.strings.whats_new_read_more), color = MaterialTheme.colors.primary, + modifier = Modifier.clickable { uriHandler.openUriCatching(url) }) + Icon(painterResource(MR.images.ic_open_in_new), stringResource(MR.strings.whats_new_read_more), tint = MaterialTheme.colors.primary) + } +} + private data class FeatureDescription( val icon: ImageResource, val titleId: StringResource, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index a78b7663ab..3dfe8c3590 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -73,6 +73,9 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerSt withAuth = ::doWithAuth, drawerState = drawerState, ) + KeyChangeEffect(chatModel.updatingProgress.value != null) { + drawerState.close() + } } val simplexTeamUri = diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index a447c9d8e5..e08ccb23d1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -776,6 +776,25 @@ App build: %s Core version: v%s simplexmq: v%s (%2s) + Check for updates + Disabled + Stable + Beta + Update available: %s + Download %s (%s) + Skip this version + Downloading app update, don\'t close the app + App update is downloaded + Open file location + Install update + Installed successfully + Please restart the app. + Update download canceled + Remind later + Check for updates + To be notified about the new releases, turn on periodic check for Stable or Beta versions. + Disable + Show: Hide: Show developer options diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 9fc84dc86f..38d87fc497 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -6,6 +6,7 @@ import chat.simplex.common.views.call.RcvCallInvitation import chat.simplex.common.views.helpers.* import java.util.* import chat.simplex.res.MR +import java.io.File actual val appPlatform = AppPlatform.DESKTOP diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 5f33e2a943..eeeb13e5cc 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -26,9 +26,13 @@ actual val databaseExportDir: File = tmpDir actual val remoteHostsDir: File = File(dataDir.absolutePath + File.separator + "remote_hosts") actual fun desktopOpenDatabaseDir() { + desktopOpenDir(dataDir) +} + +actual fun desktopOpenDir(dir: File) { if (Desktop.isDesktopSupported()) { try { - Desktop.getDesktop().open(dataDir); + Desktop.getDesktop().open(dir); } catch (e: IOException) { Log.e(TAG, e.stackTraceToString()) AlertManager.shared.showAlertMsg( diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt index 9217551a8d..97de08b07e 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt @@ -8,12 +8,12 @@ private val unixConfigPath = (System.getenv("XDG_CONFIG_HOME") ?: "$home/.config private val unixDataPath = (System.getenv("XDG_DATA_HOME") ?: "$home/.local/share") + "/simplex" val desktopPlatform = detectDesktopPlatform() -enum class DesktopPlatform(val libExtension: String, val configPath: String, val dataPath: String) { - LINUX_X86_64("so", unixConfigPath, unixDataPath), - LINUX_AARCH64("so", unixConfigPath, unixDataPath), - WINDOWS_X86_64("dll", System.getenv("AppData") + File.separator + "SimpleX", System.getenv("AppData") + File.separator + "SimpleX"), - MAC_X86_64("dylib", unixConfigPath, unixDataPath), - MAC_AARCH64("dylib", unixConfigPath, unixDataPath); +enum class DesktopPlatform(val libExtension: String, val configPath: String, val dataPath: String, val githubAssetName: String) { + LINUX_X86_64("so", unixConfigPath, unixDataPath, "simplex-desktop-x86_64.AppImage"), + LINUX_AARCH64("so", unixConfigPath, unixDataPath, " simplex-desktop-aarch64.AppImage"), + WINDOWS_X86_64("dll", System.getenv("AppData") + File.separator + "SimpleX", System.getenv("AppData") + File.separator + "SimpleX", "simplex-desktop-windows-x86_64.msi"), + MAC_X86_64("dylib", unixConfigPath, unixDataPath, "simplex-desktop-macos-x86_64.dmg"), + MAC_AARCH64("dylib", unixConfigPath, unixDataPath, "simplex-desktop-macos-aarch64.dmg"); fun isLinux() = this == LINUX_X86_64 || this == LINUX_AARCH64 fun isWindows() = this == WINDOWS_X86_64 diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt new file mode 100644 index 0000000000..faef957705 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/AppUpdater.kt @@ -0,0 +1,394 @@ +package chat.simplex.common.views.helpers + +import SectionItemView +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import chat.simplex.common.BuildConfigCommon +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.ChatController.getNetCfg +import chat.simplex.common.model.json +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.WarningOrange +import chat.simplex.common.views.onboarding.ReadMoreButton +import chat.simplex.res.MR +import kotlinx.coroutines.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import okhttp3.OkHttpClient +import okhttp3.Request +import java.io.Closeable +import java.io.File +import java.net.InetSocketAddress +import java.net.Proxy + +@Serializable +data class GitHubRelease( + @SerialName("tag_name") + val tagName: String, + @SerialName("html_url") + val htmlUrl: String, + val name: String, + val draft: Boolean, + val prerelease: Boolean, + val body: String, + @SerialName("published_at") + val publishedAt: String, + val assets: List +) + +@Serializable +data class GitHubAsset( + @SerialName("browser_download_url") + val browserDownloadUrl: String, + val name: String, + val size: Long, + + val isAppImage: Boolean = name.lowercase().contains(".appimage") +) + +fun showAppUpdateNotice() { + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.app_check_for_updates_notice_title), + text = generalGetString(MR.strings.app_check_for_updates_notice_desc), + buttons = { + Column { + SectionItemView({ + AlertManager.shared.hideAlert() + appPrefs.appUpdateChannel.set(AppUpdatesChannel.STABLE) + setupUpdateChecker() + }) { + Text(generalGetString(MR.strings.app_check_for_updates_stable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + + SectionItemView({ + AlertManager.shared.hideAlert() + appPrefs.appUpdateChannel.set(AppUpdatesChannel.BETA) + setupUpdateChecker() + }) { + Text(generalGetString(MR.strings.app_check_for_updates_beta), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + + SectionItemView({ + AlertManager.shared.hideAlert() + appPrefs.appUpdateChannel.set(AppUpdatesChannel.DISABLED) + }) { + Text(generalGetString(MR.strings.app_check_for_updates_notice_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + ) +} + +private var updateCheckerJob: Job = Job() +fun setupUpdateChecker() = withLongRunningApi { + updateCheckerJob.cancel() + if (appPrefs.appUpdateChannel.get() == AppUpdatesChannel.DISABLED) { + return@withLongRunningApi + } + checkForUpdate() + createUpdateJob() +} + +private fun createUpdateJob() { + updateCheckerJob = withLongRunningApi { + delay(24 * 60 * 60 * 1000) + checkForUpdate() + createUpdateJob() + } +} + + +fun checkForUpdate() { + Log.d(TAG, "Checking for update") + val client = setupHttpClient() + try { + val request = Request.Builder().url("https://api.github.com/repos/simplex-chat/simplex-chat/releases").addHeader("User-agent", "curl").build() + client.newCall(request).execute().use { response -> + response.body?.use { + val body = it.string() + val releases = json.decodeFromString>(body).filterNot { it.draft } + val release = when (appPrefs.appUpdateChannel.get()) { + AppUpdatesChannel.STABLE -> releases.firstOrNull { !it.prerelease } + AppUpdatesChannel.BETA -> releases.firstOrNull() + AppUpdatesChannel.DISABLED -> return + } ?: return + val currentVersionName = "v" + (if (appPlatform.isAndroid) BuildConfigCommon.ANDROID_VERSION_NAME else BuildConfigCommon.DESKTOP_VERSION_NAME) + val redactedCurrentVersionName = when { + currentVersionName.contains('-') && currentVersionName.substringBefore('-').count { it == '.' } == 1 -> "${currentVersionName.substringBefore('-')}.0-${currentVersionName.substringAfter('-')}" + currentVersionName.substringBefore('-').count { it == '.' } == 1 -> "${currentVersionName}.0" + else -> currentVersionName + } + if (release.tagName == appPrefs.appSkippedUpdate.get() || release.tagName == currentVersionName || release.tagName == redactedCurrentVersionName) { + Log.d(TAG, "Skipping update because of the same version or skipped version") + return + } + val assets = chooseGitHubReleaseAssets(release) + // No need to show an alert if no suitable packages were found. But for Flatpak users it's useful to see release notes anyway + if (assets.isEmpty() && !isRunningFromFlatpak()) { + Log.d(TAG, "No assets to download for current system") + return + } + val lines = ArrayList() + for (line in release.body.lines()) { + if (line == "Commits:") break + lines.add(line) + } + val text = lines.joinToString("\n") + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.app_check_for_updates_update_available).format(release.name), + text = text, + textAlign = TextAlign.Start, + dismissible = false, + belowTextContent = { + ReadMoreButton(release.htmlUrl) + }, + buttons = { + Column { + for (asset in assets) { + SectionItemView({ + AlertManager.shared.hideAlert() + chatModel.updatingProgress.value = 0f + withLongRunningApi { + try { + downloadAsset(asset) + } finally { + chatModel.updatingProgress.value = null + } + } + }) { + Text( + generalGetString(MR.strings.app_check_for_updates_button_download).format( + if (asset.name.length > 34) "…" + asset.name.substringAfter("simplex-desktop-") else asset.name, + formatBytes(asset.size)), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary + ) + } + } + + SectionItemView({ + AlertManager.shared.hideAlert() + skipRelease(release) + }) { + Text(generalGetString(MR.strings.app_check_for_updates_button_skip), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = WarningOrange) + } + + SectionItemView({ + AlertManager.shared.hideAlert() + }) { + Text(generalGetString(MR.strings.app_check_for_updates_button_remind_later), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + } + ) + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to get the latest release: ${e.stackTraceToString()}") + } +} + +private fun setupHttpClient(): OkHttpClient { + val netCfg = getNetCfg() + var proxy: Proxy? = null + if (netCfg.useSocksProxy && netCfg.socksProxy != null) { + val hostname = netCfg.socksProxy.substringBefore(":").ifEmpty { "localhost" } + val port = netCfg.socksProxy.substringAfter(":").toIntOrNull() + if (port != null) { + proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress(hostname, port)) + } + } + return OkHttpClient.Builder().proxy(proxy).followRedirects(true).build() +} + +private fun skipRelease(release: GitHubRelease) { + appPrefs.appSkippedUpdate.set(release.tagName) +} + +private suspend fun downloadAsset(asset: GitHubAsset) { + withContext(Dispatchers.Main) { + showToast(generalGetString(MR.strings.app_check_for_updates_download_started)) + } + val progressListener = object: ProgressListener { + override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { + if (contentLength != -1L) { + chatModel.updatingProgress.value = if (done) 1f else bytesRead / contentLength.toFloat() + } + } + } + val client = setupHttpClient().newBuilder() + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + val body = originalResponse.body + if (body != null) { + originalResponse.newBuilder().body(ProgressResponseBody(body, progressListener)).build() + } else { + originalResponse + } + } + .build() + + try { + val request = Request.Builder().url(asset.browserDownloadUrl).addHeader("User-agent", "curl").build() + val call = client.newCall(request) + chatModel.updatingRequest = Closeable { + call.cancel() + withApi { + showToast(generalGetString(MR.strings.app_check_for_updates_canceled)) + } + } + call.execute().use { response -> + response.body?.use { body -> + body.byteStream().use { stream -> + createTmpFileAndDelete { file -> + // It's important to close output stream (with use{}), otherwise, Windows cannot rename the file + file.outputStream().use { output -> + stream.copyTo(output) + } + val newFile = File(file.parentFile, asset.name) + file.renameTo(newFile) + + AlertManager.shared.showAlertDialogButtonsColumn( + generalGetString(MR.strings.app_check_for_updates_download_completed_title), + dismissible = false, + buttons = { + Column { + // It's problematic to install .deb package because it requires either root or GUI package installer which is not available on + // Debian by default. Let the user install it manually only + if (!asset.name.lowercase().endsWith(".deb")) { + SectionItemView({ + AlertManager.shared.hideAlert() + chatModel.updatingProgress.value = -1f + withLongRunningApi { + try { + installAppUpdate(newFile) + } finally { + chatModel.updatingProgress.value = null + } + } + }) { + Text(generalGetString(MR.strings.app_check_for_updates_button_install), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + } + + SectionItemView({ + desktopOpenDir(newFile.parentFile) + }) { + Text(generalGetString(MR.strings.app_check_for_updates_button_open), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary) + } + + SectionItemView({ + AlertManager.shared.hideAlert() + newFile.delete() + }) { + Text(generalGetString(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red) + } + } + } + ) + } + } + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to download the asset from release: ${e.stackTraceToString()}") + } +} + +private fun isRunningFromFlatpak(): Boolean = System.getenv("container") == "flatpak" + +private fun chooseGitHubReleaseAssets(release: GitHubRelease): List { + val res = if (isRunningFromFlatpak()) { + // No need to show download options for Flatpak users + emptyList() + } else if (Runtime.getRuntime().exec("which dpkg").onExit().join().exitValue() == 0) { + // Show all available .deb packages and user will choose the one that works on his system (for Debian derivatives) + release.assets.filter { it.name.lowercase().endsWith(".deb") } + } else { + release.assets.filter { it.name == desktopPlatform.githubAssetName } + } + return res +} + +private suspend fun installAppUpdate(file: File) = withContext(Dispatchers.IO) { + when { + desktopPlatform.isLinux() -> { + val process = Runtime.getRuntime().exec("xdg-open ${file.absolutePath}").onExit().join() + val startedInstallation = process.exitValue() == 0 && process.children().count() > 0 + if (!startedInstallation) { + Log.e(TAG, "Error starting installation: ${process.inputReader().use { it.readLines().joinToString("\n") }}${process.errorStream.use { String(it.readAllBytes()) }}") + // Failed to start installation. show directory with the file for manual installation + desktopOpenDir(file.parentFile) + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.app_check_for_updates_installed_successfully_title), + text = generalGetString(MR.strings.app_check_for_updates_installed_successfully_desc) + ) + file.delete() + } + } + desktopPlatform.isWindows() -> { + val process = Runtime.getRuntime().exec("msiexec /i ${file.absolutePath}"/* /qb */).onExit().join() + val startedInstallation = process.exitValue() == 0 + if (!startedInstallation) { + Log.e(TAG, "Error starting installation: ${process.inputReader().use { it.readLines().joinToString("\n") }}${process.errorStream.use { String(it.readAllBytes()) }}") + // Failed to start installation. show directory with the file for manual installation + desktopOpenDir(file.parentFile) + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.app_check_for_updates_installed_successfully_title), + text = generalGetString(MR.strings.app_check_for_updates_installed_successfully_desc) + ) + file.delete() + } + } + desktopPlatform.isMac() -> { + // Default mount point if no other DMGs were mounted before + var volume = "/Volumes/SimpleX" + try { + val process = Runtime.getRuntime().exec("hdiutil mount ${file.absolutePath}").onExit().join() + val startedInstallation = process.exitValue() == 0 + val lines = process.inputReader().use { it.readLines() } + // This is needed for situations when mount point has non-default path. + // For example, when a user already had mounted SimpleX.dmg before and default mount point is not available. + // Mac will make volume like /Volumes/SimpleX 1 + val lastLine = lines.lastOrNull()?.substringAfterLast('\t') + if (!startedInstallation || lastLine == null || !lastLine.lowercase().contains("/volumes/")) { + Log.e(TAG, "Error starting installation: ${process.inputReader().use { it.readLines().joinToString("\n") }}${process.errorStream.use { String(it.readAllBytes()) }}") + // Failed to start installation. show directory with the file for manual installation + desktopOpenDir(file.parentFile) + return@withContext + } + volume = lastLine + File("/Applications/SimpleX.app").renameTo(File("/Applications/SimpleX-old.app")) + val process2 = Runtime.getRuntime().exec(arrayOf("cp", "-R", "${volume}/SimpleX.app", "/Applications")).onExit().join() + val copiedSuccessfully = process2.exitValue() == 0 + if (!copiedSuccessfully) { + Log.e(TAG, "Error copying the app: ${process2.inputReader().use { it.readLines().joinToString("\n") }}${process2.errorStream.use { String(it.readAllBytes()) }}") + // Failed to start installation. show directory with the file for manual installation + desktopOpenDir(file.parentFile) + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.app_check_for_updates_installed_successfully_title), + text = generalGetString(MR.strings.app_check_for_updates_installed_successfully_desc) + ) + file.delete() + } + } finally { + try { + Runtime.getRuntime().exec(arrayOf("hdiutil", "unmount", volume)).onExit().join() + } finally { + if (!File("/Applications/SimpleX.app").exists()) { + File("/Applications/SimpleX-old.app").renameTo(File("/Applications/SimpleX.app")) + } else { + Runtime.getRuntime().exec("rm -rf /Applications/SimpleX-old.app").onExit().join() + } + } + } + } + } + Unit +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/OkHttpProgressListener.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/OkHttpProgressListener.kt new file mode 100644 index 0000000000..24fa0b8ef1 --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/OkHttpProgressListener.kt @@ -0,0 +1,46 @@ +package chat.simplex.common.views.helpers + +import okhttp3.MediaType +import okhttp3.ResponseBody +import okio.* + +// https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java +class ProgressResponseBody( + val responseBody: ResponseBody, + val progressListener: ProgressListener +): ResponseBody() { + private var bufferedSource: BufferedSource? = null + + override fun contentType(): MediaType? { + return responseBody.contentType() + } + + override fun contentLength(): Long { + return responseBody.contentLength() + } + + override fun source(): BufferedSource { + if (bufferedSource == null) { + bufferedSource = source(responseBody.source()).buffer() + } + return bufferedSource!! + } + + private fun source(source: Source): Source { + return object: ForwardingSource(source) { + var totalBytesRead = 0L + + override fun read(sink: Buffer, byteCount: Long): Long { + val bytesRead = super.read(sink, byteCount) + // read() returns the number of bytes read, or -1 if this source is exhausted. + totalBytesRead += if (bytesRead != -1L) bytesRead else 0L + progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) + return bytesRead; + } + } + } +} + +interface ProgressListener { + fun update(bytesRead: Long, contentLength: Long, done: Boolean); +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt index 5a42b4b756..ed1d820259 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.desktop.kt @@ -1,9 +1,16 @@ package chat.simplex.common.views.usersettings import SectionView +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel -import chat.simplex.common.views.helpers.ModalData +import chat.simplex.common.platform.AppUpdatesChannel +import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource @@ -17,6 +24,14 @@ actual fun SettingsSectionApp( ) { SectionView(stringResource(MR.strings.settings_section_title_app)) { SettingsActionItem(painterResource(MR.images.ic_code), stringResource(MR.strings.settings_developer_tools), showSettingsModal { DeveloperView(it, showCustomModal, withAuth) }, extraPadding = true) + val selectedChannel = remember { appPrefs.appUpdateChannel.state } + val values = AppUpdatesChannel.entries.map { it to it.text } + Box(Modifier.padding(start = DEFAULT_PADDING_HALF * 1.4f)) { + ExposedDropDownSettingRow(stringResource(MR.strings.app_check_for_updates), values, selectedChannel) { + appPrefs.appUpdateChannel.set(it) + setupUpdateChecker() + } + } AppVersionItem(showVersion) } } diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt index f69cf817e5..d6ee659dbf 100644 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt @@ -8,10 +8,12 @@ import androidx.compose.runtime.* import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.platform.DesktopPlatform import chat.simplex.common.showApp import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.OnboardingStage import kotlinx.coroutines.* import java.io.File @@ -20,6 +22,7 @@ fun main() { //System.setProperty("skiko.renderApi", "SOFTWARE") initHaskell() runMigrations() + setupUpdateChecker() initApp() tmpDir.deleteRecursively() tmpDir.mkdir() @@ -75,6 +78,31 @@ private fun initHaskell() { override fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable, scrollJob: MutableState, reversed: Boolean) { DesktopScrollBar(rememberScrollbarAdapter(scrollState = state), modifier, scrollBarAlpha, scrollJob, reversed) } + + @Composable + override fun desktopShowAppUpdateNotice() { + fun showNoticeIfNeeded() { + if ( + !chatModel.controller.appPrefs.appUpdateNoticeShown.get() + && chatModel.controller.appPrefs.onboardingStage.get() == OnboardingStage.OnboardingComplete + && chatModel.chats.size > 3 + && chatModel.activeCallInvitation.value == null + ) { + appPrefs.appUpdateNoticeShown.set(true) + showAppUpdateNotice() + } + } + // Will show notice if chats were loaded before that moment and number of chats > 3 + LaunchedEffect(Unit) { + showNoticeIfNeeded() + } + // Will show notice if chats were loaded later (a lot of chats/slow query) and number of chats > 3 + KeyChangeEffect(chatModel.chats.size) { oldSize -> + if (oldSize == 0) { + showNoticeIfNeeded() + } + } + } } } From 99888f9d5ff8e7737a5910871e20e83a662aca91 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 15 Jul 2024 08:35:37 +0100 Subject: [PATCH 24/57] ios: show admin and owner roles in the conversation (#4451) --- apps/ios/Shared/Views/Chat/ChatView.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index f8723fdba8..d3f5242cac 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -671,7 +671,12 @@ struct ChatView: View { if prevItem == nil || showMemberImage(member, prevItem) || prevMember != nil { VStack(alignment: .leading, spacing: 4) { if ci.content.showMemberName { - Text(memberNames(member, prevMember, memCount)) + let t = if memCount == 1 && member.memberRole > .member { + Text(member.memberRole.text + " ").fontWeight(.semibold) + Text(member.displayName) + } else { + Text(memberNames(member, prevMember, memCount)) + } + t .font(.caption) .foregroundStyle(.secondary) .lineLimit(2) From 5a014ba410ed3b4a39bfaffee68e3c4e080ff88b Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:35:48 +0400 Subject: [PATCH 25/57] multiplatform: show admin and owner roles in the conversation (#4455) --- .../chat/simplex/common/views/chat/ChatView.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 0cd33d659b..5578180ff8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1025,10 +1025,21 @@ fun BoxWithConstraintsScope.ChatItemsList( horizontalAlignment = Alignment.Start ) { if (cItem.content.showMemberName) { + val memberNameStyle = SpanStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary) + val memberNameString = if (memCount == 1 && member.memberRole > GroupMemberRole.Member) { + buildAnnotatedString { + withStyle(memberNameStyle.copy(fontWeight = FontWeight.Medium)) { append(member.memberRole.text) } + append(" ") + withStyle(memberNameStyle) { append(memberNames(member, prevMember, memCount)) } + } + } else { + buildAnnotatedString { + withStyle(memberNameStyle) { append(memberNames(member, prevMember, memCount)) } + } + } Text( - memberNames(member, prevMember, memCount), + memberNameString, Modifier.padding(start = MEMBER_IMAGE_SIZE + 10.dp), - style = TextStyle(fontSize = 13.5.sp, color = CurrentColors.value.colors.secondary), maxLines = 2 ) } From 0d6f43a5ace47dd34cec01a6605bf657ef2e7229 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 15 Jul 2024 08:54:31 +0100 Subject: [PATCH 26/57] ui: different label for subscribed connections: Message push, Active connections (#4453) * ui: different label for subscribed connections: Message push, Active connections * update * localizations --- .../Views/ChatList/ServersSummaryView.swift | 8 +-- .../bg.xcloc/Localized Contents/bg.xliff | 55 +++++++++++----- .../cs.xcloc/Localized Contents/cs.xliff | 55 +++++++++++----- .../de.xcloc/Localized Contents/de.xliff | 55 +++++++++++----- .../en.xcloc/Localized Contents/en.xliff | 65 +++++++++++++------ .../es.xcloc/Localized Contents/es.xliff | 55 +++++++++++----- .../fi.xcloc/Localized Contents/fi.xliff | 55 +++++++++++----- .../fr.xcloc/Localized Contents/fr.xliff | 55 +++++++++++----- .../hu.xcloc/Localized Contents/hu.xliff | 55 +++++++++++----- .../it.xcloc/Localized Contents/it.xliff | 55 +++++++++++----- .../ja.xcloc/Localized Contents/ja.xliff | 55 +++++++++++----- .../nl.xcloc/Localized Contents/nl.xliff | 55 +++++++++++----- .../pl.xcloc/Localized Contents/pl.xliff | 55 +++++++++++----- .../ru.xcloc/Localized Contents/ru.xliff | 55 +++++++++++----- .../th.xcloc/Localized Contents/th.xliff | 55 +++++++++++----- .../tr.xcloc/Localized Contents/tr.xliff | 55 +++++++++++----- .../uk.xcloc/Localized Contents/uk.xliff | 55 +++++++++++----- .../Localized Contents/zh-Hans.xliff | 55 +++++++++++----- .../commonMain/resources/MR/base/strings.xml | 4 +- 19 files changed, 643 insertions(+), 314 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 3021a7ceb2..f210809a09 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -174,11 +174,11 @@ struct ServersSummaryView: View { private func smpSubsSection(_ totals: SMPTotals) -> some View { Section { - infoRow("Connections subscribed", numOrDash(totals.subs.ssActive)) + infoRow("Active connections", numOrDash(totals.subs.ssActive)) infoRow("Total", numOrDash(totals.subs.total)) } header: { HStack { - Text("Message subscriptions") + Text("Message reception") SubscriptionStatusIndicatorView(subs: totals.subs, sess: totals.sessions) if showSubscriptionPercentage { SubscriptionStatusPercentageView(subs: totals.subs, sess: totals.sessions) @@ -474,13 +474,13 @@ struct SMPServerSummaryView: View { private func smpSubsSection(_ subs: SMPServerSubs) -> some View { Section { - infoRow("Connections subscribed", numOrDash(subs.ssActive)) + infoRow("Active connections", numOrDash(subs.ssActive)) infoRow("Pending", numOrDash(subs.ssPending)) infoRow("Total", numOrDash(subs.total)) reconnectButton() } header: { HStack { - Text("Message subscriptions") + Text("Message reception") SubscriptionStatusIndicatorView(subs: subs, sess: summary.sessionsOrNew) if showSubscriptionPercentage { SubscriptionStatusPercentageView(subs: subs, sess: summary.sessionsOrNew) diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index f3a70390da..c17ffcd571 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -127,11 +127,6 @@ %@ е потвърдено No comment provided by engineer.
- - %@ servers - %@ сървъри - No comment provided by engineer. - %@ uploaded %@ качено @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти. @@ -610,16 +609,16 @@ Добави профил No comment provided by engineer. - - Add servers by scanning QR codes. - Добави сървъри чрез сканиране на QR кодове. - No comment provided by engineer. - Add server Добави сървър No comment provided by engineer. + + Add servers by scanning QR codes. + Добави сървъри чрез сканиране на QR кодове. + No comment provided by engineer. + Add to another device Добави към друго устройство @@ -1351,6 +1350,10 @@ Конфигурирай ICE сървъри No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Потвърди @@ -1529,10 +1532,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Контактът позволява @@ -3926,6 +3925,10 @@ This is your link for group %@! Реакциите на съобщения са забранени в тази група. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback No comment provided by engineer. @@ -3947,10 +3950,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Текст на съобщението @@ -4469,6 +4468,10 @@ This is your link for group %@! Други No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING бройка @@ -4635,6 +4638,10 @@ Error: %@ Моля, съхранявайте паролата на сигурно място, НЯМА да можете да я промените, ако я загубите. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Полски интерфейс @@ -4701,6 +4708,10 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Профилни и сървърни връзки @@ -5601,6 +5612,10 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Сървърът изисква оторизация за създаване на опашки, проверете паролата @@ -5624,6 +5639,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Сървъри diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 46a890f5ba..9cc8d12f74 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -126,11 +126,6 @@ %@ je ověřený No comment provided by engineer. - - %@ servers - %@ servery - No comment provided by engineer. - %@ uploaded No comment provided by engineer. @@ -572,6 +567,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům. @@ -591,16 +590,16 @@ Přidat profil No comment provided by engineer. - - Add servers by scanning QR codes. - Přidejte servery skenováním QR kódů. - No comment provided by engineer. - Add server Přidat server No comment provided by engineer. + + Add servers by scanning QR codes. + Přidejte servery skenováním QR kódů. + No comment provided by engineer. + Add to another device Přidat do jiného zařízení @@ -1305,6 +1304,10 @@ Konfigurace serverů ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Potvrdit @@ -1467,10 +1470,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Kontakt povolil @@ -3783,6 +3782,10 @@ This is your link for group %@! Reakce na zprávy jsou v této skupině zakázány. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback No comment provided by engineer. @@ -3803,10 +3806,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Text zprávy @@ -4299,6 +4298,10 @@ This is your link for group %@! Other No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Počet PING @@ -4457,6 +4460,10 @@ Error: %@ Heslo uložte bezpečně, v případě jeho ztráty jej NEBUDE možné změnit. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Polské rozhraní @@ -4522,6 +4529,10 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profil a připojení k serveru @@ -5399,6 +5410,10 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo @@ -5422,6 +5437,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Servery diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 656cc6c70e..341839d246 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -127,11 +127,6 @@ %@ wurde erfolgreich überprüft No comment provided by engineer. - - %@ servers - %@-Server - No comment provided by engineer. - %@ uploaded %@ hochgeladen @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. @@ -610,16 +609,16 @@ Profil hinzufügen No comment provided by engineer. - - Add servers by scanning QR codes. - Fügen Sie Server durch Scannen der QR Codes hinzu. - No comment provided by engineer. - Add server Füge Server hinzu No comment provided by engineer. + + Add servers by scanning QR codes. + Fügen Sie Server durch Scannen der QR Codes hinzu. + No comment provided by engineer. + Add to another device Einem anderen Gerät hinzufügen @@ -1354,6 +1353,10 @@ ICE-Server konfigurieren No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Bestätigen @@ -1533,10 +1536,6 @@ Das ist Ihr eigener Einmal-Link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Der Kontakt erlaubt @@ -3941,6 +3940,10 @@ Das ist Ihr Link für die Gruppe %@! In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Fallback für das Nachrichten-Routing @@ -3964,10 +3967,6 @@ Das ist Ihr Link für die Gruppe %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Nachrichtentext @@ -4487,6 +4486,10 @@ Das ist Ihr Link für die Gruppe %@! Andere No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING-Zähler @@ -4653,6 +4656,10 @@ Fehler: %@ Bitte bewahren Sie das Passwort sicher auf, Sie können es NICHT mehr ändern, wenn Sie es vergessen haben oder verlieren. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Polnische Bedienoberfläche @@ -4722,6 +4729,10 @@ Fehler: %@ Privates Routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profil und Serververbindungen @@ -5629,6 +5640,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort @@ -5653,6 +5668,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Server diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 29f3f30d89..7bdf67cc94 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -127,11 +127,6 @@ %@ is verified No comment provided by engineer. - - %@ servers - %@ servers - No comment provided by engineer. - %@ uploaded %@ uploaded @@ -593,6 +588,11 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. @@ -613,16 +613,16 @@ Add profile No comment provided by engineer. - - Add servers by scanning QR codes. - Add servers by scanning QR codes. - No comment provided by engineer. - Add server Add server No comment provided by engineer. + + Add servers by scanning QR codes. + Add servers by scanning QR codes. + No comment provided by engineer. + Add to another device Add to another device @@ -1374,6 +1374,11 @@ Configure ICE servers No comment provided by engineer. + + Configured %@ servers + Configured %@ servers + No comment provided by engineer. + Confirm Confirm @@ -1558,11 +1563,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - Connections subscribed - No comment provided by engineer. - Contact allows Contact allows @@ -3998,6 +3998,11 @@ This is your link for group %@! Message reactions are prohibited in this group. No comment provided by engineer. + + Message reception + Message reception + No comment provided by engineer. + Message routing fallback Message routing fallback @@ -4023,11 +4028,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - Message subscriptions - No comment provided by engineer. - Message text Message text @@ -4552,6 +4552,11 @@ This is your link for group %@! Other No comment provided by engineer. + + Other %@ servers + Other %@ servers + No comment provided by engineer. + PING count PING count @@ -4721,6 +4726,11 @@ Error: %@ Please store passphrase securely, you will NOT be able to change it if you lose it. No comment provided by engineer. + + Please try later. + Please try later. + No comment provided by engineer. + Polish interface Polish interface @@ -4791,6 +4801,11 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + Private routing error + No comment provided by engineer. + Profile and server connections Profile and server connections @@ -5728,6 +5743,11 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Server requires authorization to create queues, check password @@ -5753,6 +5773,11 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Servers diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index a4cc2d2e68..eb4933e69a 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -127,11 +127,6 @@ %@ está verificado No comment provided by engineer. - - %@ servers - Servidores %@ - No comment provided by engineer. - %@ uploaded %@ subido @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Añade la dirección a tu perfil para que tus contactos puedan compartirla con otros. La actualización del perfil se enviará a tus contactos. @@ -610,16 +609,16 @@ Añadir perfil No comment provided by engineer. - - Add servers by scanning QR codes. - Añadir servidores mediante el escaneo de códigos QR. - No comment provided by engineer. - Add server Añadir servidor No comment provided by engineer. + + Add servers by scanning QR codes. + Añadir servidores mediante el escaneo de códigos QR. + No comment provided by engineer. + Add to another device Añadir a otro dispositivo @@ -1354,6 +1353,10 @@ Configure servidores ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Confirmar @@ -1533,10 +1536,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows El contacto permite @@ -3941,6 +3940,10 @@ This is your link for group %@! Las reacciones a los mensajes no están permitidas en este grupo. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Enrutamiento de mensajes alternativo @@ -3964,10 +3967,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Contacto y texto @@ -4487,6 +4486,10 @@ This is your link for group %@! Otro No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Contador PING @@ -4653,6 +4656,10 @@ Error: %@ Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Interfaz en polaco @@ -4722,6 +4729,10 @@ Error: %@ Enrutamiento privado No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Datos del perfil y conexiones @@ -5629,6 +5640,10 @@ Actívalo en ajustes de *Servidores y Redes*. La dirección del servidor es incompatible con la configuración de la red. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password El servidor requiere autorización para crear colas, comprueba la contraseña @@ -5653,6 +5668,10 @@ Actívalo en ajustes de *Servidores y Redes*. La versión del servidor es incompatible con la configuración de red. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Servidores diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index e132584d0e..4959324895 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -124,11 +124,6 @@ %@ on vahvistettu No comment provided by engineer. - - %@ servers - %@ palvelimet - No comment provided by engineer. - %@ uploaded No comment provided by engineer. @@ -567,6 +562,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi. @@ -586,16 +585,16 @@ Lisää profiili No comment provided by engineer. - - Add servers by scanning QR codes. - Lisää palvelimia skannaamalla QR-koodeja. - No comment provided by engineer. - Add server Lisää palvelin No comment provided by engineer. + + Add servers by scanning QR codes. + Lisää palvelimia skannaamalla QR-koodeja. + No comment provided by engineer. + Add to another device Lisää toiseen laitteeseen @@ -1298,6 +1297,10 @@ Määritä ICE-palvelimet No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Vahvista @@ -1460,10 +1463,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Kontakti sallii @@ -3773,6 +3772,10 @@ This is your link for group %@! Viestireaktiot ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback No comment provided by engineer. @@ -3793,10 +3796,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Viestin teksti @@ -4287,6 +4286,10 @@ This is your link for group %@! Other No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING-määrä @@ -4445,6 +4448,10 @@ Error: %@ Säilytä tunnuslause turvallisesti, ET voi muuttaa sitä, jos kadotat sen. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Puolalainen käyttöliittymä @@ -4510,6 +4517,10 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profiili- ja palvelinyhteydet @@ -5386,6 +5397,10 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana @@ -5409,6 +5424,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Palvelimet diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 5416324e89..a3c5bd8c65 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -127,11 +127,6 @@ %@ est vérifié·e No comment provided by engineer. - - %@ servers - Serveurs %@ - No comment provided by engineer. - %@ uploaded %@ envoyé @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts. @@ -610,16 +609,16 @@ Ajouter un profil No comment provided by engineer. - - Add servers by scanning QR codes. - Ajoutez des serveurs en scannant des codes QR. - No comment provided by engineer. - Add server Ajouter un serveur No comment provided by engineer. + + Add servers by scanning QR codes. + Ajoutez des serveurs en scannant des codes QR. + No comment provided by engineer. + Add to another device Ajouter à un autre appareil @@ -1354,6 +1353,10 @@ Configurer les serveurs ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Confirmer @@ -1533,10 +1536,6 @@ Il s'agit de votre propre lien unique ! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Votre contact autorise @@ -3941,6 +3940,10 @@ Voici votre lien pour le groupe %@ ! Les réactions aux messages sont interdites dans ce groupe. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Rabattement du routage des messages @@ -3964,10 +3967,6 @@ Voici votre lien pour le groupe %@ ! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Texte du message @@ -4487,6 +4486,10 @@ Voici votre lien pour le groupe %@ ! Autres No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Nombre de PING @@ -4653,6 +4656,10 @@ Erreur : %@ Veuillez conserver votre phrase secrète en lieu sûr, vous NE pourrez PAS la changer si vous la perdez. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Interface en polonais @@ -4722,6 +4729,10 @@ Erreur : %@ Routage privé No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profil et connexions au serveur @@ -5629,6 +5640,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. L'adresse du serveur est incompatible avec les paramètres du réseau. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe @@ -5653,6 +5668,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. La version du serveur est incompatible avec les paramètres du réseau. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Serveurs diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 0264f5ffc4..5241ae89c0 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -127,11 +127,6 @@ %@ ellenőrizve No comment provided by engineer. - - %@ servers - %@ kiszolgáló - No comment provided by engineer. - %@ uploaded %@ feltöltve @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára. @@ -610,16 +609,16 @@ Profil hozzáadása No comment provided by engineer. - - Add servers by scanning QR codes. - Kiszolgáló hozzáadása QR-kód beolvasásával. - No comment provided by engineer. - Add server Kiszolgáló hozzáadása No comment provided by engineer. + + Add servers by scanning QR codes. + Kiszolgáló hozzáadása QR-kód beolvasásával. + No comment provided by engineer. + Add to another device Hozzáadás egy másik eszközhöz @@ -1354,6 +1353,10 @@ ICE kiszolgálók beállítása No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Megerősítés @@ -1533,10 +1536,6 @@ Ez az egyszer használatos hivatkozása! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Ismerős engedélyezi @@ -3941,6 +3940,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Az üzenetreakciók küldése le van tiltva ebben a csoportban. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Üzenet útválasztási tartalék @@ -3964,10 +3967,6 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Üzenet szövege @@ -4487,6 +4486,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! További No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING számláló @@ -4653,6 +4656,10 @@ Hiba: %@ Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Lengyel kezelőfelület @@ -4722,6 +4729,10 @@ Hiba: %@ Privát útválasztás No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profil és kiszolgálókapcsolatok @@ -5629,6 +5640,10 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< A kiszolgáló címe nem kompatibilis a hálózati beállításokkal. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát @@ -5653,6 +5668,10 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Kiszolgálók diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 024950730f..2146b0115d 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -127,11 +127,6 @@ %@ è verificato/a No comment provided by engineer. - - %@ servers - Server %@ - No comment provided by engineer. - %@ uploaded %@ caricati @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti. @@ -610,16 +609,16 @@ Aggiungi profilo No comment provided by engineer. - - Add servers by scanning QR codes. - Aggiungi server scansionando codici QR. - No comment provided by engineer. - Add server Aggiungi server No comment provided by engineer. + + Add servers by scanning QR codes. + Aggiungi server scansionando codici QR. + No comment provided by engineer. + Add to another device Aggiungi ad un altro dispositivo @@ -1354,6 +1353,10 @@ Configura server ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Conferma @@ -1533,10 +1536,6 @@ Questo è il tuo link una tantum! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Il contatto lo consente @@ -3941,6 +3940,10 @@ Questo è il tuo link per il gruppo %@! Le reazioni ai messaggi sono vietate in questo gruppo. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Ripiego instradamento messaggio @@ -3964,10 +3967,6 @@ Questo è il tuo link per il gruppo %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Testo del messaggio @@ -4487,6 +4486,10 @@ Questo è il tuo link per il gruppo %@! Altro No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Conteggio PING @@ -4653,6 +4656,10 @@ Errore: %@ Conserva la password in modo sicuro, NON potrai cambiarla se la perdi. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Interfaccia polacca @@ -4722,6 +4729,10 @@ Errore: %@ Instradamento privato No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profilo e connessioni al server @@ -5629,6 +5640,10 @@ Attivalo nelle impostazioni *Rete e server*. L'indirizzo del server non è compatibile con le impostazioni di rete. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Il server richiede l'autorizzazione di creare code, controlla la password @@ -5653,6 +5668,10 @@ Attivalo nelle impostazioni *Rete e server*. La versione del server non è compatibile con le impostazioni di rete. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Server diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 589973d1c0..2e23ad7423 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -127,11 +127,6 @@ %@ は検証されています No comment provided by engineer. - - %@ servers - %@ サーバー - No comment provided by engineer. - %@ uploaded %@ アップロード済 @@ -584,6 +579,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。 @@ -603,16 +602,16 @@ プロフィールを追加 No comment provided by engineer. - - Add servers by scanning QR codes. - QRコードでサーバを追加する。 - No comment provided by engineer. - Add server サーバを追加 No comment provided by engineer. + + Add servers by scanning QR codes. + QRコードでサーバを追加する。 + No comment provided by engineer. + Add to another device 別の端末に追加 @@ -1322,6 +1321,10 @@ ICEサーバを設定 No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm 確認 @@ -1484,10 +1487,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows 連絡先の許可 @@ -3797,6 +3796,10 @@ This is your link for group %@! このグループではメッセージへのリアクションは禁止されています。 No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback No comment provided by engineer. @@ -3817,10 +3820,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text メッセージ内容 @@ -4313,6 +4312,10 @@ This is your link for group %@! Other No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING回数 @@ -4471,6 +4474,10 @@ Error: %@ パスフレーズを失くさないように保管してください。失くすと変更できなくなります。 No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface ポーランド語UI @@ -4536,6 +4543,10 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections プロフィールとサーバ接続 @@ -5404,6 +5415,10 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password キューを作成するにはサーバーの認証が必要です。パスワードを確認してください @@ -5427,6 +5442,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers サーバ diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 44fda15333..e3da8c8d41 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -127,11 +127,6 @@ %@ is geverifieerd No comment provided by engineer. - - %@ servers - %@ servers - No comment provided by engineer. - %@ uploaded %@ geüpload @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden. @@ -610,16 +609,16 @@ Profiel toevoegen No comment provided by engineer. - - Add servers by scanning QR codes. - Servers toevoegen door QR-codes te scannen. - No comment provided by engineer. - Add server Server toevoegen No comment provided by engineer. + + Add servers by scanning QR codes. + Servers toevoegen door QR-codes te scannen. + No comment provided by engineer. + Add to another device Toevoegen aan een ander apparaat @@ -1354,6 +1353,10 @@ ICE servers configureren No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Bevestigen @@ -1533,10 +1536,6 @@ Dit is uw eigen eenmalige link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Contact maakt het mogelijk @@ -3941,6 +3940,10 @@ Dit is jouw link voor groep %@! Reacties op berichten zijn verboden in deze groep. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Terugval op berichtroutering @@ -3964,10 +3967,6 @@ Dit is jouw link voor groep %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Bericht tekst @@ -4487,6 +4486,10 @@ Dit is jouw link voor groep %@! Ander No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING count @@ -4653,6 +4656,10 @@ Fout: %@ Bewaar het wachtwoord veilig, u kunt deze NIET wijzigen als u het kwijtraakt. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Poolse interface @@ -4722,6 +4729,10 @@ Fout: %@ Privéroutering No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profiel- en serververbindingen @@ -5629,6 +5640,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Serveradres is niet compatibel met netwerkinstellingen. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord @@ -5653,6 +5668,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Serverversie is incompatibel met netwerkinstellingen. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Servers diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index dd4aecf428..ce4fe705a1 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -127,11 +127,6 @@ %@ jest zweryfikowany No comment provided by engineer. - - %@ servers - %@ serwery - No comment provided by engineer. - %@ uploaded %@ wgrane @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. @@ -610,16 +609,16 @@ Dodaj profil No comment provided by engineer. - - Add servers by scanning QR codes. - Dodaj serwery, skanując kody QR. - No comment provided by engineer. - Add server Dodaj serwer No comment provided by engineer. + + Add servers by scanning QR codes. + Dodaj serwery, skanując kody QR. + No comment provided by engineer. + Add to another device Dodaj do innego urządzenia @@ -1354,6 +1353,10 @@ Skonfiguruj serwery ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Potwierdź @@ -1533,10 +1536,6 @@ To jest twój jednorazowy link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Kontakt pozwala @@ -3941,6 +3940,10 @@ To jest twój link do grupy %@! Reakcje wiadomości są zabronione w tej grupie. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Rezerwowe trasowania wiadomości @@ -3964,10 +3967,6 @@ To jest twój link do grupy %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Tekst wiadomości @@ -4487,6 +4486,10 @@ To jest twój link do grupy %@! Inne No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Liczba PINGÓW @@ -4653,6 +4656,10 @@ Błąd: %@ Przechowuj kod dostępu w bezpieczny sposób, w przypadku jego utraty NIE będzie można go zmienić. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Polski interfejs @@ -4722,6 +4729,10 @@ Błąd: %@ Prywatne trasowanie No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profil i połączenia z serwerem @@ -5629,6 +5640,10 @@ Włącz w ustawianiach *Sieć i serwery* . Adres serwera jest niekompatybilny z ustawieniami sieciowymi. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło @@ -5653,6 +5668,10 @@ Włącz w ustawianiach *Sieć i serwery* . Wersja serwera jest niekompatybilna z ustawieniami sieciowymi. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Serwery diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index f4ad23262c..b5da149546 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -127,11 +127,6 @@ %@ подтверждён No comment provided by engineer. - - %@ servers - %@ серверы - No comment provided by engineer. - %@ uploaded %@ загружено @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Добавьте адрес в свой профиль, чтобы Ваши контакты могли поделиться им. Профиль будет отправлен Вашим контактам. @@ -610,16 +609,16 @@ Добавить профиль No comment provided by engineer. - - Add servers by scanning QR codes. - Добавить серверы через QR код. - No comment provided by engineer. - Add server Добавить сервер No comment provided by engineer. + + Add servers by scanning QR codes. + Добавить серверы через QR код. + No comment provided by engineer. + Add to another device Добавить на другое устройство @@ -1354,6 +1353,10 @@ Настройка ICE серверов No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Подтвердить @@ -1533,10 +1536,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Контакт разрешает @@ -3939,6 +3938,10 @@ This is your link for group %@! Реакции на сообщения запрещены в этой группе. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Прямая доставка сообщений @@ -3962,10 +3965,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Текст сообщения @@ -4485,6 +4484,10 @@ This is your link for group %@! Другaя сеть No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Количество PING @@ -4651,6 +4654,10 @@ Error: %@ Пожалуйста, надежно сохраните пароль, Вы НЕ сможете его поменять, если потеряете. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Польский интерфейс @@ -4720,6 +4727,10 @@ Error: %@ Конфиденциальная доставка No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Профиль и соединения на сервере @@ -5627,6 +5638,10 @@ Enable in *Network & servers* settings. Адрес сервера несовместим с настройками сети. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Сервер требует авторизации для создания очередей, проверьте пароль @@ -5651,6 +5666,10 @@ Enable in *Network & servers* settings. Версия сервера несовместима с настройками сети. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Серверы diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 7e3298ed58..605a630f10 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -120,11 +120,6 @@ %@ ได้รับการตรวจสอบแล้ว No comment provided by engineer. - - %@ servers - %@ เซิร์ฟเวอร์ - No comment provided by engineer. - %@ uploaded No comment provided by engineer. @@ -559,6 +554,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ @@ -578,16 +577,16 @@ เพิ่มโปรไฟล์ No comment provided by engineer. - - Add servers by scanning QR codes. - เพิ่มเซิร์ฟเวอร์โดยการสแกนรหัสคิวอาร์โค้ด - No comment provided by engineer. - Add server เพิ่มเซิร์ฟเวอร์ No comment provided by engineer. + + Add servers by scanning QR codes. + เพิ่มเซิร์ฟเวอร์โดยการสแกนรหัสคิวอาร์โค้ด + No comment provided by engineer. + Add to another device เพิ่มเข้าไปในอุปกรณ์อื่น @@ -1290,6 +1289,10 @@ กำหนดค่าเซิร์ฟเวอร์ ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm ยืนยัน @@ -1450,10 +1453,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows ผู้ติดต่ออนุญาต @@ -3756,6 +3755,10 @@ This is your link for group %@! ปฏิกิริยาบนข้อความเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback No comment provided by engineer. @@ -3776,10 +3779,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text ข้อความ @@ -4268,6 +4267,10 @@ This is your link for group %@! Other No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count จํานวน PING @@ -4426,6 +4429,10 @@ Error: %@ โปรดจัดเก็บรหัสผ่านอย่างปลอดภัย คุณจะไม่สามารถเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface อินเตอร์เฟซภาษาโปแลนด์ @@ -4491,6 +4498,10 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections การเชื่อมต่อโปรไฟล์และเซิร์ฟเวอร์ @@ -5363,6 +5374,10 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน @@ -5386,6 +5401,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers เซิร์ฟเวอร์ diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 15ea09d060..8a09e1f21a 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -127,11 +127,6 @@ %@ onaylandı No comment provided by engineer. - - %@ servers - %@ sunucuları - No comment provided by engineer. - %@ uploaded %@ yüklendi @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek. @@ -610,16 +609,16 @@ Profil ekle No comment provided by engineer. - - Add servers by scanning QR codes. - Karekod taratarak sunucuları ekleyin. - No comment provided by engineer. - Add server Sunucu ekle No comment provided by engineer. + + Add servers by scanning QR codes. + Karekod taratarak sunucuları ekleyin. + No comment provided by engineer. + Add to another device Başka bir cihaza ekle @@ -1354,6 +1353,10 @@ ICE sunucularını ayarla No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Onayla @@ -1533,10 +1536,6 @@ Bu senin kendi tek kullanımlık bağlantın! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Kişi izin veriyor @@ -3941,6 +3940,10 @@ Bu senin grup için bağlantın %@! Mesaj tepkileri bu grupta yasaklandı. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Mesaj yönlendirme yedeklemesi @@ -3964,10 +3967,6 @@ Bu senin grup için bağlantın %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Mesaj yazısı @@ -4487,6 +4486,10 @@ Bu senin grup için bağlantın %@! Diğer No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING sayısı @@ -4653,6 +4656,10 @@ Hata: %@ Lütfen parolayı güvenli bir şekilde saklayın, kaybederseniz parolayı DEĞİŞTİREMEZSİNİZ. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Lehçe arayüz @@ -4722,6 +4729,10 @@ Hata: %@ Gizli yönlendirme No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections Profil ve sunucu bağlantıları @@ -5629,6 +5640,10 @@ Enable in *Network & servers* settings. Sunucu adresi ağ ayarlarıyla uyumlu değil. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin @@ -5653,6 +5668,10 @@ Enable in *Network & servers* settings. Sunucu sürümü ağ ayarlarıyla uyumlu değil. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Sunucular diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index aa069d444b..f210d39a78 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -127,11 +127,6 @@ %@ перевірено No comment provided by engineer. - - %@ servers - %@ сервери - No comment provided by engineer. - %@ uploaded %@ завантажено @@ -590,6 +585,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам. @@ -610,16 +609,16 @@ Додати профіль No comment provided by engineer. - - Add servers by scanning QR codes. - Додайте сервери, відсканувавши QR-код. - No comment provided by engineer. - Add server Додати сервер No comment provided by engineer. + + Add servers by scanning QR codes. + Додайте сервери, відсканувавши QR-код. + No comment provided by engineer. + Add to another device Додати до іншого пристрою @@ -1354,6 +1353,10 @@ Налаштування серверів ICE No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm Підтвердити @@ -1533,10 +1536,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows Контакт дозволяє @@ -3941,6 +3940,10 @@ This is your link for group %@! Реакції на повідомлення в цій групі заборонені. No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback Запасний варіант маршрутизації повідомлень @@ -3964,10 +3967,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text Текст повідомлення @@ -4487,6 +4486,10 @@ This is your link for group %@! Інше No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count Кількість PING @@ -4653,6 +4656,10 @@ Error: %@ Будь ласка, зберігайте пароль надійно, ви НЕ зможете змінити його, якщо втратите. No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface Польський інтерфейс @@ -4722,6 +4729,10 @@ Error: %@ Приватна маршрутизація No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections З'єднання профілю та сервера @@ -5629,6 +5640,10 @@ Enable in *Network & servers* settings. Адреса сервера несумісна з налаштуваннями мережі. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password Сервер вимагає авторизації для створення черг, перевірте пароль @@ -5653,6 +5668,10 @@ Enable in *Network & servers* settings. Серверна версія несумісна з мережевими налаштуваннями. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers Сервери diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 3823d6e6a9..bc61b16ce0 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -126,11 +126,6 @@ %@ 已认证 No comment provided by engineer. - - %@ servers - %@ 服务器 - No comment provided by engineer. - %@ uploaded No comment provided by engineer. @@ -578,6 +573,10 @@ Acknowledgement errors No comment provided by engineer. + + Active connections + No comment provided by engineer. + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. 将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。 @@ -598,16 +597,16 @@ 添加个人资料 No comment provided by engineer. - - Add servers by scanning QR codes. - 扫描二维码来添加服务器。 - No comment provided by engineer. - Add server 添加服务器 No comment provided by engineer. + + Add servers by scanning QR codes. + 扫描二维码来添加服务器。 + No comment provided by engineer. + Add to another device 添加另一设备 @@ -1337,6 +1336,10 @@ 配置 ICE 服务器 No comment provided by engineer. + + Configured %@ servers + No comment provided by engineer. + Confirm 确认 @@ -1509,10 +1512,6 @@ This is your own one-time link! Connections No comment provided by engineer. - - Connections subscribed - No comment provided by engineer. - Contact allows 联系人允许 @@ -3888,6 +3887,10 @@ This is your link for group %@! 该群组禁用了消息回应。 No comment provided by engineer. + + Message reception + No comment provided by engineer. + Message routing fallback No comment provided by engineer. @@ -3909,10 +3912,6 @@ This is your link for group %@! Message status: %@ copied message info - - Message subscriptions - No comment provided by engineer. - Message text 消息正文 @@ -4426,6 +4425,10 @@ This is your link for group %@! 其他 No comment provided by engineer. + + Other %@ servers + No comment provided by engineer. + PING count PING 次数 @@ -4589,6 +4592,10 @@ Error: %@ 请安全地保存密码,如果您丢失了密码,您将无法更改它。 No comment provided by engineer. + + Please try later. + No comment provided by engineer. + Polish interface 波兰语界面 @@ -4655,6 +4662,10 @@ Error: %@ Private routing No comment provided by engineer. + + Private routing error + No comment provided by engineer. + Profile and server connections 资料和服务器连接 @@ -5551,6 +5562,10 @@ Enable in *Network & servers* settings. Server address is incompatible with network settings. srv error text. + + Server address is incompatible with network settings: %@. + No comment provided by engineer. + Server requires authorization to create queues, check password 服务器需要授权才能创建队列,检查密码 @@ -5574,6 +5589,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with your app: %@. + No comment provided by engineer. + Servers 服务器 diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index e08ccb23d1..7fbb4395d6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2130,8 +2130,8 @@ Messages received Details Starting from %s.\nAll data is private to your device. - Messages subscriptions - Connections subscribed + Message reception + Active connections Pending Total Connected servers From 0847b725b3f9db9eb07887da0ac8df50ab7c0cf1 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 15 Jul 2024 13:14:14 +0100 Subject: [PATCH 27/57] ios: toolbar and message entry area background color (#4449) * ios: toolbar and message entry area background color * remove VStack, opacity * ios: adjust compose view background color to match top bar (#4456) * search * replace BlurView with .thinMaterial * context item background with shadow * search * Revert "context item background with shadow" This reverts commit fc4ad324179c890a83557653655fd79b1c170b11. * rework shadow * shadow on both sides * Revert "shadow on both sides" This reverts commit a07920af9167a001ba2e0aa0adf63185deece147. * Revert "rework shadow" This reverts commit 78728263fbd79d8fd699dbdd3f7871de1cc3d605. * dividers * remove paddings * height * search * focus search * color * search background --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> --- .../Shared/Views/Chat/ChatInfoToolbar.swift | 18 +- apps/ios/Shared/Views/Chat/ChatView.swift | 17 +- .../Chat/ComposeMessage/ComposeFileView.swift | 3 +- .../ComposeMessage/ComposeImageView.swift | 2 +- .../Chat/ComposeMessage/ComposeLinkView.swift | 2 +- .../Chat/ComposeMessage/ComposeView.swift | 19 +- .../ComposeMessage/ComposeVoiceView.swift | 2 +- .../ContextInvitingContactMemberView.swift | 5 +- .../Chat/ComposeMessage/ContextItemView.swift | 3 +- .../Chat/ComposeMessage/SendMessageView.swift | 8 +- .../Chat/Group/GroupMemberInfoView.swift | 246 +++++++++--------- 11 files changed, 169 insertions(+), 156 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift index 26749ed3ce..8c9112a858 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift @@ -28,15 +28,17 @@ struct ChatInfoToolbar: View { color: Color(uiColor: .tertiaryLabel) ) .padding(.trailing, 4) - VStack { - let t = Text(cInfo.displayName).font(.headline) - (cInfo.contact?.verified == true ? contactVerifiedShield + t : t) - .lineLimit(1) - if cInfo.fullName != "" && cInfo.displayName != cInfo.fullName { - Text(cInfo.fullName).font(.subheadline) - .lineLimit(1) + let t = Text(cInfo.displayName).font(.headline) + (cInfo.contact?.verified == true ? contactVerifiedShield + t : t) + .lineLimit(1) + .if (cInfo.fullName != "" && cInfo.displayName != cInfo.fullName) { v in + VStack(spacing: 0) { + v + Text(cInfo.fullName).font(.subheadline) + .lineLimit(1) + .padding(.top, -2) + } } - } } .foregroundColor(theme.colors.onBackground) .frame(width: 220) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index d3f5242cac..1f3c04085b 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -45,9 +45,14 @@ struct ChatView: View { var body: some View { if #available(iOS 16.0, *) { - viewBody + let v = viewBody .scrollDismissesKeyboard(.immediately) .keyboardPadding() + if (searchMode) { + v.toolbarBackground(.thinMaterial, for: .navigationBar) + } else { + v.toolbarBackground(.visible, for: .navigationBar) + } } else { viewBody } @@ -81,7 +86,6 @@ struct ChatView: View { ) .disabled(!cInfo.sendMsgEnabled) } - .padding(.top, 1) .navigationTitle(cInfo.chatViewName) .background(theme.colors.background) .navigationBarTitleDisplayMode(.inline) @@ -310,8 +314,9 @@ struct ChatView: View { } .padding(.horizontal) .padding(.vertical, 8) + .background(.thinMaterial) } - + private func voiceWithoutFrame(_ ci: ChatItem) -> Bool { ci.content.msgContent?.isVoice == true && ci.content.text.count == 0 && ci.quotedItem == nil && ci.meta.itemForwarded == nil } @@ -702,19 +707,19 @@ struct ChatView: View { chatItemWithMenu(ci, range, maxWidth) } } - .padding(.top, 5) + .padding(.bottom, 5) .padding(.trailing) .padding(.leading, 12) } else { chatItemWithMenu(ci, range, maxWidth) - .padding(.top, 5) + .padding(.bottom, 5) .padding(.trailing) .padding(.leading, memberImageSize + 8 + 12) } } else { chatItemWithMenu(ci, range, maxWidth) .padding(.horizontal) - .padding(.top, 5) + .padding(.bottom, 5) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeFileView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeFileView.swift index 488fe0a65d..1ec46816f5 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeFileView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeFileView.swift @@ -32,9 +32,8 @@ struct ComposeFileView: View { } .padding(.vertical, 1) .padding(.trailing, 12) - .frame(height: 50) + .frame(height: 54) .background(theme.appColors.sentMessage) .frame(maxWidth: .infinity) - .padding(.top, 8) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift index d9f63f6d33..df3a8caf55 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeImageView.swift @@ -46,8 +46,8 @@ struct ComposeImageView: View { .padding(.vertical, 1) .padding(.trailing, 12) .background(theme.appColors.sentMessage) + .frame(minHeight: 54) .frame(maxWidth: .infinity) - .padding(.top, 8) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift index 7df0a3037a..0fb48033d5 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift @@ -63,8 +63,8 @@ struct ComposeLinkView: View { .padding(.vertical, 1) .padding(.trailing, 12) .background(theme.appColors.sentMessage) + .frame(minHeight: 54) .frame(maxWidth: .infinity) - .padding(.top, 8) } private func linkPreviewView(_ linkPreview: LinkPreview) -> some View { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift index c1b709fea5..e19672c883 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift @@ -284,8 +284,10 @@ struct ComposeView: View { var body: some View { VStack(spacing: 0) { + Divider() if chat.chatInfo.contact?.nextSendGrpInv ?? false { ContextInvitingContactMemberView() + Divider() } // preference checks should match checks in forwarding list let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks) @@ -293,10 +295,13 @@ struct ComposeView: View { let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice) if simplexLinkProhibited { msgNotAllowedView("SimpleX links not allowed", icon: "link") + Divider() } else if fileProhibited { msgNotAllowedView("Files and media not allowed", icon: "doc") + Divider() } else if voiceProhibited { msgNotAllowedView("Voice messages not allowed", icon: "mic") + Divider() } contextItemView() switch (composeState.editing, composeState.preview) { @@ -359,7 +364,6 @@ struct ComposeView: View { : theme.colors.primary ) .padding(.trailing, 12) - .background(theme.colors.background) .disabled(!chat.userCanSend) if chat.userIsObserver { @@ -377,6 +381,7 @@ struct ComposeView: View { } } } + .background(.thinMaterial) .onChange(of: composeState.message) { msg in if composeState.linkPreviewAllowed { if msg.count > 0 { @@ -625,6 +630,7 @@ struct ComposeView: View { cancelPreview: cancelLinkPreview, cancelEnabled: !composeState.inProgress ) + Divider() case let .mediaPreviews(mediaPreviews: media): ComposeImageView( images: media.map { (img, _) in img }, @@ -633,6 +639,7 @@ struct ComposeView: View { chosenMedia = [] }, cancelEnabled: !composeState.editing && !composeState.inProgress) + Divider() case let .voicePreview(recordingFileName, _): ComposeVoiceView( recordingFileName: recordingFileName, @@ -645,6 +652,7 @@ struct ComposeView: View { cancelEnabled: !composeState.editing && !composeState.inProgress, stopPlayback: $stopPlayback ) + Divider() case let .filePreview(fileName, _): ComposeFileView( fileName: fileName, @@ -652,6 +660,7 @@ struct ComposeView: View { composeState = composeState.copy(preview: .noPreview) }, cancelEnabled: !composeState.editing && !composeState.inProgress) + Divider() } } @@ -661,10 +670,9 @@ struct ComposeView: View { Text(reason).italic() } .padding(12) - .frame(minHeight: 50) + .frame(minHeight: 54) .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(uiColor: .tertiarySystemGroupedBackground)) - .padding(.top, 8) + .background(.thinMaterial) } @ViewBuilder private func contextItemView() -> some View { @@ -678,6 +686,7 @@ struct ComposeView: View { contextIcon: "arrowshape.turn.up.left", cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) } ) + Divider() case let .editingItem(chatItem: editingItem): ContextItemView( chat: chat, @@ -685,6 +694,7 @@ struct ComposeView: View { contextIcon: "pencil", cancelContextItem: { clearState() } ) + Divider() case let .forwardingItem(chatItem: forwardedItem, _): ContextItemView( chat: chat, @@ -693,6 +703,7 @@ struct ComposeView: View { cancelContextItem: { composeState = composeState.copy(contextItem: .noContextItem) }, showSender: false ) + Divider() } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift index 4b813d35cb..441a68fccb 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift @@ -51,8 +51,8 @@ struct ComposeVoiceView: View { .padding(.vertical, 1) .frame(height: ComposeVoiceView.previewHeight) .background(theme.appColors.sentMessage) + .frame(minHeight: 54) .frame(maxWidth: .infinity) - .padding(.top, 8) } private func recordingMode() -> some View { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift index cd6f5961cd..82090f312a 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextInvitingContactMemberView.swift @@ -18,10 +18,9 @@ struct ContextInvitingContactMemberView: View { Text("Send direct message to connect") } .padding(12) - .frame(minHeight: 50) + .frame(minHeight: 54) .frame(maxWidth: .infinity, alignment: .leading) - .background(theme.appColors.sentMessage) - .padding(.top, 8) + .background(.thinMaterial) } } diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift index 3c89cbeb85..6245bbe21f 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextItemView.swift @@ -43,10 +43,9 @@ struct ContextItemView: View { .tint(theme.colors.primary) } .padding(12) - .frame(minHeight: 50) + .frame(minHeight: 54) .frame(maxWidth: .infinity) .background(chatItemFrameColor(contextItem, theme)) - .padding(.top, 8) } private func msgContentView(lines: Int) -> some View { diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift index a52cc7f71a..a720c3aaaf 100644 --- a/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift +++ b/apps/ios/Shared/Views/Chat/ComposeMessage/SendMessageView.swift @@ -44,6 +44,7 @@ struct SendMessageView: View { var body: some View { ZStack { + let composeShape = RoundedRectangle(cornerSize: CGSize(width: 20, height: 20)) HStack(alignment: .bottom) { ZStack(alignment: .leading) { if case .voicePreview = composeState.preview { @@ -84,10 +85,9 @@ struct SendMessageView: View { } } .padding(.vertical, 1) - .overlay( - RoundedRectangle(cornerSize: CGSize(width: 20, height: 20)) - .strokeBorder(.secondary, lineWidth: 0.3, antialiased: true) - ) + .background(theme.colors.background) + .clipShape(composeShape) + .overlay(composeShape.strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)) } .onChange(of: composeState.message, perform: { text in updateFont(text) }) .onChange(of: composeState.inProgress) { inProgress in diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index c04ac6ead9..3e4c3c9f6e 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -76,155 +76,153 @@ struct GroupMemberInfoView: View { private func groupMemberInfoView() -> some View { ZStack { - VStack { - let member = groupMember.wrapped - List { - groupMemberInfoHeader(member) - .listRowBackground(Color.clear) + let member = groupMember.wrapped + List { + groupMemberInfoHeader(member) + .listRowBackground(Color.clear) - if member.memberActive { - Section { - if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) { - knownDirectChatButton(chat) - } else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { - if let contactId = member.memberContactId { - newDirectChatButton(contactId) - } else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false { - createMemberContactButton() - } - } - if let code = connectionCode { verifyCodeButton(code) } - if let connStats = connectionStats, - connStats.ratchetSyncAllowed { - synchronizeConnectionButton() - } - // } else if developerTools { - // synchronizeConnectionButtonForce() - // } - } - } - - if let contactLink = member.contactLink { - Section { - SimpleXLinkQRCode(uri: contactLink) - Button { - showShareSheet(items: [simplexChatLink(contactLink)]) - } label: { - Label("Share address", systemImage: "square.and.arrow.up") - } + if member.memberActive { + Section { + if let contactId = member.memberContactId, let chat = knownDirectChat(contactId) { + knownDirectChatButton(chat) + } else if groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { if let contactId = member.memberContactId { - if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { - connectViaAddressButton(contactLink) - } - } else { + newDirectChatButton(contactId) + } else if member.activeConn?.peerChatVRange.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) ?? false { + createMemberContactButton() + } + } + if let code = connectionCode { verifyCodeButton(code) } + if let connStats = connectionStats, + connStats.ratchetSyncAllowed { + synchronizeConnectionButton() + } + // } else if developerTools { + // synchronizeConnectionButtonForce() + // } + } + } + + if let contactLink = member.contactLink { + Section { + SimpleXLinkQRCode(uri: contactLink) + Button { + showShareSheet(items: [simplexChatLink(contactLink)]) + } label: { + Label("Share address", systemImage: "square.and.arrow.up") + } + if let contactId = member.memberContactId { + if knownDirectChat(contactId) == nil && !groupInfo.fullGroupPreferences.directMessages.on(for: groupInfo.membership) { connectViaAddressButton(contactLink) } - } header: { - Text("Address") - .foregroundColor(theme.colors.secondary) - } footer: { - Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") - .foregroundColor(theme.colors.secondary) - } - } - - Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { - infoRow("Group", groupInfo.displayName) - - if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { - Picker("Change role", selection: $newRole) { - ForEach(roles) { role in - Text(role.text) - } - } - .frame(height: 36) } else { - infoRow("Role", member.memberRole.text) + connectViaAddressButton(contactLink) } + } header: { + Text("Address") + .foregroundColor(theme.colors.secondary) + } footer: { + Text("You can share this address with your contacts to let them connect with **\(member.displayName)**.") + .foregroundColor(theme.colors.secondary) } + } - if let connStats = connectionStats { - Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { - // TODO network connection status - Button("Change receiving address") { - alert = .switchAddressAlert + Section(header: Text("Member").foregroundColor(theme.colors.secondary)) { + infoRow("Group", groupInfo.displayName) + + if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { + Picker("Change role", selection: $newRole) { + ForEach(roles) { role in + Text(role.text) + } + } + .frame(height: 36) + } else { + infoRow("Role", member.memberRole.text) + } + } + + if let connStats = connectionStats { + Section(header: Text("Servers").foregroundColor(theme.colors.secondary)) { + // TODO network connection status + Button("Change receiving address") { + alert = .switchAddressAlert + } + .disabled( + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } + || connStats.ratchetSyncSendProhibited + ) + if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { + Button("Abort changing address") { + alert = .abortSwitchAddressAlert } .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil } + connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } || connStats.ratchetSyncSendProhibited ) - if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) { - Button("Abort changing address") { - alert = .abortSwitchAddressAlert - } - .disabled( - connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch } - || connStats.ratchetSyncSendProhibited - ) - } - smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) - smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } + smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer }, theme.colors.secondary) + smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer }, theme.colors.secondary) } + } - if groupInfo.membership.memberRole >= .admin { - adminDestructiveSection(member) - } else { - nonAdminBlockSection(member) - } + if groupInfo.membership.memberRole >= .admin { + adminDestructiveSection(member) + } else { + nonAdminBlockSection(member) + } - if developerTools { - Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { - infoRow("Local name", member.localDisplayName) - infoRow("Database ID", "\(member.groupMemberId)") - if let conn = member.activeConn { - let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) - infoRow("Connection", connLevelDesc) - } - Button ("Debug delivery") { - Task { - do { - let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) - await MainActor.run { alert = .queueInfo(info: info) } - } catch let e { - logger.error("apiContactQueueInfo error: \(responseError(e))") - let a = getErrorAlert(e, "Error") - await MainActor.run { alert = .error(title: a.title, error: a.message) } - } + if developerTools { + Section(header: Text("For console").foregroundColor(theme.colors.secondary)) { + infoRow("Local name", member.localDisplayName) + infoRow("Database ID", "\(member.groupMemberId)") + if let conn = member.activeConn { + let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) + infoRow("Connection", connLevelDesc) + } + Button ("Debug delivery") { + Task { + do { + let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) + await MainActor.run { alert = .queueInfo(info: info) } + } catch let e { + logger.error("apiContactQueueInfo error: \(responseError(e))") + let a = getErrorAlert(e, "Error") + await MainActor.run { alert = .error(title: a.title, error: a.message) } } } } } } - .navigationBarHidden(true) - .onAppear { - if #unavailable(iOS 16) { - // this condition prevents re-setting picker - if !justOpened { return } - } - justOpened = false - DispatchQueue.main.async { - newRole = member.memberRole - do { - let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) - let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) - _ = chatModel.upsertGroupMember(groupInfo, mem) - connectionStats = stats - connectionCode = code - } catch let error { - logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") - } + } + .navigationBarHidden(true) + .onAppear { + if #unavailable(iOS 16) { + // this condition prevents re-setting picker + if !justOpened { return } + } + justOpened = false + DispatchQueue.main.async { + newRole = member.memberRole + do { + let (_, stats) = try apiGroupMemberInfo(groupInfo.apiId, member.groupMemberId) + let (mem, code) = member.memberActive ? try apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId) : (member, nil) + _ = chatModel.upsertGroupMember(groupInfo, mem) + connectionStats = stats + connectionCode = code + } catch let error { + logger.error("apiGroupMemberInfo or apiGetGroupMemberCode error: \(responseError(error))") } } - .onChange(of: newRole) { newRole in - if newRole != member.memberRole { - alert = .changeMemberRoleAlert(mem: member, role: newRole) - } - } - .onChange(of: member.memberRole) { role in - newRole = role + } + .onChange(of: newRole) { newRole in + if newRole != member.memberRole { + alert = .changeMemberRoleAlert(mem: member, role: newRole) } } + .onChange(of: member.memberRole) { role in + newRole = role + } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .alert(item: $alert) { alertItem in switch(alertItem) { From c6e5d6e2b8bac02dcaf42e54405ba23fb22ea291 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Mon, 15 Jul 2024 20:21:18 +0700 Subject: [PATCH 28/57] android, desktop: chat list item layout (#4458) * android, desktop: chat list item layout * changes * changes * removed background * paddings * returned aligning --- .../common/views/chatlist/ChatListView.kt | 5 +- .../common/views/chatlist/ChatPreviewView.kt | 92 ++++++++++--------- .../views/chatlist/ContactConnectionView.kt | 32 ++++--- .../views/chatlist/ContactRequestView.kt | 33 +++---- .../common/views/chatlist/UserPicker.kt | 2 +- .../common/views/helpers/ChatInfoImage.kt | 3 +- .../simplex/common/views/helpers/Utils.kt | 6 ++ 7 files changed, 96 insertions(+), 77 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 5483b3f041..bf3e7774c7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow import androidx.compose.ui.focus.* import androidx.compose.ui.graphics.* import androidx.compose.ui.text.font.FontStyle @@ -95,7 +96,9 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf if (newChatSheetState.value.isVisible()) hideNewChatSheet(true) else showNewChatSheet() } }, - Modifier.padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp).size(AppBarHeight * fontSizeSqrtMultiplier), + Modifier + .padding(end = DEFAULT_PADDING - 16.dp + endPadding, bottom = DEFAULT_PADDING - 16.dp) + .size(AppBarHeight * fontSizeSqrtMultiplier), elevation = FloatingActionButtonDefaults.elevation( defaultElevation = 0.dp, pressedElevation = 0.dp, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index 0e215724f7..c42b7e3ec3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -25,6 +25,7 @@ import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* import chat.simplex.common.model.GroupInfo +import chat.simplex.common.platform.appPlatform import chat.simplex.common.platform.chatModel import chat.simplex.common.views.chat.item.markedDeletedText import chat.simplex.res.MR @@ -47,11 +48,10 @@ fun ChatPreviewView( @Composable fun inactiveIcon() { - val sp18 = with(LocalDensity.current) { 18.sp.toDp() } Icon( painterResource(MR.images.ic_cancel_filled), stringResource(MR.strings.icon_descr_group_inactive), - Modifier.size(sp18).background(MaterialTheme.colors.background, CircleShape), + Modifier.size(18.sp.toDp()).background(MaterialTheme.colors.background, CircleShape), tint = MaterialTheme.colors.secondary ) } @@ -88,8 +88,7 @@ fun ChatPreviewView( @Composable fun VerifiedIcon() { - val sp19 = with(LocalDensity.current) { 19.sp.toDp() } - Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(sp19).padding(end = 3.dp, top = 1.dp), tint = MaterialTheme.colors.secondary) + Icon(painterResource(MR.images.ic_verified_user), null, Modifier.size(19.sp.toDp()).padding(end = 3.sp.toDp(), top = 1.sp.toDp()), tint = MaterialTheme.colors.secondary) } fun messageDraft(draft: ComposeState, sp20: Dp): Pair Unit, Map> { @@ -193,7 +192,12 @@ fun ChatPreviewView( senderBold = true, maxLines = 2, overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.body1.copy(color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, lineHeight = 22.sp), + style = TextStyle( + fontFamily = Inter, + fontSize = 15.sp, + color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, + lineHeight = 21.sp + ), inlineContent = inlineTextContent, modifier = Modifier.fillMaxWidth(), ) @@ -223,11 +227,10 @@ fun ChatPreviewView( @Composable fun progressView() { - val sp15 = with(LocalDensity.current) { 15.sp.toDp() } CircularProgressIndicator( Modifier - .padding(horizontal = 2.dp) - .size(sp15), + .size(15.sp.toDp()) + .offset(y = 2.sp.toDp()), color = MaterialTheme.colors.secondary, strokeWidth = 1.5.dp ) @@ -235,7 +238,6 @@ fun ChatPreviewView( @Composable fun chatStatusImage() { - val sp19 = with(LocalDensity.current) { 19.sp.toDp() } if (cInfo is ChatInfo.Direct) { if (cInfo.contact.active && cInfo.contact.activeConn != null) { val descr = contactNetworkStatus?.statusString @@ -249,7 +251,8 @@ fun ChatPreviewView( contentDescription = descr, tint = MaterialTheme.colors.secondary, modifier = Modifier - .size(sp19) + .size(19.sp.toDp()) + .offset(x = 2.sp.toDp()) ) else -> @@ -272,18 +275,17 @@ fun ChatPreviewView( Row { Box(contentAlignment = Alignment.BottomEnd) { ChatInfoImage(cInfo, size = 72.dp * fontSizeSqrtMultiplier) - Box(Modifier.padding(end = 6.dp, bottom = 6.dp)) { + Box(Modifier.padding(end = 6.sp.toDp(), bottom = 6.sp.toDp())) { chatPreviewImageOverlayIcon() } } Column( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(start = 8.dp, end = 8.sp.toDp()) .weight(1F) ) { chatPreviewTitle() - val height = with(LocalDensity.current) { 46.sp.toDp() } - Row(Modifier.heightIn(min = height)) { + Row(Modifier.heightIn(min = 46.sp.toDp()).padding(top = 3.sp.toDp())) { chatPreviewText() } } @@ -292,67 +294,50 @@ fun ChatPreviewView( contentAlignment = Alignment.TopEnd ) { val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.chatTs) - Text( - ts, - color = MaterialTheme.colors.secondary, - style = MaterialTheme.typography.body2, - modifier = Modifier.padding(bottom = 5.dp) - ) + ChatListTimestampView(ts) val n = chat.chatStats.unreadCount val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group) - val sp17 = with(LocalDensity.current) { 17.sp.toDp() } - val sp21 = with(LocalDensity.current) { 21.sp.toDp() } - val sp23 = with(LocalDensity.current) { 23.sp.toDp() } - val sp46 = with(LocalDensity.current) { 46.sp.toDp() } if (n > 0 || chat.chatStats.unreadChat) { Box( - Modifier.padding(top = sp23, end = with(LocalDensity.current) { 3.sp.toDp() }), - contentAlignment = Alignment.Center - ) { + Modifier.padding(top = 24.5.sp.toDp())) { Text( if (n > 0) unreadCountStr(n) else "", color = Color.White, - fontSize = 11.sp, + fontSize = 10.sp, modifier = Modifier .background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape) .badgeLayout() - .padding(horizontal = 3.dp) - .padding(vertical = 1.dp) + .padding(horizontal = 3.sp.toDp()) + .padding(vertical = 1.sp.toDp()) ) } } else if (showNtfsIcon) { Box( - Modifier.padding(top = sp21), - contentAlignment = Alignment.Center - ) { + Modifier.padding(top = 22.sp.toDp())) { Icon( painterResource(MR.images.ic_notifications_off_filled), contentDescription = generalGetString(MR.strings.notifications), tint = MaterialTheme.colors.secondary, modifier = Modifier - .padding(horizontal = 3.dp) - .padding(vertical = 1.dp) - .size(sp17) + .size(17.sp.toDp()) + .offset(x = 2.5.sp.toDp()) ) } } else if (chat.chatInfo.chatSettings?.favorite == true) { Box( - Modifier.padding(top = sp21), - contentAlignment = Alignment.Center - ) { + Modifier.padding(top = 20.sp.toDp())) { Icon( painterResource(MR.images.ic_star_filled), contentDescription = generalGetString(MR.strings.favorite_chat), tint = MaterialTheme.colors.secondary, modifier = Modifier - .padding(horizontal = 3.dp) - .padding(vertical = 1.dp) - .size(sp17) + .size(20.sp.toDp()) + .offset(x = 2.5.sp.toDp()) ) } } Box( - Modifier.padding(top = sp46), + Modifier.padding(top = 46.sp.toDp()), contentAlignment = Alignment.Center ) { chatStatusImage() @@ -364,13 +349,13 @@ fun ChatPreviewView( @Composable fun IncognitoIcon(incognito: Boolean) { if (incognito) { - val sp21 = with(LocalDensity.current) { 21.sp.toDp() } Icon( painterResource(MR.images.ic_theater_comedy), contentDescription = null, tint = MaterialTheme.colors.secondary, modifier = Modifier - .size(sp21) + .size(21.sp.toDp()) + .offset(x = 1.sp.toDp()) ) } } @@ -388,6 +373,23 @@ fun unreadCountStr(n: Int): String { return if (n < 1000) "$n" else "${n / 1000}" + stringResource(MR.strings.thousand_abbreviation) } +@Composable fun ChatListTimestampView(ts: String) { + Box(contentAlignment = Alignment.BottomStart) { + // This should be the same font style as in title to make date located on the same line as title + Text( + " ", + style = MaterialTheme.typography.h3, + fontWeight = FontWeight.Bold, + ) + Text( + ts, + Modifier.padding(bottom = 5.sp.toDp()).offset(x = if (appPlatform.isDesktop) 1.5.sp.toDp() else 0.dp), + color = MaterialTheme.colors.secondary, + style = MaterialTheme.typography.body2.copy(fontSize = 13.sp), + ) + } +} + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt index 99d6c5db15..349f1f12d4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactConnectionView.kt @@ -1,5 +1,6 @@ package chat.simplex.common.views.chatlist +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material.MaterialTheme import androidx.compose.material.Text @@ -7,25 +8,26 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.ProfileImage import chat.simplex.common.model.PendingContactConnection import chat.simplex.common.model.getTimestampText +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable fun ContactConnectionView(contactConnection: PendingContactConnection) { Row { - Box(Modifier.size(72.dp), contentAlignment = Alignment.Center) { - ProfileImage(size = 54.dp, null, if (contactConnection.initiated) MR.images.ic_add_link else MR.images.ic_link) + Box(Modifier.size(72.dp * fontSizeSqrtMultiplier), contentAlignment = Alignment.Center) { + ProfileImage(size = 54.dp * fontSizeSqrtMultiplier, null, if (contactConnection.initiated) MR.images.ic_add_link else MR.images.ic_link) } Column( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(start = 8.dp / fontSizeSqrtMultiplier, end = 8.sp.toDp()) .weight(1F) ) { Text( @@ -36,21 +38,25 @@ fun ContactConnectionView(contactConnection: PendingContactConnection) { fontWeight = FontWeight.Bold, color = MaterialTheme.colors.secondary ) - val height = with(LocalDensity.current) { 46.sp.toDp() } - Text(contactConnection.description, Modifier.heightIn(min = height), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight) + Text( + contactConnection.description, + Modifier.heightIn(min = 46.sp.toDp()).padding(top = 3.sp.toDp()), + maxLines = 2, + style = TextStyle( + fontFamily = Inter, + fontSize = 15.sp, + color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, + lineHeight = 21.sp + ) + ) } Box( contentAlignment = Alignment.TopEnd ) { val ts = getTimestampText(contactConnection.updatedAt) - Text( - ts, - color = MaterialTheme.colors.secondary, - style = MaterialTheme.typography.body2, - modifier = Modifier.padding(bottom = 5.dp) - ) + ChatListTimestampView(ts) Box( - Modifier.padding(top = 50.dp), + Modifier.padding(top = 50.sp.toDp()), contentAlignment = Alignment.Center ) { IncognitoIcon(contactConnection.incognito) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt index 8debcce98c..901761f65c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ContactRequestView.kt @@ -6,24 +6,25 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.ChatInfoImage import chat.simplex.common.model.ChatInfo import chat.simplex.common.model.getTimestampText +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @Composable fun ContactRequestView(contactRequest: ChatInfo.ContactRequest) { Row { - ChatInfoImage(contactRequest, size = 72.dp) + ChatInfoImage(contactRequest, size = 72.dp * fontSizeSqrtMultiplier) Column( modifier = Modifier - .padding(horizontal = 8.dp) + .padding(start = 8.dp, end = 8.sp.toDp()) .weight(1F) ) { Text( @@ -32,21 +33,21 @@ fun ContactRequestView(contactRequest: ChatInfo.ContactRequest) { overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.h3, fontWeight = FontWeight.Bold, - color = MaterialTheme.colors.primary + color = MaterialTheme.colors.primary, + ) + Text( + stringResource(MR.strings.contact_wants_to_connect_with_you), + Modifier.heightIn(min = 46.sp.toDp()).padding(top = 3.sp.toDp()), + maxLines = 2, + style = TextStyle( + fontFamily = Inter, + fontSize = 15.sp, + color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight, + lineHeight = 21.sp + ) ) - val height = with(LocalDensity.current) { 46.sp.toDp() } - Text(stringResource(MR.strings.contact_wants_to_connect_with_you), Modifier.heightIn(min = height), maxLines = 2, color = if (isInDarkTheme()) MessagePreviewDark else MessagePreviewLight) } val ts = getTimestampText(contactRequest.contactRequest.updatedAt) - Column( - Modifier.fillMaxHeight(), - ) { - Text( - ts, - color = MaterialTheme.colors.secondary, - style = MaterialTheme.typography.body2, - modifier = Modifier.padding(bottom = 5.dp) - ) - } + ChatListTimestampView(ts) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 8199a3f2d6..f79ff441d3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -284,7 +284,7 @@ fun UserProfilePickerItem( Text( unreadCountStr(unreadCount), color = Color.White, - fontSize = 11.sp, + fontSize = 10.sp, modifier = Modifier .background(MaterialTheme.colors.primaryVariant, shape = CircleShape) .padding(2.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt index 3d57f0f8b7..ea692a9724 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale @@ -27,7 +28,7 @@ import dev.icerock.moko.resources.ImageResource import kotlin.math.max @Composable -fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) { +fun ChatInfoImage(chatInfo: ChatInfo, size: Dp, iconColor: Color = MaterialTheme.colors.secondaryVariant, shadow: Boolean = false) { val icon = when (chatInfo) { is ChatInfo.Group -> MR.images.ic_supervised_user_circle_filled diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index ccef86c343..cc4194d0c8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -529,6 +529,12 @@ val fontSizeSqrtMultiplier: Float val desktopDensityScaleMultiplier: Float @Composable get() = if (appPlatform.isDesktop) remember { appPrefs.densityScale.state }.value else 1f +@Composable +fun TextUnit.toDp(): Dp { + check(type == TextUnitType.Sp) { "Only Sp can convert to Px" } + return Dp(value * LocalDensity.current.fontScale) +} + @Composable fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) { DisposableEffect(Unit) { From bf594028566c48707655e8fe8a576d4a65e9b49a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 15 Jul 2024 23:04:09 +0100 Subject: [PATCH 29/57] cli: option to only use SOCKS5 proxy with onion-only relays (#4459) * cli: option to only use SOCKS5 proxy with onion-only relays * simplexmq * show socks mode in CLI, fix to use correct network config --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 19 ++++++++++--------- src/Simplex/Chat/Controller.hs | 5 +++-- src/Simplex/Chat/Options.hs | 17 +++++++++++++---- src/Simplex/Chat/Terminal.hs | 8 ++++++-- src/Simplex/Chat/Terminal/Main.hs | 16 ++++++++-------- src/Simplex/Chat/View.hs | 8 ++++---- 8 files changed, 46 insertions(+), 31 deletions(-) diff --git a/cabal.project b/cabal.project index bcaf9f2abd..cc63f998af 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: e56bd0b47b22781f9b5936e3b8a9d51cc3f9e8d5 + tag: 784d36d691381290e72884cc30c59ce646166134 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 195589ae83..a50762d6ff 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."e56bd0b47b22781f9b5936e3b8a9d51cc3f9e8d5" = "11ghsddgyrlnddxqvvb0xlqvnnhnnsbixz1kg01x3gd6vpdqkz47"; + "https://github.com/simplex-chat/simplexmq.git"."784d36d691381290e72884cc30c59ce646166134" = "17qg39xfsn8fxajylpggxwb916013m1xgpz1kpd4qfy5rdpn6gp3"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 8f5295705b..d31c2cdeca 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -103,7 +103,7 @@ import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), Migrati import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations -import Simplex.Messaging.Client (NetworkConfig (..), ProxyClientError (..), defaultNetworkConfig) +import Simplex.Messaging.Client (NetworkConfig (..), ProxyClientError (..), SocksMode (SMAlways), defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -314,10 +314,10 @@ newChatController where configServers :: DefaultAgentServers configServers = - let DefaultAgentServers {smp = defSmp, xftp = defXftp} = defaultServers + let DefaultAgentServers {smp = defSmp, xftp = defXftp, netCfg} = defaultServers smp' = maybe defSmp (L.map enabledServerCfg) (nonEmpty smpServers) xftp' = maybe defXftp (L.map enabledServerCfg) (nonEmpty xftpServers) - in defaultServers {smp = smp', xftp = xftp', netCfg = updateNetworkConfig defaultNetworkConfig simpleNetCfg} + in defaultServers {smp = smp', xftp = xftp', netCfg = updateNetworkConfig netCfg simpleNetCfg} agentServers :: ChatConfig -> IO InitialAgentServers agentServers config@ChatConfig {defaultServers = defServers@DefaultAgentServers {ntf, netCfg}} = do users <- withTransaction chatStore getUsers @@ -336,11 +336,11 @@ newChatController userServers user' = useServers config protocol <$> withTransaction chatStore (`getProtocolServers` user') updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig -updateNetworkConfig cfg SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} = +updateNetworkConfig cfg SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} = let cfg1 = maybe cfg (\smpProxyMode -> cfg {smpProxyMode}) smpProxyMode_ cfg2 = maybe cfg1 (\smpProxyFallback -> cfg1 {smpProxyFallback}) smpProxyFallback_ cfg3 = maybe cfg2 (\tcpTimeout -> cfg2 {tcpTimeout, tcpConnectTimeout = (tcpTimeout * 3) `div` 2}) tcpTimeout_ - in cfg3 {socksProxy, logTLSErrors} + in cfg3 {socksProxy, socksMode, logTLSErrors} withChatLock :: String -> CM a -> CM a withChatLock name action = asks chatLock >>= \l -> withLock l name action @@ -1360,9 +1360,9 @@ processChatCommand' vr = \case APISetNetworkConfig cfg -> withUser' $ \_ -> lift (withAgent' (`setNetworkConfig` cfg)) >> ok_ APIGetNetworkConfig -> withUser' $ \_ -> CRNetworkConfig <$> lift getNetworkConfig - SetNetworkConfig netCfg -> do - cfg <- lift getNetworkConfig - void . processChatCommand $ APISetNetworkConfig $ updateNetworkConfig cfg netCfg + SetNetworkConfig simpleNetCfg -> do + cfg <- (`updateNetworkConfig` simpleNetCfg) <$> lift getNetworkConfig + void . processChatCommand $ APISetNetworkConfig cfg pure $ CRNetworkConfig cfg APISetNetworkInfo info -> lift (withAgent' (`setUserNetworkInfo` info)) >> ok_ ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_ @@ -7770,12 +7770,13 @@ chatCommandP = <|> ("no" $> TMEDisableKeepTTL) netCfgP = do socksProxy <- "socks=" *> ("off" $> Nothing <|> "on" $> Just defaultSocksProxy <|> Just <$> strP) + socksMode <- " socks-mode=" *> strP <|> pure SMAlways smpProxyMode_ <- optional $ " smp-proxy=" *> strP smpProxyFallback_ <- optional $ " smp-proxy-fallback=" *> strP t_ <- optional $ " timeout=" *> A.decimal logTLSErrors <- " log=" *> onOffP <|> pure False let tcpTimeout_ = (1000000 *) <$> t_ - pure $ SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} + pure $ SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} dbKeyP = nonEmptyKey <$?> strP nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 6c127264d2..e08c8a287a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -76,7 +76,7 @@ import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB -import Simplex.Messaging.Client (SMPProxyFallback (..), SMPProxyMode (..)) +import Simplex.Messaging.Client (SMPProxyFallback (..), SMPProxyMode (..), SocksMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -966,6 +966,7 @@ data AppFilePathsConfig = AppFilePathsConfig data SimpleNetCfg = SimpleNetCfg { socksProxy :: Maybe SocksProxy, + socksMode :: SocksMode, smpProxyMode_ :: Maybe SMPProxyMode, smpProxyFallback_ :: Maybe SMPProxyFallback, tcpTimeout_ :: Maybe Int, @@ -974,7 +975,7 @@ data SimpleNetCfg = SimpleNetCfg deriving (Show) defaultSimpleNetCfg :: SimpleNetCfg -defaultSimpleNetCfg = SimpleNetCfg Nothing Nothing Nothing Nothing False +defaultSimpleNetCfg = SimpleNetCfg Nothing SMAlways Nothing Nothing Nothing False data ContactSubStatus = ContactSubStatus { contact :: Contact, diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index 14e5603976..0b5961b042 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -27,6 +27,7 @@ import Numeric.Natural (Natural) import Options.Applicative import Simplex.Chat.Controller (ChatLogLevel (..), SimpleNetCfg (..), updateStr, versionNumber, versionString) import Simplex.FileTransfer.Description (mb) +import Simplex.Messaging.Client (SocksMode (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth) @@ -130,21 +131,29 @@ coreChatOptsP appDir defaultDbFileName = do <> help "Use SOCKS5 proxy at `ipv4:port` or `:port`" <> value Nothing ) + socksMode <- + option + strParse + ( long "socks-mode" + <> metavar "SOCKS_MODE" + <> help "Use SOCKS5 proxy: always (default), onion (with onion-only relays)" + <> value SMAlways + ) smpProxyMode_ <- optional $ option strParse ( long "smp-proxy" <> metavar "SMP_PROXY_MODE" - <> help "Use private message routing: always, unknown, unprotected, never (default)" - ) + <> help "Use private message routing: always, unknown (default), unprotected, never" + ) smpProxyFallback_ <- optional $ option strParse ( long "smp-proxy-fallback" <> metavar "SMP_PROXY_FALLBACK_MODE" - <> help "Allow downgrade and connect directly: no, [when IP address is] protected, yes (default)" + <> help "Allow downgrade and connect directly: no, [when IP address is] protected (default), yes" ) t <- option @@ -217,7 +226,7 @@ coreChatOptsP appDir defaultDbFileName = do dbKey, smpServers, xftpServers, - simpleNetCfg = SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_ = Just $ useTcpTimeout socksProxy t, logTLSErrors}, + simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_, tcpTimeout_ = Just $ useTcpTimeout socksProxy t, logTLSErrors}, logLevel, logConnections = logConnections || logLevel <= CLLInfo, logServerHosts = logServerHosts || logLevel <= CLLInfo, diff --git a/src/Simplex/Chat/Terminal.hs b/src/Simplex/Chat/Terminal.hs index ca9b2edb65..eb11bb0205 100644 --- a/src/Simplex/Chat/Terminal.hs +++ b/src/Simplex/Chat/Terminal.hs @@ -22,7 +22,7 @@ import Simplex.Chat.Terminal.Input import Simplex.Chat.Terminal.Output import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.Messaging.Agent.Env.SQLite (presetServerCfg) -import Simplex.Messaging.Client (defaultNetworkConfig) +import Simplex.Messaging.Client (NetworkConfig (..), SMPProxyFallback (..), SMPProxyMode (..), defaultNetworkConfig) import Simplex.Messaging.Util (raceAny_) import System.IO (hFlush, hSetEcho, stdin, stdout) @@ -41,7 +41,11 @@ terminalChatConfig = ], ntf = ["ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,ntg7jdjy2i3qbib3sykiho3enekwiaqg3icctliqhtqcg6jmoh6cxiad.onion"], xftp = L.map (presetServerCfg True) defaultXFTPServers, - netCfg = defaultNetworkConfig + netCfg = + defaultNetworkConfig + { smpProxyMode = SPMUnknown, + smpProxyFallback = SPFAllowProtected + } }, deviceNameForRemote = "SimpleX CLI" } diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index 3e7d933669..5d684d7283 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -10,12 +10,12 @@ import Data.Maybe (fromMaybe) import Data.Time.Clock (getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Network.Socket -import Simplex.Chat.Controller (ChatConfig, ChatController (..), ChatResponse (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) +import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatResponse (..), DefaultAgentServers (DefaultAgentServers, netCfg), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) import Simplex.Chat.Core import Simplex.Chat.Options import Simplex.Chat.Terminal import Simplex.Chat.View (serializeChatResponse, smpProxyModeStr) -import Simplex.Messaging.Client (NetworkConfig (..), defaultNetworkConfig) +import Simplex.Messaging.Client (NetworkConfig (..), SocksMode (..)) import System.Directory (getAppUserDataDirectory) import System.Exit (exitFailure) import System.Terminal (withTerminal) @@ -33,7 +33,7 @@ simplexChatCLI cfg server_ = do else simplexChatCore cfg opts $ runCommand opts where runCLI opts = do - welcome opts + welcome cfg opts t <- withTerminal pure simplexChatTerminal cfg opts t runCommand ChatOpts {chatCmd, chatCmdLog, chatCmdDelay} user cc = do @@ -51,18 +51,18 @@ simplexChatCLI cfg server_ = do rh <- readTVarIO $ currentRemoteHost cc putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r -welcome :: ChatOpts -> IO () -welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, simpleNetCfg = SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_}}} = +welcome :: ChatConfig -> ChatOpts -> IO () +welcome ChatConfig {defaultServers = DefaultAgentServers {netCfg}} ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, simpleNetCfg = SimpleNetCfg {socksProxy, socksMode, smpProxyMode_, smpProxyFallback_}}} = mapM_ putStrLn [ versionString versionNumber, "db: " <> dbFilePrefix <> "_chat.db, " <> dbFilePrefix <> "_agent.db", maybe "direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050" - (("using SOCKS5 proxy " <>) . show) + ((\sp -> "using SOCKS5 proxy " <> sp <> if socksMode == SMOnion then " for onion servers ONLY." else " for ALL servers.") . show) socksProxy, smpProxyModeStr - (fromMaybe (smpProxyMode defaultNetworkConfig) smpProxyMode_) - (fromMaybe (smpProxyFallback defaultNetworkConfig) smpProxyFallback_), + (fromMaybe (smpProxyMode netCfg) smpProxyMode_) + (fromMaybe (smpProxyFallback netCfg) smpProxyFallback_), "type \"/help\" or \"/h\" for usage info" ] diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 4dffa41dea..b0d3032ed3 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -55,7 +55,7 @@ import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestSte import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..), ServerCfg (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) -import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..)) +import Simplex.Messaging.Client (SMPProxyFallback, SMPProxyMode (..), SocksMode (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.Ratchet as CR @@ -1217,11 +1217,11 @@ viewChatItemTTL = \case deletedAfter ttlStr = ["old messages are set to be deleted after: " <> ttlStr] viewNetworkConfig :: NetworkConfig -> [StyledString] -viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout, smpProxyMode, smpProxyFallback} = - [ plain $ maybe "direct network connection" (("using SOCKS5 proxy " <>) . show) socksProxy, +viewNetworkConfig NetworkConfig {socksProxy, socksMode, tcpTimeout, smpProxyMode, smpProxyFallback} = + [ plain $ maybe "direct network connection" ((\sp -> "using SOCKS5 proxy " <> sp <> if socksMode == SMOnion then " for onion servers ONLY." else " for ALL servers.") . show) socksProxy, "TCP timeout: " <> sShow tcpTimeout, plain $ smpProxyModeStr smpProxyMode smpProxyFallback, - "use " <> highlight' "/network socks=[ timeout=][ smp-proxy=always/unknown/unprotected/never][ smp-proxy-fallback=no/protected/yes]" <> " to change settings" + "use " <> highlight' "/network socks=[ socks-mode=always/onion][ smp-proxy=always/unknown/unprotected/never][ smp-proxy-fallback=no/protected/yes][ timeout=]" <> " to change settings" ] smpProxyModeStr :: SMPProxyMode -> SMPProxyFallback -> String From 7138b09baefad8f391e17ea544c06245f3645125 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 15 Jul 2024 23:06:11 +0100 Subject: [PATCH 30/57] core: 6.0.0.1 (simplexmq 6.0.0.2) --- cabal.project | 2 +- package.yaml | 2 +- scripts/nix/sha256map.nix | 2 +- simplex-chat.cabal | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cabal.project b/cabal.project index cc63f998af..e7e30afb3c 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 784d36d691381290e72884cc30c59ce646166134 + tag: b40d55c358ebabeb6cf1eca8e64016e2bae09a15 source-repository-package type: git diff --git a/package.yaml b/package.yaml index 82ab5a7447..1c9b372b20 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 6.0.0.0 +version: 6.0.0.1 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index a50762d6ff..6eaf36e709 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."784d36d691381290e72884cc30c59ce646166134" = "17qg39xfsn8fxajylpggxwb916013m1xgpz1kpd4qfy5rdpn6gp3"; + "https://github.com/simplex-chat/simplexmq.git"."b40d55c358ebabeb6cf1eca8e64016e2bae09a15" = "1ppn2yvml12yr5k5d6hjn7r7xy6a83ig9mys49jzzk034rzwxcd2"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index ee154eceac..a05608c5ad 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 6.0.0.0 +version: 6.0.0.1 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat From b4ae3b943181a1080db8229825e73645d0925913 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 15 Jul 2024 23:13:33 +0100 Subject: [PATCH 31/57] ui: enable private message routing by default for all unknown servers (#4461) --- apps/ios/SimpleXChat/APITypes.swift | 26 +++++---------- .../chat/simplex/common/model/SimpleXAPI.kt | 32 ++++++------------- 2 files changed, 17 insertions(+), 41 deletions(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index d5cd9c8f3b..81b638d7be 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1287,42 +1287,32 @@ public struct NetCfg: Codable, Equatable, Hashable { var socksMode: SocksMode = .always public var hostMode: HostMode = .publicHost public var requiredHostMode = true - public var sessionMode: TransportSessionMode - public var smpProxyMode: SMPProxyMode = .never - public var smpProxyFallback: SMPProxyFallback = .allow + public var sessionMode = TransportSessionMode.user + public var smpProxyMode: SMPProxyMode = .unknown + public var smpProxyFallback: SMPProxyFallback = .allowProtected public var tcpConnectTimeout: Int // microseconds public var tcpTimeout: Int // microseconds public var tcpTimeoutPerKb: Int // microseconds public var rcvConcurrency: Int // pool size - public var tcpKeepAlive: KeepAliveOpts? + public var tcpKeepAlive: KeepAliveOpts? = KeepAliveOpts.defaults public var smpPingInterval: Int // microseconds - public var smpPingCount: Int // times - public var logTLSErrors: Bool + public var smpPingCount: Int = 3 // times + public var logTLSErrors: Bool = false public static let defaults: NetCfg = NetCfg( - socksProxy: nil, - sessionMode: TransportSessionMode.user, tcpConnectTimeout: 25_000_000, tcpTimeout: 15_000_000, tcpTimeoutPerKb: 10_000, rcvConcurrency: 12, - tcpKeepAlive: KeepAliveOpts.defaults, - smpPingInterval: 1200_000_000, - smpPingCount: 3, - logTLSErrors: false + smpPingInterval: 1200_000_000 ) public static let proxyDefaults: NetCfg = NetCfg( - socksProxy: nil, - sessionMode: TransportSessionMode.user, tcpConnectTimeout: 35_000_000, tcpTimeout: 20_000_000, tcpTimeoutPerKb: 15_000, rcvConcurrency: 8, - tcpKeepAlive: KeepAliveOpts.defaults, - smpPingInterval: 1200_000_000, - smpPingCount: 3, - logTLSErrors: false + smpPingInterval: 1200_000_000 ) public var enableKeepAlive: Bool { tcpKeepAlive != nil } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index d259aa65c8..7648770afa 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -3306,18 +3306,18 @@ data class ParsedServerAddress ( data class NetCfg( val socksProxy: String?, val socksMode: SocksMode = SocksMode.Always, - val hostMode: HostMode, - val requiredHostMode: Boolean, - val sessionMode: TransportSessionMode, - val smpProxyMode: SMPProxyMode, - val smpProxyFallback: SMPProxyFallback, + val hostMode: HostMode = HostMode.OnionViaSocks, + val requiredHostMode: Boolean = false, + val sessionMode: TransportSessionMode = TransportSessionMode.User, + val smpProxyMode: SMPProxyMode = SMPProxyMode.Unknown, + val smpProxyFallback: SMPProxyFallback = SMPProxyFallback.AllowProtected, val tcpConnectTimeout: Long, // microseconds val tcpTimeout: Long, // microseconds val tcpTimeoutPerKb: Long, // microseconds val rcvConcurrency: Int, // pool size - val tcpKeepAlive: KeepAliveOpts?, + val tcpKeepAlive: KeepAliveOpts? = KeepAliveOpts.defaults, val smpPingInterval: Long, // microseconds - val smpPingCount: Int, + val smpPingCount: Int = 3, val logTLSErrors: Boolean = false, ) { val useSocksProxy: Boolean get() = socksProxy != null @@ -3336,35 +3336,21 @@ data class NetCfg( val defaults: NetCfg = NetCfg( socksProxy = null, - hostMode = HostMode.OnionViaSocks, - requiredHostMode = false, - sessionMode = TransportSessionMode.User, - smpProxyMode = SMPProxyMode.Never, - smpProxyFallback = SMPProxyFallback.Allow, tcpConnectTimeout = 25_000_000, tcpTimeout = 15_000_000, tcpTimeoutPerKb = 10_000, rcvConcurrency = 12, - tcpKeepAlive = KeepAliveOpts.defaults, - smpPingInterval = 1200_000_000, - smpPingCount = 3 + smpPingInterval = 1200_000_000 ) val proxyDefaults: NetCfg = NetCfg( socksProxy = ":9050", - hostMode = HostMode.OnionViaSocks, - requiredHostMode = false, - sessionMode = TransportSessionMode.User, - smpProxyMode = SMPProxyMode.Never, - smpProxyFallback = SMPProxyFallback.Allow, tcpConnectTimeout = 35_000_000, tcpTimeout = 20_000_000, tcpTimeoutPerKb = 15_000, rcvConcurrency = 8, - tcpKeepAlive = KeepAliveOpts.defaults, - smpPingInterval = 1200_000_000, - smpPingCount = 3 + smpPingInterval = 1200_000_000 ) } From c7017b3efc9da40d1687f8d72edd6b99661be07c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 16 Jul 2024 09:52:55 +0100 Subject: [PATCH 32/57] 6.0-beta.1: ios 228, android 226, desktop 57 --- apps/ios/SimpleX.xcodeproj/project.pbxproj | 52 +++++++++++----------- apps/multiplatform/gradle.properties | 8 ++-- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index dba8db792a..5f209793f5 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -204,12 +204,12 @@ D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; }; D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; - E50581002C3DDD7F009C3F71 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FB2C3DDD7F009C3F71 /* libffi.a */; }; - E50581012C3DDD7F009C3F71 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FC2C3DDD7F009C3F71 /* libgmp.a */; }; - E50581022C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FD2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a */; }; - E50581032C3DDD7F009C3F71 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FE2C3DDD7F009C3F71 /* libgmpxx.a */; }; - E50581042C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50580FF2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a */; }; E50581062C3DDD9D009C3F71 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = E50581052C3DDD9D009C3F71 /* Yams */; }; + E505810C2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50581072C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a */; }; + E505810D2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50581082C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a */; }; + E505810E2C4656A4009C3F71 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50581092C4656A4009C3F71 /* libgmpxx.a */; }; + E505810F2C4656A4009C3F71 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E505810A2C4656A4009C3F71 /* libffi.a */; }; + E50581102C4656A4009C3F71 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E505810B2C4656A4009C3F71 /* libgmp.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -510,11 +510,11 @@ D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - E50580FB2C3DDD7F009C3F71 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E50580FC2C3DDD7F009C3F71 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - E50580FD2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a"; sourceTree = ""; }; - E50580FE2C3DDD7F009C3F71 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E50580FF2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a"; sourceTree = ""; }; + E50581072C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a"; sourceTree = ""; }; + E50581082C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a"; sourceTree = ""; }; + E50581092C4656A4009C3F71 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + E505810A2C4656A4009C3F71 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + E505810B2C4656A4009C3F71 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -553,15 +553,15 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E50581032C3DDD7F009C3F71 /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E50581002C3DDD7F009C3F71 /* libffi.a in Frameworks */, - E50581012C3DDD7F009C3F71 /* libgmp.a in Frameworks */, + E50581102C4656A4009C3F71 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E50581022C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a in Frameworks */, + E505810C2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a in Frameworks */, + E505810F2C4656A4009C3F71 /* libffi.a in Frameworks */, E50581062C3DDD9D009C3F71 /* Yams in Frameworks */, - E50581042C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a in Frameworks */, + E505810D2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, + E505810E2C4656A4009C3F71 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -628,11 +628,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E50580FB2C3DDD7F009C3F71 /* libffi.a */, - E50580FC2C3DDD7F009C3F71 /* libgmp.a */, - E50580FE2C3DDD7F009C3F71 /* libgmpxx.a */, - E50580FD2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN-ghc9.6.3.a */, - E50580FF2C3DDD7F009C3F71 /* libHSsimplex-chat-6.0.0.0-IhofDzGnTMcDdW5i3Fb7xN.a */, + E505810A2C4656A4009C3F71 /* libffi.a */, + E505810B2C4656A4009C3F71 /* libgmp.a */, + E50581092C4656A4009C3F71 /* libgmpxx.a */, + E50581072C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a */, + E50581082C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a */, ); path = Libraries; sourceTree = ""; @@ -1618,7 +1618,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 227; + CURRENT_PROJECT_VERSION = 228; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1667,7 +1667,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 227; + CURRENT_PROJECT_VERSION = 228; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1753,7 +1753,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 227; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -1790,7 +1790,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 227; + CURRENT_PROJECT_VERSION = 228; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -1827,7 +1827,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 227; + CURRENT_PROJECT_VERSION = 228; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1878,7 +1878,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 227; + CURRENT_PROJECT_VERSION = 228; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 2923c39742..35bc671ccf 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=6.0-beta.0 -android.version_code=225 +android.version_name=6.0-beta.1 +android.version_code=226 -desktop.version_name=6.0-beta.0 -desktop.version_code=56 +desktop.version_name=6.0-beta.1 +desktop.version_code=57 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 From 4251762553e3845e77777c575e66982fcc73904c Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:19:37 +0400 Subject: [PATCH 33/57] ios: better errors (#4462) --- apps/ios/Shared/Model/SimpleXAPI.swift | 34 +++++++++---------- .../Views/Migration/MigrateFromDevice.swift | 2 +- apps/ios/SimpleXChat/API.swift | 22 ++++++++---- apps/ios/SimpleXChat/APITypes.swift | 4 +-- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index dd0610e48c..fff1d6d497 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -397,7 +397,7 @@ private func sendMessageErrorAlert(_ r: ChatResponse) { logger.error("send message error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error sending message", - message: "Error: \(String(describing: r))" + message: "Error: \(responseError(r))" ) } @@ -405,7 +405,7 @@ private func createChatItemErrorAlert(_ r: ChatResponse) { logger.error("apiCreateChatItem error: \(String(describing: r))") AlertManager.shared.showAlertMsg( title: "Error creating message", - message: "Error: \(String(describing: r))" + message: "Error: \(responseError(r))" ) } @@ -699,7 +699,7 @@ func apiConnect_(incognito: Bool, connReq: String) async -> ((ConnReqType, Pendi message: "Please check that you used the correct link or ask your contact to send you another one." ) return (nil, alert) - case .chatCmdError(_, .errorAgent(.SMP(.AUTH))): + case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): let alert = mkAlert( title: "Connection error (AUTH)", message: "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." @@ -732,7 +732,7 @@ private func connectionErrorAlert(_ r: ChatResponse) -> Alert { } else { return mkAlert( title: "Connection error", - message: "Error: \(String(describing: r))" + message: "Error: \(responseError(r))" ) } } @@ -898,7 +898,7 @@ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Cont let am = AlertManager.shared if case let .acceptingContactRequest(_, contact) = r { return contact } - if case .chatCmdError(_, .errorAgent(.SMP(.AUTH))) = r { + if case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))) = r { am.showAlertMsg( title: "Connection error (AUTH)", message: "Sender may have deleted the connection request." @@ -909,7 +909,7 @@ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Cont logger.error("apiAcceptContactRequest error: \(String(describing: r))") am.showAlertMsg( title: "Error accepting contact request", - message: "Error: \(String(describing: r))" + message: "Error: \(responseError(r))" ) } return nil @@ -935,7 +935,7 @@ func uploadStandaloneFile(user: any UserLike, file: CryptoFile, ctrl: chat_ctrl? return (fileTransferMeta, nil) } else { logger.error("uploadStandaloneFile error: \(String(describing: r))") - return (nil, String(describing: r)) + return (nil, responseError(r)) } } @@ -945,7 +945,7 @@ func downloadStandaloneFile(user: any UserLike, url: String, file: CryptoFile, c return (rcvFileTransfer, nil) } else { logger.error("downloadStandaloneFile error: \(String(describing: r))") - return (nil, String(describing: r)) + return (nil, responseError(r)) } } @@ -1025,7 +1025,7 @@ func apiReceiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool, in if !auto { am.showAlertMsg( title: "Error receiving file", - message: "Error: \(String(describing: r))" + message: "Error: \(responseError(r))" ) } } @@ -1106,16 +1106,16 @@ func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? { return ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).") case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))): return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") - case let .chatCmdError(_, .errorAgent(.SMP(.PROXY(proxyErr)))): - return proxyErrorAlert(proxyErr) - case let .chatCmdError(_, .errorAgent(.PROXY(_, _, .protocolError(.PROXY(proxyErr))))): - return proxyErrorAlert(proxyErr) + case let .chatCmdError(_, .errorAgent(.SMP(serverAddress, .PROXY(proxyErr)))): + return proxyErrorAlert(proxyErr, serverAddress) + case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, _, .protocolError(.PROXY(proxyErr))))): + return proxyErrorAlert(proxyErr, proxyServer) default: return nil } } -private func proxyErrorAlert(_ proxyErr: ProxyError) -> ErrorAlert? { +private func proxyErrorAlert(_ proxyErr: ProxyError, _ srvAddr: String) -> ErrorAlert? { switch proxyErr { case .BROKER(brokerErr: .TIMEOUT): return ErrorAlert(title: "Private routing error", message: "Please try later.") @@ -1124,9 +1124,9 @@ private func proxyErrorAlert(_ proxyErr: ProxyError) -> ErrorAlert? { case .NO_SESSION: return ErrorAlert(title: "Private routing error", message: "Please try later.") case .BROKER(brokerErr: .HOST): - return ErrorAlert(title: "Private routing error", message: "Server address is incompatible with network settings.") + return ErrorAlert(title: "Private routing error", message: "Server address is incompatible with network settings: \(serverHostname(srvAddr)).") case .BROKER(brokerErr: .TRANSPORT(.version)): - return ErrorAlert(title: "Private routing error", message: "Server version is incompatible with network settings.") + return ErrorAlert(title: "Private routing error", message: "Server version is incompatible with network settings: \(serverHostname(srvAddr)).") default: return nil } @@ -1280,7 +1280,7 @@ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult { let r = await chatSendCmd(.apiJoinGroup(groupId: groupId)) switch r { case let .userAcceptedGroupSent(_, groupInfo, _): return .joined(groupInfo: groupInfo) - case .chatCmdError(_, .errorAgent(.SMP(.AUTH))): return .invitationRemoved + case .chatCmdError(_, .errorAgent(.SMP(_, .AUTH))): return .invitationRemoved case .chatCmdError(_, .errorStore(.groupNotFound)): return .groupNotFound default: throw r } diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift index ec2ce883c5..028a6d179f 100644 --- a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -662,7 +662,7 @@ private struct PassphraseConfirmationView: View { if case .chatCmdError(_, .errorDatabase(.errorOpen(.errorNotADatabase))) = error as? ChatResponse { showErrorOnMigrationIfNeeded(.errorNotADatabase(dbFile: ""), $alert) } else { - alert = .error(title: "Error", error: NSLocalizedString("Error verifying passphrase:", comment: "") + " " + String(String(describing: error))) + alert = .error(title: "Error", error: NSLocalizedString("Error verifying passphrase:", comment: "") + " " + String(responseError(error))) } } } diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index e2f4adc60f..bc8a413e8f 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -205,7 +205,7 @@ public func chatResponse(_ s: String) -> ChatResponse { if let chatData = try? parseChatData(jChat) { return chatData } - return ChatData.invalidJSON(prettyJSON(jChat) ?? "") + return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted) ?? "") } return .apiChats(user: user, chats: chats) } @@ -218,15 +218,15 @@ public func chatResponse(_ s: String) -> ChatResponse { } } else if type == "chatCmdError" { if let jError = jResp["chatCmdError"] as? NSDictionary { - return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: prettyJSON(jError) ?? "")) + return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) } } else if type == "chatError" { if let jError = jResp["chatError"] as? NSDictionary { - return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: prettyJSON(jError) ?? "")) + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? "")) } } } - json = prettyJSON(j) + json = serializeJSON(j, options: .prettyPrinted) } return ChatResponse.response(type: type ?? "invalid", json: json ?? s) } @@ -239,6 +239,14 @@ private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { } } +private func errorJson(_ jDict: NSDictionary) -> String? { + if let chatError = jDict["chatError"] { + serializeJSON(chatError) + } else { + serializeJSON(jDict) + } +} + func parseChatData(_ jChat: Any) throws -> ChatData { let jChatDict = jChat as! NSDictionary let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!) @@ -251,7 +259,7 @@ func parseChatData(_ jChat: Any) throws -> ChatData { return ChatItem.invalidJSON( chatDir: decodeProperty(jCI, "chatDir"), meta: decodeProperty(jCI, "meta"), - json: prettyJSON(jCI) ?? "" + json: serializeJSON(jCI, options: .prettyPrinted) ?? "" ) } return ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats) @@ -268,8 +276,8 @@ func decodeProperty(_ obj: Any, _ prop: NSString) -> T? { return nil } -func prettyJSON(_ obj: Any) -> String? { - if let d = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) { +func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? { + if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) { return String(decoding: d, as: UTF8.self) } return nil diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 81b638d7be..4478aa5b62 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -1228,7 +1228,7 @@ public struct ProtocolTestFailure: Decodable, Error, Equatable { public var localizedDescription: String { let err = String.localizedStringWithFormat(NSLocalizedString("Test failed at step %@.", comment: "server test failure"), testStep.text) switch testError { - case .SMP(.AUTH): + case .SMP(_, .AUTH): return err + " " + NSLocalizedString("Server requires authorization to create queues, check password", comment: "server test error") case .XFTP(.AUTH): return err + " " + NSLocalizedString("Server requires authorization to upload, check password", comment: "server test error") @@ -1887,7 +1887,7 @@ public enum SQLiteError: Decodable, Hashable { public enum AgentErrorType: Decodable, Hashable { case CMD(cmdErr: CommandErrorType) case CONN(connErr: ConnectionErrorType) - case SMP(smpErr: ProtocolErrorType) + case SMP(serverAddress: String, smpErr: ProtocolErrorType) case NTF(ntfErr: ProtocolErrorType) case XFTP(xftpErr: XFTPErrorType) case PROXY(proxyServer: String, relayServer: String, proxyErr: ProxyClientError) From 065d9be614fd58ca07e5f6316edfa5a3acc8700b Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:19:58 +0400 Subject: [PATCH 34/57] multiplatform: better errors (#4463) --- .../kotlin/chat/simplex/common/model/SimpleXAPI.kt | 12 ++++++------ .../src/commonMain/resources/MR/base/strings.xml | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 7648770afa..0b153ae7cf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1837,17 +1837,17 @@ object ChatController { r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.SMP && r.chatError.agentError.smpErr is SMPErrorType.PROXY -> - proxyErrorAlert(r.chatError.agentError.smpErr.proxyErr) + proxyErrorAlert(r.chatError.agentError.smpErr.proxyErr, r.chatError.agentError.serverAddress) r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.PROXY && r.chatError.agentError.proxyErr is ProxyClientError.ProxyProtocolError && r.chatError.agentError.proxyErr.protocolErr is SMPErrorType.PROXY -> - proxyErrorAlert(r.chatError.agentError.proxyErr.protocolErr.proxyErr) + proxyErrorAlert(r.chatError.agentError.proxyErr.protocolErr.proxyErr, r.chatError.agentError.proxyServer) else -> false } } - private fun proxyErrorAlert(pe: ProxyError): Boolean { + private fun proxyErrorAlert(pe: ProxyError, srvAddr: String): Boolean { return when { pe is ProxyError.BROKER && pe.brokerErr is BrokerErrorType.TIMEOUT -> { @@ -1876,7 +1876,7 @@ object ChatController { && pe.brokerErr is BrokerErrorType.HOST -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - generalGetString(MR.strings.srv_error_host) + String.format(generalGetString(MR.strings.network_error_broker_host_desc), serverHostname(srvAddr)) ) true } @@ -1885,7 +1885,7 @@ object ChatController { && pe.brokerErr.transportErr is SMPTransportError.Version -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - generalGetString(MR.strings.srv_error_version) + String.format(generalGetString(MR.strings.proxy_error_broker_version_desc), serverHostname(srvAddr)) ) true } @@ -5569,7 +5569,7 @@ sealed class AgentErrorType { } @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType() @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() - @Serializable @SerialName("SMP") class SMP(val smpErr: SMPErrorType): AgentErrorType() + @Serializable @SerialName("SMP") class SMP(val serverAddress: String, val smpErr: SMPErrorType): AgentErrorType() // @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType() @Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType() @Serializable @SerialName("PROXY") class PROXY(val proxyServer: String, val relayServer: String, val proxyErr: ProxyClientError): AgentErrorType() diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 7fbb4395d6..24bc6194cc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -116,6 +116,7 @@ Server version is incompatible with your app: %1$s. Private routing error Please try later. + Server version is incompatible with network settings: %1$s. Error sending message Error creating message Error loading details From dae9f8575d969823464cc518ca73127b0a36c5d2 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 16 Jul 2024 20:41:00 +0100 Subject: [PATCH 35/57] ui: change labels in server stats, move percentage toggle (#4468) * ui: change labels in server stats, move percentage toggle * localizations --- .../Views/ChatList/ServersSummaryView.swift | 5 ++-- .../UserSettings/NetworkAndServers.swift | 3 --- .../bg.xcloc/Localized Contents/bg.xliff | 20 ++++++++------ .../cs.xcloc/Localized Contents/cs.xliff | 20 ++++++++------ .../de.xcloc/Localized Contents/de.xliff | 20 ++++++++------ .../en.xcloc/Localized Contents/en.xliff | 27 +++++++++++-------- .../es.xcloc/Localized Contents/es.xliff | 20 ++++++++------ .../fi.xcloc/Localized Contents/fi.xliff | 20 ++++++++------ .../fr.xcloc/Localized Contents/fr.xliff | 20 ++++++++------ .../hu.xcloc/Localized Contents/hu.xliff | 20 ++++++++------ .../it.xcloc/Localized Contents/it.xliff | 20 ++++++++------ .../ja.xcloc/Localized Contents/ja.xliff | 20 ++++++++------ .../nl.xcloc/Localized Contents/nl.xliff | 20 ++++++++------ .../pl.xcloc/Localized Contents/pl.xliff | 20 ++++++++------ .../ru.xcloc/Localized Contents/ru.xliff | 20 ++++++++------ .../th.xcloc/Localized Contents/th.xliff | 20 ++++++++------ .../tr.xcloc/Localized Contents/tr.xliff | 20 ++++++++------ .../uk.xcloc/Localized Contents/uk.xliff | 20 ++++++++------ .../Localized Contents/zh-Hans.xliff | 20 ++++++++------ .../views/chatlist/ServersSummaryView.kt | 5 ++-- .../views/usersettings/NetworkAndServers.kt | 10 ------- .../commonMain/resources/MR/base/strings.xml | 6 ++--- 22 files changed, 217 insertions(+), 159 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index f210809a09..30e32ba9d0 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -75,8 +75,8 @@ struct ServersSummaryView: View { Group { if m.users.filter({ u in u.user.activeUser || !u.user.hidden }).count > 1 { Picker("User selection", selection: $selectedUserCategory) { - Text("All users").tag(PresentedUserCategory.allUsers) - Text("Current user").tag(PresentedUserCategory.currentUser) + Text("All profiles").tag(PresentedUserCategory.allUsers) + Text("Current profile").tag(PresentedUserCategory.currentUser) } .pickerStyle(.segmented) } @@ -176,6 +176,7 @@ struct ServersSummaryView: View { Section { infoRow("Active connections", numOrDash(totals.subs.ssActive)) infoRow("Total", numOrDash(totals.subs.total)) + Toggle("Show percentage", isOn: $showSubscriptionPercentage) } header: { HStack { Text("Message reception") diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift index b07f0f6a13..3d93a92e08 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift @@ -32,7 +32,6 @@ struct NetworkAndServers: View { @EnvironmentObject var theme: AppTheme @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @AppStorage(DEFAULT_SHOW_SENT_VIA_RPOXY) private var showSentViaProxy = false - @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @State private var cfgLoaded = false @State private var currentNetCfg = NetCfg.defaults @State private var netCfg = NetCfg.defaults @@ -62,8 +61,6 @@ struct NetworkAndServers: View { Text("XFTP servers") } - Toggle("Subscription percentage", isOn: $showSubscriptionPercentage) - Picker("Use .onion hosts", selection: $onionHosts) { ForEach(OnionHosts.values, id: \.self) { Text($0.text) } } diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index c17ffcd571..e005b44466 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -709,8 +709,8 @@ Всички нови съобщения от %@ ще бъдат скрити! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1705,8 +1705,8 @@ This is your own one-time link! Текуща парола… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5639,6 +5639,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5779,6 +5783,10 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Показване на визуализация @@ -6002,10 +6010,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 9cc8d12f74..972d06f001 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -687,8 +687,8 @@ All new messages from %@ will be hidden! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1635,8 +1635,8 @@ This is your own one-time link! Aktuální přístupová fráze… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5437,6 +5437,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5572,6 +5576,10 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Zobrazení náhledu @@ -5789,10 +5797,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 341839d246..26f020ffbe 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -709,8 +709,8 @@ Von %@ werden alle neuen Nachrichten ausgeblendet! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ Das ist Ihr eigener Einmal-Link! Aktuelles Passwort… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Nachrichtenstatus anzeigen No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Vorschau anzeigen @@ -6033,10 +6041,6 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 7bdf67cc94..73e6fb5a23 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -718,9 +718,9 @@ All new messages from %@ will be hidden! No comment provided by engineer. - - All users - All users + + All profiles + All profiles No comment provided by engineer. @@ -1738,9 +1738,9 @@ This is your own one-time link! Current passphrase… No comment provided by engineer. - - Current user - Current user + + Current profile + Current profile No comment provided by engineer. @@ -5773,6 +5773,11 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. Server version is incompatible with your app: %@. @@ -5918,6 +5923,11 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + Show percentage + No comment provided by engineer. + Show preview Show preview @@ -6148,11 +6158,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - Subscription percentage - No comment provided by engineer. - Subscriptions ignored Subscriptions ignored diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index eb4933e69a..455b74de5d 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -709,8 +709,8 @@ ¡Los mensajes nuevos de %@ estarán ocultos! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ This is your own one-time link! Contraseña actual… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Actívalo en ajustes de *Servidores y Redes*. La versión del servidor es incompatible con la configuración de red. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Actívalo en ajustes de *Servidores y Redes*. Estado del mensaje No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Mostrar vista previa @@ -6033,10 +6041,6 @@ Actívalo en ajustes de *Servidores y Redes*. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 4959324895..92070c316c 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -682,8 +682,8 @@ All new messages from %@ will be hidden! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1628,8 +1628,8 @@ This is your own one-time link! Nykyinen tunnuslause… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5424,6 +5424,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5559,6 +5563,10 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Näytä esikatselu @@ -5775,10 +5783,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index a3c5bd8c65..c2a801b0b5 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -709,8 +709,8 @@ Tous les nouveaux messages de %@ seront cachés ! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ Il s'agit de votre propre lien unique ! Phrase secrète actuelle… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. La version du serveur est incompatible avec les paramètres du réseau. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Activez-le dans les paramètres *Réseau et serveurs*. Afficher le statut du message No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Afficher l'aperçu @@ -6033,10 +6041,6 @@ Activez-le dans les paramètres *Réseau et serveurs*. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 5241ae89c0..9bc5a073b8 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -709,8 +709,8 @@ Minden új üzenet elrejtésre kerül tőle: %@! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ Ez az egyszer használatos hivatkozása! Jelenlegi jelmondat… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Üzenet állapot megjelenítése No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Előnézet megjelenítése @@ -6033,10 +6041,6 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index 2146b0115d..b41a5ddb86 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -709,8 +709,8 @@ Tutti i nuovi messaggi da %@ verrranno nascosti! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ Questo è il tuo link una tantum! Password attuale… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Attivalo nelle impostazioni *Rete e server*. La versione del server non è compatibile con le impostazioni di rete. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Attivalo nelle impostazioni *Rete e server*. Mostra stato del messaggio No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Mostra anteprima @@ -6033,10 +6041,6 @@ Attivalo nelle impostazioni *Rete e server*. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 2e23ad7423..510766fa70 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -699,8 +699,8 @@ All new messages from %@ will be hidden! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1652,8 +1652,8 @@ This is your own one-time link! 現在の暗証フレーズ… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5442,6 +5442,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5577,6 +5581,10 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview プレビューを表示 @@ -5794,10 +5802,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index e3da8c8d41..f06fac9b97 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -709,8 +709,8 @@ Alle nieuwe berichten van %@ worden verborgen! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ Dit is uw eigen eenmalige link! Huidige wachtwoord… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Serverversie is incompatibel met netwerkinstellingen. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Schakel dit in in *Netwerk en servers*-instellingen. Toon berichtstatus No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Toon voorbeeld @@ -6033,10 +6041,6 @@ Schakel dit in in *Netwerk en servers*-instellingen. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index ce4fe705a1..fd380d7e89 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -709,8 +709,8 @@ Wszystkie nowe wiadomości z %@ zostaną ukryte! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ To jest twój jednorazowy link! Obecne hasło… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Włącz w ustawianiach *Sieć i serwery* . Wersja serwera jest niekompatybilna z ustawieniami sieciowymi. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Włącz w ustawianiach *Sieć i serwery* . Pokaż status wiadomości No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Pokaż podgląd @@ -6033,10 +6041,6 @@ Włącz w ustawianiach *Sieć i serwery* . Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index b5da149546..a31e6168e1 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -709,8 +709,8 @@ Все новые сообщения от %@ будут скрыты! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ This is your own one-time link! Текущий пароль… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5666,6 +5666,10 @@ Enable in *Network & servers* settings. Версия сервера несовместима с настройками сети. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5807,6 +5811,10 @@ Enable in *Network & servers* settings. Показать статус сообщения No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Показывать уведомления @@ -6031,10 +6039,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 605a630f10..caeefc37ed 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -674,8 +674,8 @@ All new messages from %@ will be hidden! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1617,8 +1617,8 @@ This is your own one-time link! รหัสผ่านปัจจุบัน… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5401,6 +5401,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5535,6 +5539,10 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview แสดงตัวอย่าง @@ -5750,10 +5758,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 8a09e1f21a..5f1a48b381 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -709,8 +709,8 @@ %@ 'den gelen bütün yeni mesajlar saklı olacak! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ Bu senin kendi tek kullanımlık bağlantın! Şu anki parola… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Enable in *Network & servers* settings. Sunucu sürümü ağ ayarlarıyla uyumlu değil. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Enable in *Network & servers* settings. Mesaj durumunu göster No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Ön gösterimi göser @@ -6033,10 +6041,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index f210d39a78..318d8a877b 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -709,8 +709,8 @@ Всі нові повідомлення від %@ будуть приховані! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1709,8 +1709,8 @@ This is your own one-time link! Поточна парольна фраза… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5668,6 +5668,10 @@ Enable in *Network & servers* settings. Серверна версія несумісна з мережевими налаштуваннями. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5809,6 +5813,10 @@ Enable in *Network & servers* settings. Показати статус повідомлення No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview Показати попередній перегляд @@ -6033,10 +6041,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index bc61b16ce0..e53ab5b1a7 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -696,8 +696,8 @@ All new messages from %@ will be hidden! No comment provided by engineer. - - All users + + All profiles No comment provided by engineer. @@ -1683,8 +1683,8 @@ This is your own one-time link! 现有密码…… No comment provided by engineer. - - Current user + + Current profile No comment provided by engineer. @@ -5589,6 +5589,10 @@ Enable in *Network & servers* settings. Server version is incompatible with network settings. srv error text + + Server version is incompatible with network settings: %@. + No comment provided by engineer. + Server version is incompatible with your app: %@. No comment provided by engineer. @@ -5729,6 +5733,10 @@ Enable in *Network & servers* settings. Show message status No comment provided by engineer. + + Show percentage + No comment provided by engineer. + Show preview 显示预览 @@ -5952,10 +5960,6 @@ Enable in *Network & servers* settings. Subscription errors No comment provided by engineer. - - Subscription percentage - No comment provided by engineer. - Subscriptions ignored No comment provided by engineer. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index 5a3f09919e..74f4fcdcc3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -50,10 +50,10 @@ import chat.simplex.common.platform.appPlatform import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.ProtocolServersView +import chat.simplex.common.views.usersettings.SettingsPreferenceItem import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.datetime.Instant import numOrDash @@ -345,6 +345,7 @@ private fun SMPSubscriptionsSection(totals: SMPTotals) { generalGetString(MR.strings.servers_info_subscriptions_total), numOrDash(totals.subs.total) ) + SettingsPreferenceItem(null, stringResource(MR.strings.subscription_percentage), chatModel.controller.appPrefs.networkShowSubscriptionPercentage) } } } @@ -768,7 +769,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta ) { PresentedServerType.entries.size } KeyChangeEffect(serverTypePagerState.currentPage) { - selectedServerType.value = PresentedServerType.values()[serverTypePagerState.currentPage] + selectedServerType.value = PresentedServerType.entries[serverTypePagerState.currentPage] } TabRow( selectedTabIndex = serverTypePagerState.currentPage, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index e7033e88f4..61c8e1b75f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -42,7 +42,6 @@ fun NetworkAndServersView() { // It's not a state, just a one-time value. Shouldn't be used in any state-related situations val netCfg = remember { chatModel.controller.getNetCfg() } val networkUseSocksProxy: MutableState = remember { mutableStateOf(netCfg.useSocksProxy) } - val networkShowSubscriptionPercentage: MutableState = remember { mutableStateOf(chatModel.controller.appPrefs.networkShowSubscriptionPercentage.get()) } val developerTools = chatModel.controller.appPrefs.developerTools.get() val onionHosts = remember { mutableStateOf(netCfg.onionHosts) } val sessionMode = remember { mutableStateOf(netCfg.sessionMode) } @@ -54,7 +53,6 @@ fun NetworkAndServersView() { currentRemoteHost = currentRemoteHost, developerTools = developerTools, networkUseSocksProxy = networkUseSocksProxy, - networkShowSubscriptionPercentage = networkShowSubscriptionPercentage, onionHosts = onionHosts, sessionMode = sessionMode, smpProxyMode = smpProxyMode, @@ -119,9 +117,6 @@ fun NetworkAndServersView() { ) } }, - toggleNetworkShowSubscriptionPercentage = { enable -> - networkShowSubscriptionPercentage.value = enable - }, useOnion = { if (onionHosts.value == it) return@NetworkAndServersLayout val prevValue = onionHosts.value @@ -235,14 +230,12 @@ fun NetworkAndServersView() { currentRemoteHost: RemoteHostInfo?, developerTools: Boolean, networkUseSocksProxy: MutableState, - networkShowSubscriptionPercentage: MutableState, onionHosts: MutableState, sessionMode: MutableState, smpProxyMode: MutableState, smpProxyFallback: MutableState, proxyPort: State, toggleSocksProxy: (Boolean) -> Unit, - toggleNetworkShowSubscriptionPercentage: (Boolean) -> Unit, useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, updateSMPProxyMode: (SMPProxyMode) -> Unit, @@ -263,7 +256,6 @@ fun NetworkAndServersView() { SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) } }) if (currentRemoteHost == null) { - SettingsPreferenceItem(painterResource(MR.images.ic_radiowaves_up_forward_4_bar),stringResource(MR.strings.subscription_percentage), chatModel.controller.appPrefs.networkShowSubscriptionPercentage) UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showModal, chatModel.controller.appPrefs.networkProxyHostPort, false) UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) if (developerTools) { @@ -685,10 +677,8 @@ fun PreviewNetworkAndServersLayout() { currentRemoteHost = null, developerTools = true, networkUseSocksProxy = remember { mutableStateOf(true) }, - networkShowSubscriptionPercentage = remember { mutableStateOf(false) }, proxyPort = remember { mutableStateOf(9050) }, toggleSocksProxy = {}, - toggleNetworkShowSubscriptionPercentage = {}, onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, smpProxyMode = remember { mutableStateOf(SMPProxyMode.Never) }, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 24bc6194cc..d8fdfac45b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -700,7 +700,7 @@ XFTP servers Configured XFTP servers Other XFTP servers - Subscription percentage + Show percentage Install SimpleX Chat for terminal Star on GitHub Contribute @@ -2120,8 +2120,8 @@ Files No info, try to reload Showing info for - All users - Current user + All profiles + Current profile Transport sessions Connected Connecting From 391e9d57f231d1bb6b4901c384ff910eb49cd09f Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Tue, 16 Jul 2024 21:31:15 +0100 Subject: [PATCH 36/57] docs: update transparency --- docs/TRANSPARENCY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/TRANSPARENCY.md b/docs/TRANSPARENCY.md index 547660861b..43fdd12ac5 100644 --- a/docs/TRANSPARENCY.md +++ b/docs/TRANSPARENCY.md @@ -1,12 +1,12 @@ --- title: Transparency Reports permalink: /transparency/index.html -revision: 26.04.2024 +revision: 16.07.2024 --- # Transparency Reports -**Updated**: Apr 26, 2024 +**Updated**: Jul 16, 2024 SimpleX Chat Ltd. is a company registered in the UK – it develops communication software enabling users to operate and communicate via SimpleX network, without user profile identifiers of any kind, and without having their data hosted by any network infrastructure operators. From ff8bbf11e7b8be8efcd1f318e0052b08f79dbe1c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 17 Jul 2024 14:14:19 +0100 Subject: [PATCH 37/57] core: allow start in extension without subscriptions but with enabled files (#4464) * core: allow start in extension without subscriptions but with enabled files * only start sending files * update * update * update simplexmq --- apps/ios/Shared/Model/SimpleXAPI.swift | 2 +- .../ios/SimpleX NSE/NotificationService.swift | 2 +- apps/ios/SimpleXChat/APITypes.swift | 4 +-- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 33 +++++++++++-------- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Core.hs | 2 +- 8 files changed, 28 insertions(+), 21 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index fff1d6d497..3fa219128b 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -217,7 +217,7 @@ func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) asyn } func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool { - let r = chatSendCmdSync(.startChat(mainApp: true), ctrl) + let r = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl) switch r { case .chatStarted: return true case .chatRunning: return false diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 764415b1aa..3850664429 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -636,7 +636,7 @@ func apiGetActiveUser() -> User? { } func apiStartChat() throws -> Bool { - let r = sendSimpleXCmd(.startChat(mainApp: false)) + let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: false)) switch r { case .chatStarted: return true case .chatRunning: return false diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 4478aa5b62..2da068653d 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -26,7 +26,7 @@ public enum ChatCommand { case apiMuteUser(userId: Int64) case apiUnmuteUser(userId: Int64) case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?) - case startChat(mainApp: Bool) + case startChat(mainApp: Bool, enableSndFiles: Bool) case apiStopChat case apiActivateChat(restoreChat: Bool) case apiSuspendChat(timeoutMicroseconds: Int) @@ -171,7 +171,7 @@ public enum ChatCommand { case let .apiMuteUser(userId): return "/_mute user \(userId)" case let .apiUnmuteUser(userId): return "/_unmute user \(userId)" case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))" - case let .startChat(mainApp): return "/_start main=\(onOff(mainApp))" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(enableSndFiles)" case .apiStopChat: return "/_stop" case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" diff --git a/cabal.project b/cabal.project index e7e30afb3c..3573976290 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: b40d55c358ebabeb6cf1eca8e64016e2bae09a15 + tag: 8d56b0ba8519bdc68a3777d8d0672fbe01957479 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 6eaf36e709..9f1dd6d69c 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."b40d55c358ebabeb6cf1eca8e64016e2bae09a15" = "1ppn2yvml12yr5k5d6hjn7r7xy6a83ig9mys49jzzk034rzwxcd2"; + "https://github.com/simplex-chat/simplexmq.git"."8d56b0ba8519bdc68a3777d8d0672fbe01957479" = "1yfcgnkhal6kj26kcl805152b1gai2ncyfq9lyq5k8jvdh0hpq99"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index d31c2cdeca..46c8f97d53 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -384,8 +384,9 @@ cfgServers p DefaultAgentServers {smp, xftp} = case p of SPSMP -> smp SPXFTP -> xftp -startChatController :: Bool -> CM' (Async ()) -startChatController mainApp = do +-- enableSndFiles has no effect when mainApp is True +startChatController :: Bool -> Bool -> CM' (Async ()) +startChatController mainApp enableSndFiles = do asks smpAgent >>= liftIO . resumeAgentClient unless mainApp $ chatWriteVar' subscriptionMode SMOnlyCreate users <- fromRight [] <$> runExceptT (withStore' getUsers) @@ -400,15 +401,18 @@ startChatController mainApp = do then Just <$> async (subscribeUsers False users) else pure Nothing atomically . writeTVar s $ Just (a1, a2) - when mainApp $ do - startXFTP - void $ forkIO $ startFilesToReceive users - startCleanupManager - startExpireCIs users + if mainApp + then do + startXFTP xftpStartWorkers + void $ forkIO $ startFilesToReceive users + startCleanupManager + startExpireCIs users + else + when enableSndFiles $ startXFTP xftpStartSndWorkers pure a1 - startXFTP = do + startXFTP startWorkers = do tmp <- readTVarIO =<< asks tempDirectory - runExceptT (withAgent $ \a -> xftpStartWorkers a tmp) >>= \case + runExceptT (withAgent $ \a -> startWorkers a tmp) >>= \case Left e -> liftIO $ print $ "Error starting XFTP workers: " <> show e Right _ -> pure () startCleanupManager = do @@ -617,10 +621,10 @@ processChatCommand' vr = \case checkDeleteChatUser user' withChatLock "deleteUser" . procCmd $ deleteChatUser user' delSMPQueues DeleteUser uName delSMPQueues viewPwd_ -> withUserName uName $ \userId -> APIDeleteUser userId delSMPQueues viewPwd_ - StartChat mainApp -> withUser' $ \_ -> + StartChat {mainApp, enableSndFiles} -> withUser' $ \_ -> asks agentAsync >>= readTVarIO >>= \case Just _ -> pure CRChatRunning - _ -> checkStoreNotChanged . lift $ startChatController mainApp $> CRChatStarted + _ -> checkStoreNotChanged . lift $ startChatController mainApp enableSndFiles $> CRChatStarted APIStopChat -> do ask >>= liftIO . stopChatController pure CRChatStopped @@ -7355,8 +7359,11 @@ chatCommandP = "/_delete user " *> (APIDeleteUser <$> A.decimal <* " del_smp=" <*> onOffP <*> optional (A.space *> jsonP)), "/delete user " *> (DeleteUser <$> displayName <*> pure True <*> optional (A.space *> pwdP)), ("/user" <|> "/u") $> ShowActiveUser, - "/_start main=" *> (StartChat <$> onOffP), - "/_start" $> StartChat True, + "/_start " *> do + mainApp <- "main=" *> onOffP + enableSndFiles <- " snd_files=" *> onOffP <|> pure mainApp + pure StartChat {mainApp, enableSndFiles}, + "/_start" $> StartChat True True, "/_stop" $> APIStopChat, "/_app activate restore=" *> (APIActivateChat <$> onOffP), "/_app activate" $> APIActivateChat True, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index e08c8a287a..8e2a308986 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -263,7 +263,7 @@ data ChatCommand | UnmuteUser | APIDeleteUser UserId Bool (Maybe UserPwd) | DeleteUser UserName Bool (Maybe UserPwd) - | StartChat {mainApp :: Bool} + | StartChat {mainApp :: Bool, enableSndFiles :: Bool} -- enableSndFiles has no effect when mainApp is True | APIStopChat | APIActivateChat {restoreChat :: Bool} | APISuspendChat {suspendTimeout :: Int} diff --git a/src/Simplex/Chat/Core.hs b/src/Simplex/Chat/Core.hs index a8580746d1..07fac82677 100644 --- a/src/Simplex/Chat/Core.hs +++ b/src/Simplex/Chat/Core.hs @@ -54,7 +54,7 @@ runSimplexChat :: ChatOpts -> User -> ChatController -> (User -> ChatController runSimplexChat ChatOpts {maintenance} u cc chat | maintenance = wait =<< async (chat u cc) | otherwise = do - a1 <- runReaderT (startChatController True) cc + a1 <- runReaderT (startChatController True True) cc a2 <- async $ chat u cc waitEither_ a1 a2 From fea51b75e94d239f8af78c1ad2bb3e2f2eb44bc4 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 17 Jul 2024 14:20:58 +0100 Subject: [PATCH 38/57] ios: fix start chat api --- apps/ios/SimpleXChat/APITypes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 2da068653d..7a33253c04 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -171,7 +171,7 @@ public enum ChatCommand { case let .apiMuteUser(userId): return "/_mute user \(userId)" case let .apiUnmuteUser(userId): return "/_unmute user \(userId)" case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))" - case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(enableSndFiles)" + case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))" case .apiStopChat: return "/_stop" case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))" case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)" From a1e707ac1bb531611ee5c41653d819f5044c1840 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Wed, 17 Jul 2024 19:00:47 +0100 Subject: [PATCH 39/57] ui: translations (#4469) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 99.6% (1928 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1681 of 1681 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 99.6% (1928 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1681 of 1681 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 2.8% (55 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (German) Currently translated at 97.7% (1908 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 99.6% (1928 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Italian) Currently translated at 100.0% (1681 of 1681 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/it/ * Translated using Weblate (Dutch) Currently translated at 99.6% (1928 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/nl/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1934 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1681 of 1681 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 2.8% (55 of 1934 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/id/ * Translated using Weblate (German) Currently translated at 97.7% (1908 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 98.7% (1927 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (1952 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/zh_Hans/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1952 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * Translated using Weblate (German) Currently translated at 100.0% (1952 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/de/ * Translated using Weblate (German) Currently translated at 100.0% (1681 of 1681 strings) Translation: SimpleX Chat/SimpleX Chat iOS Translate-URL: https://hosted.weblate.org/projects/simplex-chat/ios/de/ * Translated using Weblate (Italian) Currently translated at 100.0% (1952 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/it/ * Translated using Weblate (Japanese) Currently translated at 92.8% (1813 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ja/ * Translated using Weblate (Arabic) Currently translated at 100.0% (1952 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/ar/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (1952 of 1952 strings) Translation: SimpleX Chat/SimpleX Chat Android Translate-URL: https://hosted.weblate.org/projects/simplex-chat/android/hu/ * process localizations --------- Co-authored-by: 大王叫我来巡山 Co-authored-by: summoner001 Co-authored-by: Random Co-authored-by: M1K4 Co-authored-by: Ghost of Sparta Co-authored-by: MM Co-authored-by: mlanp Co-authored-by: Miyu Sakatsuki Co-authored-by: jonnysemon --- .../de.xcloc/Localized Contents/de.xliff | 148 ++++++- .../hu.xcloc/Localized Contents/hu.xliff | 130 ++++++ .../it.xcloc/Localized Contents/it.xliff | 130 ++++++ apps/ios/bg.lproj/Localizable.strings | 3 - apps/ios/cs.lproj/Localizable.strings | 3 - apps/ios/de.lproj/Localizable.strings | 406 +++++++++++++++++- .../de.lproj/SimpleX--iOS--InfoPlist.strings | 2 +- apps/ios/es.lproj/Localizable.strings | 3 - apps/ios/fi.lproj/Localizable.strings | 3 - apps/ios/fr.lproj/Localizable.strings | 3 - apps/ios/hu.lproj/Localizable.strings | 390 ++++++++++++++++- apps/ios/it.lproj/Localizable.strings | 390 ++++++++++++++++- apps/ios/ja.lproj/Localizable.strings | 3 - apps/ios/nl.lproj/Localizable.strings | 3 - apps/ios/pl.lproj/Localizable.strings | 3 - apps/ios/ru.lproj/Localizable.strings | 3 - apps/ios/th.lproj/Localizable.strings | 3 - apps/ios/tr.lproj/Localizable.strings | 3 - apps/ios/uk.lproj/Localizable.strings | 3 - apps/ios/zh-Hans.lproj/Localizable.strings | 3 - .../commonMain/resources/MR/ar/strings.xml | 134 +++++- .../commonMain/resources/MR/de/strings.xml | 143 +++++- .../commonMain/resources/MR/hu/strings.xml | 116 +++++ .../commonMain/resources/MR/in/strings.xml | 3 + .../commonMain/resources/MR/it/strings.xml | 116 +++++ .../commonMain/resources/MR/ja/strings.xml | 8 + .../commonMain/resources/MR/nl/strings.xml | 92 ++++ .../resources/MR/zh-rCN/strings.xml | 116 +++++ 28 files changed, 2275 insertions(+), 88 deletions(-) diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 26f020ffbe..24a40bfd1a 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -554,6 +554,7 @@ Accent + Akzent No comment provided by engineer. @@ -579,10 +580,12 @@ Acknowledged + Bestätigt No comment provided by engineer. Acknowledgement errors + Fehler bei der Bestätigung No comment provided by engineer. @@ -631,14 +634,17 @@ Additional accent + Erste Akzentfarbe No comment provided by engineer. Additional accent 2 + Zusätzlicher Akzent 2 No comment provided by engineer. Additional secondary + Zweite Akzentfarbe No comment provided by engineer. @@ -668,6 +674,7 @@ Advanced settings + Erweiterte Einstellungen No comment provided by engineer. @@ -677,7 +684,7 @@ All chats and messages will be deleted - this cannot be undone! - Alle Chats und Nachrichten werden gelöscht! Dies kann nicht rückgängig gemacht werden! + Alle Chats und Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -687,6 +694,7 @@ All data is private to your device. + Alle Daten sind auf Ihrem Gerät geschützt. No comment provided by engineer. @@ -696,12 +704,12 @@ All messages will be deleted - this cannot be undone! - Es werden alle Nachrichten gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden! + Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. - Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. + Alle Nachrichten werden gelöscht . Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. No comment provided by engineer. @@ -711,6 +719,7 @@ All profiles + Alle Profile No comment provided by engineer. @@ -915,6 +924,7 @@ Apply to + Anwenden auf No comment provided by engineer. @@ -994,6 +1004,7 @@ Background + Hintergrund-Farbe No comment provided by engineer. @@ -1023,6 +1034,7 @@ Black + Schwarz No comment provided by engineer. @@ -1137,6 +1149,7 @@ Cannot forward message + Die Nachricht kann nicht weitergeleitet werden No comment provided by engineer. @@ -1212,6 +1225,7 @@ Chat colors + Chat-Farben No comment provided by engineer. @@ -1261,6 +1275,7 @@ Chat theme + Chat-Design No comment provided by engineer. @@ -1295,14 +1310,17 @@ Chunks deleted + Daten-Pakete gelöscht No comment provided by engineer. Chunks downloaded + Daten-Pakete heruntergeladen No comment provided by engineer. Chunks uploaded + Daten-Pakete hochgeladen No comment provided by engineer. @@ -1332,6 +1350,7 @@ Color mode + Farbvariante No comment provided by engineer. @@ -1346,6 +1365,7 @@ Completed + Abgeschlossen No comment provided by engineer. @@ -1463,6 +1483,7 @@ Das ist Ihr eigener Einmal-Link! Connected + Verbunden No comment provided by engineer. @@ -1472,6 +1493,7 @@ Das ist Ihr eigener Einmal-Link! Connected servers + Verbundene Server No comment provided by engineer. @@ -1481,6 +1503,7 @@ Das ist Ihr eigener Einmal-Link! Connecting + Verbinden No comment provided by engineer. @@ -1530,10 +1553,12 @@ Das ist Ihr eigener Einmal-Link! Connection with desktop stopped + Die Verbindung mit dem Desktop wurde gestoppt No comment provided by engineer. Connections + Verbindungen No comment provided by engineer. @@ -1593,6 +1618,7 @@ Das ist Ihr eigener Einmal-Link! Copy error + Fehlermeldung kopieren No comment provided by engineer. @@ -1672,6 +1698,7 @@ Das ist Ihr eigener Einmal-Link! Created + Erstellt No comment provided by engineer. @@ -1711,6 +1738,7 @@ Das ist Ihr eigener Einmal-Link! Current profile + Aktueller Profil No comment provided by engineer. @@ -1725,6 +1753,7 @@ Das ist Ihr eigener Einmal-Link! Customize theme + Design anpassen No comment provided by engineer. @@ -1734,6 +1763,7 @@ Das ist Ihr eigener Einmal-Link! Dark mode colors + Farben für die dunkle Variante No comment provided by engineer. @@ -1923,7 +1953,7 @@ Das ist Ihr eigener Einmal-Link! Delete contact? This cannot be undone! Kontakt löschen? -Das kann nicht rückgängig gemacht werden! +Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -2023,7 +2053,7 @@ Das kann nicht rückgängig gemacht werden! Delete pending connection? - Die ausstehende Verbindung löschen? + Ausstehende Verbindung löschen? No comment provided by engineer. @@ -2043,6 +2073,7 @@ Das kann nicht rückgängig gemacht werden! Deleted + Gelöscht No comment provided by engineer. @@ -2057,6 +2088,7 @@ Das kann nicht rückgängig gemacht werden! Deletion errors + Fehler beim Löschen No comment provided by engineer. @@ -2101,10 +2133,12 @@ Das kann nicht rückgängig gemacht werden! Detailed statistics + Detaillierte Statistiken No comment provided by engineer. Details + Details No comment provided by engineer. @@ -2264,6 +2298,7 @@ Das kann nicht rückgängig gemacht werden! Download errors + Fehler beim Herunterladen No comment provided by engineer. @@ -2278,10 +2313,12 @@ Das kann nicht rückgängig gemacht werden! Downloaded + Heruntergeladen No comment provided by engineer. Downloaded files + Heruntergeladene Dateien No comment provided by engineer. @@ -2656,6 +2693,7 @@ Das kann nicht rückgängig gemacht werden! Error exporting theme: %@ + Fehler beim Exportieren des Designs: %@ No comment provided by engineer. @@ -2685,10 +2723,12 @@ Das kann nicht rückgängig gemacht werden! Error reconnecting server + Fehler beim Wiederherstellen der Verbindung zum Server No comment provided by engineer. Error reconnecting servers + Fehler beim Wiederherstellen der Verbindungen zu den Servern No comment provided by engineer. @@ -2698,6 +2738,7 @@ Das kann nicht rückgängig gemacht werden! Error resetting statistics + Fehler beim Zurücksetzen der Statistiken No comment provided by engineer. @@ -2833,6 +2874,7 @@ Das kann nicht rückgängig gemacht werden! Errors + Fehler No comment provided by engineer. @@ -2862,6 +2904,7 @@ Das kann nicht rückgängig gemacht werden! Export theme + Design exportieren No comment provided by engineer. @@ -2901,22 +2944,27 @@ Das kann nicht rückgängig gemacht werden! File error + Datei-Fehler No comment provided by engineer. File not found - most likely file was deleted or cancelled. + Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen. file error text File server error: %@ + Datei-Server Fehler: %@ file error text File status + Datei-Status No comment provided by engineer. File status: %@ + Datei-Status: %@ copied message info @@ -3110,10 +3158,12 @@ Fehler: %2$@ Good afternoon! + Guten Nachmittag! message preview Good morning! + Guten Morgen! message preview @@ -3238,12 +3288,12 @@ Fehler: %2$@ Group will be deleted for all members - this cannot be undone! - Die Gruppe wird für alle Mitglieder gelöscht - dies kann nicht rückgängig gemacht werden! + Die Gruppe wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Group will be deleted for you - this cannot be undone! - Die Gruppe wird für Sie gelöscht - dies kann nicht rückgängig gemacht werden! + Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -3398,6 +3448,7 @@ Fehler: %2$@ Import theme + Design importieren No comment provided by engineer. @@ -3524,6 +3575,7 @@ Fehler: %2$@ Interface colors + Interface-Farben No comment provided by engineer. @@ -3871,6 +3923,7 @@ Das ist Ihr Link für die Gruppe %@! Member inactive + Mitglied inaktiv item status text @@ -3885,11 +3938,12 @@ Das ist Ihr Link für die Gruppe %@! Member will be removed from group - this cannot be undone! - Das Mitglied wird aus der Gruppe entfernt - dies kann nicht rückgängig gemacht werden! + Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. Menus + Menüs No comment provided by engineer. @@ -3914,10 +3968,12 @@ Das ist Ihr Link für die Gruppe %@! Message forwarded + Nachricht weitergeleitet item status text Message may be delivered later if member becomes active. + Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird. item status description @@ -3961,10 +4017,12 @@ Das ist Ihr Link für die Gruppe %@! Message status + Nachrichten-Status No comment provided by engineer. Message status: %@ + Nachrichten-Status: %@ copied message info @@ -3994,10 +4052,12 @@ Das ist Ihr Link für die Gruppe %@! Messages received + Empfangene Nachrichten No comment provided by engineer. Messages sent + Gesendete Nachrichten No comment provided by engineer. @@ -4237,6 +4297,7 @@ Das ist Ihr Link für die Gruppe %@! No direct connection yet, message is forwarded by admin. + Bisher keine direkte Verbindung. Nachricht wird von einem Admin weitergeleitet. item status description @@ -4256,6 +4317,7 @@ Das ist Ihr Link für die Gruppe %@! No info, try to reload + Keine Information - es wird versucht neu zu laden No comment provided by engineer. @@ -4444,6 +4506,7 @@ Das ist Ihr Link für die Gruppe %@! Open server settings + Server-Einstellungen öffnen No comment provided by engineer. @@ -4557,6 +4620,7 @@ Das ist Ihr Link für die Gruppe %@! Pending + Ausstehend No comment provided by engineer. @@ -4587,6 +4651,8 @@ Das ist Ihr Link für die Gruppe %@! Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + Bitte überprüfen Sie, ob sich das Mobiltelefon und die Desktop-App im gleichen lokalen Netzwerk befinden, und die Desktop-Firewall die Verbindung erlaubt. +Bitte teilen Sie weitere mögliche Probleme den Entwicklern mit. No comment provided by engineer. @@ -4692,6 +4758,7 @@ Fehler: %@ Previously connected servers + Bisher verbundene Server No comment provided by engineer. @@ -4765,6 +4832,7 @@ Fehler: %@ Profile theme + Profil-Design No comment provided by engineer. @@ -4851,10 +4919,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Proxied + Proxy No comment provided by engineer. Proxied servers + Proxy-Server No comment provided by engineer. @@ -4924,6 +4994,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Receive errors + Fehler beim Empfang No comment provided by engineer. @@ -4948,14 +5019,17 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Received messages + Empfangene Nachrichten No comment provided by engineer. Received reply + Empfangene Antwort No comment provided by engineer. Received total + Summe aller empfangenen Nachrichten No comment provided by engineer. @@ -4990,6 +5064,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reconnect + Neu verbinden No comment provided by engineer. @@ -4999,18 +5074,22 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reconnect all servers + Alle Server neu verbinden No comment provided by engineer. Reconnect all servers? + Alle Server neu verbinden? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + Um die Auslieferung von Nachrichten zu erzwingen, wird der Server neu verbunden. Dafür wird weiterer Datenverkehr benötigt. No comment provided by engineer. Reconnect server? + Server neu verbinden? No comment provided by engineer. @@ -5065,6 +5144,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Remove image + Bild entfernen No comment provided by engineer. @@ -5139,10 +5219,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reset all statistics + Alle Statistiken zurücksetzen No comment provided by engineer. Reset all statistics? + Alle Statistiken zurücksetzen? No comment provided by engineer. @@ -5152,6 +5234,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reset to app theme + Auf das App-Design zurücksetzen No comment provided by engineer. @@ -5161,6 +5244,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Reset to user theme + Auf das Benutzer-spezifische Design zurücksetzen No comment provided by engineer. @@ -5235,6 +5319,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SMP server + SMP-Server No comment provided by engineer. @@ -5354,10 +5439,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Scale + Skalieren No comment provided by engineer. Scan / Paste link + Link scannen / einfügen No comment provided by engineer. @@ -5402,6 +5489,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Secondary + Zweite Farbe No comment provided by engineer. @@ -5411,6 +5499,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Secured + Abgesichert No comment provided by engineer. @@ -5430,6 +5519,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Selected chat preferences prohibit this message. + Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. No comment provided by engineer. @@ -5484,6 +5574,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Send errors + Fehler beim Senden No comment provided by engineer. @@ -5598,6 +5689,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sent directly + Direkt gesendet No comment provided by engineer. @@ -5612,6 +5704,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sent messages + Gesendete Nachrichten No comment provided by engineer. @@ -5621,18 +5714,22 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Sent reply + Gesendete Antwort No comment provided by engineer. Sent total + Summe aller gesendeten Nachrichten No comment provided by engineer. Sent via proxy + Über einen Proxy gesendet No comment provided by engineer. Server address + Server-Adresse No comment provided by engineer. @@ -5661,6 +5758,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Server type + Server-Typ No comment provided by engineer. @@ -5683,10 +5781,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Servers info + Server-Informationen No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + Die Serverstatistiken werden zurückgesetzt. Dies kann nicht rückgängig gemacht werden! No comment provided by engineer. @@ -5706,6 +5806,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Set default theme + Default-Design einstellen No comment provided by engineer. @@ -5834,6 +5935,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. SimpleX + SimpleX No comment provided by engineer. @@ -5913,6 +6015,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Size + Größe No comment provided by engineer. @@ -5962,10 +6065,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Starting from %@. + Beginnend mit %@. No comment provided by engineer. Statistics + Statistiken No comment provided by engineer. @@ -6035,14 +6140,17 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Subscribed + Abonniert No comment provided by engineer. Subscription errors + Fehler beim Abonnieren No comment provided by engineer. Subscriptions ignored + Nicht beachtete Abonnements No comment provided by engineer. @@ -6127,6 +6235,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. Temporary file error + Temporärer Datei-Fehler No comment provided by engineer. @@ -6268,6 +6377,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Themes + Design No comment provided by engineer. @@ -6337,6 +6447,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro This link was used with another mobile device, please create a new link on the desktop. + Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App. No comment provided by engineer. @@ -6346,6 +6457,7 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Title + Bezeichnung No comment provided by engineer. @@ -6417,6 +6529,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Total + Summe aller Abonnements No comment provided by engineer. @@ -6426,6 +6539,7 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Transport sessions + Transport-Sitzungen No comment provided by engineer. @@ -6622,6 +6736,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Upload errors + Fehler beim Hochladen No comment provided by engineer. @@ -6636,10 +6751,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Uploaded + Hochgeladen No comment provided by engineer. Uploaded files + Hochgeladene Dateien No comment provided by engineer. @@ -6719,6 +6836,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s User selection + Benutzer-Auswahl No comment provided by engineer. @@ -6858,10 +6976,12 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wallpaper accent + Wallpaper-Akzent No comment provided by engineer. Wallpaper background + Wallpaper-Hintergrund No comment provided by engineer. @@ -6971,6 +7091,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Wrong key or unknown file chunk address - most likely file is deleted. + Falscher Schlüssel oder unbekannte Daten-Paketadresse der Datei - höchstwahrscheinlich wurde die Datei gelöscht. file error text @@ -6980,6 +7101,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s XFTP server + XFTP-Server No comment provided by engineer. @@ -7066,6 +7188,7 @@ Verbindungsanfrage wiederholen? You are not connected to these servers. Private routing is used to deliver messages to them. + Sie sind nicht mit diesen Servern verbunden. Zur Auslieferung von Nachrichten an diese Server wird privates Routing genutzt. No comment provided by engineer. @@ -7476,6 +7599,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. attempts + Versuche No comment provided by engineer. @@ -7670,6 +7794,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. decryption errors + Entschlüsselungs-Fehler No comment provided by engineer. @@ -7724,6 +7849,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. duplicates + Duplikate No comment provided by engineer. @@ -7808,6 +7934,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. expired + abgelaufen No comment provided by engineer. @@ -7842,6 +7969,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. inactive + Inaktiv No comment provided by engineer. @@ -8023,10 +8151,12 @@ SimpleX-Server können Ihr Profil nicht einsehen. other + andere No comment provided by engineer. other errors + Andere Fehler No comment provided by engineer. @@ -8352,7 +8482,7 @@ Zuletzt empfangene Nachricht: %2$@ SimpleX needs camera access to scan QR codes to connect to other users and for video calls. - SimpleX benötigt Zugriff auf die Kamera, um QR Codes für die Verbindung mit anderen Nutzern zu scannen und Videoanrufe durchzuführen. + SimpleX benötigt Zugriff auf die Kamera, um QR Codes für die Verbindung mit anderen Benutzern zu scannen und Videoanrufe durchzuführen. Privacy - Camera Usage Description diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 9bc5a073b8..411317b715 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -554,6 +554,7 @@ Accent + Kiemelés No comment provided by engineer. @@ -579,10 +580,12 @@ Acknowledged + Nyugtázva No comment provided by engineer. Acknowledgement errors + Nyugtázott hibák No comment provided by engineer. @@ -631,14 +634,17 @@ Additional accent + További kiemelés No comment provided by engineer. Additional accent 2 + További kiemelés 2 No comment provided by engineer. Additional secondary + További másodlagos No comment provided by engineer. @@ -668,6 +674,7 @@ Advanced settings + Haladó beállítások No comment provided by engineer. @@ -687,6 +694,7 @@ All data is private to your device. + Minden adat biztonságban van a készülékén. No comment provided by engineer. @@ -711,6 +719,7 @@ All profiles + Minden profil No comment provided by engineer. @@ -915,6 +924,7 @@ Apply to + Alkalmazás erre No comment provided by engineer. @@ -994,6 +1004,7 @@ Background + Háttér No comment provided by engineer. @@ -1023,6 +1034,7 @@ Black + Fekete No comment provided by engineer. @@ -1137,6 +1149,7 @@ Cannot forward message + Nem lehet továbbítani az üzenetet No comment provided by engineer. @@ -1212,6 +1225,7 @@ Chat colors + Csevegés színei No comment provided by engineer. @@ -1261,6 +1275,7 @@ Chat theme + Csevegés témája No comment provided by engineer. @@ -1295,14 +1310,17 @@ Chunks deleted + Törölt fájltöredékek No comment provided by engineer. Chunks downloaded + Letöltött fájltöredékek No comment provided by engineer. Chunks uploaded + Feltöltött fájltöredékek No comment provided by engineer. @@ -1332,6 +1350,7 @@ Color mode + Színmód No comment provided by engineer. @@ -1346,6 +1365,7 @@ Completed + Elkészült No comment provided by engineer. @@ -1463,6 +1483,7 @@ Ez az egyszer használatos hivatkozása! Connected + Kapcsolódva No comment provided by engineer. @@ -1472,6 +1493,7 @@ Ez az egyszer használatos hivatkozása! Connected servers + Kapcsolódott kiszolgálók No comment provided by engineer. @@ -1481,6 +1503,7 @@ Ez az egyszer használatos hivatkozása! Connecting + Kapcsolódás No comment provided by engineer. @@ -1530,10 +1553,12 @@ Ez az egyszer használatos hivatkozása! Connection with desktop stopped + A kapcsolat a számítógéppel megszakadt No comment provided by engineer. Connections + Kapcsolatok No comment provided by engineer. @@ -1593,6 +1618,7 @@ Ez az egyszer használatos hivatkozása! Copy error + Másolási hiba No comment provided by engineer. @@ -1672,6 +1698,7 @@ Ez az egyszer használatos hivatkozása! Created + Létrehozva No comment provided by engineer. @@ -1711,6 +1738,7 @@ Ez az egyszer használatos hivatkozása! Current profile + Jelenlegi profil No comment provided by engineer. @@ -1725,6 +1753,7 @@ Ez az egyszer használatos hivatkozása! Customize theme + Téma személyre szabása No comment provided by engineer. @@ -1734,6 +1763,7 @@ Ez az egyszer használatos hivatkozása! Dark mode colors + Sötét mód színei No comment provided by engineer. @@ -2043,6 +2073,7 @@ Ez a művelet nem vonható vissza! Deleted + Törölve No comment provided by engineer. @@ -2057,6 +2088,7 @@ Ez a művelet nem vonható vissza! Deletion errors + Törlési hibák No comment provided by engineer. @@ -2101,10 +2133,12 @@ Ez a művelet nem vonható vissza! Detailed statistics + Részletes statisztikák No comment provided by engineer. Details + Részletek No comment provided by engineer. @@ -2264,6 +2298,7 @@ Ez a művelet nem vonható vissza! Download errors + Letöltési hibák No comment provided by engineer. @@ -2278,10 +2313,12 @@ Ez a művelet nem vonható vissza! Downloaded + Letöltve No comment provided by engineer. Downloaded files + Letöltött fájlok No comment provided by engineer. @@ -2656,6 +2693,7 @@ Ez a művelet nem vonható vissza! Error exporting theme: %@ + Hiba a téma exportálásakor: %@ No comment provided by engineer. @@ -2685,10 +2723,12 @@ Ez a művelet nem vonható vissza! Error reconnecting server + Hiba a kiszolgálóhoz való újrakapcsolódáskor No comment provided by engineer. Error reconnecting servers + Hiba a kiszolgálókhoz való újrakapcsolódáskor No comment provided by engineer. @@ -2698,6 +2738,7 @@ Ez a művelet nem vonható vissza! Error resetting statistics + Hiba a statisztikák visszaállításakor No comment provided by engineer. @@ -2833,6 +2874,7 @@ Ez a művelet nem vonható vissza! Errors + Hibák No comment provided by engineer. @@ -2862,6 +2904,7 @@ Ez a művelet nem vonható vissza! Export theme + Téma exportálása No comment provided by engineer. @@ -2901,22 +2944,27 @@ Ez a művelet nem vonható vissza! File error + Fájlhiba No comment provided by engineer. File not found - most likely file was deleted or cancelled. + A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták. file error text File server error: %@ + Fájlkiszolgáló hiba: %@ file error text File status + Fájlállapot No comment provided by engineer. File status: %@ + Fájlállapot: %@ copied message info @@ -3110,10 +3158,12 @@ Hiba: %2$@ Good afternoon! + Jó napot! message preview Good morning! + Jó reggelt! message preview @@ -3398,6 +3448,7 @@ Hiba: %2$@ Import theme + Téma importálása No comment provided by engineer. @@ -3524,6 +3575,7 @@ Hiba: %2$@ Interface colors + Kezelőfelület színei No comment provided by engineer. @@ -3871,6 +3923,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Member inactive + Inaktív tag item status text @@ -3890,6 +3943,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Menus + Menük No comment provided by engineer. @@ -3914,10 +3968,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message forwarded + Továbbított üzenet item status text Message may be delivered later if member becomes active. + Az üzenet később is kézbesíthető, ha a tag aktívvá válik. item status description @@ -3961,10 +4017,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message status + Üzenetállapot No comment provided by engineer. Message status: %@ + Üzenetállapot: %@ copied message info @@ -3994,10 +4052,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Messages received + Fogadott üzenetek No comment provided by engineer. Messages sent + Elküldött üzenetek No comment provided by engineer. @@ -4237,6 +4297,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! No direct connection yet, message is forwarded by admin. + Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja. item status description @@ -4256,6 +4317,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! No info, try to reload + Nincs információ, próbálja meg újratölteni No comment provided by engineer. @@ -4444,6 +4506,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Open server settings + Kiszolgáló beállításainak megnyitása No comment provided by engineer. @@ -4557,6 +4620,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Pending + Függő No comment provided by engineer. @@ -4587,6 +4651,8 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + Ellenőrizze, hogy a mobil és az asztali számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint az asztali számítógép tűzfalában engedélyezve van-e a kapcsolat. +Minden további problémát osszon meg a fejlesztőkkel. No comment provided by engineer. @@ -4692,6 +4758,7 @@ Hiba: %@ Previously connected servers + Korábban kapcsolódott kiszolgálók No comment provided by engineer. @@ -4765,6 +4832,7 @@ Hiba: %@ Profile theme + Profiltéma No comment provided by engineer. @@ -4851,10 +4919,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Proxied + Proxyzott No comment provided by engineer. Proxied servers + Proxyzott kiszolgálók No comment provided by engineer. @@ -4924,6 +4994,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Receive errors + Üzenetfogadási hibák No comment provided by engineer. @@ -4948,14 +5019,17 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Received messages + Fogadott üzenetek No comment provided by engineer. Received reply + Fogadott válasz No comment provided by engineer. Received total + Összes fogadott No comment provided by engineer. @@ -4990,6 +5064,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Reconnect + Újrakapcsolás No comment provided by engineer. @@ -4999,18 +5074,22 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Reconnect all servers + Újrakapcsolódás minden kiszolgálóhoz No comment provided by engineer. Reconnect all servers? + Újrakapcsolódás minden kiszolgálóhoz? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. No comment provided by engineer. Reconnect server? + Újrakapcsolódás a kiszolgálóhoz? No comment provided by engineer. @@ -5065,6 +5144,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Remove image + Kép eltávolítása No comment provided by engineer. @@ -5139,10 +5219,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Reset all statistics + Minden statisztika visszaállítása No comment provided by engineer. Reset all statistics? + Minden statisztika visszaállítása? No comment provided by engineer. @@ -5152,6 +5234,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Reset to app theme + Alkalmazás témájának visszaállítása No comment provided by engineer. @@ -5161,6 +5244,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Reset to user theme + Felhasználó által létrehozott téma visszaállítása No comment provided by engineer. @@ -5235,6 +5319,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< SMP server + SMP-kiszolgáló No comment provided by engineer. @@ -5354,10 +5439,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Scale + Méretezés No comment provided by engineer. Scan / Paste link + Hivatkozás beolvasása / beillesztése No comment provided by engineer. @@ -5402,6 +5489,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Secondary + Másodlagos No comment provided by engineer. @@ -5411,6 +5499,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Secured + Biztosítva No comment provided by engineer. @@ -5430,6 +5519,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Selected chat preferences prohibit this message. + A kiválasztott csevegési beállítások tiltják ezt az üzenetet. No comment provided by engineer. @@ -5484,6 +5574,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Send errors + Üzenetküldési hibák No comment provided by engineer. @@ -5598,6 +5689,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Sent directly + Közvetlenül küldött No comment provided by engineer. @@ -5612,6 +5704,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Sent messages + Elküldött üzenetek No comment provided by engineer. @@ -5621,18 +5714,22 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Sent reply + Elküldött válasz No comment provided by engineer. Sent total + Összes elküldött No comment provided by engineer. Sent via proxy + Proxyn keresztül küldve No comment provided by engineer. Server address + Kiszolgáló címe No comment provided by engineer. @@ -5661,6 +5758,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Server type + Kiszolgáló típusa No comment provided by engineer. @@ -5683,10 +5781,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Servers info + információk a kiszolgálókról No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza! No comment provided by engineer. @@ -5706,6 +5806,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Set default theme + Alapértelmezett téma beállítása No comment provided by engineer. @@ -5834,6 +5935,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< SimpleX + SimpleX No comment provided by engineer. @@ -5913,6 +6015,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Size + Méret No comment provided by engineer. @@ -5962,10 +6065,12 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Starting from %@. + Kezdve ettől %@. No comment provided by engineer. Statistics + Statisztikák No comment provided by engineer. @@ -6035,14 +6140,17 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Subscribed + Feliratkozva No comment provided by engineer. Subscription errors + Feliratkozási hibák No comment provided by engineer. Subscriptions ignored + Elutasított feliratkozások No comment provided by engineer. @@ -6127,6 +6235,7 @@ Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban.< Temporary file error + Ideiglenes fájlhiba No comment provided by engineer. @@ -6268,6 +6377,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Themes + Témák No comment provided by engineer. @@ -6337,6 +6447,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. This link was used with another mobile device, please create a new link on the desktop. + Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén. No comment provided by engineer. @@ -6346,6 +6457,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. Title + Cím No comment provided by engineer. @@ -6417,6 +6529,7 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej Total + Összesen No comment provided by engineer. @@ -6426,6 +6539,7 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej Transport sessions + Munkamenetek átvitele No comment provided by engineer. @@ -6622,6 +6736,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Upload errors + Feltöltési hibák No comment provided by engineer. @@ -6636,10 +6751,12 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Uploaded + Feltöltve No comment provided by engineer. Uploaded files + Feltöltött fájlok No comment provided by engineer. @@ -6719,6 +6836,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol User selection + Felhasználó kiválasztása No comment provided by engineer. @@ -6858,10 +6976,12 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Wallpaper accent + Háttérkép kiemelés No comment provided by engineer. Wallpaper background + Háttérkép háttérszíne No comment provided by engineer. @@ -6971,6 +7091,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Wrong key or unknown file chunk address - most likely file is deleted. + Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött. file error text @@ -6980,6 +7101,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol XFTP server + XFTP-kiszolgáló No comment provided by engineer. @@ -7066,6 +7188,7 @@ Csatlakozási kérés megismétlése? You are not connected to these servers. Private routing is used to deliver messages to them. + Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál. No comment provided by engineer. @@ -7476,6 +7599,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. attempts + próbálkozások No comment provided by engineer. @@ -7670,6 +7794,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. decryption errors + visszafejtési hibák No comment provided by engineer. @@ -7724,6 +7849,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. duplicates + duplikációk No comment provided by engineer. @@ -7808,6 +7934,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. expired + lejárt No comment provided by engineer. @@ -7842,6 +7969,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. inactive + inaktív No comment provided by engineer. @@ -8023,10 +8151,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. other + egyéb No comment provided by engineer. other errors + egyéb hibák No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index b41a5ddb86..b0d9308716 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -554,6 +554,7 @@ Accent + Principale No comment provided by engineer. @@ -579,10 +580,12 @@ Acknowledged + Riconosciuto No comment provided by engineer. Acknowledgement errors + Errori di riconoscimento No comment provided by engineer. @@ -631,14 +634,17 @@ Additional accent + Principale aggiuntivo No comment provided by engineer. Additional accent 2 + Principale aggiuntivo 2 No comment provided by engineer. Additional secondary + Secondario aggiuntivo No comment provided by engineer. @@ -668,6 +674,7 @@ Advanced settings + Impostazioni avanzate No comment provided by engineer. @@ -687,6 +694,7 @@ All data is private to your device. + Tutti i dati sono privati, nel tuo dispositivo. No comment provided by engineer. @@ -711,6 +719,7 @@ All profiles + Tutti gli profili No comment provided by engineer. @@ -915,6 +924,7 @@ Apply to + Applica a No comment provided by engineer. @@ -994,6 +1004,7 @@ Background + Sfondo No comment provided by engineer. @@ -1023,6 +1034,7 @@ Black + Nero No comment provided by engineer. @@ -1137,6 +1149,7 @@ Cannot forward message + Impossibile inoltrare il messaggio No comment provided by engineer. @@ -1212,6 +1225,7 @@ Chat colors + Colori della chat No comment provided by engineer. @@ -1261,6 +1275,7 @@ Chat theme + Tema della chat No comment provided by engineer. @@ -1295,14 +1310,17 @@ Chunks deleted + Blocchi eliminati No comment provided by engineer. Chunks downloaded + Blocchi scaricati No comment provided by engineer. Chunks uploaded + Blocchi inviati No comment provided by engineer. @@ -1332,6 +1350,7 @@ Color mode + Modalità di colore No comment provided by engineer. @@ -1346,6 +1365,7 @@ Completed + Completato No comment provided by engineer. @@ -1463,6 +1483,7 @@ Questo è il tuo link una tantum! Connected + Connesso No comment provided by engineer. @@ -1472,6 +1493,7 @@ Questo è il tuo link una tantum! Connected servers + Server connessi No comment provided by engineer. @@ -1481,6 +1503,7 @@ Questo è il tuo link una tantum! Connecting + In connessione No comment provided by engineer. @@ -1530,10 +1553,12 @@ Questo è il tuo link una tantum! Connection with desktop stopped + Connessione con il desktop fermata No comment provided by engineer. Connections + Connessioni No comment provided by engineer. @@ -1593,6 +1618,7 @@ Questo è il tuo link una tantum! Copy error + Copia errore No comment provided by engineer. @@ -1672,6 +1698,7 @@ Questo è il tuo link una tantum! Created + Creato No comment provided by engineer. @@ -1711,6 +1738,7 @@ Questo è il tuo link una tantum! Current profile + Profilo attuale No comment provided by engineer. @@ -1725,6 +1753,7 @@ Questo è il tuo link una tantum! Customize theme + Personalizza il tema No comment provided by engineer. @@ -1734,6 +1763,7 @@ Questo è il tuo link una tantum! Dark mode colors + Colori modalità scura No comment provided by engineer. @@ -2043,6 +2073,7 @@ Non è reversibile! Deleted + Eliminato No comment provided by engineer. @@ -2057,6 +2088,7 @@ Non è reversibile! Deletion errors + Errori di eliminazione No comment provided by engineer. @@ -2101,10 +2133,12 @@ Non è reversibile! Detailed statistics + Statistiche dettagliate No comment provided by engineer. Details + Dettagli No comment provided by engineer. @@ -2264,6 +2298,7 @@ Non è reversibile! Download errors + Errori di scaricamento No comment provided by engineer. @@ -2278,10 +2313,12 @@ Non è reversibile! Downloaded + Scaricato No comment provided by engineer. Downloaded files + File scaricati No comment provided by engineer. @@ -2656,6 +2693,7 @@ Non è reversibile! Error exporting theme: %@ + Errore di esportazione del tema: %@ No comment provided by engineer. @@ -2685,10 +2723,12 @@ Non è reversibile! Error reconnecting server + Errore di riconnessione al server No comment provided by engineer. Error reconnecting servers + Errore di riconnessione ai server No comment provided by engineer. @@ -2698,6 +2738,7 @@ Non è reversibile! Error resetting statistics + Errore di azzeramento statistiche No comment provided by engineer. @@ -2833,6 +2874,7 @@ Non è reversibile! Errors + Errori No comment provided by engineer. @@ -2862,6 +2904,7 @@ Non è reversibile! Export theme + Esporta tema No comment provided by engineer. @@ -2901,22 +2944,27 @@ Non è reversibile! File error + Errore del file No comment provided by engineer. File not found - most likely file was deleted or cancelled. + File non trovato - probabilmente è stato eliminato o annullato. file error text File server error: %@ + Errore del server dei file: %@ file error text File status + Stato del file No comment provided by engineer. File status: %@ + Stato del file: %@ copied message info @@ -3110,10 +3158,12 @@ Errore: %2$@ Good afternoon! + Buon pomeriggio! message preview Good morning! + Buongiorno! message preview @@ -3398,6 +3448,7 @@ Errore: %2$@ Import theme + Importa tema No comment provided by engineer. @@ -3524,6 +3575,7 @@ Errore: %2$@ Interface colors + Colori dell'interfaccia No comment provided by engineer. @@ -3871,6 +3923,7 @@ Questo è il tuo link per il gruppo %@! Member inactive + Membro inattivo item status text @@ -3890,6 +3943,7 @@ Questo è il tuo link per il gruppo %@! Menus + Menu No comment provided by engineer. @@ -3914,10 +3968,12 @@ Questo è il tuo link per il gruppo %@! Message forwarded + Messaggio inoltrato item status text Message may be delivered later if member becomes active. + Il messaggio può essere consegnato più tardi se il membro diventa attivo. item status description @@ -3961,10 +4017,12 @@ Questo è il tuo link per il gruppo %@! Message status + Stato del messaggio No comment provided by engineer. Message status: %@ + Stato del messaggio: %@ copied message info @@ -3994,10 +4052,12 @@ Questo è il tuo link per il gruppo %@! Messages received + Messaggi ricevuti No comment provided by engineer. Messages sent + Messaggi inviati No comment provided by engineer. @@ -4237,6 +4297,7 @@ Questo è il tuo link per il gruppo %@! No direct connection yet, message is forwarded by admin. + Ancora nessuna connessione diretta, il messaggio viene inoltrato dall'amministratore. item status description @@ -4256,6 +4317,7 @@ Questo è il tuo link per il gruppo %@! No info, try to reload + Nessuna informazione, prova a ricaricare No comment provided by engineer. @@ -4444,6 +4506,7 @@ Questo è il tuo link per il gruppo %@! Open server settings + Apri impostazioni server No comment provided by engineer. @@ -4557,6 +4620,7 @@ Questo è il tuo link per il gruppo %@! Pending + In attesa No comment provided by engineer. @@ -4587,6 +4651,8 @@ Questo è il tuo link per il gruppo %@! Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection. Please share any other issues with the developers. + Controlla che mobile e desktop siano collegati alla stessa rete locale e che il firewall del desktop consenta la connessione. +Si prega di condividere qualsiasi altro problema con gli sviluppatori. No comment provided by engineer. @@ -4692,6 +4758,7 @@ Errore: %@ Previously connected servers + Server precedentemente connessi No comment provided by engineer. @@ -4765,6 +4832,7 @@ Errore: %@ Profile theme + Tema del profilo No comment provided by engineer. @@ -4851,10 +4919,12 @@ Attivalo nelle impostazioni *Rete e server*. Proxied + Via proxy No comment provided by engineer. Proxied servers + Server via proxy No comment provided by engineer. @@ -4924,6 +4994,7 @@ Attivalo nelle impostazioni *Rete e server*. Receive errors + Errori di ricezione No comment provided by engineer. @@ -4948,14 +5019,17 @@ Attivalo nelle impostazioni *Rete e server*. Received messages + Messaggi ricevuti No comment provided by engineer. Received reply + Risposta ricevuta No comment provided by engineer. Received total + Totale ricevuto No comment provided by engineer. @@ -4990,6 +5064,7 @@ Attivalo nelle impostazioni *Rete e server*. Reconnect + Riconnetti No comment provided by engineer. @@ -4999,18 +5074,22 @@ Attivalo nelle impostazioni *Rete e server*. Reconnect all servers + Riconnetti tutti i server No comment provided by engineer. Reconnect all servers? + Riconnettere tutti i server? No comment provided by engineer. Reconnect server to force message delivery. It uses additional traffic. + Riconnetti il server per forzare la consegna dei messaggi. Usa traffico aggiuntivo. No comment provided by engineer. Reconnect server? + Riconnettere il server? No comment provided by engineer. @@ -5065,6 +5144,7 @@ Attivalo nelle impostazioni *Rete e server*. Remove image + Rimuovi immagine No comment provided by engineer. @@ -5139,10 +5219,12 @@ Attivalo nelle impostazioni *Rete e server*. Reset all statistics + Azzera tutte le statistiche No comment provided by engineer. Reset all statistics? + Azzerare tutte le statistiche? No comment provided by engineer. @@ -5152,6 +5234,7 @@ Attivalo nelle impostazioni *Rete e server*. Reset to app theme + Ripristina al tema dell'app No comment provided by engineer. @@ -5161,6 +5244,7 @@ Attivalo nelle impostazioni *Rete e server*. Reset to user theme + Ripristina al tema dell'utente No comment provided by engineer. @@ -5235,6 +5319,7 @@ Attivalo nelle impostazioni *Rete e server*. SMP server + Server SMP No comment provided by engineer. @@ -5354,10 +5439,12 @@ Attivalo nelle impostazioni *Rete e server*. Scale + Scala No comment provided by engineer. Scan / Paste link + Scansiona / Incolla link No comment provided by engineer. @@ -5402,6 +5489,7 @@ Attivalo nelle impostazioni *Rete e server*. Secondary + Secondario No comment provided by engineer. @@ -5411,6 +5499,7 @@ Attivalo nelle impostazioni *Rete e server*. Secured + Protetto No comment provided by engineer. @@ -5430,6 +5519,7 @@ Attivalo nelle impostazioni *Rete e server*. Selected chat preferences prohibit this message. + Le preferenze della chat selezionata vietano questo messaggio. No comment provided by engineer. @@ -5484,6 +5574,7 @@ Attivalo nelle impostazioni *Rete e server*. Send errors + Errori di invio No comment provided by engineer. @@ -5598,6 +5689,7 @@ Attivalo nelle impostazioni *Rete e server*. Sent directly + Inviato direttamente No comment provided by engineer. @@ -5612,6 +5704,7 @@ Attivalo nelle impostazioni *Rete e server*. Sent messages + Messaggi inviati No comment provided by engineer. @@ -5621,18 +5714,22 @@ Attivalo nelle impostazioni *Rete e server*. Sent reply + Risposta inviata No comment provided by engineer. Sent total + Totale inviato No comment provided by engineer. Sent via proxy + Inviato via proxy No comment provided by engineer. Server address + Indirizzo server No comment provided by engineer. @@ -5661,6 +5758,7 @@ Attivalo nelle impostazioni *Rete e server*. Server type + Tipo server No comment provided by engineer. @@ -5683,10 +5781,12 @@ Attivalo nelle impostazioni *Rete e server*. Servers info + Info dei server No comment provided by engineer. Servers statistics will be reset - this cannot be undone! + Le statistiche dei server verranno azzerate - è irreversibile! No comment provided by engineer. @@ -5706,6 +5806,7 @@ Attivalo nelle impostazioni *Rete e server*. Set default theme + Imposta tema predefinito No comment provided by engineer. @@ -5834,6 +5935,7 @@ Attivalo nelle impostazioni *Rete e server*. SimpleX + SimpleX No comment provided by engineer. @@ -5913,6 +6015,7 @@ Attivalo nelle impostazioni *Rete e server*. Size + Dimensione No comment provided by engineer. @@ -5962,10 +6065,12 @@ Attivalo nelle impostazioni *Rete e server*. Starting from %@. + Inizio da %@. No comment provided by engineer. Statistics + Statistiche No comment provided by engineer. @@ -6035,14 +6140,17 @@ Attivalo nelle impostazioni *Rete e server*. Subscribed + Iscritto No comment provided by engineer. Subscription errors + Errori di iscrizione No comment provided by engineer. Subscriptions ignored + Iscrizioni ignorate No comment provided by engineer. @@ -6127,6 +6235,7 @@ Attivalo nelle impostazioni *Rete e server*. Temporary file error + Errore del file temporaneo No comment provided by engineer. @@ -6268,6 +6377,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. Themes + Temi No comment provided by engineer. @@ -6337,6 +6447,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. This link was used with another mobile device, please create a new link on the desktop. + Questo link è stato usato con un altro dispositivo mobile, creane uno nuovo sul desktop. No comment provided by engineer. @@ -6346,6 +6457,7 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa. Title + Titoli No comment provided by engineer. @@ -6417,6 +6529,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Total + Totale No comment provided by engineer. @@ -6426,6 +6539,7 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Transport sessions + Sessioni di trasporto No comment provided by engineer. @@ -6622,6 +6736,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Upload errors + Errori di invio No comment provided by engineer. @@ -6636,10 +6751,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Uploaded + Inviato No comment provided by engineer. Uploaded files + File inviati No comment provided by engineer. @@ -6719,6 +6836,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e User selection + Selezione utente No comment provided by engineer. @@ -6858,10 +6976,12 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Wallpaper accent + Tinta dello sfondo No comment provided by engineer. Wallpaper background + Retro dello sfondo No comment provided by engineer. @@ -6971,6 +7091,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Wrong key or unknown file chunk address - most likely file is deleted. + Chiave sbagliata o indirizzo sconosciuto per frammento del file - probabilmente il file è stato eliminato. file error text @@ -6980,6 +7101,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e XFTP server + Server XFTP No comment provided by engineer. @@ -7066,6 +7188,7 @@ Ripetere la richiesta di ingresso? You are not connected to these servers. Private routing is used to deliver messages to them. + Non sei connesso/a a questi server. L'instradamento privato è usato per consegnare loro i messaggi. No comment provided by engineer. @@ -7476,6 +7599,7 @@ I server di SimpleX non possono vedere il tuo profilo. attempts + tentativi No comment provided by engineer. @@ -7670,6 +7794,7 @@ I server di SimpleX non possono vedere il tuo profilo. decryption errors + errori di decifrazione No comment provided by engineer. @@ -7724,6 +7849,7 @@ I server di SimpleX non possono vedere il tuo profilo. duplicates + doppi No comment provided by engineer. @@ -7808,6 +7934,7 @@ I server di SimpleX non possono vedere il tuo profilo. expired + scaduto No comment provided by engineer. @@ -7842,6 +7969,7 @@ I server di SimpleX non possono vedere il tuo profilo. inactive + inattivo No comment provided by engineer. @@ -8023,10 +8151,12 @@ I server di SimpleX non possono vedere il tuo profilo. other + altro No comment provided by engineer. other errors + altri errori No comment provided by engineer. diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 9748a26730..0d5dbb7575 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ е потвърдено"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ сървъри"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ качено"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 95c79efd8b..e12a12f474 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -130,9 +130,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ je ověřený"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ servery"; - /* notification title */ "%@ wants to connect!" = "%@ se chce připojit!"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 7464e096c4..79a7e8536d 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ wurde erfolgreich überprüft"; -/* No comment provided by engineer. */ -"%@ servers" = "%@-Server"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ hochgeladen"; @@ -337,6 +334,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "Danach die gewünschte Aktion auswählen:"; +/* No comment provided by engineer. */ +"Accent" = "Akzent"; + /* accept contact request via notification accept incoming call via notification */ "Accept" = "Annehmen"; @@ -353,6 +353,12 @@ /* call status */ "accepted call" = "Anruf angenommen"; +/* No comment provided by engineer. */ +"Acknowledged" = "Bestätigt"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "Fehler bei der Bestätigung"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; @@ -377,6 +383,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "Begrüßungsmeldung hinzufügen"; +/* No comment provided by engineer. */ +"Additional accent" = "Erste Akzentfarbe"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "Zusätzlicher Akzent 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "Zweite Akzentfarbe"; + /* No comment provided by engineer. */ "Address" = "Adresse"; @@ -398,6 +413,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "Erweiterte Netzwerkeinstellungen"; +/* No comment provided by engineer. */ +"Advanced settings" = "Erweiterte Einstellungen"; + /* chat item text */ "agreeing encryption for %@…" = "Verschlüsselung von %@ zustimmen…"; @@ -408,11 +426,14 @@ "All app data is deleted." = "Werden die App-Daten komplett gelöscht."; /* No comment provided by engineer. */ -"All chats and messages will be deleted - this cannot be undone!" = "Alle Chats und Nachrichten werden gelöscht! Dies kann nicht rückgängig gemacht werden!"; +"All chats and messages will be deleted - this cannot be undone!" = "Alle Chats und Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "All data is erased when it is entered." = "Alle Daten werden gelöscht, sobald dieser eingegeben wird."; +/* No comment provided by engineer. */ +"All data is private to your device." = "Alle Daten sind auf Ihrem Gerät geschützt."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Alle Gruppenmitglieder bleiben verbunden."; @@ -420,14 +441,17 @@ "all members" = "Alle Mitglieder"; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone!" = "Es werden alle Nachrichten gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden!"; +"All messages will be deleted - this cannot be undone!" = "Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ -"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht."; +"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Alle Nachrichten werden gelöscht . Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht."; /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Von %@ werden alle neuen Nachrichten ausgeblendet!"; +/* No comment provided by engineer. */ +"All profiles" = "Alle Profile"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Alle Ihre Kontakte bleiben verbunden."; @@ -554,6 +578,9 @@ /* No comment provided by engineer. */ "Apply" = "Anwenden"; +/* No comment provided by engineer. */ +"Apply to" = "Anwenden auf"; + /* No comment provided by engineer. */ "Archive and upload" = "Archivieren und Hochladen"; @@ -563,6 +590,9 @@ /* No comment provided by engineer. */ "Attach" = "Anhängen"; +/* No comment provided by engineer. */ +"attempts" = "Versuche"; + /* No comment provided by engineer. */ "Audio & video calls" = "Audio- & Videoanrufe"; @@ -605,6 +635,9 @@ /* No comment provided by engineer. */ "Back" = "Zurück"; +/* No comment provided by engineer. */ +"Background" = "Hintergrund-Farbe"; + /* No comment provided by engineer. */ "Bad desktop address" = "Falsche Desktop-Adresse"; @@ -626,6 +659,9 @@ /* No comment provided by engineer. */ "Better messages" = "Verbesserungen bei Nachrichten"; +/* No comment provided by engineer. */ +"Black" = "Schwarz"; + /* No comment provided by engineer. */ "Block" = "Blockieren"; @@ -716,6 +752,9 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Die App kann nicht auf den Schlüsselbund zugreifen, um das Datenbank-Passwort zu speichern"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Die Nachricht kann nicht weitergeleitet werden"; + /* No comment provided by engineer. */ "Cannot receive file" = "Datei kann nicht empfangen werden"; @@ -774,6 +813,9 @@ /* No comment provided by engineer. */ "Chat archive" = "Datenbank Archiv"; +/* No comment provided by engineer. */ +"Chat colors" = "Chat-Farben"; + /* No comment provided by engineer. */ "Chat console" = "Chat-Konsole"; @@ -801,6 +843,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Chat-Präferenzen"; +/* No comment provided by engineer. */ +"Chat theme" = "Chat-Design"; + /* No comment provided by engineer. */ "Chats" = "Chats"; @@ -819,6 +864,15 @@ /* No comment provided by engineer. */ "Choose from library" = "Aus dem Fotoalbum auswählen"; +/* No comment provided by engineer. */ +"Chunks deleted" = "Daten-Pakete gelöscht"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "Daten-Pakete heruntergeladen"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "Daten-Pakete hochgeladen"; + /* No comment provided by engineer. */ "Clear" = "Löschen"; @@ -834,6 +888,9 @@ /* No comment provided by engineer. */ "Clear verification" = "Überprüfung zurücknehmen"; +/* No comment provided by engineer. */ +"Color mode" = "Farbvariante"; + /* No comment provided by engineer. */ "colored" = "farbig"; @@ -846,6 +903,9 @@ /* No comment provided by engineer. */ "complete" = "vollständig"; +/* No comment provided by engineer. */ +"Completed" = "Abgeschlossen"; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE-Server konfigurieren"; @@ -915,18 +975,27 @@ /* No comment provided by engineer. */ "connected" = "Verbunden"; +/* No comment provided by engineer. */ +"Connected" = "Verbunden"; + /* No comment provided by engineer. */ "Connected desktop" = "Verbundener Desktop"; /* rcv group event chat item */ "connected directly" = "Direkt miteinander verbunden"; +/* No comment provided by engineer. */ +"Connected servers" = "Verbundene Server"; + /* No comment provided by engineer. */ "Connected to desktop" = "Mit dem Desktop verbunden"; /* No comment provided by engineer. */ "connecting" = "verbinde"; +/* No comment provided by engineer. */ +"Connecting" = "Verbinden"; + /* No comment provided by engineer. */ "connecting (accepted)" = "Verbindung (angenommen)"; @@ -975,9 +1044,15 @@ /* No comment provided by engineer. */ "Connection timeout" = "Verbindungszeitüberschreitung"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "Die Verbindung mit dem Desktop wurde gestoppt"; + /* connection information */ "connection:%@" = "Verbindung:%@"; +/* No comment provided by engineer. */ +"Connections" = "Verbindungen"; + /* profile update event chat item */ "contact %@ changed to %@" = "Der Kontaktname wurde von %1$@ auf %2$@ geändert"; @@ -1020,6 +1095,9 @@ /* No comment provided by engineer. */ "Copy" = "Kopieren"; +/* No comment provided by engineer. */ +"Copy error" = "Fehlermeldung kopieren"; + /* No comment provided by engineer. */ "Core version: v%@" = "Core Version: v%@"; @@ -1065,6 +1143,9 @@ /* No comment provided by engineer. */ "Create your profile" = "Erstellen Sie Ihr Profil"; +/* No comment provided by engineer. */ +"Created" = "Erstellt"; + /* No comment provided by engineer. */ "Created at" = "Erstellt um"; @@ -1089,6 +1170,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "Aktuelles Passwort…"; +/* No comment provided by engineer. */ +"Current profile" = "Aktueller Profil"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Die derzeit maximal unterstützte Dateigröße beträgt %@."; @@ -1098,9 +1182,15 @@ /* No comment provided by engineer. */ "Custom time" = "Zeit anpassen"; +/* No comment provided by engineer. */ +"Customize theme" = "Design anpassen"; + /* No comment provided by engineer. */ "Dark" = "Dunkel"; +/* No comment provided by engineer. */ +"Dark mode colors" = "Farben für die dunkle Variante"; + /* No comment provided by engineer. */ "Database downgrade" = "Datenbank auf alte Version herabstufen"; @@ -1170,6 +1260,9 @@ /* message decrypt error item */ "Decryption error" = "Entschlüsselungsfehler"; +/* No comment provided by engineer. */ +"decryption errors" = "Entschlüsselungs-Fehler"; + /* pref value */ "default (%@)" = "Voreinstellung (%@)"; @@ -1222,7 +1315,7 @@ "Delete Contact" = "Kontakt löschen"; /* No comment provided by engineer. */ -"Delete contact?\nThis cannot be undone!" = "Kontakt löschen?\nDas kann nicht rückgängig gemacht werden!"; +"Delete contact?\nThis cannot be undone!" = "Kontakt löschen?\nDies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "Delete database" = "Datenbank löschen"; @@ -1282,7 +1375,7 @@ "Delete pending connection" = "Ausstehende Verbindung löschen"; /* No comment provided by engineer. */ -"Delete pending connection?" = "Die ausstehende Verbindung löschen?"; +"Delete pending connection?" = "Ausstehende Verbindung löschen?"; /* No comment provided by engineer. */ "Delete profile" = "Profil löschen"; @@ -1296,6 +1389,9 @@ /* deleted chat item */ "deleted" = "Gelöscht"; +/* No comment provided by engineer. */ +"Deleted" = "Gelöscht"; + /* No comment provided by engineer. */ "Deleted at" = "Gelöscht um"; @@ -1308,6 +1404,9 @@ /* rcv group event chat item */ "deleted group" = "Gruppe gelöscht"; +/* No comment provided by engineer. */ +"Deletion errors" = "Fehler beim Löschen"; + /* No comment provided by engineer. */ "Delivery" = "Zustellung"; @@ -1332,6 +1431,12 @@ /* snd error text */ "Destination server error: %@" = "Zielserver-Fehler: %@"; +/* No comment provided by engineer. */ +"Detailed statistics" = "Detaillierte Statistiken"; + +/* No comment provided by engineer. */ +"Details" = "Details"; + /* No comment provided by engineer. */ "Develop" = "Entwicklung"; @@ -1434,12 +1539,21 @@ /* chat item action */ "Download" = "Herunterladen"; +/* No comment provided by engineer. */ +"Download errors" = "Fehler beim Herunterladen"; + /* No comment provided by engineer. */ "Download failed" = "Herunterladen fehlgeschlagen"; /* server test step */ "Download file" = "Datei herunterladen"; +/* No comment provided by engineer. */ +"Downloaded" = "Heruntergeladen"; + +/* No comment provided by engineer. */ +"Downloaded files" = "Heruntergeladene Dateien"; + /* No comment provided by engineer. */ "Downloading archive" = "Archiv wird heruntergeladen"; @@ -1452,6 +1566,9 @@ /* integrity error chat item */ "duplicate message" = "Doppelte Nachricht"; +/* No comment provided by engineer. */ +"duplicates" = "Duplikate"; + /* No comment provided by engineer. */ "Duration" = "Dauer"; @@ -1710,6 +1827,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "Fehler beim Exportieren der Chat-Datenbank"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "Fehler beim Exportieren des Designs: %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "Fehler beim Importieren der Chat-Datenbank"; @@ -1725,9 +1845,18 @@ /* No comment provided by engineer. */ "Error receiving file" = "Fehler beim Empfangen der Datei"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "Fehler beim Wiederherstellen der Verbindung zum Server"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "Fehler beim Wiederherstellen der Verbindungen zu den Servern"; + /* No comment provided by engineer. */ "Error removing member" = "Fehler beim Entfernen des Mitglieds"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "Fehler beim Zurücksetzen der Statistiken"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "Fehler beim Speichern der %@-Server"; @@ -1807,6 +1936,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "Fehler: URL ist ungültig"; +/* No comment provided by engineer. */ +"Errors" = "Fehler"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Auch wenn sie im Chat deaktiviert sind."; @@ -1819,12 +1951,18 @@ /* chat item action */ "Expand" = "Erweitern"; +/* No comment provided by engineer. */ +"expired" = "abgelaufen"; + /* No comment provided by engineer. */ "Export database" = "Datenbank exportieren"; /* No comment provided by engineer. */ "Export error:" = "Fehler beim Export:"; +/* No comment provided by engineer. */ +"Export theme" = "Design exportieren"; + /* No comment provided by engineer. */ "Exported database archive." = "Exportiertes Datenbankarchiv."; @@ -1846,6 +1984,21 @@ /* No comment provided by engineer. */ "Favorite" = "Favorit"; +/* No comment provided by engineer. */ +"File error" = "Datei-Fehler"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen."; + +/* file error text */ +"File server error: %@" = "Datei-Server Fehler: %@"; + +/* No comment provided by engineer. */ +"File status" = "Datei-Status"; + +/* copied message info */ +"File status: %@" = "Datei-Status: %@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "Die Datei wird von den Servern gelöscht."; @@ -1960,6 +2113,12 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GIFs und Sticker"; +/* message preview */ +"Good afternoon!" = "Guten Nachmittag!"; + +/* message preview */ +"Good morning!" = "Guten Morgen!"; + /* No comment provided by engineer. */ "Group" = "Gruppe"; @@ -2039,10 +2198,10 @@ "Group welcome message" = "Gruppen-Begrüßungsmeldung"; /* No comment provided by engineer. */ -"Group will be deleted for all members - this cannot be undone!" = "Die Gruppe wird für alle Mitglieder gelöscht - dies kann nicht rückgängig gemacht werden!"; +"Group will be deleted for all members - this cannot be undone!" = "Die Gruppe wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ -"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird für Sie gelöscht - dies kann nicht rückgängig gemacht werden!"; +"Group will be deleted for you - this cannot be undone!" = "Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!"; /* No comment provided by engineer. */ "Help" = "Hilfe"; @@ -2137,6 +2296,9 @@ /* No comment provided by engineer. */ "Import failed" = "Import ist fehlgeschlagen"; +/* No comment provided by engineer. */ +"Import theme" = "Design importieren"; + /* No comment provided by engineer. */ "Importing archive" = "Archiv wird importiert"; @@ -2158,6 +2320,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "Klingeltöne"; +/* No comment provided by engineer. */ +"inactive" = "Inaktiv"; + /* No comment provided by engineer. */ "Incognito" = "Inkognito"; @@ -2221,6 +2386,9 @@ /* No comment provided by engineer. */ "Interface" = "Schnittstelle"; +/* No comment provided by engineer. */ +"Interface colors" = "Interface-Farben"; + /* invalid chat data */ "invalid chat" = "Ungültiger Chat"; @@ -2473,6 +2641,9 @@ /* rcv group event chat item */ "member connected" = "ist der Gruppe beigetreten"; +/* item status text */ +"Member inactive" = "Mitglied inaktiv"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Die Mitgliederrolle wird auf \"%@\" geändert. Alle Mitglieder der Gruppe werden benachrichtigt."; @@ -2480,7 +2651,10 @@ "Member role will be changed to \"%@\". The member will receive a new invitation." = "Die Mitgliederrolle wird auf \"%@\" geändert. Das Mitglied wird eine neue Einladung erhalten."; /* No comment provided by engineer. */ -"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt - dies kann nicht rückgängig gemacht werden!"; +"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!"; + +/* No comment provided by engineer. */ +"Menus" = "Menüs"; /* item status text */ "Message delivery error" = "Fehler bei der Nachrichtenzustellung"; @@ -2494,6 +2668,12 @@ /* No comment provided by engineer. */ "Message draft" = "Nachrichtenentwurf"; +/* item status text */ +"Message forwarded" = "Nachricht weitergeleitet"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird."; + /* No comment provided by engineer. */ "Message queue info" = "Nachrichten-Warteschlangen-Information"; @@ -2518,6 +2698,12 @@ /* No comment provided by engineer. */ "Message source remains private." = "Die Nachrichtenquelle bleibt privat."; +/* No comment provided by engineer. */ +"Message status" = "Nachrichten-Status"; + +/* copied message info */ +"Message status: %@" = "Nachrichten-Status: %@"; + /* No comment provided by engineer. */ "Message text" = "Nachrichtentext"; @@ -2533,6 +2719,12 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!"; +/* No comment provided by engineer. */ +"Messages received" = "Empfangene Nachrichten"; + +/* No comment provided by engineer. */ +"Messages sent" = "Gesendete Nachrichten"; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; @@ -2698,6 +2890,9 @@ /* No comment provided by engineer. */ "No device token!" = "Kein Geräte-Token!"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "Bisher keine direkte Verbindung. Nachricht wird von einem Admin weitergeleitet."; + /* No comment provided by engineer. */ "no e2e encryption" = "Keine E2E-Verschlüsselung"; @@ -2710,6 +2905,9 @@ /* No comment provided by engineer. */ "No history" = "Kein Nachrichtenverlauf"; +/* No comment provided by engineer. */ +"No info, try to reload" = "Keine Information - es wird versucht neu zu laden"; + /* No comment provided by engineer. */ "No network connection" = "Keine Netzwerkverbindung"; @@ -2835,6 +3033,9 @@ /* authentication reason */ "Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; +/* No comment provided by engineer. */ +"Open server settings" = "Server-Einstellungen öffnen"; + /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; @@ -2859,9 +3060,15 @@ /* No comment provided by engineer. */ "Or show this code" = "Oder diesen QR-Code anzeigen"; +/* No comment provided by engineer. */ +"other" = "andere"; + /* No comment provided by engineer. */ "Other" = "Andere"; +/* No comment provided by engineer. */ +"other errors" = "Andere Fehler"; + /* member role */ "owner" = "Eigentümer"; @@ -2904,6 +3111,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "Peer-to-Peer"; +/* No comment provided by engineer. */ +"Pending" = "Ausstehend"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Verbindungen mit Kontakten sind nur über Links möglich, die Sie oder Ihre Kontakte untereinander teilen."; @@ -2925,6 +3135,9 @@ /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Bitten Sie Ihren Kontakt darum, das Senden von Sprachnachrichten zu aktivieren."; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Bitte überprüfen Sie, ob sich das Mobiltelefon und die Desktop-App im gleichen lokalen Netzwerk befinden, und die Desktop-Firewall die Verbindung erlaubt.\nBitte teilen Sie weitere mögliche Probleme den Entwicklern mit."; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Überprüfen Sie bitte, ob Sie den richtigen Link genutzt haben oder bitten Sie Ihren Kontakt nochmal darum, Ihnen einen Link zuzusenden."; @@ -2982,6 +3195,9 @@ /* No comment provided by engineer. */ "Preview" = "Vorschau"; +/* No comment provided by engineer. */ +"Previously connected servers" = "Bisher verbundene Server"; + /* No comment provided by engineer. */ "Privacy & security" = "Datenschutz & Sicherheit"; @@ -3021,6 +3237,9 @@ /* No comment provided by engineer. */ "Profile password" = "Passwort für Profil"; +/* No comment provided by engineer. */ +"Profile theme" = "Profil-Design"; + /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "Profil-Aktualisierung wird an Ihre Kontakte gesendet."; @@ -3069,6 +3288,12 @@ /* No comment provided by engineer. */ "Protocol timeout per KB" = "Protokollzeitüberschreitung pro kB"; +/* No comment provided by engineer. */ +"Proxied" = "Proxy"; + +/* No comment provided by engineer. */ +"Proxied servers" = "Proxy-Server"; + /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; @@ -3111,6 +3336,9 @@ /* No comment provided by engineer. */ "Receipts are disabled" = "Bestätigungen sind deaktiviert"; +/* No comment provided by engineer. */ +"Receive errors" = "Fehler beim Empfang"; + /* No comment provided by engineer. */ "received answer…" = "Antwort erhalten…"; @@ -3129,6 +3357,15 @@ /* message info title */ "Received message" = "Empfangene Nachricht"; +/* No comment provided by engineer. */ +"Received messages" = "Empfangene Nachrichten"; + +/* No comment provided by engineer. */ +"Received reply" = "Empfangene Antwort"; + +/* No comment provided by engineer. */ +"Received total" = "Summe aller empfangenen Nachrichten"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Die Empfängeradresse wird auf einen anderen Server geändert. Der Adresswechsel wird abgeschlossen, wenn der Absender wieder online ist."; @@ -3147,9 +3384,24 @@ /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Die Empfänger sehen Nachrichtenaktualisierungen, während Sie sie eingeben."; +/* No comment provided by engineer. */ +"Reconnect" = "Neu verbinden"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Alle verbundenen Server werden neu verbunden, um die Zustellung der Nachricht zu erzwingen. Dies verursacht zusätzlichen Datenverkehr."; +/* No comment provided by engineer. */ +"Reconnect all servers" = "Alle Server neu verbinden"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "Alle Server neu verbinden?"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "Um die Auslieferung von Nachrichten zu erzwingen, wird der Server neu verbunden. Dafür wird weiterer Datenverkehr benötigt."; + +/* No comment provided by engineer. */ +"Reconnect server?" = "Server neu verbinden?"; + /* No comment provided by engineer. */ "Reconnect servers?" = "Die Server neu verbinden?"; @@ -3183,6 +3435,9 @@ /* No comment provided by engineer. */ "Remove" = "Entfernen"; +/* No comment provided by engineer. */ +"Remove image" = "Bild entfernen"; + /* No comment provided by engineer. */ "Remove member" = "Mitglied entfernen"; @@ -3240,12 +3495,24 @@ /* No comment provided by engineer. */ "Reset" = "Zurücksetzen"; +/* No comment provided by engineer. */ +"Reset all statistics" = "Alle Statistiken zurücksetzen"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "Alle Statistiken zurücksetzen?"; + /* No comment provided by engineer. */ "Reset colors" = "Farben zurücksetzen"; +/* No comment provided by engineer. */ +"Reset to app theme" = "Auf das App-Design zurücksetzen"; + /* No comment provided by engineer. */ "Reset to defaults" = "Auf Voreinstellungen zurücksetzen"; +/* No comment provided by engineer. */ +"Reset to user theme" = "Auf das Benutzer-spezifische Design zurücksetzen"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "Um ein neues Chat-Profil zu erstellen, starten Sie die App neu"; @@ -3360,6 +3627,12 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Gespeicherte WebRTC ICE-Server werden entfernt"; +/* No comment provided by engineer. */ +"Scale" = "Skalieren"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "Link scannen / einfügen"; + /* No comment provided by engineer. */ "Scan code" = "Code scannen"; @@ -3387,6 +3660,9 @@ /* network option */ "sec" = "sek"; +/* No comment provided by engineer. */ +"Secondary" = "Zweite Farbe"; + /* time unit */ "seconds" = "Sekunden"; @@ -3396,6 +3672,9 @@ /* server test step */ "Secure queue" = "Sichere Warteschlange"; +/* No comment provided by engineer. */ +"Secured" = "Abgesichert"; + /* No comment provided by engineer. */ "Security assessment" = "Sicherheits-Gutachten"; @@ -3408,6 +3687,9 @@ /* No comment provided by engineer. */ "Select" = "Auswählen"; +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt."; + /* No comment provided by engineer. */ "Self-destruct" = "Selbstzerstörung"; @@ -3441,6 +3723,9 @@ /* No comment provided by engineer. */ "Send disappearing message" = "Verschwindende Nachricht senden"; +/* No comment provided by engineer. */ +"Send errors" = "Fehler beim Senden"; + /* No comment provided by engineer. */ "Send link previews" = "Link-Vorschau senden"; @@ -3507,15 +3792,33 @@ /* copied message info */ "Sent at: %@" = "Gesendet um: %@"; +/* No comment provided by engineer. */ +"Sent directly" = "Direkt gesendet"; + /* notification */ "Sent file event" = "Datei-Ereignis wurde gesendet"; /* message info title */ "Sent message" = "Gesendete Nachricht"; +/* No comment provided by engineer. */ +"Sent messages" = "Gesendete Nachrichten"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht."; +/* No comment provided by engineer. */ +"Sent reply" = "Gesendete Antwort"; + +/* No comment provided by engineer. */ +"Sent total" = "Summe aller gesendeten Nachrichten"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "Über einen Proxy gesendet"; + +/* No comment provided by engineer. */ +"Server address" = "Server-Adresse"; + /* srv error text. */ "Server address is incompatible with network settings." = "Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel."; @@ -3531,12 +3834,21 @@ /* No comment provided by engineer. */ "Server test failed!" = "Server Test ist fehlgeschlagen!"; +/* No comment provided by engineer. */ +"Server type" = "Server-Typ"; + /* srv error text */ "Server version is incompatible with network settings." = "Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel."; /* No comment provided by engineer. */ "Servers" = "Server"; +/* No comment provided by engineer. */ +"Servers info" = "Server-Informationen"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "Die Serverstatistiken werden zurückgesetzt. Dies kann nicht rückgängig gemacht werden!"; + /* No comment provided by engineer. */ "Session code" = "Sitzungscode"; @@ -3546,6 +3858,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "Kontaktname festlegen…"; +/* No comment provided by engineer. */ +"Set default theme" = "Default-Design einstellen"; + /* No comment provided by engineer. */ "Set group preferences" = "Gruppen-Präferenzen einstellen"; @@ -3624,6 +3939,9 @@ /* No comment provided by engineer. */ "Show:" = "Anzeigen:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "SimpleX-Adresse"; @@ -3669,6 +3987,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "Vereinfachter Inkognito-Modus"; +/* No comment provided by engineer. */ +"Size" = "Größe"; + /* No comment provided by engineer. */ "Skip" = "Überspringen"; @@ -3678,6 +3999,9 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "Kleine Gruppen (max. 20)"; +/* No comment provided by engineer. */ +"SMP server" = "SMP-Server"; + /* No comment provided by engineer. */ "SMP servers" = "SMP-Server"; @@ -3702,9 +4026,15 @@ /* No comment provided by engineer. */ "Start migration" = "Starten Sie die Migration"; +/* No comment provided by engineer. */ +"Starting from %@." = "Beginnend mit %@."; + /* No comment provided by engineer. */ "starting…" = "Verbindung wird gestartet…"; +/* No comment provided by engineer. */ +"Statistics" = "Statistiken"; + /* No comment provided by engineer. */ "Stop" = "Beenden"; @@ -3747,6 +4077,15 @@ /* No comment provided by engineer. */ "Submit" = "Bestätigen"; +/* No comment provided by engineer. */ +"Subscribed" = "Abonniert"; + +/* No comment provided by engineer. */ +"Subscription errors" = "Fehler beim Abonnieren"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "Nicht beachtete Abonnements"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "Unterstützung von SimpleX Chat"; @@ -3795,6 +4134,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; +/* No comment provided by engineer. */ +"Temporary file error" = "Temporärer Datei-Fehler"; + /* server test failure */ "Test failed at step %@." = "Der Test ist beim Schritt %@ fehlgeschlagen."; @@ -3876,6 +4218,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Der von Ihnen eingefügte Text ist kein SimpleX-Link."; +/* No comment provided by engineer. */ +"Themes" = "Design"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Diese Einstellungen betreffen Ihr aktuelles Profil **%@**."; @@ -3918,9 +4263,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Das ist Ihre eigene SimpleX-Adresse!"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "Dieser Link wurde schon mit einem anderen Mobiltelefon genutzt. Bitte erstellen sie einen neuen Link in der Desktop-App."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Diese Einstellung gilt für Nachrichten in Ihrem aktuellen Chat-Profil **%@**."; +/* No comment provided by engineer. */ +"Title" = "Bezeichnung"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "Um Fragen zu stellen und aktuelle Informationen zu erhalten:"; @@ -3960,9 +4311,15 @@ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Inkognito beim Verbinden einschalten."; +/* No comment provided by engineer. */ +"Total" = "Summe aller Abonnements"; + /* No comment provided by engineer. */ "Transport isolation" = "Transport-Isolation"; +/* No comment provided by engineer. */ +"Transport sessions" = "Transport-Sitzungen"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Beim Versuch die Verbindung mit dem Server aufzunehmen, der für den Empfang von Nachrichten mit diesem Kontakt genutzt wird, ist ein Fehler aufgetreten (Fehler: %@)."; @@ -4098,12 +4455,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Aktualisieren und den Chat öffnen"; +/* No comment provided by engineer. */ +"Upload errors" = "Fehler beim Hochladen"; + /* No comment provided by engineer. */ "Upload failed" = "Hochladen fehlgeschlagen"; /* server test step */ "Upload file" = "Datei hochladen"; +/* No comment provided by engineer. */ +"Uploaded" = "Hochgeladen"; + +/* No comment provided by engineer. */ +"Uploaded files" = "Hochgeladene Dateien"; + /* No comment provided by engineer. */ "Uploading archive" = "Archiv wird hochgeladen"; @@ -4149,6 +4515,9 @@ /* No comment provided by engineer. */ "User profile" = "Benutzerprofil"; +/* No comment provided by engineer. */ +"User selection" = "Benutzer-Auswahl"; + /* No comment provided by engineer. */ "Using .onion hosts requires compatible VPN provider." = "Für die Nutzung von .onion-Hosts sind kompatible VPN-Anbieter erforderlich."; @@ -4257,6 +4626,12 @@ /* No comment provided by engineer. */ "Waiting for video" = "Auf das Video warten"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "Wallpaper-Akzent"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "Wallpaper-Hintergrund"; + /* No comment provided by engineer. */ "wants to connect to you!" = "möchte sich mit Ihnen verbinden!"; @@ -4329,9 +4704,15 @@ /* snd error text */ "Wrong key or unknown connection - most likely this connection is deleted." = "Falscher Schlüssel oder unbekannte Verbindung - höchstwahrscheinlich ist diese Verbindung gelöscht worden."; +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "Falscher Schlüssel oder unbekannte Daten-Paketadresse der Datei - höchstwahrscheinlich wurde die Datei gelöscht."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Falsches Passwort!"; +/* No comment provided by engineer. */ +"XFTP server" = "XFTP-Server"; + /* No comment provided by engineer. */ "XFTP servers" = "XFTP-Server"; @@ -4389,6 +4770,9 @@ /* No comment provided by engineer. */ "You are invited to group" = "Sie sind zu der Gruppe eingeladen"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "Sie sind nicht mit diesen Servern verbunden. Zur Auslieferung von Nachrichten an diese Server wird privates Routing genutzt."; + /* No comment provided by engineer. */ "you are observer" = "Sie sind Beobachter"; diff --git a/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings index 5fe2ef2d09..0dee85ad95 100644 --- a/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings +++ b/apps/ios/de.lproj/SimpleX--iOS--InfoPlist.strings @@ -2,7 +2,7 @@ "CFBundleName" = "SimpleX"; /* Privacy - Camera Usage Description */ -"NSCameraUsageDescription" = "SimpleX benötigt Zugriff auf die Kamera, um QR Codes für die Verbindung mit anderen Nutzern zu scannen und Videoanrufe durchzuführen."; +"NSCameraUsageDescription" = "SimpleX benötigt Zugriff auf die Kamera, um QR Codes für die Verbindung mit anderen Benutzern zu scannen und Videoanrufe durchzuführen."; /* Privacy - Face ID Usage Description */ "NSFaceIDUsageDescription" = "Face ID wird von SimpleX für die lokale Authentifizierung genutzt"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 54316b092f..6aca830a88 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ está verificado"; -/* No comment provided by engineer. */ -"%@ servers" = "Servidores %@"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ subido"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 1405f14694..8fe0c07a13 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -121,9 +121,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ on vahvistettu"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ palvelimet"; - /* notification title */ "%@ wants to connect!" = "%@ haluaa muodostaa yhteyden!"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index aa9bfef3e5..6d19ccc6a9 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ est vérifié·e"; -/* No comment provided by engineer. */ -"%@ servers" = "Serveurs %@"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ envoyé"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 44b3447ef8..3feb67b764 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ ellenőrizve"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ kiszolgáló"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ feltöltve"; @@ -337,6 +334,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "gombra fent, majd válassza ki:"; +/* No comment provided by engineer. */ +"Accent" = "Kiemelés"; + /* accept contact request via notification accept incoming call via notification */ "Accept" = "Elfogadás"; @@ -353,6 +353,12 @@ /* call status */ "accepted call" = "elfogadott hívás"; +/* No comment provided by engineer. */ +"Acknowledged" = "Nyugtázva"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "Nyugtázott hibák"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Cím hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősei számára."; @@ -377,6 +383,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "Üdvözlő üzenet hozzáadása"; +/* No comment provided by engineer. */ +"Additional accent" = "További kiemelés"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "További kiemelés 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "További másodlagos"; + /* No comment provided by engineer. */ "Address" = "Cím"; @@ -398,6 +413,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "Speciális hálózati beállítások"; +/* No comment provided by engineer. */ +"Advanced settings" = "Haladó beállítások"; + /* chat item text */ "agreeing encryption for %@…" = "titkosítás jóváhagyása %@ számára…"; @@ -413,6 +431,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "A jelkód megadása után minden adat törlésre kerül."; +/* No comment provided by engineer. */ +"All data is private to your device." = "Minden adat biztonságban van a készülékén."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Minden csoporttag kapcsolódva marad."; @@ -428,6 +449,9 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Minden új üzenet elrejtésre kerül tőle: %@!"; +/* No comment provided by engineer. */ +"All profiles" = "Minden profil"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Minden ismerős kapcsolódva marad."; @@ -554,6 +578,9 @@ /* No comment provided by engineer. */ "Apply" = "Alkalmaz"; +/* No comment provided by engineer. */ +"Apply to" = "Alkalmazás erre"; + /* No comment provided by engineer. */ "Archive and upload" = "Archiválás és feltöltés"; @@ -563,6 +590,9 @@ /* No comment provided by engineer. */ "Attach" = "Csatolás"; +/* No comment provided by engineer. */ +"attempts" = "próbálkozások"; + /* No comment provided by engineer. */ "Audio & video calls" = "Hang- és videóhívások"; @@ -605,6 +635,9 @@ /* No comment provided by engineer. */ "Back" = "Vissza"; +/* No comment provided by engineer. */ +"Background" = "Háttér"; + /* No comment provided by engineer. */ "Bad desktop address" = "Hibás számítógép cím"; @@ -626,6 +659,9 @@ /* No comment provided by engineer. */ "Better messages" = "Jobb üzenetek"; +/* No comment provided by engineer. */ +"Black" = "Fekete"; + /* No comment provided by engineer. */ "Block" = "Blokkolás"; @@ -716,6 +752,9 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Nem lehet hozzáférni a kulcstartóhoz az adatbázis jelszavának mentéséhez"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Nem lehet továbbítani az üzenetet"; + /* No comment provided by engineer. */ "Cannot receive file" = "Nem lehet fogadni a fájlt"; @@ -774,6 +813,9 @@ /* No comment provided by engineer. */ "Chat archive" = "Csevegési archívum"; +/* No comment provided by engineer. */ +"Chat colors" = "Csevegés színei"; + /* No comment provided by engineer. */ "Chat console" = "Csevegési konzol"; @@ -801,6 +843,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Csevegési beállítások"; +/* No comment provided by engineer. */ +"Chat theme" = "Csevegés témája"; + /* No comment provided by engineer. */ "Chats" = "Csevegések"; @@ -819,6 +864,15 @@ /* No comment provided by engineer. */ "Choose from library" = "Választás a könyvtárból"; +/* No comment provided by engineer. */ +"Chunks deleted" = "Törölt fájltöredékek"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "Letöltött fájltöredékek"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "Feltöltött fájltöredékek"; + /* No comment provided by engineer. */ "Clear" = "Kiürítés"; @@ -834,6 +888,9 @@ /* No comment provided by engineer. */ "Clear verification" = "Hitelesítés törlése"; +/* No comment provided by engineer. */ +"Color mode" = "Színmód"; + /* No comment provided by engineer. */ "colored" = "színes"; @@ -846,6 +903,9 @@ /* No comment provided by engineer. */ "complete" = "befejezett"; +/* No comment provided by engineer. */ +"Completed" = "Elkészült"; + /* No comment provided by engineer. */ "Configure ICE servers" = "ICE kiszolgálók beállítása"; @@ -915,18 +975,27 @@ /* No comment provided by engineer. */ "connected" = "kapcsolódva"; +/* No comment provided by engineer. */ +"Connected" = "Kapcsolódva"; + /* No comment provided by engineer. */ "Connected desktop" = "Csatlakoztatott számítógép"; /* rcv group event chat item */ "connected directly" = "közvetlenül kapcsolódva"; +/* No comment provided by engineer. */ +"Connected servers" = "Kapcsolódott kiszolgálók"; + /* No comment provided by engineer. */ "Connected to desktop" = "Kapcsolódva a számítógéphez"; /* No comment provided by engineer. */ "connecting" = "kapcsolódás"; +/* No comment provided by engineer. */ +"Connecting" = "Kapcsolódás"; + /* No comment provided by engineer. */ "connecting (accepted)" = "kapcsolódás (elfogadva)"; @@ -975,9 +1044,15 @@ /* No comment provided by engineer. */ "Connection timeout" = "Kapcsolat időtúllépés"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "A kapcsolat a számítógéppel megszakadt"; + /* connection information */ "connection:%@" = "kapcsolat: %@"; +/* No comment provided by engineer. */ +"Connections" = "Kapcsolatok"; + /* profile update event chat item */ "contact %@ changed to %@" = "%1$@ megváltoztatta a nevét erre: %2$@"; @@ -1020,6 +1095,9 @@ /* No comment provided by engineer. */ "Copy" = "Másolás"; +/* No comment provided by engineer. */ +"Copy error" = "Másolási hiba"; + /* No comment provided by engineer. */ "Core version: v%@" = "Alapverziószám: v%@"; @@ -1065,6 +1143,9 @@ /* No comment provided by engineer. */ "Create your profile" = "Saját profil létrehozása"; +/* No comment provided by engineer. */ +"Created" = "Létrehozva"; + /* No comment provided by engineer. */ "Created at" = "Létrehozva ekkor:"; @@ -1089,6 +1170,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "Jelenlegi jelmondat…"; +/* No comment provided by engineer. */ +"Current profile" = "Jelenlegi profil"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Jelenleg a maximális támogatott fájlméret %@."; @@ -1098,9 +1182,15 @@ /* No comment provided by engineer. */ "Custom time" = "Személyreszabott idő"; +/* No comment provided by engineer. */ +"Customize theme" = "Téma személyre szabása"; + /* No comment provided by engineer. */ "Dark" = "Sötét"; +/* No comment provided by engineer. */ +"Dark mode colors" = "Sötét mód színei"; + /* No comment provided by engineer. */ "Database downgrade" = "Visszatérés a korábbi adatbázis verzióra"; @@ -1170,6 +1260,9 @@ /* message decrypt error item */ "Decryption error" = "Titkosítás visszafejtési hiba"; +/* No comment provided by engineer. */ +"decryption errors" = "visszafejtési hibák"; + /* pref value */ "default (%@)" = "alapértelmezett (%@)"; @@ -1296,6 +1389,9 @@ /* deleted chat item */ "deleted" = "törölve"; +/* No comment provided by engineer. */ +"Deleted" = "Törölve"; + /* No comment provided by engineer. */ "Deleted at" = "Törölve ekkor:"; @@ -1308,6 +1404,9 @@ /* rcv group event chat item */ "deleted group" = "törölt csoport"; +/* No comment provided by engineer. */ +"Deletion errors" = "Törlési hibák"; + /* No comment provided by engineer. */ "Delivery" = "Kézbesítés"; @@ -1332,6 +1431,12 @@ /* snd error text */ "Destination server error: %@" = "Célkiszolgáló hiba: %@"; +/* No comment provided by engineer. */ +"Detailed statistics" = "Részletes statisztikák"; + +/* No comment provided by engineer. */ +"Details" = "Részletek"; + /* No comment provided by engineer. */ "Develop" = "Fejlesztés"; @@ -1434,12 +1539,21 @@ /* chat item action */ "Download" = "Letöltés"; +/* No comment provided by engineer. */ +"Download errors" = "Letöltési hibák"; + /* No comment provided by engineer. */ "Download failed" = "Sikertelen letöltés"; /* server test step */ "Download file" = "Fájl letöltése"; +/* No comment provided by engineer. */ +"Downloaded" = "Letöltve"; + +/* No comment provided by engineer. */ +"Downloaded files" = "Letöltött fájlok"; + /* No comment provided by engineer. */ "Downloading archive" = "Archívum letöltése"; @@ -1452,6 +1566,9 @@ /* integrity error chat item */ "duplicate message" = "duplikált üzenet"; +/* No comment provided by engineer. */ +"duplicates" = "duplikációk"; + /* No comment provided by engineer. */ "Duration" = "Időtartam"; @@ -1710,6 +1827,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "Hiba a csevegési adatbázis exportálásakor"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "Hiba a téma exportálásakor: %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "Hiba a csevegési adatbázis importálásakor"; @@ -1725,9 +1845,18 @@ /* No comment provided by engineer. */ "Error receiving file" = "Hiba a fájl fogadásakor"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "Hiba a kiszolgálóhoz való újrakapcsolódáskor"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "Hiba a kiszolgálókhoz való újrakapcsolódáskor"; + /* No comment provided by engineer. */ "Error removing member" = "Hiba a tag eltávolításakor"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "Hiba a statisztikák visszaállításakor"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "Hiba történt a %@ kiszolgálók mentése közben"; @@ -1807,6 +1936,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "Hiba: az URL érvénytelen"; +/* No comment provided by engineer. */ +"Errors" = "Hibák"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Akkor is, ha le van tiltva a beszélgetésben."; @@ -1819,12 +1951,18 @@ /* chat item action */ "Expand" = "Kibontás"; +/* No comment provided by engineer. */ +"expired" = "lejárt"; + /* No comment provided by engineer. */ "Export database" = "Adatbázis exportálása"; /* No comment provided by engineer. */ "Export error:" = "Exportálási hiba:"; +/* No comment provided by engineer. */ +"Export theme" = "Téma exportálása"; + /* No comment provided by engineer. */ "Exported database archive." = "Exportált adatbázis-archívum."; @@ -1846,6 +1984,21 @@ /* No comment provided by engineer. */ "Favorite" = "Kedvenc"; +/* No comment provided by engineer. */ +"File error" = "Fájlhiba"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "A fájl nem található - valószínűleg a fájlt törölték vagy visszavonták."; + +/* file error text */ +"File server error: %@" = "Fájlkiszolgáló hiba: %@"; + +/* No comment provided by engineer. */ +"File status" = "Fájlállapot"; + +/* copied message info */ +"File status: %@" = "Fájlállapot: %@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "A fájl törölve lesz a kiszolgálóról."; @@ -1960,6 +2113,12 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GIF-ek és matricák"; +/* message preview */ +"Good afternoon!" = "Jó napot!"; + +/* message preview */ +"Good morning!" = "Jó reggelt!"; + /* No comment provided by engineer. */ "Group" = "Csoport"; @@ -2137,6 +2296,9 @@ /* No comment provided by engineer. */ "Import failed" = "Sikertelen importálás"; +/* No comment provided by engineer. */ +"Import theme" = "Téma importálása"; + /* No comment provided by engineer. */ "Importing archive" = "Archívum importálása"; @@ -2158,6 +2320,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "Bejövő hívás csengőhangja"; +/* No comment provided by engineer. */ +"inactive" = "inaktív"; + /* No comment provided by engineer. */ "Incognito" = "Inkognitó"; @@ -2221,6 +2386,9 @@ /* No comment provided by engineer. */ "Interface" = "Felület"; +/* No comment provided by engineer. */ +"Interface colors" = "Kezelőfelület színei"; + /* invalid chat data */ "invalid chat" = "érvénytelen csevegés"; @@ -2473,6 +2641,9 @@ /* rcv group event chat item */ "member connected" = "kapcsolódott"; +/* item status text */ +"Member inactive" = "Inaktív tag"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: „%@”. A csoport minden tagja értesítést kap róla."; @@ -2482,6 +2653,9 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "A tag eltávolítása a csoportból - ez a művelet nem vonható vissza!"; +/* No comment provided by engineer. */ +"Menus" = "Menük"; + /* item status text */ "Message delivery error" = "Üzenetkézbesítési hiba"; @@ -2494,6 +2668,12 @@ /* No comment provided by engineer. */ "Message draft" = "Üzenetvázlat"; +/* item status text */ +"Message forwarded" = "Továbbított üzenet"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "Az üzenet később is kézbesíthető, ha a tag aktívvá válik."; + /* No comment provided by engineer. */ "Message queue info" = "Üzenet-várakoztatási információ"; @@ -2518,6 +2698,12 @@ /* No comment provided by engineer. */ "Message source remains private." = "Az üzenet forrása titokban marad."; +/* No comment provided by engineer. */ +"Message status" = "Üzenetállapot"; + +/* copied message info */ +"Message status: %@" = "Üzenetállapot: %@"; + /* No comment provided by engineer. */ "Message text" = "Üzenet szövege"; @@ -2533,6 +2719,12 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "A(z) %@ által írt üzenetek megjelennek!"; +/* No comment provided by engineer. */ +"Messages received" = "Fogadott üzenetek"; + +/* No comment provided by engineer. */ +"Messages sent" = "Elküldött üzenetek"; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi."; @@ -2698,6 +2890,9 @@ /* No comment provided by engineer. */ "No device token!" = "Nincs eszköztoken!"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja."; + /* No comment provided by engineer. */ "no e2e encryption" = "nincs e2e titkosítás"; @@ -2710,6 +2905,9 @@ /* No comment provided by engineer. */ "No history" = "Nincsenek előzmények"; +/* No comment provided by engineer. */ +"No info, try to reload" = "Nincs információ, próbálja meg újratölteni"; + /* No comment provided by engineer. */ "No network connection" = "Nincs hálózati kapcsolat"; @@ -2835,6 +3033,9 @@ /* authentication reason */ "Open migration to another device" = "Átköltöztetés megkezdése egy másik eszközre"; +/* No comment provided by engineer. */ +"Open server settings" = "Kiszolgáló beállításainak megnyitása"; + /* No comment provided by engineer. */ "Open Settings" = "Beállítások megnyitása"; @@ -2859,9 +3060,15 @@ /* No comment provided by engineer. */ "Or show this code" = "Vagy mutassa meg ezt a kódot"; +/* No comment provided by engineer. */ +"other" = "egyéb"; + /* No comment provided by engineer. */ "Other" = "További"; +/* No comment provided by engineer. */ +"other errors" = "egyéb hibák"; + /* member role */ "owner" = "tulajdonos"; @@ -2904,6 +3111,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "ponttól-pontig"; +/* No comment provided by engineer. */ +"Pending" = "Függő"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak."; @@ -2925,6 +3135,9 @@ /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Ismerős felkérése, hogy engedélyezze a hangüzenetek küldését."; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Ellenőrizze, hogy a mobil és az asztali számítógép ugyanahhoz a helyi hálózathoz csatlakozik-e, valamint az asztali számítógép tűzfalában engedélyezve van-e a kapcsolat.\nMinden további problémát osszon meg a fejlesztőkkel."; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat."; @@ -2982,6 +3195,9 @@ /* No comment provided by engineer. */ "Preview" = "Előnézet"; +/* No comment provided by engineer. */ +"Previously connected servers" = "Korábban kapcsolódott kiszolgálók"; + /* No comment provided by engineer. */ "Privacy & security" = "Adatvédelem és biztonság"; @@ -3021,6 +3237,9 @@ /* No comment provided by engineer. */ "Profile password" = "Profiljelszó"; +/* No comment provided by engineer. */ +"Profile theme" = "Profiltéma"; + /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "A profilfrissítés elküldésre került az ismerősök számára."; @@ -3069,6 +3288,12 @@ /* No comment provided by engineer. */ "Protocol timeout per KB" = "Protokoll időkorlát KB-onként"; +/* No comment provided by engineer. */ +"Proxied" = "Proxyzott"; + +/* No comment provided by engineer. */ +"Proxied servers" = "Proxyzott kiszolgálók"; + /* No comment provided by engineer. */ "Push notifications" = "Push értesítések"; @@ -3111,6 +3336,9 @@ /* No comment provided by engineer. */ "Receipts are disabled" = "Üzenet kézbesítési jelentés letiltva"; +/* No comment provided by engineer. */ +"Receive errors" = "Üzenetfogadási hibák"; + /* No comment provided by engineer. */ "received answer…" = "fogadott válasz…"; @@ -3129,6 +3357,15 @@ /* message info title */ "Received message" = "Fogadott üzenet"; +/* No comment provided by engineer. */ +"Received messages" = "Fogadott üzenetek"; + +/* No comment provided by engineer. */ +"Received reply" = "Fogadott válasz"; + +/* No comment provided by engineer. */ +"Received total" = "Összes fogadott"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be."; @@ -3147,9 +3384,24 @@ /* No comment provided by engineer. */ "Recipients see updates as you type them." = "A címzettek a beírás közben látják a frissítéseket."; +/* No comment provided by engineer. */ +"Reconnect" = "Újrakapcsolás"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Újrakapcsolódás az összes kiszolgálóhoz az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ."; +/* No comment provided by engineer. */ +"Reconnect all servers" = "Újrakapcsolódás minden kiszolgálóhoz"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "Újrakapcsolódás minden kiszolgálóhoz?"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ."; + +/* No comment provided by engineer. */ +"Reconnect server?" = "Újrakapcsolódás a kiszolgálóhoz?"; + /* No comment provided by engineer. */ "Reconnect servers?" = "Újrakapcsolódás a kiszolgálókhoz?"; @@ -3183,6 +3435,9 @@ /* No comment provided by engineer. */ "Remove" = "Eltávolítás"; +/* No comment provided by engineer. */ +"Remove image" = "Kép eltávolítása"; + /* No comment provided by engineer. */ "Remove member" = "Eltávolítás"; @@ -3240,12 +3495,24 @@ /* No comment provided by engineer. */ "Reset" = "Alaphelyzetbe állítás"; +/* No comment provided by engineer. */ +"Reset all statistics" = "Minden statisztika visszaállítása"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "Minden statisztika visszaállítása?"; + /* No comment provided by engineer. */ "Reset colors" = "Színek alaphelyzetbe állítása"; +/* No comment provided by engineer. */ +"Reset to app theme" = "Alkalmazás témájának visszaállítása"; + /* No comment provided by engineer. */ "Reset to defaults" = "Alaphelyzetbe állítás"; +/* No comment provided by engineer. */ +"Reset to user theme" = "Felhasználó által létrehozott téma visszaállítása"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "Új csevegési profil létrehozásához indítsa újra az alkalmazást"; @@ -3360,6 +3627,12 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "A mentett WebRTC ICE kiszolgálók eltávolításra kerülnek"; +/* No comment provided by engineer. */ +"Scale" = "Méretezés"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "Hivatkozás beolvasása / beillesztése"; + /* No comment provided by engineer. */ "Scan code" = "Beolvasás"; @@ -3387,6 +3660,9 @@ /* network option */ "sec" = "mp"; +/* No comment provided by engineer. */ +"Secondary" = "Másodlagos"; + /* time unit */ "seconds" = "másodperc"; @@ -3396,6 +3672,9 @@ /* server test step */ "Secure queue" = "Biztonságos várólista"; +/* No comment provided by engineer. */ +"Secured" = "Biztosítva"; + /* No comment provided by engineer. */ "Security assessment" = "Biztonsági kiértékelés"; @@ -3408,6 +3687,9 @@ /* No comment provided by engineer. */ "Select" = "Választás"; +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet."; + /* No comment provided by engineer. */ "Self-destruct" = "Önmegsemmisítés"; @@ -3441,6 +3723,9 @@ /* No comment provided by engineer. */ "Send disappearing message" = "Eltűnő üzenet küldése"; +/* No comment provided by engineer. */ +"Send errors" = "Üzenetküldési hibák"; + /* No comment provided by engineer. */ "Send link previews" = "Hivatkozás előnézetek küldése"; @@ -3507,15 +3792,33 @@ /* copied message info */ "Sent at: %@" = "Elküldve ekkor: %@"; +/* No comment provided by engineer. */ +"Sent directly" = "Közvetlenül küldött"; + /* notification */ "Sent file event" = "Elküldött fájl esemény"; /* message info title */ "Sent message" = "Elküldött üzenet"; +/* No comment provided by engineer. */ +"Sent messages" = "Elküldött üzenetek"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Az elküldött üzenetek törlésre kerülnek a beállított idő után."; +/* No comment provided by engineer. */ +"Sent reply" = "Elküldött válasz"; + +/* No comment provided by engineer. */ +"Sent total" = "Összes elküldött"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "Proxyn keresztül küldve"; + +/* No comment provided by engineer. */ +"Server address" = "Kiszolgáló címe"; + /* srv error text. */ "Server address is incompatible with network settings." = "A kiszolgáló címe nem kompatibilis a hálózati beállításokkal."; @@ -3531,12 +3834,21 @@ /* No comment provided by engineer. */ "Server test failed!" = "Sikertelen kiszolgáló-teszt!"; +/* No comment provided by engineer. */ +"Server type" = "Kiszolgáló típusa"; + /* srv error text */ "Server version is incompatible with network settings." = "A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal."; /* No comment provided by engineer. */ "Servers" = "Kiszolgálók"; +/* No comment provided by engineer. */ +"Servers info" = "információk a kiszolgálókról"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza!"; + /* No comment provided by engineer. */ "Session code" = "Munkamenet kód"; @@ -3546,6 +3858,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "Ismerős nevének beállítása…"; +/* No comment provided by engineer. */ +"Set default theme" = "Alapértelmezett téma beállítása"; + /* No comment provided by engineer. */ "Set group preferences" = "Csoportbeállítások megadása"; @@ -3624,6 +3939,9 @@ /* No comment provided by engineer. */ "Show:" = "Megjelenítés:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "SimpleX cím"; @@ -3669,6 +3987,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "Egyszerűsített inkognító mód"; +/* No comment provided by engineer. */ +"Size" = "Méret"; + /* No comment provided by engineer. */ "Skip" = "Kihagyás"; @@ -3678,6 +3999,9 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "Kis csoportok (max. 20 tag)"; +/* No comment provided by engineer. */ +"SMP server" = "SMP-kiszolgáló"; + /* No comment provided by engineer. */ "SMP servers" = "SMP kiszolgálók"; @@ -3702,9 +4026,15 @@ /* No comment provided by engineer. */ "Start migration" = "Átköltöztetés indítása"; +/* No comment provided by engineer. */ +"Starting from %@." = "Kezdve ettől %@."; + /* No comment provided by engineer. */ "starting…" = "indítás…"; +/* No comment provided by engineer. */ +"Statistics" = "Statisztikák"; + /* No comment provided by engineer. */ "Stop" = "Megállítás"; @@ -3747,6 +4077,15 @@ /* No comment provided by engineer. */ "Submit" = "Elküldés"; +/* No comment provided by engineer. */ +"Subscribed" = "Feliratkozva"; + +/* No comment provided by engineer. */ +"Subscription errors" = "Feliratkozási hibák"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "Elutasított feliratkozások"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "Támogassa a SimpleX Chatet"; @@ -3795,6 +4134,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; +/* No comment provided by engineer. */ +"Temporary file error" = "Ideiglenes fájlhiba"; + /* server test failure */ "Test failed at step %@." = "A teszt sikertelen volt a(z) %@ lépésnél."; @@ -3876,6 +4218,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "A beillesztett szöveg nem egy SimpleX hivatkozás."; +/* No comment provided by engineer. */ +"Themes" = "Témák"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak."; @@ -3918,9 +4263,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Ez az ön SimpleX címe!"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "Ezt a hivatkozást egy másik mobilleszközön már használták, hozzon létre egy új hivatkozást az asztali számítógépén."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes."; +/* No comment provided by engineer. */ +"Title" = "Cím"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "Bármilyen kérdés feltevéséhez és a frissítésekért:"; @@ -3960,9 +4311,15 @@ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Inkognitó mód kapcsolódáskor."; +/* No comment provided by engineer. */ +"Total" = "Összesen"; + /* No comment provided by engineer. */ "Transport isolation" = "Kapcsolat izolációs mód"; +/* No comment provided by engineer. */ +"Transport sessions" = "Munkamenetek átvitele"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Kapcsolódási kísérlet ahhoz a kiszolgálóhoz, amely az adott ismerőstől érkező üzenetek fogadására szolgál (hiba: %@)."; @@ -4098,12 +4455,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "A csevegés frissítése és megnyitása"; +/* No comment provided by engineer. */ +"Upload errors" = "Feltöltési hibák"; + /* No comment provided by engineer. */ "Upload failed" = "Sikertelen feltöltés"; /* server test step */ "Upload file" = "Fájl feltöltése"; +/* No comment provided by engineer. */ +"Uploaded" = "Feltöltve"; + +/* No comment provided by engineer. */ +"Uploaded files" = "Feltöltött fájlok"; + /* No comment provided by engineer. */ "Uploading archive" = "Archívum feltöltése"; @@ -4149,6 +4515,9 @@ /* No comment provided by engineer. */ "User profile" = "Felhasználói profil"; +/* No comment provided by engineer. */ +"User selection" = "Felhasználó kiválasztása"; + /* No comment provided by engineer. */ "Using .onion hosts requires compatible VPN provider." = "A .onion kiszolgálók használatához kompatibilis VPN szolgáltatóra van szükség."; @@ -4257,6 +4626,12 @@ /* No comment provided by engineer. */ "Waiting for video" = "Videóra várakozás"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "Háttérkép kiemelés"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "Háttérkép háttérszíne"; + /* No comment provided by engineer. */ "wants to connect to you!" = "kapcsolatba akar lépni önnel!"; @@ -4329,9 +4704,15 @@ /* snd error text */ "Wrong key or unknown connection - most likely this connection is deleted." = "Rossz kulcs vagy ismeretlen kapcsolat - valószínűleg ez a kapcsolat törlődött."; +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "Hibás kulcs vagy ismeretlen fájltöredék cím - valószínűleg a fájl törlődött."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Téves jelmondat!"; +/* No comment provided by engineer. */ +"XFTP server" = "XFTP-kiszolgáló"; + /* No comment provided by engineer. */ "XFTP servers" = "XFTP kiszolgálók"; @@ -4389,6 +4770,9 @@ /* No comment provided by engineer. */ "You are invited to group" = "Meghívást kapott a csoportba"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál."; + /* No comment provided by engineer. */ "you are observer" = "megfigyelő szerep"; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index addb026ab7..ffd4df5ddf 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ è verificato/a"; -/* No comment provided by engineer. */ -"%@ servers" = "Server %@"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ caricati"; @@ -337,6 +334,9 @@ /* No comment provided by engineer. */ "above, then choose:" = "sopra, quindi scegli:"; +/* No comment provided by engineer. */ +"Accent" = "Principale"; + /* accept contact request via notification accept incoming call via notification */ "Accept" = "Accetta"; @@ -353,6 +353,12 @@ /* call status */ "accepted call" = "chiamata accettata"; +/* No comment provided by engineer. */ +"Acknowledged" = "Riconosciuto"; + +/* No comment provided by engineer. */ +"Acknowledgement errors" = "Errori di riconoscimento"; + /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Aggiungi l'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; @@ -377,6 +383,15 @@ /* No comment provided by engineer. */ "Add welcome message" = "Aggiungi messaggio di benvenuto"; +/* No comment provided by engineer. */ +"Additional accent" = "Principale aggiuntivo"; + +/* No comment provided by engineer. */ +"Additional accent 2" = "Principale aggiuntivo 2"; + +/* No comment provided by engineer. */ +"Additional secondary" = "Secondario aggiuntivo"; + /* No comment provided by engineer. */ "Address" = "Indirizzo"; @@ -398,6 +413,9 @@ /* No comment provided by engineer. */ "Advanced network settings" = "Impostazioni di rete avanzate"; +/* No comment provided by engineer. */ +"Advanced settings" = "Impostazioni avanzate"; + /* chat item text */ "agreeing encryption for %@…" = "concordando la crittografia per %@…"; @@ -413,6 +431,9 @@ /* No comment provided by engineer. */ "All data is erased when it is entered." = "Tutti i dati vengono cancellati quando inserito."; +/* No comment provided by engineer. */ +"All data is private to your device." = "Tutti i dati sono privati, nel tuo dispositivo."; + /* No comment provided by engineer. */ "All group members will remain connected." = "Tutti i membri del gruppo resteranno connessi."; @@ -428,6 +449,9 @@ /* No comment provided by engineer. */ "All new messages from %@ will be hidden!" = "Tutti i nuovi messaggi da %@ verrranno nascosti!"; +/* No comment provided by engineer. */ +"All profiles" = "Tutti gli profili"; + /* No comment provided by engineer. */ "All your contacts will remain connected." = "Tutti i tuoi contatti resteranno connessi."; @@ -554,6 +578,9 @@ /* No comment provided by engineer. */ "Apply" = "Applica"; +/* No comment provided by engineer. */ +"Apply to" = "Applica a"; + /* No comment provided by engineer. */ "Archive and upload" = "Archivia e carica"; @@ -563,6 +590,9 @@ /* No comment provided by engineer. */ "Attach" = "Allega"; +/* No comment provided by engineer. */ +"attempts" = "tentativi"; + /* No comment provided by engineer. */ "Audio & video calls" = "Chiamate audio e video"; @@ -605,6 +635,9 @@ /* No comment provided by engineer. */ "Back" = "Indietro"; +/* No comment provided by engineer. */ +"Background" = "Sfondo"; + /* No comment provided by engineer. */ "Bad desktop address" = "Indirizzo desktop errato"; @@ -626,6 +659,9 @@ /* No comment provided by engineer. */ "Better messages" = "Messaggi migliorati"; +/* No comment provided by engineer. */ +"Black" = "Nero"; + /* No comment provided by engineer. */ "Block" = "Blocca"; @@ -716,6 +752,9 @@ /* No comment provided by engineer. */ "Cannot access keychain to save database password" = "Impossibile accedere al portachiavi per salvare la password del database"; +/* No comment provided by engineer. */ +"Cannot forward message" = "Impossibile inoltrare il messaggio"; + /* No comment provided by engineer. */ "Cannot receive file" = "Impossibile ricevere il file"; @@ -774,6 +813,9 @@ /* No comment provided by engineer. */ "Chat archive" = "Archivio chat"; +/* No comment provided by engineer. */ +"Chat colors" = "Colori della chat"; + /* No comment provided by engineer. */ "Chat console" = "Console della chat"; @@ -801,6 +843,9 @@ /* No comment provided by engineer. */ "Chat preferences" = "Preferenze della chat"; +/* No comment provided by engineer. */ +"Chat theme" = "Tema della chat"; + /* No comment provided by engineer. */ "Chats" = "Chat"; @@ -819,6 +864,15 @@ /* No comment provided by engineer. */ "Choose from library" = "Scegli dalla libreria"; +/* No comment provided by engineer. */ +"Chunks deleted" = "Blocchi eliminati"; + +/* No comment provided by engineer. */ +"Chunks downloaded" = "Blocchi scaricati"; + +/* No comment provided by engineer. */ +"Chunks uploaded" = "Blocchi inviati"; + /* No comment provided by engineer. */ "Clear" = "Svuota"; @@ -834,6 +888,9 @@ /* No comment provided by engineer. */ "Clear verification" = "Annulla la verifica"; +/* No comment provided by engineer. */ +"Color mode" = "Modalità di colore"; + /* No comment provided by engineer. */ "colored" = "colorato"; @@ -846,6 +903,9 @@ /* No comment provided by engineer. */ "complete" = "completo"; +/* No comment provided by engineer. */ +"Completed" = "Completato"; + /* No comment provided by engineer. */ "Configure ICE servers" = "Configura server ICE"; @@ -915,18 +975,27 @@ /* No comment provided by engineer. */ "connected" = "connesso/a"; +/* No comment provided by engineer. */ +"Connected" = "Connesso"; + /* No comment provided by engineer. */ "Connected desktop" = "Desktop connesso"; /* rcv group event chat item */ "connected directly" = "si è connesso/a direttamente"; +/* No comment provided by engineer. */ +"Connected servers" = "Server connessi"; + /* No comment provided by engineer. */ "Connected to desktop" = "Connesso al desktop"; /* No comment provided by engineer. */ "connecting" = "in connessione"; +/* No comment provided by engineer. */ +"Connecting" = "In connessione"; + /* No comment provided by engineer. */ "connecting (accepted)" = "in connessione (accettato)"; @@ -975,9 +1044,15 @@ /* No comment provided by engineer. */ "Connection timeout" = "Connessione scaduta"; +/* No comment provided by engineer. */ +"Connection with desktop stopped" = "Connessione con il desktop fermata"; + /* connection information */ "connection:%@" = "connessione:% @"; +/* No comment provided by engineer. */ +"Connections" = "Connessioni"; + /* profile update event chat item */ "contact %@ changed to %@" = "contatto %1$@ cambiato in %2$@"; @@ -1020,6 +1095,9 @@ /* No comment provided by engineer. */ "Copy" = "Copia"; +/* No comment provided by engineer. */ +"Copy error" = "Copia errore"; + /* No comment provided by engineer. */ "Core version: v%@" = "Versione core: v%@"; @@ -1065,6 +1143,9 @@ /* No comment provided by engineer. */ "Create your profile" = "Crea il tuo profilo"; +/* No comment provided by engineer. */ +"Created" = "Creato"; + /* No comment provided by engineer. */ "Created at" = "Creato il"; @@ -1089,6 +1170,9 @@ /* No comment provided by engineer. */ "Current passphrase…" = "Password attuale…"; +/* No comment provided by engineer. */ +"Current profile" = "Profilo attuale"; + /* No comment provided by engineer. */ "Currently maximum supported file size is %@." = "Attualmente la dimensione massima supportata è di %@."; @@ -1098,9 +1182,15 @@ /* No comment provided by engineer. */ "Custom time" = "Tempo personalizzato"; +/* No comment provided by engineer. */ +"Customize theme" = "Personalizza il tema"; + /* No comment provided by engineer. */ "Dark" = "Scuro"; +/* No comment provided by engineer. */ +"Dark mode colors" = "Colori modalità scura"; + /* No comment provided by engineer. */ "Database downgrade" = "Downgrade del database"; @@ -1170,6 +1260,9 @@ /* message decrypt error item */ "Decryption error" = "Errore di decifrazione"; +/* No comment provided by engineer. */ +"decryption errors" = "errori di decifrazione"; + /* pref value */ "default (%@)" = "predefinito (%@)"; @@ -1296,6 +1389,9 @@ /* deleted chat item */ "deleted" = "eliminato"; +/* No comment provided by engineer. */ +"Deleted" = "Eliminato"; + /* No comment provided by engineer. */ "Deleted at" = "Eliminato il"; @@ -1308,6 +1404,9 @@ /* rcv group event chat item */ "deleted group" = "gruppo eliminato"; +/* No comment provided by engineer. */ +"Deletion errors" = "Errori di eliminazione"; + /* No comment provided by engineer. */ "Delivery" = "Consegna"; @@ -1332,6 +1431,12 @@ /* snd error text */ "Destination server error: %@" = "Errore del server di destinazione: %@"; +/* No comment provided by engineer. */ +"Detailed statistics" = "Statistiche dettagliate"; + +/* No comment provided by engineer. */ +"Details" = "Dettagli"; + /* No comment provided by engineer. */ "Develop" = "Sviluppa"; @@ -1434,12 +1539,21 @@ /* chat item action */ "Download" = "Scarica"; +/* No comment provided by engineer. */ +"Download errors" = "Errori di scaricamento"; + /* No comment provided by engineer. */ "Download failed" = "Scaricamento fallito"; /* server test step */ "Download file" = "Scarica file"; +/* No comment provided by engineer. */ +"Downloaded" = "Scaricato"; + +/* No comment provided by engineer. */ +"Downloaded files" = "File scaricati"; + /* No comment provided by engineer. */ "Downloading archive" = "Scaricamento archivio"; @@ -1452,6 +1566,9 @@ /* integrity error chat item */ "duplicate message" = "messaggio duplicato"; +/* No comment provided by engineer. */ +"duplicates" = "doppi"; + /* No comment provided by engineer. */ "Duration" = "Durata"; @@ -1710,6 +1827,9 @@ /* No comment provided by engineer. */ "Error exporting chat database" = "Errore nell'esportazione del database della chat"; +/* No comment provided by engineer. */ +"Error exporting theme: %@" = "Errore di esportazione del tema: %@"; + /* No comment provided by engineer. */ "Error importing chat database" = "Errore nell'importazione del database della chat"; @@ -1725,9 +1845,18 @@ /* No comment provided by engineer. */ "Error receiving file" = "Errore nella ricezione del file"; +/* No comment provided by engineer. */ +"Error reconnecting server" = "Errore di riconnessione al server"; + +/* No comment provided by engineer. */ +"Error reconnecting servers" = "Errore di riconnessione ai server"; + /* No comment provided by engineer. */ "Error removing member" = "Errore nella rimozione del membro"; +/* No comment provided by engineer. */ +"Error resetting statistics" = "Errore di azzeramento statistiche"; + /* No comment provided by engineer. */ "Error saving %@ servers" = "Errore nel salvataggio dei server %@"; @@ -1807,6 +1936,9 @@ /* No comment provided by engineer. */ "Error: URL is invalid" = "Errore: l'URL non è valido"; +/* No comment provided by engineer. */ +"Errors" = "Errori"; + /* No comment provided by engineer. */ "Even when disabled in the conversation." = "Anche quando disattivato nella conversazione."; @@ -1819,12 +1951,18 @@ /* chat item action */ "Expand" = "Espandi"; +/* No comment provided by engineer. */ +"expired" = "scaduto"; + /* No comment provided by engineer. */ "Export database" = "Esporta database"; /* No comment provided by engineer. */ "Export error:" = "Errore di esportazione:"; +/* No comment provided by engineer. */ +"Export theme" = "Esporta tema"; + /* No comment provided by engineer. */ "Exported database archive." = "Archivio database esportato."; @@ -1846,6 +1984,21 @@ /* No comment provided by engineer. */ "Favorite" = "Preferito"; +/* No comment provided by engineer. */ +"File error" = "Errore del file"; + +/* file error text */ +"File not found - most likely file was deleted or cancelled." = "File non trovato - probabilmente è stato eliminato o annullato."; + +/* file error text */ +"File server error: %@" = "Errore del server dei file: %@"; + +/* No comment provided by engineer. */ +"File status" = "Stato del file"; + +/* copied message info */ +"File status: %@" = "Stato del file: %@"; + /* No comment provided by engineer. */ "File will be deleted from servers." = "Il file verrà eliminato dai server."; @@ -1960,6 +2113,12 @@ /* No comment provided by engineer. */ "GIFs and stickers" = "GIF e adesivi"; +/* message preview */ +"Good afternoon!" = "Buon pomeriggio!"; + +/* message preview */ +"Good morning!" = "Buongiorno!"; + /* No comment provided by engineer. */ "Group" = "Gruppo"; @@ -2137,6 +2296,9 @@ /* No comment provided by engineer. */ "Import failed" = "Importazione fallita"; +/* No comment provided by engineer. */ +"Import theme" = "Importa tema"; + /* No comment provided by engineer. */ "Importing archive" = "Importazione archivio"; @@ -2158,6 +2320,9 @@ /* No comment provided by engineer. */ "In-call sounds" = "Suoni nelle chiamate"; +/* No comment provided by engineer. */ +"inactive" = "inattivo"; + /* No comment provided by engineer. */ "Incognito" = "Incognito"; @@ -2221,6 +2386,9 @@ /* No comment provided by engineer. */ "Interface" = "Interfaccia"; +/* No comment provided by engineer. */ +"Interface colors" = "Colori dell'interfaccia"; + /* invalid chat data */ "invalid chat" = "chat non valida"; @@ -2473,6 +2641,9 @@ /* rcv group event chat item */ "member connected" = "si è connesso/a"; +/* item status text */ +"Member inactive" = "Membro inattivo"; + /* No comment provided by engineer. */ "Member role will be changed to \"%@\". All group members will be notified." = "Il ruolo del membro verrà cambiato in \"%@\". Tutti i membri del gruppo verranno avvisati."; @@ -2482,6 +2653,9 @@ /* No comment provided by engineer. */ "Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!"; +/* No comment provided by engineer. */ +"Menus" = "Menu"; + /* item status text */ "Message delivery error" = "Errore di recapito del messaggio"; @@ -2494,6 +2668,12 @@ /* No comment provided by engineer. */ "Message draft" = "Bozza dei messaggi"; +/* item status text */ +"Message forwarded" = "Messaggio inoltrato"; + +/* item status description */ +"Message may be delivered later if member becomes active." = "Il messaggio può essere consegnato più tardi se il membro diventa attivo."; + /* No comment provided by engineer. */ "Message queue info" = "Info coda messaggi"; @@ -2518,6 +2698,12 @@ /* No comment provided by engineer. */ "Message source remains private." = "La fonte del messaggio resta privata."; +/* No comment provided by engineer. */ +"Message status" = "Stato del messaggio"; + +/* copied message info */ +"Message status: %@" = "Stato del messaggio: %@"; + /* No comment provided by engineer. */ "Message text" = "Testo del messaggio"; @@ -2533,6 +2719,12 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "I messaggi da %@ verranno mostrati!"; +/* No comment provided by engineer. */ +"Messages received" = "Messaggi ricevuti"; + +/* No comment provided by engineer. */ +"Messages sent" = "Messaggi inviati"; + /* No comment provided by engineer. */ "Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione."; @@ -2698,6 +2890,9 @@ /* No comment provided by engineer. */ "No device token!" = "Nessun token del dispositivo!"; +/* item status description */ +"No direct connection yet, message is forwarded by admin." = "Ancora nessuna connessione diretta, il messaggio viene inoltrato dall'amministratore."; + /* No comment provided by engineer. */ "no e2e encryption" = "nessuna crittografia e2e"; @@ -2710,6 +2905,9 @@ /* No comment provided by engineer. */ "No history" = "Nessuna cronologia"; +/* No comment provided by engineer. */ +"No info, try to reload" = "Nessuna informazione, prova a ricaricare"; + /* No comment provided by engineer. */ "No network connection" = "Nessuna connessione di rete"; @@ -2835,6 +3033,9 @@ /* authentication reason */ "Open migration to another device" = "Apri migrazione ad un altro dispositivo"; +/* No comment provided by engineer. */ +"Open server settings" = "Apri impostazioni server"; + /* No comment provided by engineer. */ "Open Settings" = "Apri le impostazioni"; @@ -2859,9 +3060,15 @@ /* No comment provided by engineer. */ "Or show this code" = "O mostra questo codice"; +/* No comment provided by engineer. */ +"other" = "altro"; + /* No comment provided by engineer. */ "Other" = "Altro"; +/* No comment provided by engineer. */ +"other errors" = "altri errori"; + /* member role */ "owner" = "proprietario"; @@ -2904,6 +3111,9 @@ /* No comment provided by engineer. */ "peer-to-peer" = "peer-to-peer"; +/* No comment provided by engineer. */ +"Pending" = "In attesa"; + /* No comment provided by engineer. */ "People can connect to you only via the links you share." = "Le persone possono connettersi a te solo tramite i link che condividi."; @@ -2925,6 +3135,9 @@ /* No comment provided by engineer. */ "Please ask your contact to enable sending voice messages." = "Chiedi al tuo contatto di attivare l'invio dei messaggi vocali."; +/* No comment provided by engineer. */ +"Please check that mobile and desktop are connected to the same local network, and that desktop firewall allows the connection.\nPlease share any other issues with the developers." = "Controlla che mobile e desktop siano collegati alla stessa rete locale e che il firewall del desktop consenta la connessione.\nSi prega di condividere qualsiasi altro problema con gli sviluppatori."; + /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Controlla di aver usato il link giusto o chiedi al tuo contatto di inviartene un altro."; @@ -2982,6 +3195,9 @@ /* No comment provided by engineer. */ "Preview" = "Anteprima"; +/* No comment provided by engineer. */ +"Previously connected servers" = "Server precedentemente connessi"; + /* No comment provided by engineer. */ "Privacy & security" = "Privacy e sicurezza"; @@ -3021,6 +3237,9 @@ /* No comment provided by engineer. */ "Profile password" = "Password del profilo"; +/* No comment provided by engineer. */ +"Profile theme" = "Tema del profilo"; + /* No comment provided by engineer. */ "Profile update will be sent to your contacts." = "L'aggiornamento del profilo verrà inviato ai tuoi contatti."; @@ -3069,6 +3288,12 @@ /* No comment provided by engineer. */ "Protocol timeout per KB" = "Scadenza del protocollo per KB"; +/* No comment provided by engineer. */ +"Proxied" = "Via proxy"; + +/* No comment provided by engineer. */ +"Proxied servers" = "Server via proxy"; + /* No comment provided by engineer. */ "Push notifications" = "Notifiche push"; @@ -3111,6 +3336,9 @@ /* No comment provided by engineer. */ "Receipts are disabled" = "Le ricevute sono disattivate"; +/* No comment provided by engineer. */ +"Receive errors" = "Errori di ricezione"; + /* No comment provided by engineer. */ "received answer…" = "risposta ricevuta…"; @@ -3129,6 +3357,15 @@ /* message info title */ "Received message" = "Messaggio ricevuto"; +/* No comment provided by engineer. */ +"Received messages" = "Messaggi ricevuti"; + +/* No comment provided by engineer. */ +"Received reply" = "Risposta ricevuta"; + +/* No comment provided by engineer. */ +"Received total" = "Totale ricevuto"; + /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "L'indirizzo di ricezione verrà cambiato in un server diverso. La modifica dell'indirizzo verrà completata dopo che il mittente sarà in linea."; @@ -3147,9 +3384,24 @@ /* No comment provided by engineer. */ "Recipients see updates as you type them." = "I destinatari vedono gli aggiornamenti mentre li digiti."; +/* No comment provided by engineer. */ +"Reconnect" = "Riconnetti"; + /* No comment provided by engineer. */ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Riconnetti tutti i server connessi per imporre il recapito dei messaggi. Utilizza traffico aggiuntivo."; +/* No comment provided by engineer. */ +"Reconnect all servers" = "Riconnetti tutti i server"; + +/* No comment provided by engineer. */ +"Reconnect all servers?" = "Riconnettere tutti i server?"; + +/* No comment provided by engineer. */ +"Reconnect server to force message delivery. It uses additional traffic." = "Riconnetti il server per forzare la consegna dei messaggi. Usa traffico aggiuntivo."; + +/* No comment provided by engineer. */ +"Reconnect server?" = "Riconnettere il server?"; + /* No comment provided by engineer. */ "Reconnect servers?" = "Riconnettere i server?"; @@ -3183,6 +3435,9 @@ /* No comment provided by engineer. */ "Remove" = "Rimuovi"; +/* No comment provided by engineer. */ +"Remove image" = "Rimuovi immagine"; + /* No comment provided by engineer. */ "Remove member" = "Rimuovi membro"; @@ -3240,12 +3495,24 @@ /* No comment provided by engineer. */ "Reset" = "Ripristina"; +/* No comment provided by engineer. */ +"Reset all statistics" = "Azzera tutte le statistiche"; + +/* No comment provided by engineer. */ +"Reset all statistics?" = "Azzerare tutte le statistiche?"; + /* No comment provided by engineer. */ "Reset colors" = "Ripristina i colori"; +/* No comment provided by engineer. */ +"Reset to app theme" = "Ripristina al tema dell'app"; + /* No comment provided by engineer. */ "Reset to defaults" = "Ripristina i predefiniti"; +/* No comment provided by engineer. */ +"Reset to user theme" = "Ripristina al tema dell'utente"; + /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "Riavvia l'app per creare un nuovo profilo di chat"; @@ -3360,6 +3627,12 @@ /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "I server WebRTC ICE salvati verranno rimossi"; +/* No comment provided by engineer. */ +"Scale" = "Scala"; + +/* No comment provided by engineer. */ +"Scan / Paste link" = "Scansiona / Incolla link"; + /* No comment provided by engineer. */ "Scan code" = "Scansiona codice"; @@ -3387,6 +3660,9 @@ /* network option */ "sec" = "sec"; +/* No comment provided by engineer. */ +"Secondary" = "Secondario"; + /* time unit */ "seconds" = "secondi"; @@ -3396,6 +3672,9 @@ /* server test step */ "Secure queue" = "Coda sicura"; +/* No comment provided by engineer. */ +"Secured" = "Protetto"; + /* No comment provided by engineer. */ "Security assessment" = "Valutazione della sicurezza"; @@ -3408,6 +3687,9 @@ /* No comment provided by engineer. */ "Select" = "Seleziona"; +/* No comment provided by engineer. */ +"Selected chat preferences prohibit this message." = "Le preferenze della chat selezionata vietano questo messaggio."; + /* No comment provided by engineer. */ "Self-destruct" = "Autodistruzione"; @@ -3441,6 +3723,9 @@ /* No comment provided by engineer. */ "Send disappearing message" = "Invia messaggio a tempo"; +/* No comment provided by engineer. */ +"Send errors" = "Errori di invio"; + /* No comment provided by engineer. */ "Send link previews" = "Invia anteprime dei link"; @@ -3507,15 +3792,33 @@ /* copied message info */ "Sent at: %@" = "Inviato il: %@"; +/* No comment provided by engineer. */ +"Sent directly" = "Inviato direttamente"; + /* notification */ "Sent file event" = "Evento file inviato"; /* message info title */ "Sent message" = "Messaggio inviato"; +/* No comment provided by engineer. */ +"Sent messages" = "Messaggi inviati"; + /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "I messaggi inviati verranno eliminati dopo il tempo impostato."; +/* No comment provided by engineer. */ +"Sent reply" = "Risposta inviata"; + +/* No comment provided by engineer. */ +"Sent total" = "Totale inviato"; + +/* No comment provided by engineer. */ +"Sent via proxy" = "Inviato via proxy"; + +/* No comment provided by engineer. */ +"Server address" = "Indirizzo server"; + /* srv error text. */ "Server address is incompatible with network settings." = "L'indirizzo del server non è compatibile con le impostazioni di rete."; @@ -3531,12 +3834,21 @@ /* No comment provided by engineer. */ "Server test failed!" = "Test del server fallito!"; +/* No comment provided by engineer. */ +"Server type" = "Tipo server"; + /* srv error text */ "Server version is incompatible with network settings." = "La versione del server non è compatibile con le impostazioni di rete."; /* No comment provided by engineer. */ "Servers" = "Server"; +/* No comment provided by engineer. */ +"Servers info" = "Info dei server"; + +/* No comment provided by engineer. */ +"Servers statistics will be reset - this cannot be undone!" = "Le statistiche dei server verranno azzerate - è irreversibile!"; + /* No comment provided by engineer. */ "Session code" = "Codice di sessione"; @@ -3546,6 +3858,9 @@ /* No comment provided by engineer. */ "Set contact name…" = "Imposta nome del contatto…"; +/* No comment provided by engineer. */ +"Set default theme" = "Imposta tema predefinito"; + /* No comment provided by engineer. */ "Set group preferences" = "Imposta le preferenze del gruppo"; @@ -3624,6 +3939,9 @@ /* No comment provided by engineer. */ "Show:" = "Mostra:"; +/* No comment provided by engineer. */ +"SimpleX" = "SimpleX"; + /* No comment provided by engineer. */ "SimpleX address" = "Indirizzo SimpleX"; @@ -3669,6 +3987,9 @@ /* No comment provided by engineer. */ "Simplified incognito mode" = "Modalità incognito semplificata"; +/* No comment provided by engineer. */ +"Size" = "Dimensione"; + /* No comment provided by engineer. */ "Skip" = "Salta"; @@ -3678,6 +3999,9 @@ /* No comment provided by engineer. */ "Small groups (max 20)" = "Piccoli gruppi (max 20)"; +/* No comment provided by engineer. */ +"SMP server" = "Server SMP"; + /* No comment provided by engineer. */ "SMP servers" = "Server SMP"; @@ -3702,9 +4026,15 @@ /* No comment provided by engineer. */ "Start migration" = "Avvia la migrazione"; +/* No comment provided by engineer. */ +"Starting from %@." = "Inizio da %@."; + /* No comment provided by engineer. */ "starting…" = "avvio…"; +/* No comment provided by engineer. */ +"Statistics" = "Statistiche"; + /* No comment provided by engineer. */ "Stop" = "Ferma"; @@ -3747,6 +4077,15 @@ /* No comment provided by engineer. */ "Submit" = "Invia"; +/* No comment provided by engineer. */ +"Subscribed" = "Iscritto"; + +/* No comment provided by engineer. */ +"Subscription errors" = "Errori di iscrizione"; + +/* No comment provided by engineer. */ +"Subscriptions ignored" = "Iscrizioni ignorate"; + /* No comment provided by engineer. */ "Support SimpleX Chat" = "Supporta SimpleX Chat"; @@ -3795,6 +4134,9 @@ /* No comment provided by engineer. */ "TCP_KEEPINTVL" = "TCP_KEEPINTVL"; +/* No comment provided by engineer. */ +"Temporary file error" = "Errore del file temporaneo"; + /* server test failure */ "Test failed at step %@." = "Test fallito al passo %@."; @@ -3876,6 +4218,9 @@ /* No comment provided by engineer. */ "The text you pasted is not a SimpleX link." = "Il testo che hai incollato non è un link SimpleX."; +/* No comment provided by engineer. */ +"Themes" = "Temi"; + /* No comment provided by engineer. */ "These settings are for your current profile **%@**." = "Queste impostazioni sono per il tuo profilo attuale **%@**."; @@ -3918,9 +4263,15 @@ /* No comment provided by engineer. */ "This is your own SimpleX address!" = "Questo è il tuo indirizzo SimpleX!"; +/* No comment provided by engineer. */ +"This link was used with another mobile device, please create a new link on the desktop." = "Questo link è stato usato con un altro dispositivo mobile, creane uno nuovo sul desktop."; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "Questa impostazione si applica ai messaggi del profilo di chat attuale **%@**."; +/* No comment provided by engineer. */ +"Title" = "Titoli"; + /* No comment provided by engineer. */ "To ask any questions and to receive updates:" = "Per porre domande e ricevere aggiornamenti:"; @@ -3960,9 +4311,15 @@ /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Attiva/disattiva l'incognito quando ti colleghi."; +/* No comment provided by engineer. */ +"Total" = "Totale"; + /* No comment provided by engineer. */ "Transport isolation" = "Isolamento del trasporto"; +/* No comment provided by engineer. */ +"Transport sessions" = "Sessioni di trasporto"; + /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact (error: %@)." = "Tentativo di connessione al server usato per ricevere messaggi da questo contatto (errore: %@)."; @@ -4098,12 +4455,21 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Aggiorna e apri chat"; +/* No comment provided by engineer. */ +"Upload errors" = "Errori di invio"; + /* No comment provided by engineer. */ "Upload failed" = "Invio fallito"; /* server test step */ "Upload file" = "Invia file"; +/* No comment provided by engineer. */ +"Uploaded" = "Inviato"; + +/* No comment provided by engineer. */ +"Uploaded files" = "File inviati"; + /* No comment provided by engineer. */ "Uploading archive" = "Invio dell'archivio"; @@ -4149,6 +4515,9 @@ /* No comment provided by engineer. */ "User profile" = "Profilo utente"; +/* No comment provided by engineer. */ +"User selection" = "Selezione utente"; + /* No comment provided by engineer. */ "Using .onion hosts requires compatible VPN provider." = "L'uso di host .onion richiede un fornitore di VPN compatibile."; @@ -4257,6 +4626,12 @@ /* No comment provided by engineer. */ "Waiting for video" = "In attesa del video"; +/* No comment provided by engineer. */ +"Wallpaper accent" = "Tinta dello sfondo"; + +/* No comment provided by engineer. */ +"Wallpaper background" = "Retro dello sfondo"; + /* No comment provided by engineer. */ "wants to connect to you!" = "vuole connettersi con te!"; @@ -4329,9 +4704,15 @@ /* snd error text */ "Wrong key or unknown connection - most likely this connection is deleted." = "Chiave sbagliata o connessione sconosciuta - molto probabilmente questa connessione è stata eliminata."; +/* file error text */ +"Wrong key or unknown file chunk address - most likely file is deleted." = "Chiave sbagliata o indirizzo sconosciuto per frammento del file - probabilmente il file è stato eliminato."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Password sbagliata!"; +/* No comment provided by engineer. */ +"XFTP server" = "Server XFTP"; + /* No comment provided by engineer. */ "XFTP servers" = "Server XFTP"; @@ -4389,6 +4770,9 @@ /* No comment provided by engineer. */ "You are invited to group" = "Sei stato/a invitato/a al gruppo"; +/* No comment provided by engineer. */ +"You are not connected to these servers. Private routing is used to deliver messages to them." = "Non sei connesso/a a questi server. L'instradamento privato è usato per consegnare loro i messaggi."; + /* No comment provided by engineer. */ "you are observer" = "sei un osservatore"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index d66f5bee3d..bd33a36f0c 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -148,9 +148,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ は検証されています"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ サーバー"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ アップロード済"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index c361c2c64e..89089ee726 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ is geverifieerd"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ servers"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ geüpload"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 5860ed1868..cca88e9987 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ jest zweryfikowany"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ serwery"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ wgrane"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 450a6147b7..fcab550e81 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ подтверждён"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ серверы"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ загружено"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 5ce2e40e14..ee379ec4a4 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -109,9 +109,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ ได้รับการตรวจสอบแล้ว"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ เซิร์ฟเวอร์"; - /* notification title */ "%@ wants to connect!" = "%@ อยากเชื่อมต่อ!"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 65496a82bf..d007f05406 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ onaylandı"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ sunucuları"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ yüklendi"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index 69aac64ab5..eb29f07687 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -154,9 +154,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ перевірено"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ сервери"; - /* No comment provided by engineer. */ "%@ uploaded" = "%@ завантажено"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 94588646c8..5d9fd6bf80 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -133,9 +133,6 @@ /* No comment provided by engineer. */ "%@ is verified" = "%@ 已认证"; -/* No comment provided by engineer. */ -"%@ servers" = "%@ 服务器"; - /* notification title */ "%@ wants to connect!" = "%@ 要连接!"; diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index f7c6e03811..2b87fdd91c 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -295,7 +295,7 @@ المنشئ خطأ في إضافة الأعضاء خطأ في إنشاء العنوان - خطأ في حذف اتصال جهة الاتصال المعلق + خطأ في حذف اتصال جهة الاتصال المنتظر أدخل رسالة ترحيب… متصل جار الاتصال @@ -793,7 +793,7 @@ لن يتم استخدام مضيفات البصل. اسم عرض جديد: عبارة مرور جديدة… - يرجى الانتظار + قيد الانتظار كلمة المرور مطلوبة ألصِق الرابط الذي استلمته فقط مالكي المجموعة يمكنهم تفعيل الملفات والوسائط. @@ -828,9 +828,9 @@ كلمة المرور للإظهار ندّ لِندّ يمكن للناس التواصل معك فقط عبر الرابط الذي تقوم بمشاركته - مكالمة في الانتظار + مكالمة قيد الانتظار تعمية ثنائية الطبقات من بين الطريفين.]]> - إعادة تعيين الألوان + صفّر الألوان حفظ عنوان الخادم المحدد مسبقًا حفظ وإشعار أعضاء المجموعة @@ -910,7 +910,7 @@ استلمت في إزالة العضو إزالة - إعادة التعيين إلى الإعدادات الافتراضية + صفّر إلى الإعدادات الافتراضية بينج الفاصل الزمني كلمة مرور الملف الشخصي منع إرسال الرسائل التي تختفي. @@ -938,7 +938,7 @@ يرجى مطالبة جهة اتصالك بتفعيل إرسال الرسائل الصوتية. العنصر النائب لصورة الملف الشخصي رمز QR - إعادة التعيين + صفّر المنفذ %d خادم محدد مسبقًا قراءة المزيد في مستودعنا على GitHub. @@ -1812,7 +1812,7 @@ تلقى الرد أزِل الصورة تكرار - إعادة تعيين اللون + صفّر اللون أرسلت رد تعيين السمة الافتراضية النظام @@ -1840,10 +1840,10 @@ اجعل محادثاتك تبدو مختلفة! تلقي الملفات بأمان واجهة المستخدم الفارسية - إعادة التعيين إلى سمة التطبيق + صفّر إلى سمة التطبيق سمة التطبيق تأكيد الملفات من خوادم غير معروفة. - إعادة التعيين إلى سمة المستخدم + صفّر إلى سمة المستخدم معلومات قائمة انتظار الخادم: %1$s \n \nآخر رسالة تم استلامها: %2$s @@ -1871,4 +1871,120 @@ \nيُرجى مشاركة أي مشاكل أُخرى مع المطورين. لا يمكن إرسال الرسالة تفضيلات الدردشة المحدّدة تحظر هذه الرسالة. + التفاصيل + بدءًا من %s. +\nجميع البيانات خاصة بجهازك. + أرسلت الإجمالي + الحجم + الملفات المرفوعة + يُرجى المحاولة لاحقا. + خطأ في التوجيه الخاص + عنوان الخادم غير متوافق مع إعدادات الشبكة: %1$s. + إصدار الخادم غير متوافق مع تطبيقك: %1$s. + العضو غير نشط + رسالة محوّلة + لا يوجد اتصال مباشر حتى الآن، يتم تحويل من قِبل المشرف. + امسح / ألصِق الرابط + خوادم SMP المهيأة + خوادم SMP أخرى + خوادم XFTP المهيأة + خوادم XFTP أخرى + نسبة الاشتراك + مُعطّل + مستقرّ + يتوفر تحديث: %s + التمس التحديثات + نزّل %s (%s) + ثُبّت بنجاح + افتح مكان الملف + يُرجى إعادة تشغيل التطبيق. + تذكر لاحقا + تخطي هذه النسخة + أُلغيت تنزيل التحديث + مُعطّل + غير نشط + معلومات الخوادم + عرض المعلومات ل + الأخطاء + الرسائل المُرسلة + الإجمالي + الخوادم المتصلة سابقًا + حدث خطأ أثناء إعادة الاتصال بالخادم + أعِد توصيل الخادم؟ + أعِد التوصيل بالخادم لفرض تسليم الرسالة. يستخدم حركة مرور إضافية. + صفّر جميع الإحصائيات + صفّر جميع الإحصائيات؟ + نُزّلت + الرسائل المُستلمة + الرسائل المُرسلة + سيتم تصفير إحصائيات الخوادم - لا يمكن التراجع عن هذا! + رُفع + صفّر + بدءًا من %s. + خادم SMP + خادم XFTP + معترف به + حُذفت القطع + نُزّلت القطع + اكتملت + الاتصالات + أُنشئت + أخطاء فك التعمية + حُذِفت + الملفات التي نُزّلت + أخطاء التنزيل + منتهية الصلاحيّة + افتح إعدادات الخادم + أخرى + موّكل + مؤمن + أرسِل الأخطاء + أُرسلت مباشرةً + مُرسَل عبر الوكيل + مشترك + أخطاء الاشتراك + رفع الأخطاء + التمس التحديثات + أخطاء معترف بها + نُزّل تحديث التطبيق + جميع المستخدمين + المحاولات + تجريبي + رُفع القطع + متصل + الخوادم المتصلة + جارِ الاتصال + الاتصالات المشتركة + المستخدم الحالي + أخطاء الحذف + إحصائيات مفصلة + عطّل + خطأ في تصفير الإحصائيات + التكرارات + خطأ + حدث خطأ أثناء إعادة الاتصال بالخوادم + جارٍ تنزيل تحديث التطبيق، لا تغلق التطبيق + الملفات + حجم الخط + ثبّت التحديث + قد يتم تسليم الرسالة لاحقًا إذا أصبح العضو نشطًا. + الرسائل المُستلمة + اشتراكات الرسائل + لا توجد معلومات، حاول إعادة التحميل + أخطاء أخرى + قيد الانتظار + أعِد التوصيل + أعِد توصيل كافة الخوادم المتصلة لفرض تسليم الرسالة. يستخدم حركة مرور إضافية. + خوادم موّكلة + تلقي الأخطاء + تلقى الإجمالي + أعِد توصيل جميع الخوادم + أعِد توصيل الخوادم؟ + عنوان الخادم + الإحصائيات + تم تجاهل الاشتراكات + جلسات النقل + لكي يتم إعلامك بالإصدارات الجديدة، شغّل الفحص الدوري للإصدارات المستقرة أو التجريبية. + أنت غير متصل بهذه الخوادم. يتم استخدام التوجيه الخاص لتسليم الرسائل إليهم. + قرّب \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index cdc5720e37..2029650eb8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -74,7 +74,7 @@ Fehler beim Löschen des Kontakts Fehler beim Löschen der Gruppe Fehler beim Löschen der Kontaktanfrage - Fehler beim Löschen der anstehenden Kontaktaufnahme + Fehler beim Löschen der ausstehenden Kontaktaufnahme Fehler beim Wechseln der Empfängeradresse Der Test ist beim Schritt %s fehlgeschlagen. Um Warteschlangen zu erzeugen, benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort. @@ -157,7 +157,7 @@ Verbergen Erlauben Die Nachricht löschen? - Nachricht wird gelöscht – dies kann nicht rückgängig gemacht werden! + Nachricht wird gelöscht. Dies kann nicht rückgängig gemacht werden! Die Nachricht wird zum Löschen markiert. Der/die Empfänger kann/können diese Nachricht aufdecken. Für mich löschen Für alle @@ -218,7 +218,7 @@ Benachrichtigungen Kontakt löschen? - Der Kontakt und alle Nachrichten werden gelöscht – dies kann nicht rückgängig gemacht werden! + Der Kontakt und alle Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden! Kontakt löschen Kontaktname festlegen… Verbunden @@ -278,7 +278,7 @@ Ablehnen Chatinhalte löschen? - Alle Nachrichten werden gelöscht - dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. + Alle Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden! Die Nachrichten werden NUR bei Ihnen gelöscht. Löschen Chatinhalte löschen Chatinhalte löschen @@ -298,7 +298,8 @@ Die von Ihnen akzeptierte Verbindung wird abgebrochen! Ihr Kontakt ist noch nicht verbunden! - Ihr Kontakt muss online sein, damit die Verbindung hergestellt werden kann.\nSie können diese Verbindung abbrechen und den Kontakt entfernen (und es später nochmals mit einem neuen Link versuchen). + Ihr Kontakt muss online sein, damit die Verbindung hergestellt werden kann. +\nSie können diese Verbindung abbrechen, den Kontakt entfernen und es später nochmals mit einem neuen Link versuchen. möchte sich mit Ihnen verbinden! @@ -531,7 +532,7 @@ Lautsprecher an Kamera umdrehen - Anstehender Anruf + Ausstehender Anruf Verpasster Anruf Abgelehnter Anruf Anruf wird verbunden @@ -766,8 +767,8 @@ Sie: %1$s Gruppe löschen Gruppe löschen? - Die Gruppe wird für alle Mitglieder gelöscht – dies kann nicht rückgängig gemacht werden! - Die Gruppe wird für Sie gelöscht – dies kann nicht rückgängig gemacht werden! + Die Gruppe wird für alle Mitglieder gelöscht. Dies kann nicht rückgängig gemacht werden! + Die Gruppe wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden! Gruppe verlassen Gruppenprofil bearbeiten Gruppen-Link @@ -786,7 +787,7 @@ Mitglied entfernen Direktnachricht senden - Das Mitglied wird aus der Gruppe entfernt – dies kann nicht rückgängig gemacht werden! + Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden! Entfernen MITGLIED Rolle @@ -988,7 +989,7 @@ App-Version: v%s Core Version: v%s Profil hinzufügen - Alle Chats und Nachrichten werden gelöscht! Dies kann nicht rückgängig gemacht werden! + Alle Chats und Nachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden! Chat-Profil löschen für PING-Zähler Transport-Isolations-Modus aktualisieren\? @@ -1675,7 +1676,7 @@ unbekannter Gruppenmitglieds-Status Mit verschlüsselten Dateien und Medien. Private Notizen - Es werden alle Nachrichten gelöscht. Dieser Vorgang kann nicht rückgängig gemacht werden! + Es werden alle Nachrichten gelöscht. Dies kann nicht rückgängig gemacht werden! Private Notizen löschen? %s wurde blockiert %s wurde freigegeben @@ -1940,8 +1941,8 @@ \n \nZuletzt empfangene Nachricht: %2$s Datei nicht gefunden - höchstwahrscheinlich wurde die Datei gelöscht oder der Transfer abgebrochen. - Datei-Server-Fehler: %1$s - Falscher Schlüssel oder unbekannte Datei-Chunk-Adresse - höchstwahrscheinlich wurde die Datei gelöscht. + Datei-Server Fehler: %1$s + Falscher Schlüssel oder unbekannte Daten-Paketadresse der Datei - höchstwahrscheinlich wurde die Datei gelöscht. Datei-Fehler Nachrichten-Status Nachrichten-Status: %s @@ -1954,4 +1955,120 @@ \nBitte teilen Sie weitere mögliche Probleme den Entwicklern mit. Nachricht wurde nicht gesendet Diese Nachricht ist wegen der gewählten Chat-Einstellungen nicht erlaubt. + Bitte versuchen Sie es später erneut. + Fehler beim privaten Routing + Die Nachricht kann später zugestellt werden, wenn das Mitglied aktiv wird. + Bisher keine direkte Verbindung. Nachricht wird von einem Admin weitergeleitet. + Konfigurierte SMP-Server + Konfigurierte XFTP-Server + Andere SMP-Server + Andere XFTP-Server + Abgeschlossen + Inaktiv + Verbunden + Verbinden + Abonnierte Verbindungen + Aktueller Nutzer + Detaillierte Statistiken + Details + Heruntergeladen + Fehler + Fehler beim Wiederherstellen der Verbindungen zu den Servern + Fehler beim Zurücksetzen der Statistiken + Fehler + Empfangene Nachrichten + Nachrichten-Abonnements + Ausstehend + Bisher verbundene Server + Proxy-Server + Empfangene Nachrichten + Summe aller empfangenen Nachrichten + Fehler bei der Bestätigung + Versuche + Daten-Pakete gelöscht + Daten-Pakete hochgeladen + Verbindungen + Erstellt + Entschlüsselungs-Fehler + Fehler beim Löschen + Heruntergeladene Dateien + Fehler beim Herunterladen + Duplikate + abgelaufen + Server-Einstellungen öffnen + Andere Fehler + Proxy + Fehler beim Empfang + Neu verbinden + Deaktiviert + Beta + App-Aktualisierung wird heruntergeladen. App nicht schließen! + Heruntergeladen %s (%s) + Nach Aktualisierungen suchen + Deaktivieren + Erfolgreich installiert + Aktualisierung installieren + Bitte starten Sie die App neu. + Bestätigt + Alle Nutzer + App-Aktualisierung wurde heruntergeladen + Nach Aktualisierungen suchen + Daten-Pakete heruntergeladen + Verbundene Server + Gelöscht + deaktiviert + Fehler beim Wiederherstellen der Verbindung zum Server + Nachricht weitergeleitet + Dateien + Schriftgröße + Mitglied inaktiv + Gesendete Nachrichten + Keine Information - es wird versucht neu zu laden + Dateispeicherort öffnen + andere + Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel: %1$s. + Link scannen / einfügen + Alle Server neu verbinden + Server neu verbinden? + Alle Server neu verbinden? + Um die Auslieferung von Nachrichten zu erzwingen, werden alle Server neu verbunden. Dafür wird weiterer Datenverkehr benötigt. + Um die Auslieferung von Nachrichten zu erzwingen, wird der Server neu verbunden. Dafür wird weiterer Datenverkehr benötigt. + Zurücksetzen + Alle Statistiken zurücksetzen + Alle Statistiken zurücksetzen? + Gesendete Nachrichten + Summe aller gesendeten Nachrichten + Abgesichert + Fehler beim Senden + Direkt gesendet + Über einen Proxy gesendet + Server-Adresse + Später erinnern + Server-Informationen + Ihre App ist nicht kompatibel mit der Server-Version: %1$s. + Prozentualer Anteil der Abonnements + Zoom + SMP-Server + Informationen zeigen für + Beginnend mit %s. +\nAlle Daten sind auf Ihrem Gerät geschützt. + Statistiken + Transport-Sitzungen + Hochgeladen + Sie sind nicht mit diesen Servern verbunden. Zur Auslieferung von Nachrichten an diese Server wird privates Routing genutzt. + Summe aller Abonnements + Die Serverstatistiken werden zurückgesetzt. Dies kann nicht rückgängig gemacht werden! + Größe + Beginnend mit %s. + Abonniert + Fehler beim Abonnieren + Nicht beachtete Abonnements + Hochgeladene Dateien + Fehler beim Hochladen + XFTP-Server + Stabil + Diese Version überspringen + Aktualisierung verfügbar: %s + Aktivieren Sie die periodische Überprüfung auf stabile oder Beta-Versionen der App, um über neue Versionen benachrichtigt zu werden. + Herunterladen der Aktualisierung abgebrochen \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 66ea969b09..7195024ab5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -1867,4 +1867,120 @@ \nMinden további problémát osszon meg a fejlesztőkkel. Nem lehet üzenetet küldeni A kiválasztott csevegési beállítások tiltják ezt az üzenetet. + Próbálja meg később. + A kiszolgáló címe nem kompatibilis a hálózati beállításokkal: %1$s. + Inaktív tag + Továbbított üzenet + Az üzenet később is kézbesíthető, ha a tag aktívvá válik. + Még nincs közvetlen kapcsolat, az üzenetet az admin továbbítja. + Hivatkozás beolvasása / beillesztése + Beállított SMP-kiszolgálók + Egyéb SMP-kiszolgálók + Egyéb XFTP-kiszolgálók + letiltva + inaktív + Nagyítás + információk a kiszolgálókról + Kapcsolódás + Hibák + Függőben + Kezdve ettől: %s. +\nMinden adat biztonságban van a készülékén. + Elküldött üzenetek + Proxyzott kiszolgálók + Újrakapcsolódás a kiszolgálókhoz? + Újrakapcsolódás a kiszolgálóhoz? + Hiba a kiszolgálóhoz való újrakapcsolódáskor + Újrakapcsolódás minden kiszolgálóhoz + Hiba a statisztikák visszaállításakor + Visszaállítás + Minden statisztika visszaállítása + Minden statisztika visszaállítása? + A kiszolgálók statisztikái visszaállnak - ez nem vonható vissza! + Részletes statisztikák + Letöltve + lejárt + egyéb + Összes fogadott + Üzenetfogadási hibák + Újrakapcsolás + Üzenetküldési hibák + Közvetlenül küldött + Összes elküldött + Proxyn keresztül küldve + SMP-kiszolgáló + Kezdve ettől: %s. + Feltöltve + XFTP-kiszolgáló + Proxyzott + duplikációk + egyéb hibák + Kapcsolatok + Létrehozva + Biztosítva + Törlési hibák + Méret + Feltöltött fájlok + Letöltött fájltöredékek + Letöltött fájlok + Kiszolgáló beállításainak megnyitása + Kiszolgáló címe + Feltöltési hibák + Nyugtázva + Nyugtázott hibák + próbálkozások + Törölt fájltöredékek + Minden felhasználó + Feltöltött fájltöredékek + Elkészült + Kapcsolódott kiszolgálók + Beállított XFTP-kiszolgálók + Kapcsolódva + Jelenlegi felhasználó + Részletek + visszafejtési hibák + Törölve + Fogadott üzenetek + Letöltési hibák + Hiba + Hiba a kiszolgálókhoz való újrakapcsolódáskor + Fájlok + Betűméret + Nincs információ, próbálja meg újratölteni + Korábban kapcsolódott kiszolgálók + Privát útválasztási hiba + Fogadott üzenetek + Az összes kiszolgálóhoz való újrakapcsolás az üzenetek kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. + A kiszolgálóhoz való újrakapcsolódás az üzenet kézbesítésének kikényszerítéséhez. Ez további adatforgalmat használ. + Elküldött üzenetek + Munkamenetek átvitele + Összesen + Statisztikák + Információk megjelenítése ehhez: + A kiszolgáló verziója nem kompatibilis az alkalmazással: %1$s. + Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál. + Feliratkozott kapcsolatok + Üzenet feliratkozások + Feliratkozva + Feliratkozási hibák + Elutasított feliratkozások + Feliratkozási százalék + Alkalmazásfrissítés letöltve + Frissítések keresése + Frissítések keresése + Alkalmazásfrissítés letöltése, ne zárja be az alkalmazást + Letöltés %s (%s) + Sikeresen telepítve + Frissítés telepítése + Fájl helyének megnyitása + Indítsa újra az alkalmazást. + Emlékeztessen később + Hagyja ki ezt a verziót + Ha értesítést szeretne kapni az új kiadásokról, kapcsolja be a stabil vagy béta verziók időszakos ellenőrzését. + Frissítés érhető el: %s + A frissítés letöltése megszakítva + Béta + Letiltás + Letiltva + Stabil \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml index 3cace47d15..d41069a1b2 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml @@ -46,4 +46,7 @@ Selalu APLIKASI Tampilan + Tentang SimpleX Chat + Terima + Terima \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 690c826549..f2f62c1fb9 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1873,4 +1873,120 @@ Copia errore Impossibile inviare il messaggio Le preferenze della chat selezionata vietano questo messaggio. + Connessioni + Connessioni sottoscritte + Creato + errori di decifrazione + Statistiche dettagliate + doppi + Errore + Errore di riconnessione al server + Errore di riconnessione ai server + scaduto + Iscrizioni ai messaggi + altro + altri errori + In attesa + Via proxy + Server via proxy + Totale ricevuto + Errori di ricezione + Riconnetti + Riconnetti tutti i server connessi per forzare la consegna dei messaggi. Usa traffico aggiuntivo. + Riconnettere il server? + Riconnettere i server? + Riconnetti il server per forzare la consegna dei messaggi. Usa traffico aggiuntivo. + Errori di invio + Inviato direttamente + Messaggi inviati + Inviato via proxy + Le statistiche dei server verranno azzerate - è irreversibile! + Azzera tutte le statistiche + Azzerare tutte le statistiche? + Azzera + Errore di azzeramento statistiche + Server SMP + Inizio da %s. + Totale + Inviato + Server XFTP + Riconosciuto + Errori di riconoscimento + Blocchi eliminati + Blocchi scaricati + Blocchi inviati + Eliminato + Errori di eliminazione + File scaricati + Apri impostazioni server + Protetto + Indirizzo server + Dimensione + Iscritto + Errori di iscrizione + Iscrizioni ignorate + File inviati + Errori di invio + Errore di instradamento privato + La versione del server non è compatibile con la tua app: %1$s. + Membro inattivo + Il messaggio può essere consegnato più tardi se il membro diventa attivo. + Server SMP configurati + Altri server SMP + Percentuale di iscrizione + inattivo + Zoom + Connesso + In connessione + Utente attuale + Dettagli + Errori + Messaggi ricevuti + Messaggi inviati + Nessuna informazione, prova a ricaricare + Info dei server + Informazioni di + Statistiche + Sessioni di trasporto + Tutti gli utenti + tentativi + Server XFTP configurati + Completato + Server connessi + disattivato + Scaricato + Messaggi ricevuti + Errori di scaricamento + Riconnetti tutti i server + L\'indirizzo del server non è compatibile con le impostazioni di rete: %1$s. + File + Scansiona / Incolla link + Dimensione carattere + Totale inviato + Messaggio inoltrato + Inizio da %s. +\nTutti i dati sono privati, nel tuo dispositivo. + Ancora nessuna connessione diretta, il messaggio viene inoltrato dall\'amministratore. + Non sei connesso/a a questi server. L\'instradamento privato è usato per consegnare loro i messaggi. + Altri server XFTP + Server precedentemente connessi + Riprova più tardi. + Beta + Disattiva + Disattivato + Scaricamento dell\'aggiornamento, non chiudere l\'app + Scarica %s (%s) + Installato correttamente + Apri percorso file + Riavvia l\'app. + Ricordamelo più tardi + Salta questa versione + Stabile + Per essere avvisato sulle nuove versioni, attiva il controllo periodico di versioni stabili o beta. + Aggiornamento disponibile: %s + Scaricamento aggiornamento annullato + Cerca aggiornamenti + Aggiornamento dell\'app scaricato + Cerca aggiornamenti + Installa aggiornamento \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index ac3f75780e..7d97b6fa03 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -1834,4 +1834,12 @@ いいえ はい メッセージルーティングモード + フォントサイズ + ベータ + アップデートを確認 + アップデートを確認 + 完了 + SMPサーバーの構成 + 接続中 + XFTPサーバーの構成 \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index ef66bf6e10..34255ce99b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -1871,4 +1871,96 @@ Berichtstatus Kan bericht niet verzenden Geselecteerde chatvoorkeuren verbieden dit bericht. + Fout in privéroutering + Serverversie is niet compatibel met uw app: %1$s. + Bericht doorgestuurd + Nog geen directe verbinding, bericht wordt doorgestuurd door beheerder. + Overige XFTP servers + Link scannen/plakken + Zoom + Huidige gebruiker + Bestanden + Server informatie + Informatie weergeven voor + Fouten + Statistieken + Transportsessies + Verbindingen geabonneerd + Details + Berichten ontvangen + Berichten abonnementen + Beginnend vanaf %s. +\nAlle gegevens zijn privé op uw apparaat. + Verbonden servers + in behandeling + Eerder verbonden servers + Proxied servers + Totaal + Server opnieuw verbinden? + Servers opnieuw verbinden? + Maak opnieuw verbinding met de server om de bezorging van berichten te forceren. Er wordt gebruik gemaakt van extra data. + U bent niet verbonden met deze servers. Privéroutering wordt gebruikt om berichten bij hen af te leveren. + Maak opnieuw verbinding met alle servers + Reset + Reset alle statistieken + Alle statistieken resetten? + Geüpload + Gedetailleerde statistieken + Ontvangen berichten + Verzonden berichten + Totaal verzonden + Proxied + Fouten ontvangen + opnieuw verbinden + Direct verzonden + Verzonden via proxy + SMP server + XFTP server + Erkend + Bevestigingsfouten + Verbindingen + Gemaakt + decoderingsfouten + duplicaten + verlopen + overige fouten + overig + Verzend fouten + Stukken gedownload + Stukken geüpload + Verwijderd + Verwijderingsfouten + Gedownloade bestanden + Beveiligd + Maat + Geabonneerd + Geüploade bestanden + Upload fouten + Downloadfouten + Server instellingen openen + Server adres + Alle gebruikers + pogingen + Stukken verwijderd + voltooid + Verbonden + Verbinden + Fout bij opnieuw verbinding maken met de server + Letter grootte + inactief + Lid inactief + Gedownload + Fout + Fout bij opnieuw verbinden van servers + Fout bij het resetten van statistieken + Het bericht kan later worden bezorgd als het lid actief wordt. + Berichten verzonden + Geen info, probeer opnieuw te laden + Overige SMP servers + Totaal ontvangen + Probeer het later. + Maak opnieuw verbinding met alle verbonden servers om de bezorging van berichten te forceren. Er wordt gebruik gemaakt van extra data. + Het serveradres is niet compatibel met de netwerkinstellingen: %1$s. + Serverstatistieken worden gereset - dit kan niet ongedaan worden gemaakt! + Beginnend vanaf %s. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 3bb4d1b72b..dd675791c6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -1873,4 +1873,120 @@ 复制错误 无法发送消息 选择的聊天首选项禁止此条消息。 + 请稍后尝试。 + 私密路由出错 + 已转发的消息 + 尚无直接连接,消息由管理员转发。 + 其他 SMP 服务器 + 其他 XFTP 服务器 + 扫描/粘贴链接 + 订阅百分比 + 不活跃 + 缩放 + 所有用户 + 文件 + 没有信息,试试重新加载 + 服务器信息 + 尝试 + 已连接 + 已连接的服务器 + 连接中 + 订阅的连接 + 详细统计数据 + 详情 + 已下载 + 错误 + 重连服务器出错 + 重连服务器出错 + 重设统计数据出错 + 错误 + 收到的消息 + 消息订阅 + 待连接 + 先前连接的服务器 + 已代理的服务器 + 接收到的消息 + 接收总计 + 接收错误 + 重连 + 重连服务器? + 重连服务器? + 重连服务器强制消息传输。这会使用额外流量。 + 重置所有统计数据 + 重置所有统计数据吗? + 直接发送 + 已发送消息 + 发送总计 + 通过代理发送 + 服务器统计数据将被重置。此操作无法撤销! + XFTP 服务器 + 认可出错 + 块已删除 + 块已下载 + 已完毕 + 连接数 + 已创建 + 解密出错 + 已删除 + 删除错误 + 已下载的文件 + 下载出错 + 重复 + 已过期 + 其他 + 其他错误 + 已代理 + 已受保护 + 发送错误 + 服务器地址 + 大小 + 已上传的文件 + 上传出错 + 重新连接所有已连接的服务器来强制消息传输。这会使用额外流量。 + 你没有连接到这些服务器。私密路由被用于向它们传输消息。 + 重连所有服务器 + 重置 + 服务器地址不兼容网络设置:%1$s。 + 起始自 %s。 + 起始自 %s. +\n所有数据都是设备的私有数据。 + 已订阅 + 已认可 + 服务器版本不兼容你的应用:%1$s. + 信息主体 + SMP 服务器 + 统计数据 + 订阅错误 + 总计 + 块已上传 + 订阅被忽略 + 已配置的 SMP 服务器 + 已配置的 XFTP 服务器 + 当前用户 + 传输会话 + 已上传 + 已停用 + 字体大小 + 成员不活跃 + 如果成员变得活跃,可能会在之后传输消息。 + 发送的消息 + 打开服务器设置 + 检查更新 + 检查更新 + 停用 + 已停用 + 正在下载应用更新,不要关闭应用 + 下载 %s(%s) + 打开文件位置 + 请重启应用。 + 稍后提醒 + 跳过此版本 + 稳定版 + 有更新可用:%s + 取消了更新下载 + 要接收新版本通知,请打开“定期检查稳定或测试版本”。 + 应用更新已下载 + 测试版 + 安装成功 + 安装更新 \ No newline at end of file From 905295ee5f11c0f0a57fcc97d3a0ebb86cffe3b6 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 18 Jul 2024 22:26:06 +0700 Subject: [PATCH 40/57] android, desktop: interactive media and link previews in the list of chats (#4460) * android, desktop: chat item content preview in chat list * better * moved code * layout * multiplier for files * small * changes * changes * changes * no padding * changes * color * multiplier * changes * fix state inconsistency in gallery * voice messages improvements * showing draft * re-layout preview * rename and padding * fix * padding * link icon * without offset * image * hand on hover * color --------- Co-authored-by: Evgeny Poberezkin --- .../common/platform/RecAndPlay.android.kt | 26 +- .../chat/simplex/common/model/ChatModel.kt | 18 ++ .../simplex/common/platform/RecAndPlay.kt | 12 + .../simplex/common/views/chat/ChatView.kt | 2 +- .../common/views/chat/ComposeVoiceView.kt | 2 +- .../common/views/chat/item/CIFileView.kt | 88 +++---- .../common/views/chat/item/CIImageView.kt | 17 +- .../common/views/chat/item/CIVIdeoView.kt | 114 +++++++-- .../common/views/chat/item/CIVoiceView.kt | 138 +++++++---- .../common/views/chat/item/ChatItemView.kt | 2 +- .../common/views/chat/item/FramedItemView.kt | 8 +- .../views/chat/item/ImageFullScreenView.kt | 9 +- .../common/views/chatlist/ChatListView.kt | 2 +- .../common/views/chatlist/ChatPreviewView.kt | 228 +++++++++++++----- .../resources/MR/images/ic_arrow_outward.svg | 1 + .../common/platform/RecAndPlay.desktop.kt | 36 +-- 16 files changed, 482 insertions(+), 221 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_outward.svg diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt index 4dbc9bd9a9..e5dda23f0f 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/RecAndPlay.android.kt @@ -126,16 +126,11 @@ actual object AudioPlayer: AudioPlayerInterface { .build() ) } - // Filepath: String, onProgressUpdate - private val currentlyPlaying: MutableState Unit>?> = mutableStateOf(null) + override val currentlyPlaying: MutableState = mutableStateOf(null) private var progressJob: Job? = null - enum class TrackState { - PLAYING, PAUSED, REPLACED - } - // Returns real duration of the track - private fun start(fileSource: CryptoFile, seek: Int? = null, onProgressUpdate: (position: Int?, state: TrackState) -> Unit): Int? { + private fun start(fileSource: CryptoFile, smallView: Boolean, seek: Int? = null, onProgressUpdate: (position: Int?, state: TrackState) -> Unit): Int? { val absoluteFilePath = if (fileSource.isAbsolutePath) fileSource.filePath else getAppFilePath(fileSource.filePath) if (!File(absoluteFilePath).exists()) { Log.e(TAG, "No such file: ${fileSource.filePath}") @@ -145,7 +140,7 @@ actual object AudioPlayer: AudioPlayerInterface { VideoPlayerHolder.stopAll() RecorderInterface.stopRecording?.invoke() val current = currentlyPlaying.value - if (current == null || current.first != fileSource.filePath) { + if (current == null || current.fileSource.filePath != fileSource.filePath || smallView != current.smallView) { stopListener() player.reset() runCatching { @@ -168,7 +163,7 @@ actual object AudioPlayer: AudioPlayerInterface { } if (seek != null) player.seekTo(seek) player.start() - currentlyPlaying.value = fileSource.filePath to onProgressUpdate + currentlyPlaying.value = CurrentlyPlayingState(fileSource, onProgressUpdate, smallView) progressJob = CoroutineScope(Dispatchers.Default).launch { onProgressUpdate(player.currentPosition, TrackState.PLAYING) while(isActive && player.isPlaying) { @@ -192,6 +187,10 @@ actual object AudioPlayer: AudioPlayerInterface { } keepScreenOn(false) onProgressUpdate(null, TrackState.PAUSED) + + if (smallView && isActive) { + stopListener() + } } return player.duration } @@ -215,7 +214,7 @@ actual object AudioPlayer: AudioPlayerInterface { // FileName or filePath are ok override fun stop(fileName: String?) { - if (fileName != null && currentlyPlaying.value?.first?.endsWith(fileName) == true) { + if (fileName != null && currentlyPlaying.value?.fileSource?.filePath?.endsWith(fileName) == true) { stop() } } @@ -223,7 +222,7 @@ actual object AudioPlayer: AudioPlayerInterface { private fun stopListener() { val afterCoroutineCancel: CompletionHandler = { // Notify prev audio listener about stop - currentlyPlaying.value?.second?.invoke(null, TrackState.REPLACED) + currentlyPlaying.value?.onProgressUpdate?.invoke(null, TrackState.REPLACED) currentlyPlaying.value = null } /** Preventing race by calling a code AFTER coroutine ends, so [TrackState] will be: @@ -244,11 +243,12 @@ actual object AudioPlayer: AudioPlayerInterface { progress: MutableState, duration: MutableState, resetOnEnd: Boolean, + smallView: Boolean, ) { if (progress.value == duration.value) { progress.value = 0 } - val realDuration = start(fileSource, progress.value) { pro, state -> + val realDuration = start(fileSource, smallView, progress.value) { pro, state -> if (pro != null) { progress.value = pro } @@ -274,7 +274,7 @@ actual object AudioPlayer: AudioPlayerInterface { override fun seekTo(ms: Int, pro: MutableState, filePath: String?) { pro.value = ms - if (currentlyPlaying.value?.first == filePath) { + if (currentlyPlaying.value?.fileSource?.filePath == filePath) { player.seekTo(ms) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 5fd813f09f..e96b8a8eb1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2765,6 +2765,24 @@ data class CIFile( is CIFileStatus.Invalid -> null } + val showStatusIconInSmallView: Boolean = when (fileStatus) { + is CIFileStatus.SndStored -> fileProtocol != FileProtocol.LOCAL + is CIFileStatus.SndTransfer -> true + is CIFileStatus.SndComplete -> false + is CIFileStatus.SndCancelled -> true + is CIFileStatus.SndError -> true + is CIFileStatus.SndWarning -> true + is CIFileStatus.RcvInvitation -> false + is CIFileStatus.RcvAccepted -> true + is CIFileStatus.RcvTransfer -> true + is CIFileStatus.RcvAborted -> true + is CIFileStatus.RcvCancelled -> true + is CIFileStatus.RcvComplete -> false + is CIFileStatus.RcvError -> true + is CIFileStatus.RcvWarning -> true + is CIFileStatus.Invalid -> true + } + /** * DO NOT CALL this function in compose scope, [LaunchedEffect], [DisposableEffect] and so on. Only with [withBGApi] or [runBlocking]. * Otherwise, it will be canceled when moving to another screen/item/view, etc diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt index 1e902b5d88..fd1824d5b6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/RecAndPlay.kt @@ -16,13 +16,25 @@ interface RecorderInterface { expect class RecorderNative(): RecorderInterface +enum class TrackState { + PLAYING, PAUSED, REPLACED +} + +data class CurrentlyPlayingState( + val fileSource: CryptoFile, + val onProgressUpdate: (position: Int?, state: TrackState) -> Unit, + val smallView: Boolean, +) + interface AudioPlayerInterface { + val currentlyPlaying: MutableState fun play( fileSource: CryptoFile, audioPlaying: MutableState, progress: MutableState, duration: MutableState, resetOnEnd: Boolean, + smallView: Boolean, ) fun stop() fun stop(item: ChatItem) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 5578180ff8..7af08107bb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1416,7 +1416,7 @@ sealed class ProviderMedia { data class Video(val uri: URI, val fileSource: CryptoFile?, val preview: String): ProviderMedia() } -private fun providerForGallery( +fun providerForGallery( listStateIndex: Int, chatItems: List, cItemId: Long, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt index b71d090a4e..b070dce1d1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt @@ -53,7 +53,7 @@ fun ComposeVoiceView( IconButton( onClick = { if (!audioPlaying.value) { - AudioPlayer.play(CryptoFile.plain(filePath), audioPlaying, progress, duration, false) + AudioPlayer.play(CryptoFile.plain(filePath), audioPlaying, progress, duration, resetOnEnd = false, smallView = false) } else { AudioPlayer.pause(audioPlaying, progress) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index f181126b33..59643afdf4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -1,5 +1,6 @@ package chat.simplex.common.views.chat.item +import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CornerSize @@ -12,10 +13,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* @@ -29,14 +29,17 @@ fun CIFileView( file: CIFile?, edited: Boolean, showMenu: MutableState, + smallView: Boolean = false, receiveFile: (Long) -> Unit ) { val saveFileLauncher = rememberSaveFileLauncher(ciFile = file) - + val sizeMultiplier = 1f + val progressSizeMultiplier = if (smallView) 0.7f else 1f @Composable fun fileIcon( innerIcon: Painter? = null, - color: Color = if (isInDarkTheme()) FileDark else FileLight + color: Color = if (isInDarkTheme()) FileDark else FileLight, + topPadding: Dp = 12.sp.toDp() ) { Box( contentAlignment = Alignment.Center @@ -52,8 +55,9 @@ fun CIFileView( innerIcon, stringResource(MR.strings.icon_descr_file), Modifier - .size(32.dp) - .padding(top = 12.dp), + .padding(top = topPadding * sizeMultiplier) + .height(20.sp.toDp() * sizeMultiplier) + .width(32.sp.toDp() * sizeMultiplier), tint = Color.White ) } @@ -132,39 +136,39 @@ fun CIFileView( fun fileIndicator() { Box( Modifier - .size(42.dp) - .clip(RoundedCornerShape(4.dp)), + .size(42.sp.toDp() * sizeMultiplier) + .clip(RoundedCornerShape(4.sp.toDp() * sizeMultiplier)), contentAlignment = Alignment.Center ) { if (file != null) { when (file.fileStatus) { is CIFileStatus.SndStored -> when (file.fileProtocol) { - FileProtocol.XFTP -> CIFileViewScope.progressIndicator() + FileProtocol.XFTP -> CIFileViewScope.progressIndicator(progressSizeMultiplier) FileProtocol.SMP -> fileIcon() FileProtocol.LOCAL -> fileIcon() } is CIFileStatus.SndTransfer -> when (file.fileProtocol) { - FileProtocol.XFTP -> CIFileViewScope.progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal) - FileProtocol.SMP -> CIFileViewScope.progressIndicator() + FileProtocol.XFTP -> CIFileViewScope.progressCircle(file.fileStatus.sndProgress, file.fileStatus.sndTotal, progressSizeMultiplier) + FileProtocol.SMP -> CIFileViewScope.progressIndicator(progressSizeMultiplier) FileProtocol.LOCAL -> {} } - is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled)) + is CIFileStatus.SndComplete -> fileIcon(innerIcon = if (!smallView) painterResource(MR.images.ic_check_filled) else null) is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.SndWarning -> fileIcon(innerIcon = painterResource(MR.images.ic_warning_filled)) is CIFileStatus.RcvInvitation -> if (fileSizeValid(file)) - fileIcon(innerIcon = painterResource(MR.images.ic_arrow_downward), color = MaterialTheme.colors.primary) + fileIcon(innerIcon = painterResource(MR.images.ic_arrow_downward), color = MaterialTheme.colors.primary, topPadding = 10.sp.toDp()) else fileIcon(innerIcon = painterResource(MR.images.ic_priority_high), color = WarningOrange) is CIFileStatus.RcvAccepted -> fileIcon(innerIcon = painterResource(MR.images.ic_more_horiz)) is CIFileStatus.RcvTransfer -> if (file.fileProtocol == FileProtocol.XFTP && file.fileStatus.rcvProgress < file.fileStatus.rcvTotal) { - CIFileViewScope.progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal) + CIFileViewScope.progressCircle(file.fileStatus.rcvProgress, file.fileStatus.rcvTotal, progressSizeMultiplier) } else { - CIFileViewScope.progressIndicator() + CIFileViewScope.progressIndicator(progressSizeMultiplier) } is CIFileStatus.RcvAborted -> fileIcon(innerIcon = painterResource(MR.images.ic_sync_problem), color = MaterialTheme.colors.primary) @@ -186,31 +190,33 @@ fun CIFileView( onClick = { fileAction() }, onLongClick = { showMenu.value = true } ) - .padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp), + .padding(if (smallView) PaddingValues() else PaddingValues(top = 4.sp.toDp(), bottom = 6.sp.toDp(), start = 6.sp.toDp(), end = 12.sp.toDp())), //Modifier.clickable(enabled = file?.fileSource != null) { if (file?.fileSource != null && getLoadedFilePath(file) != null) openFile(file.fileSource) }.padding(top = 4.dp, bottom = 6.dp, start = 6.dp, end = 12.dp), verticalAlignment = Alignment.Bottom, - horizontalArrangement = Arrangement.spacedBy(2.dp) + horizontalArrangement = Arrangement.spacedBy(2.sp.toDp()) ) { fileIndicator() - val metaReserve = if (edited) - " " - else - " " - if (file != null) { - Column { - Text( - file.fileName, - maxLines = 1 - ) - Text( - formatBytes(file.fileSize) + metaReserve, - color = MaterialTheme.colors.secondary, - fontSize = 14.sp, - maxLines = 1 - ) + if (!smallView) { + val metaReserve = if (edited) + " " + else + " " + if (file != null) { + Column { + Text( + file.fileName, + maxLines = 1 + ) + Text( + formatBytes(file.fileSize) + metaReserve, + color = MaterialTheme.colors.secondary, + fontSize = 14.sp, + maxLines = 1 + ) + } + } else { + Text(metaReserve) } - } else { - Text(metaReserve) } } } @@ -243,18 +249,18 @@ fun rememberSaveFileLauncher(ciFile: CIFile?): FileChooserLauncher = object CIFileViewScope { @Composable - fun progressIndicator() { + fun progressIndicator(sizeMultiplier: Float = 1f) { CircularProgressIndicator( - Modifier.size(32.dp), + Modifier.size(32.sp.toDp() * sizeMultiplier), color = if (isInDarkTheme()) FileDark else FileLight, - strokeWidth = 3.dp + strokeWidth = 3.sp.toDp() * sizeMultiplier ) } @Composable - fun progressCircle(progress: Long, total: Long) { + fun progressCircle(progress: Long, total: Long, sizeMultiplier: Float = 1f) { val angle = 360f * (progress.toDouble() / total.toDouble()).toFloat() - val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() } + val strokeWidth = with(LocalDensity.current) { 3.sp.toPx() } val strokeColor = if (isInDarkTheme()) FileDark else FileLight Surface( Modifier.drawRingModifier(angle, strokeColor, strokeWidth), @@ -262,7 +268,7 @@ object CIFileViewScope { shape = MaterialTheme.shapes.small.copy(CornerSize(percent = 50)), contentColor = LocalContentColor.current ) { - Box(Modifier.size(32.dp)) + Box(Modifier.size(32.sp.toDp() * sizeMultiplier)) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 5aa3bfab05..59740711fe 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -29,6 +29,7 @@ fun CIImageView( file: CIFile?, imageProvider: () -> ImageGalleryProvider, showMenu: MutableState, + smallView: Boolean, receiveFile: (Long) -> Unit ) { @Composable @@ -55,7 +56,7 @@ fun CIImageView( if (file != null) { Box( Modifier - .padding(8.dp) + .padding(if (smallView) 0.dp else 8.dp) .size(20.dp), contentAlignment = Alignment.Center ) { @@ -105,7 +106,7 @@ fun CIImageView( onClick = onClick ) .onRightClick { showMenu.value = true }, - contentScale = ContentScale.FillWidth, + contentScale = if (smallView) ContentScale.Crop else ContentScale.FillWidth, ) } @@ -128,7 +129,7 @@ fun CIImageView( onClick = onClick ) .onRightClick { showMenu.value = true }, - contentScale = ContentScale.FillWidth, + contentScale = if (smallView) ContentScale.Crop else ContentScale.FillWidth, ) } else { Box(Modifier @@ -191,7 +192,7 @@ fun CIImageView( } } else { KeyChangeEffect(file) { - if (res.value == null) { + if (res.value == null || res.value!!.third != getLoadedFilePath(file)) { res.value = imageAndFilePath(file) } } @@ -255,7 +256,13 @@ fun CIImageView( } }) } - loadingIndicator() + if (!smallView) { + loadingIndicator() + } else if (file?.showStatusIconInSmallView == true) { + Box(Modifier.align(Alignment.Center)) { + loadingIndicator() + } + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt index e655b73b02..dd6e8a28fd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt @@ -31,6 +31,7 @@ fun CIVideoView( file: CIFile?, imageProvider: () -> ImageGalleryProvider, showMenu: MutableState, + smallView: Boolean = false, receiveFile: (Long) -> Unit ) { Box( @@ -39,6 +40,7 @@ fun CIVideoView( ) { val preview = remember(image) { base64ToBitmap(image) } val filePath = remember(file, CIFile.cachedRemoteFileRequests.toList()) { mutableStateOf(getLoadedFilePath(file)) } + val sizeMultiplier = if (smallView) 0.38f else 1f if (chatModel.connectedToRemote()) { LaunchedEffect(file) { withLongRunningApi(slow = 600_000) { @@ -63,8 +65,12 @@ fun CIVideoView( val autoPlay = remember { mutableStateOf(false) } val uriDecrypted = remember(filePath) { mutableStateOf(if (file.fileSource?.cryptoArgs == null) uri else file.fileSource.decryptedGet()) } val decrypted = uriDecrypted.value - if (decrypted != null) { + if (decrypted != null && smallView) { + SmallVideoView(decrypted, file, preview, duration * 1000L, autoPlay, sizeMultiplier, openFullscreen = openFullscreen) + } else if (decrypted != null) { VideoView(decrypted, file, preview, duration * 1000L, autoPlay, showMenu, openFullscreen = openFullscreen) + } else if (smallView) { + SmallVideoViewEncrypted(uriDecrypted, file, preview, autoPlay, showMenu, sizeMultiplier, openFullscreen = openFullscreen) } else { VideoViewEncrypted(uriDecrypted, file, preview, duration * 1000L, autoPlay, showMenu, openFullscreen = openFullscreen) } @@ -96,18 +102,25 @@ fun CIVideoView( } } }, + smallView = smallView, onLongClick = { showMenu.value = true }) - if (file != null) { + if (file != null && !smallView) { DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/) } if (file?.fileStatus is CIFileStatus.RcvInvitation || file?.fileStatus is CIFileStatus.RcvAborted) { - PlayButton(error = false, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) } + PlayButton(error = false, sizeMultiplier, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) } } } } - fileStatusIcon(file) + if (!smallView) { + fileStatusIcon(file, false) + } else if (file?.showStatusIconInSmallView == true) { + Box(Modifier.align(Alignment.Center)) { + fileStatusIcon(file, true) + } + } } } @@ -124,11 +137,11 @@ private fun VideoViewEncrypted( var decryptionInProgress by rememberSaveable(file.fileName) { mutableStateOf(false) } val onLongClick = { showMenu.value = true } Box { - VideoPreviewImageView(defaultPreview, if (decryptionInProgress) {{}} else openFullscreen, onLongClick) + VideoPreviewImageView(defaultPreview, smallView = false, if (decryptionInProgress) {{}} else openFullscreen, onLongClick) if (decryptionInProgress) { - VideoDecryptionProgress(onLongClick = onLongClick) + VideoDecryptionProgress(1f, onLongClick = onLongClick) } else { - PlayButton(false, onLongClick = onLongClick) { + PlayButton(false, 1f, onLongClick = onLongClick) { decryptionInProgress = true withBGApi { try { @@ -144,6 +157,64 @@ private fun VideoViewEncrypted( } } +@Composable +private fun SmallVideoViewEncrypted( + uriUnencrypted: MutableState, + file: CIFile, + defaultPreview: ImageBitmap, + autoPlay: MutableState, + showMenu: MutableState, + sizeMultiplier: Float, + openFullscreen: () -> Unit, +) { + var decryptionInProgress by rememberSaveable(file.fileName) { mutableStateOf(false) } + val onLongClick = { showMenu.value = true } + Box { + VideoPreviewImageView(defaultPreview, smallView = true, if (decryptionInProgress) {{}} else openFullscreen, onLongClick) + if (decryptionInProgress) { + VideoDecryptionProgress(sizeMultiplier, onLongClick = onLongClick) + } else if (!file.showStatusIconInSmallView) { + PlayButton(false, sizeMultiplier, onLongClick = onLongClick) { + decryptionInProgress = true + withBGApi { + try { + uriUnencrypted.value = file.fileSource?.decryptedGetOrCreate() + autoPlay.value = uriUnencrypted.value != null + } finally { + decryptionInProgress = false + } + } + } + } + } +} + +@Composable +private fun SmallVideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defaultDuration: Long, autoPlay: MutableState, sizeMultiplier: Float, openFullscreen: () -> Unit) { + val player = remember(uri) { VideoPlayerHolder.getOrCreate(uri, true, defaultPreview, defaultDuration, true) } + val preview by remember { player.preview } + // val soundEnabled by rememberSaveable(uri.path) { player.soundEnabled } + val brokenVideo by rememberSaveable(uri.path) { player.brokenVideo } + Box { + val windowWidth = LocalWindowWidth() + val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else DEFAULT_MAX_IMAGE_WIDTH } + PlayerView( + player, + width, + onClick = openFullscreen, + onLongClick = {}, + {} + ) + VideoPreviewImageView(preview, smallView = true, openFullscreen, onLongClick = {}) + if (!file.showStatusIconInSmallView) { + PlayButton(brokenVideo, sizeMultiplier, onLongClick = {}, onClick = openFullscreen) + } + } + LaunchedEffect(uri) { + if (autoPlay.value) openFullscreen() + } +} + @Composable private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defaultDuration: Long, autoPlay: MutableState, showMenu: MutableState, openFullscreen: () -> Unit) { val player = remember(uri) { VideoPlayerHolder.getOrCreate(uri, false, defaultPreview, defaultDuration, true) } @@ -186,9 +257,9 @@ private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defau stop ) if (showPreview.value) { - VideoPreviewImageView(preview, openFullscreen, onLongClick) + VideoPreviewImageView(preview, smallView = false, openFullscreen, onLongClick) if (!autoPlay.value) { - PlayButton(brokenVideo, onLongClick = onLongClick, play) + PlayButton(brokenVideo, onLongClick = onLongClick, onClick = play) } } DurationProgress(file, videoPlaying, duration, progress/*, soundEnabled*/) @@ -199,16 +270,16 @@ private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defau expect fun PlayerView(player: VideoPlayer, width: Dp, onClick: () -> Unit, onLongClick: () -> Unit, stop: () -> Unit) @Composable -private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, onClick: () -> Unit) { +private fun BoxScope.PlayButton(error: Boolean = false, sizeMultiplier: Float = 1f, onLongClick: () -> Unit, onClick: () -> Unit) { Surface( - Modifier.align(Alignment.Center), + Modifier.align(if (sizeMultiplier != 1f) Alignment.TopStart else Alignment.Center), color = Color.Black.copy(alpha = 0.25f), shape = RoundedCornerShape(percent = 50), contentColor = LocalContentColor.current ) { Box( Modifier - .defaultMinSize(minWidth = 40.dp, minHeight = 40.dp) + .defaultMinSize(minWidth = if (sizeMultiplier != 1f) 40.sp.toDp() * sizeMultiplier else 40.sp.toDp(), minHeight = if (sizeMultiplier != 1f) 40.sp.toDp() * sizeMultiplier else 40.sp.toDp()) .combinedClickable(onClick = onClick, onLongClick = onLongClick) .onRightClick { onLongClick.invoke() }, contentAlignment = Alignment.Center @@ -216,6 +287,7 @@ private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, Icon( painterResource(MR.images.ic_play_arrow_filled), contentDescription = null, + Modifier.size(if (sizeMultiplier != 1f) 24.sp.toDp() * sizeMultiplier * 1.6f else 24.sp.toDp()), tint = if (error) WarningOrange else Color.White ) } @@ -223,25 +295,25 @@ private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, } @Composable -fun BoxScope.VideoDecryptionProgress(onLongClick: () -> Unit) { +fun BoxScope.VideoDecryptionProgress(sizeMultiplier: Float = 1f, onLongClick: () -> Unit) { Surface( - Modifier.align(Alignment.Center), + Modifier.align(if (sizeMultiplier != 1f) Alignment.TopStart else Alignment.Center), color = Color.Black.copy(alpha = 0.25f), shape = RoundedCornerShape(percent = 50), contentColor = LocalContentColor.current ) { Box( Modifier - .defaultMinSize(minWidth = 40.dp, minHeight = 40.dp) + .defaultMinSize(minWidth = if (sizeMultiplier != 1f) 40.sp.toDp() * sizeMultiplier else 40.sp.toDp(), minHeight = if (sizeMultiplier != 1f) 40.sp.toDp() * sizeMultiplier else 40.sp.toDp()) .combinedClickable(onClick = {}, onLongClick = onLongClick) .onRightClick { onLongClick.invoke() }, contentAlignment = Alignment.Center ) { CircularProgressIndicator( Modifier - .size(30.dp), + .size(if (sizeMultiplier != 1f) 30.sp.toDp() * sizeMultiplier else 30.sp.toDp()), color = Color.White, - strokeWidth = 2.5.dp + strokeWidth = 2.5.sp.toDp() * sizeMultiplier ) } } @@ -293,7 +365,7 @@ private fun DurationProgress(file: CIFile, playing: MutableState, durat } @Composable -fun VideoPreviewImageView(preview: ImageBitmap, onClick: () -> Unit, onLongClick: () -> Unit) { +fun VideoPreviewImageView(preview: ImageBitmap, smallView: Boolean, onClick: () -> Unit, onLongClick: () -> Unit) { val windowWidth = LocalWindowWidth() val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else DEFAULT_MAX_IMAGE_WIDTH } Image( @@ -306,7 +378,7 @@ fun VideoPreviewImageView(preview: ImageBitmap, onClick: () -> Unit, onLongClick onClick = onClick ) .onRightClick(onLongClick), - contentScale = ContentScale.FillWidth, + contentScale = if (smallView) ContentScale.Crop else ContentScale.FillWidth, ) } @@ -366,11 +438,11 @@ private fun progressCircle(progress: Long, total: Long) { } @Composable -private fun fileStatusIcon(file: CIFile?) { +private fun fileStatusIcon(file: CIFile?, smallView: Boolean) { if (file != null) { Box( Modifier - .padding(8.dp) + .padding(if (smallView) 0.dp else 8.dp) .size(20.dp), contentAlignment = Alignment.Center ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt index 040dd97474..5ae46ef4e7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt @@ -24,6 +24,7 @@ import chat.simplex.common.platform.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import kotlinx.coroutines.flow.* +import kotlin.math.* // TODO refactor https://github.com/simplex-chat/simplex-chat/pull/1451#discussion_r1033429901 @@ -37,11 +38,18 @@ fun CIVoiceView( ci: ChatItem, timedMessagesTTL: Int?, showViaProxy: Boolean, + smallView: Boolean = false, longClick: () -> Unit, receiveFile: (Long) -> Unit, ) { + val sizeMultiplier = if (smallView) voiceMessageSizeBasedOnSquareSize(36f) / 56f else 1f + val padding = when { + smallView -> PaddingValues() + hasText -> PaddingValues(top = 14.sp.toDp() * sizeMultiplier, bottom = 14.sp.toDp() * sizeMultiplier, start = 6.sp.toDp() * sizeMultiplier, end = 6.sp.toDp() * sizeMultiplier) + else -> PaddingValues(top = 4.sp.toDp() * sizeMultiplier, bottom = 6.sp.toDp() * sizeMultiplier, start = 0.dp, end = 0.dp) + } Row( - Modifier.padding(top = if (hasText) 14.dp else 4.dp, bottom = if (hasText) 14.dp else 6.dp, start = if (hasText) 6.dp else 0.dp, end = if (hasText) 6.dp else 0.dp), + Modifier.padding(padding), verticalAlignment = Alignment.CenterVertically ) { if (file != null) { @@ -54,7 +62,7 @@ fun CIVoiceView( val play: () -> Unit = { val playIfExists = { if (fileSource.value != null) { - AudioPlayer.play(fileSource.value!!, audioPlaying, progress, duration, true) + AudioPlayer.play(fileSource.value!!, audioPlaying, progress, duration, resetOnEnd = true, smallView = smallView) brokenAudio = !audioPlaying.value } } @@ -69,7 +77,7 @@ fun CIVoiceView( val pause = { AudioPlayer.pause(audioPlaying, progress) } - val text = remember { + val text = remember(ci.file?.fileId, ci.file?.fileStatus) { derivedStateOf { val time = when { audioPlaying.value || progress.value != 0 -> progress.value @@ -78,11 +86,18 @@ fun CIVoiceView( durationText(time / 1000) } } - VoiceLayout(file, ci, text, audioPlaying, progress, duration, brokenAudio, sent, hasText, timedMessagesTTL, showViaProxy, play, pause, longClick, receiveFile) { + VoiceLayout(file, ci, text, audioPlaying, progress, duration, brokenAudio, sent, hasText, timedMessagesTTL, showViaProxy, sizeMultiplier, play, pause, longClick, receiveFile) { AudioPlayer.seekTo(it, progress, fileSource.value?.filePath) } + if (smallView) { + KeyChangeEffect(chatModel.chatId.value, chatModel.currentUser.value?.userId, chatModel.currentRemoteHost.value) { + AudioPlayer.stop() + } + } + } else if (smallView) { + VoiceMsgIndicator(null, false, sent, hasText, null, null, false, sizeMultiplier, {}, {}, longClick, receiveFile) } else { - VoiceMsgIndicator(null, false, sent, hasText, null, null, false, {}, {}, longClick, receiveFile) + VoiceMsgIndicator(null, false, sent, hasText, null, null, false, 1f, {}, {}, longClick, receiveFile) val metaReserve = if (edited) " " else @@ -105,6 +120,7 @@ private fun VoiceLayout( hasText: Boolean, timedMessagesTTL: Int?, showViaProxy: Boolean, + sizeMultiplier: Float, play: () -> Unit, pause: () -> Unit, longClick: () -> Unit, @@ -116,15 +132,16 @@ private fun VoiceLayout( var movedManuallyTo by rememberSaveable(file.fileId) { mutableStateOf(-1) } if (audioPlaying.value || progress.value > 0 || movedManuallyTo == progress.value) { val dp4 = with(LocalDensity.current) { 4.dp.toPx() } - val dp10 = with(LocalDensity.current) { 10.dp.toPx() } val primary = MaterialTheme.colors.primary val inactiveTrackColor = MaterialTheme.colors.primary.mixWith( backgroundColor.copy(1f).mixWith(MaterialTheme.colors.background, backgroundColor.alpha), 0.24f) val width = LocalWindowWidth() + // Built-in slider has rounded corners but we need square corners, so drawing a track manually val colors = SliderDefaults.colors( - inactiveTrackColor = inactiveTrackColor + inactiveTrackColor = Color.Transparent, + activeTrackColor = Color.Transparent ) Slider( progress.value.toFloat(), @@ -133,12 +150,12 @@ private fun VoiceLayout( movedManuallyTo = it.toInt() }, Modifier - .size(width, 48.dp) + .size(width, 48.sp.toDp()) .weight(1f) .padding(padding) .drawBehind { - drawRect(primary, Offset(0f, (size.height - dp4) / 2), size = androidx.compose.ui.geometry.Size(dp10, dp4)) - drawRect(inactiveTrackColor, Offset(size.width - dp10, (size.height - dp4) / 2), size = androidx.compose.ui.geometry.Size(dp10, dp4)) + drawRect(inactiveTrackColor, Offset(0f, (size.height - dp4) / 2), size = Size(size.width, dp4)) + drawRect(primary, Offset(0f, (size.height - dp4) / 2), size = Size(progress.value.toFloat() / max(0.00001f, duration.value.toFloat()) * size.width, dp4)) }, valueRange = 0f..duration.value.toFloat(), colors = colors @@ -153,13 +170,22 @@ private fun VoiceLayout( } } when { + sizeMultiplier != 1f -> { + Row(verticalAlignment = Alignment.CenterVertically) { + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, sizeMultiplier, play, pause, longClick, receiveFile) + Row(Modifier.weight(1f, false), verticalAlignment = Alignment.CenterVertically) { + DurationText(text, PaddingValues(start = 8.sp.toDp()), true) + Slider(MaterialTheme.colors.background, PaddingValues(start = 7.sp.toDp())) + } + } + } hasText -> { val sentColor = MaterialTheme.appColors.sentMessage val receivedColor = MaterialTheme.appColors.receivedMessage - Spacer(Modifier.width(6.dp)) - VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) + Spacer(Modifier.width(6.sp.toDp() * sizeMultiplier)) + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, 1f, play, pause, longClick, receiveFile) Row(verticalAlignment = Alignment.CenterVertically) { - DurationText(text, PaddingValues(start = 12.dp)) + DurationText(text, PaddingValues(start = 12.sp.toDp() * sizeMultiplier)) Slider(if (ci.chatDir.sent) sentColor else receivedColor) } } @@ -167,13 +193,13 @@ private fun VoiceLayout( Column(horizontalAlignment = Alignment.End) { Row { Row(Modifier.weight(1f, false), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.End) { - Spacer(Modifier.height(56.dp)) + Spacer(Modifier.height(56.sp.toDp() * sizeMultiplier)) Slider(MaterialTheme.colors.background, PaddingValues(end = DEFAULT_PADDING_HALF + 3.dp)) - DurationText(text, PaddingValues(end = 12.dp)) + DurationText(text, PaddingValues(end = 12.sp.toDp() * sizeMultiplier)) } - VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, 1f, play, pause, longClick, receiveFile) } - Box(Modifier.padding(top = 6.dp, end = 6.dp)) { + Box(Modifier.padding(top = 6.sp.toDp() * sizeMultiplier, end = 6.sp.toDp() * sizeMultiplier)) { CIMetaView(ci, timedMessagesTTL, showViaProxy = showViaProxy) } } @@ -181,14 +207,14 @@ private fun VoiceLayout( else -> { Column(horizontalAlignment = Alignment.Start) { Row { - VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) + VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, 1f, play, pause, longClick, receiveFile) Row(Modifier.weight(1f, false), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) { - DurationText(text, PaddingValues(start = 12.dp)) + DurationText(text, PaddingValues(start = 12.sp.toDp() * sizeMultiplier)) Slider(MaterialTheme.colors.background, PaddingValues(start = DEFAULT_PADDING_HALF + 3.dp)) - Spacer(Modifier.height(56.dp)) + Spacer(Modifier.height(56.sp.toDp() * sizeMultiplier)) } } - Box(Modifier.padding(top = 6.dp)) { + Box(Modifier.padding(top = 6.sp.toDp() * sizeMultiplier)) { CIMetaView(ci, timedMessagesTTL, showViaProxy = showViaProxy) } } @@ -197,7 +223,7 @@ private fun VoiceLayout( } @Composable -private fun DurationText(text: State, padding: PaddingValues) { +private fun DurationText(text: State, padding: PaddingValues, smallView: Boolean = false) { val minWidth = with(LocalDensity.current) { 45.sp.toDp() } Text( text.value, @@ -205,7 +231,7 @@ private fun DurationText(text: State, padding: PaddingValues) { .padding(padding) .widthIn(min = minWidth), color = MaterialTheme.colors.secondary, - fontSize = 16.sp, + fontSize = if (smallView) 15.sp else 16.sp, maxLines = 1 ) } @@ -219,6 +245,7 @@ private fun PlayPauseButton( strokeColor: Color, enabled: Boolean, error: Boolean, + sizeMultiplier: Float = 1f, play: () -> Unit, pause: () -> Unit, longClick: () -> Unit, @@ -234,7 +261,7 @@ private fun PlayPauseButton( ) { Box( Modifier - .defaultMinSize(minWidth = 56.dp, minHeight = 56.dp) + .defaultMinSize(minWidth = 56.sp.toDp() * sizeMultiplier, minHeight = 56.sp.toDp() * sizeMultiplier) .combinedClickable( onClick = { if (!audioPlaying) play() else pause() }, onLongClick = longClick @@ -245,7 +272,7 @@ private fun PlayPauseButton( Icon( if (audioPlaying) painterResource(MR.images.ic_pause_filled) else painterResource(icon), contentDescription = null, - Modifier.size(36.dp), + Modifier.size(36.sp.toDp() * sizeMultiplier), tint = if (error) WarningOrange else if (!enabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary ) } @@ -262,34 +289,42 @@ private fun PlayablePlayPauseButton( strokeWidth: Float, strokeColor: Color, error: Boolean, + sizeMultiplier: Float = 1f, play: () -> Unit, pause: () -> Unit, longClick: () -> Unit, ) { val angle = 360f * (progress.value.toDouble() / duration.value).toFloat() if (hasText) { - IconButton({ if (!audioPlaying) play() else pause() }, Modifier.size(56.dp).drawRingModifier(angle, strokeColor, strokeWidth)) { + Box( + Modifier + .defaultMinSize(minWidth = 56.sp.toDp() * sizeMultiplier, minHeight = 56.sp.toDp() * sizeMultiplier) + .clip(MaterialTheme.shapes.small.copy(CornerSize(percent = 50))) + .combinedClickable(onClick = { if (!audioPlaying) play() else pause() } ) + .drawRingModifier(angle, strokeColor, strokeWidth), + contentAlignment = Alignment.Center + ) { Icon( if (audioPlaying) painterResource(MR.images.ic_pause_filled) else painterResource(MR.images.ic_play_arrow_filled), contentDescription = null, - Modifier.size(36.dp), + Modifier.size(36.sp.toDp() * sizeMultiplier), tint = MaterialTheme.colors.primary ) } } else { - PlayPauseButton(audioPlaying, sent, angle, strokeWidth, strokeColor, true, error, play, pause, longClick = longClick) + PlayPauseButton(audioPlaying, sent, angle, strokeWidth, strokeColor, true, error, sizeMultiplier, play, pause, longClick = longClick) } } @Composable -private fun VoiceMsgLoadingProgressIndicator() { +private fun VoiceMsgLoadingProgressIndicator(sizeMultiplier: Float) { Box( Modifier - .size(56.dp) - .clip(RoundedCornerShape(4.dp)), + .size(56.sp.toDp() * sizeMultiplier) + .clip(RoundedCornerShape(4.sp.toDp() * sizeMultiplier)), contentAlignment = Alignment.Center ) { - ProgressIndicator() + ProgressIndicator(sizeMultiplier) } } @@ -297,6 +332,7 @@ private fun VoiceMsgLoadingProgressIndicator() { private fun FileStatusIcon( sent: Boolean, icon: ImageResource, + sizeMultiplier: Float, longClick: () -> Unit, onClick: () -> Unit, ) { @@ -309,7 +345,7 @@ private fun FileStatusIcon( ) { Box( Modifier - .defaultMinSize(minWidth = 56.dp, minHeight = 56.dp) + .defaultMinSize(minWidth = 56.sp.toDp() * sizeMultiplier, minHeight = 56.sp.toDp() * sizeMultiplier) .combinedClickable( onClick = onClick, onLongClick = longClick @@ -320,7 +356,7 @@ private fun FileStatusIcon( Icon( painterResource(icon), contentDescription = null, - Modifier.size(36.dp), + Modifier.size(36.sp.toDp() * sizeMultiplier), tint = MaterialTheme.colors.secondary ) } @@ -336,26 +372,28 @@ private fun VoiceMsgIndicator( progress: State?, duration: State?, error: Boolean, + sizeMultiplier: Float, play: () -> Unit, pause: () -> Unit, longClick: () -> Unit, receiveFile: (Long) -> Unit, ) { - val strokeWidth = with(LocalDensity.current) { 3.dp.toPx() } + val strokeWidth = with(LocalDensity.current) { 3.sp.toPx() } * sizeMultiplier val strokeColor = MaterialTheme.colors.primary when { file?.fileStatus is CIFileStatus.SndStored -> if (file.fileProtocol == FileProtocol.LOCAL && progress != null && duration != null) { - PlayablePlayPauseButton(audioPlaying, sent, hasText, progress, duration, strokeWidth, strokeColor, error, play, pause, longClick = longClick) + PlayablePlayPauseButton(audioPlaying, sent, hasText, progress, duration, strokeWidth, strokeColor, error, sizeMultiplier, play, pause, longClick = longClick) } else { - VoiceMsgLoadingProgressIndicator() + VoiceMsgLoadingProgressIndicator(sizeMultiplier) } file?.fileStatus is CIFileStatus.SndTransfer -> - VoiceMsgLoadingProgressIndicator() + VoiceMsgLoadingProgressIndicator(sizeMultiplier) file != null && file.fileStatus is CIFileStatus.SndError -> FileStatusIcon( sent, MR.images.ic_close, + sizeMultiplier, longClick, onClick = { AlertManager.shared.showAlertMsg( @@ -368,6 +406,7 @@ private fun VoiceMsgIndicator( FileStatusIcon( sent, MR.images.ic_warning_filled, + sizeMultiplier, longClick, onClick = { AlertManager.shared.showAlertMsg( @@ -377,15 +416,16 @@ private fun VoiceMsgIndicator( } ) file?.fileStatus is CIFileStatus.RcvInvitation -> - PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, { receiveFile(file.fileId) }, {}, longClick = longClick) + PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, sizeMultiplier, { receiveFile(file.fileId) }, {}, longClick = longClick) file?.fileStatus is CIFileStatus.RcvTransfer || file?.fileStatus is CIFileStatus.RcvAccepted -> - VoiceMsgLoadingProgressIndicator() + VoiceMsgLoadingProgressIndicator(sizeMultiplier) file?.fileStatus is CIFileStatus.RcvAborted -> - PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, { receiveFile(file.fileId) }, {}, longClick = longClick, icon = MR.images.ic_sync_problem) + PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, true, error, sizeMultiplier, { receiveFile(file.fileId) }, {}, longClick = longClick, icon = MR.images.ic_sync_problem) file != null && file.fileStatus is CIFileStatus.RcvError -> FileStatusIcon( sent, MR.images.ic_close, + sizeMultiplier, longClick, onClick = { AlertManager.shared.showAlertMsg( @@ -398,6 +438,7 @@ private fun VoiceMsgIndicator( FileStatusIcon( sent, MR.images.ic_warning_filled, + sizeMultiplier, longClick, onClick = { AlertManager.shared.showAlertMsg( @@ -407,9 +448,9 @@ private fun VoiceMsgIndicator( } ) file != null && file.loaded && progress != null && duration != null -> - PlayablePlayPauseButton(audioPlaying, sent, hasText, progress, duration, strokeWidth, strokeColor, error, play, pause, longClick = longClick) + PlayablePlayPauseButton(audioPlaying, sent, hasText, progress, duration, strokeWidth, strokeColor, error, sizeMultiplier, play, pause, longClick = longClick) else -> - PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, false, false, {}, {}, longClick) + PlayPauseButton(audioPlaying, sent, 0f, strokeWidth, strokeColor, false, false, sizeMultiplier, {}, {}, longClick) } } @@ -435,11 +476,16 @@ fun Modifier.drawRingModifier(angle: Float, color: Color, strokeWidth: Float) = } } +fun voiceMessageSizeBasedOnSquareSize(squareSize: Float): Float { + val squareToCircleRatio = 0.935f + return squareSize + squareSize * (1 - squareToCircleRatio) +} + @Composable -private fun ProgressIndicator() { +private fun ProgressIndicator(sizeMultiplier: Float) { CircularProgressIndicator( - Modifier.size(32.dp), + Modifier.size(32.sp.toDp() * sizeMultiplier), color = if (isInDarkTheme()) FileDark else FileLight, - strokeWidth = 4.dp + strokeWidth = 4.sp.toDp() * sizeMultiplier ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index c195a1a299..6f5cb63262 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -341,7 +341,7 @@ fun ChatItemView( if (mc is MsgContent.MCText && isShortEmoji(cItem.content.text)) { EmojiItemView(cItem, cInfo.timedMessagesTTL, showViaProxy = showViaProxy) } else if (mc is MsgContent.MCVoice && cItem.content.text.isEmpty()) { - CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, showViaProxy = showViaProxy, longClick = { onLinkLongClick("") }, receiveFile) + CIVoiceView(mc.duration, cItem.file, cItem.meta.itemEdited, cItem.chatDir.sent, hasText = false, cItem, cInfo.timedMessagesTTL, showViaProxy = showViaProxy, longClick = { onLinkLongClick("") }, receiveFile = receiveFile) } else { framedItemView() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index b2777a7042..16d2a2dad2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -176,7 +176,7 @@ fun FramedItemView( @Composable fun ciFileView(ci: ChatItem, text: String) { - CIFileView(ci.file, ci.meta.itemEdited, showMenu, receiveFile) + CIFileView(ci.file, ci.meta.itemEdited, showMenu, false, receiveFile) if (text != "" || ci.meta.isLive) { CIMarkdownText(ci, chatTTL, linkMode = linkMode, uriHandler, showViaProxy = showViaProxy) } @@ -238,7 +238,7 @@ fun FramedItemView( } else { when (val mc = ci.content.msgContent) { is MsgContent.MCImage -> { - CIImageView(image = mc.image, file = ci.file, imageProvider ?: return@PriorityLayout, showMenu, receiveFile) + CIImageView(image = mc.image, file = ci.file, imageProvider ?: return@PriorityLayout, showMenu, false, receiveFile) if (mc.text == "" && !ci.meta.isLive) { metaColor = Color.White } else { @@ -246,7 +246,7 @@ fun FramedItemView( } } is MsgContent.MCVideo -> { - CIVideoView(image = mc.image, mc.duration, file = ci.file, imageProvider ?: return@PriorityLayout, showMenu, receiveFile) + CIVideoView(image = mc.image, mc.duration, file = ci.file, imageProvider ?: return@PriorityLayout, showMenu, smallView = false, receiveFile = receiveFile) if (mc.text == "" && !ci.meta.isLive) { metaColor = Color.White } else { @@ -254,7 +254,7 @@ fun FramedItemView( } } is MsgContent.MCVoice -> { - CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, hasText = true, ci, timedMessagesTTL = chatTTL, showViaProxy = showViaProxy, longClick = { onLinkLongClick("") }, receiveFile) + CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, hasText = true, ci, timedMessagesTTL = chatTTL, showViaProxy = showViaProxy, longClick = { onLinkLongClick("") }, receiveFile = receiveFile) if (mc.text != "") { CIMarkdownText(ci, chatTTL, linkMode, uriHandler, showViaProxy = showViaProxy) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt index 1dd5e4ee69..09838796c5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ImageFullScreenView.kt @@ -16,7 +16,6 @@ import chat.simplex.common.model.CryptoFile import chat.simplex.common.platform.* import chat.simplex.common.views.chat.ProviderMedia import chat.simplex.common.views.helpers.* -import chat.simplex.res.MR import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch import java.net.URI @@ -40,14 +39,17 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> ) { provider.totalMediaSize.value } + val firstValidPageBeforeScrollingToStart = remember { mutableStateOf(0) } val goBack = { provider.onDismiss(pagerState.currentPage); close() } BackHandler(onBack = goBack) // Pager doesn't ask previous page at initialization step who knows why. By not doing this, prev page is not checked and can be blank, // which makes this blank page visible for a moment. Prevent it by doing the check ourselves LaunchedEffect(Unit) { if (provider.getMedia(provider.initialIndex - 1) == null) { + firstValidPageBeforeScrollingToStart.value = provider.initialIndex provider.scrollToStart() pagerState.scrollToPage(0) + firstValidPageBeforeScrollingToStart.value = 0 } } val scope = rememberCoroutineScope() @@ -58,6 +60,9 @@ fun ImageFullScreenView(imageProvider: () -> ImageGalleryProvider, close: () -> @Composable fun Content(index: Int) { + // Index can be huge but in reality at that moment pager state scrolls to 0 and that page should have index 0 too if it's the first one. + // Or index 1 if it's the second page + val index = index - firstValidPageBeforeScrollingToStart.value Column( Modifier .fillMaxSize() @@ -174,7 +179,7 @@ private fun VideoViewEncrypted(uriUnencrypted: MutableState, fileSource: C } Box(contentAlignment = Alignment.Center) { VideoPreviewImageViewFullScreen(defaultPreview, {}, {}) - VideoDecryptionProgress {} + VideoDecryptionProgress() {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index bf3e7774c7..5759877b07 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -518,7 +518,7 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState + itemsIndexed(chats, key = { _, chat -> chat.remoteHostId to chat.id }) { index, chat -> val nextChatSelected = remember(chat.id, chats) { derivedStateOf { chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index c42b7e3ec3..3f66c7d7d8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -1,6 +1,5 @@ package chat.simplex.common.views.chatlist -import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.InlineTextContent @@ -15,19 +14,22 @@ import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.draw.clip +import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.text.style.* import androidx.compose.ui.unit.* import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.chat.ComposePreview -import chat.simplex.common.views.chat.ComposeState -import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* import chat.simplex.common.model.GroupInfo -import chat.simplex.common.platform.appPlatform -import chat.simplex.common.platform.chatModel -import chat.simplex.common.views.chat.item.markedDeletedText +import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.* +import chat.simplex.common.views.chat.item.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource @@ -225,6 +227,50 @@ fun ChatPreviewView( } } + @Composable + fun chatItemContentPreview(chat: Chat, ci: ChatItem?) { + val mc = ci?.content?.msgContent + val provider by remember(chat.id, ci?.id, ci?.file?.fileStatus) { + mutableStateOf({ providerForGallery(0, chat.chatItems, ci?.id ?: 0) {} }) + } + val uriHandler = LocalUriHandler.current + when (mc) { + is MsgContent.MCLink -> SmallContentPreview { + IconButton({ uriHandler.openUriCatching(mc.preview.uri) }, Modifier.desktopPointerHoverIconHand()) { + Image(base64ToBitmap(mc.preview.image), null, contentScale = ContentScale.Crop) + } + Box(Modifier.align(Alignment.TopEnd).size(15.sp.toDp()).background(Color.Black.copy(0.25f), CircleShape), contentAlignment = Alignment.Center) { + Icon(painterResource(MR.images.ic_arrow_outward), null, Modifier.size(13.sp.toDp()), tint = Color.White) + } + } + is MsgContent.MCImage -> SmallContentPreview { + CIImageView(image = mc.image, file = ci.file, provider, remember { mutableStateOf(false) }, smallView = true) { + val user = chatModel.currentUser.value ?: return@CIImageView + withBGApi { chatModel.controller.receiveFile(chat.remoteHostId, user, it) } + } + } + is MsgContent.MCVideo -> SmallContentPreview { + CIVideoView(image = mc.image, mc.duration, file = ci.file, provider, remember { mutableStateOf(false) }, smallView = true) { + val user = chatModel.currentUser.value ?: return@CIVideoView + withBGApi { chatModel.controller.receiveFile(chat.remoteHostId, user, it) } + } + } + is MsgContent.MCVoice -> SmallContentPreviewVoice() { + CIVoiceView(mc.duration, ci.file, ci.meta.itemEdited, ci.chatDir.sent, hasText = false, ci, cInfo.timedMessagesTTL, showViaProxy = false, smallView = true, longClick = {}) { + val user = chatModel.currentUser.value ?: return@CIVoiceView + withBGApi { chatModel.controller.receiveFile(chat.remoteHostId, user, it) } + } + } + is MsgContent.MCFile -> SmallContentPreviewFile { + CIFileView(ci.file, false, remember { mutableStateOf(false) }, smallView = true) { + val user = chatModel.currentUser.value ?: return@CIFileView + withBGApi { chatModel.controller.receiveFile(chat.remoteHostId, user, it) } + } + } + else -> {} + } + } + @Composable fun progressView() { CircularProgressIndicator( @@ -279,73 +325,115 @@ fun ChatPreviewView( chatPreviewImageOverlayIcon() } } - Column( - modifier = Modifier - .padding(start = 8.dp, end = 8.sp.toDp()) - .weight(1F) - ) { - chatPreviewTitle() - Row(Modifier.heightIn(min = 46.sp.toDp()).padding(top = 3.sp.toDp())) { - chatPreviewText() + Spacer(Modifier.width(8.dp)) + Column(Modifier.weight(1f)) { + Row { + Box(Modifier.weight(1f)) { + chatPreviewTitle() + } + Spacer(Modifier.width(8.sp.toDp())) + val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.chatTs) + ChatListTimestampView(ts) } - } + Row(Modifier.heightIn(min = 46.sp.toDp()).fillMaxWidth()) { + Row(Modifier.padding(top = 3.sp.toDp()).weight(1f)) { + val activeVoicePreview: MutableState<(ActiveVoicePreview)?> = remember(chat.id) { mutableStateOf(null) } + val chat = activeVoicePreview.value?.chat ?: chat + val ci = activeVoicePreview.value?.ci ?: chat.chatItems.lastOrNull() + val mc = ci?.content?.msgContent + if ((showChatPreviews && chatModelDraftChatId != chat.id) || activeVoicePreview.value != null) { + chatItemContentPreview(chat, ci) + } + if (mc !is MsgContent.MCVoice || mc.text.isNotEmpty() || chatModelDraftChatId == chat.id) { + Box(Modifier.offset(x = if (mc is MsgContent.MCFile) -15.sp.toDp() else 0.dp)) { + chatPreviewText() + } + } + LaunchedEffect(AudioPlayer.currentlyPlaying.value, activeVoicePreview.value) { + val playing = AudioPlayer.currentlyPlaying.value + when { + playing == null -> activeVoicePreview.value = null + activeVoicePreview.value == null -> if (mc is MsgContent.MCVoice && playing.fileSource.filePath == ci.file?.fileSource?.filePath) { + activeVoicePreview.value = ActiveVoicePreview(chat, ci, mc) + } + else -> if (playing.fileSource.filePath != ci?.file?.fileSource?.filePath) { + activeVoicePreview.value = null + } + } + } + } - Box( - contentAlignment = Alignment.TopEnd - ) { - val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.chatTs) - ChatListTimestampView(ts) - val n = chat.chatStats.unreadCount - val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group) - if (n > 0 || chat.chatStats.unreadChat) { - Box( - Modifier.padding(top = 24.5.sp.toDp())) { - Text( - if (n > 0) unreadCountStr(n) else "", - color = Color.White, - fontSize = 10.sp, - modifier = Modifier - .background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape) - .badgeLayout() - .padding(horizontal = 3.sp.toDp()) - .padding(vertical = 1.sp.toDp()) - ) + Spacer(Modifier.width(8.sp.toDp())) + + Box(Modifier.widthIn(min = 34.sp.toDp()), contentAlignment = Alignment.TopEnd) { + val n = chat.chatStats.unreadCount + val showNtfsIcon = !chat.chatInfo.ntfsEnabled && (chat.chatInfo is ChatInfo.Direct || chat.chatInfo is ChatInfo.Group) + if (n > 0 || chat.chatStats.unreadChat) { + Text( + if (n > 0) unreadCountStr(n) else "", + color = Color.White, + fontSize = 10.sp, + style = TextStyle(textAlign = TextAlign.Center), + modifier = Modifier + .offset(y = 3.sp.toDp()) + .background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape) + .badgeLayout() + .padding(horizontal = 2.sp.toDp()) + .padding(vertical = 1.sp.toDp()) + ) + } else if (showNtfsIcon) { + Icon( + painterResource(MR.images.ic_notifications_off_filled), + contentDescription = generalGetString(MR.strings.notifications), + tint = MaterialTheme.colors.secondary, + modifier = Modifier + .padding(start = 2.sp.toDp()) + .size(18.sp.toDp()) + .offset(x = 2.5.sp.toDp(), y = 2.sp.toDp()) + ) + } else if (chat.chatInfo.chatSettings?.favorite == true) { + Icon( + painterResource(MR.images.ic_star_filled), + contentDescription = generalGetString(MR.strings.favorite_chat), + tint = MaterialTheme.colors.secondary, + modifier = Modifier + .size(20.sp.toDp()) + .offset(x = 2.5.sp.toDp()) + ) + } + Box( + Modifier.offset(y = 28.sp.toDp()), + contentAlignment = Alignment.Center + ) { + chatStatusImage() + } } - } else if (showNtfsIcon) { - Box( - Modifier.padding(top = 22.sp.toDp())) { - Icon( - painterResource(MR.images.ic_notifications_off_filled), - contentDescription = generalGetString(MR.strings.notifications), - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .size(17.sp.toDp()) - .offset(x = 2.5.sp.toDp()) - ) - } - } else if (chat.chatInfo.chatSettings?.favorite == true) { - Box( - Modifier.padding(top = 20.sp.toDp())) { - Icon( - painterResource(MR.images.ic_star_filled), - contentDescription = generalGetString(MR.strings.favorite_chat), - tint = MaterialTheme.colors.secondary, - modifier = Modifier - .size(20.sp.toDp()) - .offset(x = 2.5.sp.toDp()) - ) - } - } - Box( - Modifier.padding(top = 46.sp.toDp()), - contentAlignment = Alignment.Center - ) { - chatStatusImage() } } } } +@Composable +private fun SmallContentPreview(content: @Composable BoxScope.() -> Unit) { + Box(Modifier.padding(top = 2.sp.toDp(), end = 8.sp.toDp()).size(36.sp.toDp()).border(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.12f), RoundedCornerShape(22)).clip(RoundedCornerShape(22))) { + content() + } +} + +@Composable +private fun SmallContentPreviewVoice(content: @Composable () -> Unit) { + Box(Modifier.padding(top = 2.sp.toDp(), end = 8.sp.toDp()).height(voiceMessageSizeBasedOnSquareSize(36f).sp.toDp())) { + content() + } +} + +@Composable +private fun SmallContentPreviewFile(content: @Composable () -> Unit) { + Box(Modifier.padding(top = 3.sp.toDp(), end = 8.sp.toDp()).offset(x = -8.sp.toDp(), y = -4.sp.toDp()).height(41.sp.toDp())) { + content() + } +} + @Composable fun IncognitoIcon(incognito: Boolean) { if (incognito) { @@ -390,6 +478,12 @@ fun unreadCountStr(n: Int): String { } } +private data class ActiveVoicePreview( + val chat: Chat, + val ci: ChatItem, + val mc: MsgContent.MCVoice +) + @Preview/*( uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_outward.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_outward.svg new file mode 100644 index 0000000000..2391aba06c --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_arrow_outward.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt index e1dba29f04..9f34891b37 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt @@ -25,18 +25,13 @@ actual class RecorderNative: RecorderInterface { } actual object AudioPlayer: AudioPlayerInterface { - val player by lazy { AudioPlayerComponent().mediaPlayer() } + private val player by lazy { AudioPlayerComponent().mediaPlayer() } - // Filepath: String, onProgressUpdate - private val currentlyPlaying: MutableState Unit>?> = mutableStateOf(null) + override val currentlyPlaying: MutableState = mutableStateOf(null) private var progressJob: Job? = null - enum class TrackState { - PLAYING, PAUSED, REPLACED - } - // Returns real duration of the track - private fun start(fileSource: CryptoFile, seek: Int? = null, onProgressUpdate: (position: Int?, state: TrackState) -> Unit): Int? { + private fun start(fileSource: CryptoFile, smallView: Boolean, seek: Int? = null, onProgressUpdate: (position: Int?, state: TrackState) -> Unit): Int? { val absoluteFilePath = if (fileSource.isAbsolutePath) fileSource.filePath else getAppFilePath(fileSource.filePath) if (!File(absoluteFilePath).exists()) { Log.e(TAG, "No such file: ${fileSource.filePath}") @@ -46,7 +41,7 @@ actual object AudioPlayer: AudioPlayerInterface { VideoPlayerHolder.stopAll() RecorderInterface.stopRecording?.invoke() val current = currentlyPlaying.value - if (current == null || current.first != fileSource || !player.status().isPlayable) { + if (current == null || current.fileSource != fileSource || !player.status().isPlayable || smallView != current.smallView) { stopListener() player.stop() runCatching { @@ -66,7 +61,7 @@ actual object AudioPlayer: AudioPlayerInterface { } if (seek != null) player.seekTo(seek) player.start() - currentlyPlaying.value = fileSource to onProgressUpdate + currentlyPlaying.value = CurrentlyPlayingState(fileSource, onProgressUpdate, smallView) progressJob = CoroutineScope(Dispatchers.Default).launch { onProgressUpdate(player.currentPosition, TrackState.PLAYING) while(isActive && (player.isPlaying || player.status().state() == State.OPENING)) { @@ -80,7 +75,11 @@ actual object AudioPlayer: AudioPlayerInterface { onProgressUpdate(player.currentPosition, TrackState.PLAYING) } onProgressUpdate(null, TrackState.PAUSED) - currentlyPlaying.value?.first?.deleteTmpFile() + currentlyPlaying.value?.fileSource?.deleteTmpFile() + // Since coroutine is still NOT canceled, means player ended (no stop/no pause). + if (smallView && isActive) { + stopListener() + } } return player.duration } @@ -103,7 +102,7 @@ actual object AudioPlayer: AudioPlayerInterface { // FileName or filePath are ok override fun stop(fileName: String?) { - if (fileName != null && currentlyPlaying.value?.first?.filePath?.endsWith(fileName) == true) { + if (fileName != null && currentlyPlaying.value?.fileSource?.filePath?.endsWith(fileName) == true) { stop() } } @@ -111,8 +110,8 @@ actual object AudioPlayer: AudioPlayerInterface { private fun stopListener() { val afterCoroutineCancel: CompletionHandler = { // Notify prev audio listener about stop - currentlyPlaying.value?.second?.invoke(null, TrackState.REPLACED) - currentlyPlaying.value?.first?.deleteTmpFile() + currentlyPlaying.value?.onProgressUpdate?.invoke(null, TrackState.REPLACED) + currentlyPlaying.value?.fileSource?.deleteTmpFile() currentlyPlaying.value = null } /** Preventing race by calling a code AFTER coroutine ends, so [TrackState] will be: @@ -133,11 +132,12 @@ actual object AudioPlayer: AudioPlayerInterface { progress: MutableState, duration: MutableState, resetOnEnd: Boolean, + smallView: Boolean, ) { if (progress.value == duration.value) { progress.value = 0 } - val realDuration = start(fileSource, progress.value) { pro, state -> + val realDuration = start(fileSource, smallView = smallView, progress.value) { pro, state -> if (pro != null) { progress.value = pro } @@ -162,7 +162,7 @@ actual object AudioPlayer: AudioPlayerInterface { override fun seekTo(ms: Int, pro: MutableState, filePath: String?) { pro.value = ms - if (currentlyPlaying.value?.first?.filePath == filePath) { + if (currentlyPlaying.value?.fileSource?.filePath == filePath) { player.seekTo(ms) } } @@ -217,7 +217,7 @@ actual object SoundPlayer: SoundPlayerInterface { playing = true scope.launch { while (playing && sound) { - AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), true) + AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), resetOnEnd = true, smallView = false) delay(3500) } } @@ -239,7 +239,7 @@ actual object CallSoundsPlayer: CallSoundsPlayerInterface { SoundPlayer::class.java.getResource(soundPath)!!.openStream()!!.use { it.copyTo(tmpFile.outputStream()) } playingJob = scope.launch { while (isActive) { - AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), true) + AudioPlayer.play(CryptoFile.plain(tmpFile.absolutePath), mutableStateOf(true), mutableStateOf(0), mutableStateOf(0), resetOnEnd = true, smallView = false) delay(delay) } } From bfab76ed905bf67eade32305128bbe660954f026 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:33:51 +0400 Subject: [PATCH 41/57] core: allow sending messages immediately on joinConnection, acceptContact (#4465) --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 41 ++++++++++++++----- src/Simplex/Chat/Controller.hs | 1 + src/Simplex/Chat/Store/Direct.hs | 6 +-- src/Simplex/Chat/Store/Files.hs | 2 +- src/Simplex/Chat/Store/Groups.hs | 10 ++--- src/Simplex/Chat/Store/Profiles.hs | 2 +- src/Simplex/Chat/Store/Shared.hs | 8 ++-- src/Simplex/Chat/Terminal/Output.hs | 2 + src/Simplex/Chat/Types.hs | 6 +-- src/Simplex/Chat/View.hs | 8 +++- tests/ChatClient.hs | 5 ++- tests/ChatTests/Direct.hs | 10 ++--- tests/ChatTests/Groups.hs | 32 +++++---------- tests/ChatTests/Profiles.hs | 62 +++++++++++++++-------------- 16 files changed, 111 insertions(+), 88 deletions(-) diff --git a/cabal.project b/cabal.project index 3573976290..663bff2624 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 8d56b0ba8519bdc68a3777d8d0672fbe01957479 + tag: 0be7ebed5c343e9f55a93224121f8a6df9333522 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 9f1dd6d69c..9a22ea9fe4 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."8d56b0ba8519bdc68a3777d8d0672fbe01957479" = "1yfcgnkhal6kj26kcl805152b1gai2ncyfq9lyq5k8jvdh0hpq99"; + "https://github.com/simplex-chat/simplexmq.git"."0be7ebed5c343e9f55a93224121f8a6df9333522" = "1bx6p06nwrqsc5wc8hp86k8198vn36cs5zva2rb9zhixnjcx79ap"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 46c8f97d53..2983afa29a 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -3323,8 +3323,9 @@ acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId inv chatV = vr `peerConnChatVersion` cReqChatVRange pqSup' = pqSup `CR.pqSupportAnd` pqSupport dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend - acId <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode - withStore' $ \db -> createAcceptedContact db user acId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed + (acId, sqSecured) <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode + let connStatus = if sqSecured then ConnSndReady else ConnNew + withStore' $ \db -> createAcceptedContact db user acId connStatus chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed acceptContactRequestAsync :: User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> PQSupport -> CM Contact acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed pqSup = do @@ -3334,7 +3335,7 @@ acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvI let chatV = vr `peerConnChatVersion` cReqChatVRange (cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode pqSup chatV withStore' $ \db -> do - ct@Contact {activeConn} <- createAcceptedContact db user acId chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed + ct@Contact {activeConn} <- createAcceptedContact db user acId ConnNew chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed forM_ activeConn $ \Connection {connId} -> setCommandConnId db user cmdId connId pure ct @@ -3997,6 +3998,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = agentMsgConnStatus :: AEvent e -> Maybe ConnStatus agentMsgConnStatus = \case + JOINED True -> Just ConnSndReady CONF {} -> Just ConnRequested INFO {} -> Just ConnSndReady CON _ -> Just ConnReady @@ -4045,6 +4047,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + JOINED _ -> + -- [async agent commands] continuation on receiving JOINED + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () QCONT -> void $ continueSending connEntity conn MWARN _ err -> @@ -4164,12 +4169,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> resetContactConnInitiated db user conn' forM_ viaUserContactLink $ \userContactLinkId -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId - let (UserContactLink {autoAccept}, groupId_, gLinkMemRole) = ucl - forM_ autoAccept $ \(AutoAccept {autoReply = mc_}) -> - forM_ mc_ $ \mc -> do - (msg, _) <- sendDirectContactMessage user ct' (XMsgNew $ MCSimple (extMsgContent mc Nothing)) - ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct') ci) + let (_, groupId_, gLinkMemRole) = ucl forM_ groupId_ $ \groupId -> do groupInfo <- withStore $ \db -> getGroupInfo db vr user groupId subMode <- chatReadVar subscriptionMode @@ -4223,6 +4223,20 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + JOINED sqSecured -> + -- [async agent commands] continuation on receiving JOINED + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> + when (directOrUsed ct && sqSecured) $ do + lift $ setContactNetworkStatus ct NSConnected + toView $ CRContactSndReady user ct + forM_ viaUserContactLink $ \userContactLinkId -> do + ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId + let (UserContactLink {autoAccept}, _, _) = ucl + forM_ autoAccept $ \(AutoAccept {autoReply = mc_}) -> + forM_ mc_ $ \mc -> do + (msg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) + ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc) + toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) QCONT -> void $ continueSending connEntity conn MWARN msgId err -> do @@ -4620,6 +4634,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + JOINED _ -> + -- [async agent commands] continuation on receiving JOINED + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () QCONT -> do continued <- continueSending connEntity conn when continued $ sendPendingGroupMessages user m conn @@ -4712,6 +4729,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + JOINED _ -> + -- [async agent commands] continuation on receiving JOINED + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () @@ -4758,6 +4778,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + JOINED _ -> + -- [async agent commands] continuation on receiving JOINED + when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR _ err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) processConnMERR connEntity conn err diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 8e2a308986..fedfc463a3 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -675,6 +675,7 @@ data ChatResponse | CRContactPrefsUpdated {user :: User, fromContact :: Contact, toContact :: Contact} | CRContactConnecting {user :: User, contact :: Contact} | CRContactConnected {user :: User, contact :: Contact, userCustomProfile :: Maybe Profile} + | CRContactSndReady {user :: User, contact :: Contact} | CRContactAnotherClient {user :: User, contact :: Contact} | CRSubscriptionEnd {user :: User, connectionEntity :: ConnectionEntity} | CRContactsDisconnected {server :: SMPServer, contactRefs :: [ContactRef]} diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index 1145c2494b..508a992543 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -752,8 +752,8 @@ deleteContactRequest db User {userId} contactRequestId = do (userId, userId, contactRequestId, userId) DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND contact_request_id = ?" (userId, contactRequestId) -createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO Contact -createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do +createAcceptedContact :: DB.Connection -> User -> ConnId -> ConnStatus -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO Contact +createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId connStatus connChatVersion cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed = do DB.execute db "DELETE FROM contact_requests WHERE user_id = ? AND local_display_name = ?" (userId, localDisplayName) createdAt <- getCurrentTime customUserProfileId <- forM incognitoProfile $ \case @@ -765,7 +765,7 @@ createAcceptedContact db user@User {userId, profile = LocalProfile {preferences} "INSERT INTO contacts (user_id, local_display_name, contact_profile_id, enable_ntfs, user_preferences, created_at, updated_at, chat_ts, xcontact_id, contact_used) VALUES (?,?,?,?,?,?,?,?,?,?)" (userId, localDisplayName, profileId, True, userPreferences, createdAt, createdAt, createdAt, xContactId, contactUsed) contactId <- insertedRowId db - conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup + conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId connStatus connChatVersion cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode pqSup let mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito conn pure $ Contact diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index d70bbb8970..d1da081cee 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -455,7 +455,7 @@ lookupChatRefByFileId db User {userId} fileId = createSndFileConnection_ :: DB.Connection -> VersionRangeChat -> UserId -> Int64 -> ConnId -> SubscriptionMode -> IO Connection createSndFileConnection_ db vr userId fileId agentConnId subMode = do currentTs <- getCurrentTime - createConnection_ db userId ConnSndFile (Just fileId) agentConnId (minVersion vr) chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff + createConnection_ db userId ConnSndFile (Just fileId) agentConnId ConnNew (minVersion vr) chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff updateSndFileStatus :: DB.Connection -> SndFileTransfer -> FileStatus -> IO () updateSndFileStatus db SndFileTransfer {fileId, connId} status = do diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 42637d4169..55847114ca 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -191,7 +191,7 @@ createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName} "INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" (userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, True, currentTs, currentTs) userContactLinkId <- insertedRowId db - void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff + void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff getGroupLinkConnection :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ExceptT StoreError IO Connection getGroupLinkConnection db vr User {userId} groupInfo@GroupInfo {groupId} = @@ -914,7 +914,7 @@ createAcceptedMemberConnection groupMemberId subMode = do createdAt <- liftIO getCurrentTime - Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff + Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff setCommandConnId db user cmdId connId getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact @@ -1250,7 +1250,7 @@ createIntroReMember currentTs <- liftIO getCurrentTime newMember <- case directConnIds of Just (directCmdId, directAgentConnId) -> do - Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId chatV mcvr memberContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff + Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId ConnNew chatV mcvr memberContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff liftIO $ setCommandConnId db user directCmdId directConnId (localDisplayName, contactId, memProfileId) <- createContact_ db userId memberProfile "" (Just groupId) currentTs False liftIO $ DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, currentTs, directConnId) @@ -1271,7 +1271,7 @@ createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = Connection {connId = groupConnId} <- createMemberConnection_ db userId groupMemberId groupAgentConnId chatV mcvr viaContactId cLevel currentTs subMode setCommandConnId db user groupCmdId groupConnId forM_ directConnIds $ \(directCmdId, directAgentConnId) -> do - Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId chatV mcvr viaContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff + Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId ConnNew chatV mcvr viaContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff setCommandConnId db user directCmdId directConnId contactId <- createMemberContact_ directConnId currentTs updateMember_ contactId currentTs @@ -1303,7 +1303,7 @@ createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe Int64 -> Int -> UTCTime -> SubscriptionMode -> IO Connection createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange viaContact connLevel currentTs subMode = - createConnection_ db userId ConnMember (Just groupMemberId) agentConnId chatV peerChatVRange viaContact Nothing Nothing connLevel currentTs subMode PQSupportOff + createConnection_ db userId ConnMember (Just groupMemberId) agentConnId ConnNew chatV peerChatVRange viaContact Nothing Nothing connLevel currentTs subMode PQSupportOff getViaGroupMember :: DB.Connection -> VersionRangeChat -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember)) getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index cad06448e6..fb87662c27 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -328,7 +328,7 @@ createUserContactLink db User {userId} agentConnId cReq subMode = "INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?)" (userId, cReq, currentTs, currentTs) userContactLinkId <- insertedRowId db - void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff + void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId ConnNew initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff getUserAddressConnections :: DB.Connection -> VersionRangeChat -> User -> ExceptT StoreError IO [Connection] getUserAddressConnections db vr User {userId} = do diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index c364cf10c2..b80e2ce805 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -208,8 +208,8 @@ toMaybeConnection vr ((Just connId, Just agentConnId, Just connLevel, viaContact Just $ toConnection vr ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled_, pqRcvEnabled_, authErrCounter, quotaErrCounter, connChatVersion, minVer, maxVer)) toMaybeConnection _ _ = Nothing -createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> SubscriptionMode -> PQSupport -> IO Connection -createConnection_ db userId connType entityId acId connChatVersion peerChatVRange@(VersionRange minV maxV) viaContact viaUserContactLink customUserProfileId connLevel currentTs subMode pqSup = do +createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> ConnStatus -> VersionChat -> VersionRangeChat -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> SubscriptionMode -> PQSupport -> IO Connection +createConnection_ db userId connType entityId acId connStatus connChatVersion peerChatVRange@(VersionRange minV maxV) viaContact viaUserContactLink customUserProfileId connLevel currentTs subMode pqSup = do viaLinkGroupId :: Maybe Int64 <- fmap join . forM viaUserContactLink $ \ucLinkId -> maybeFirstRow fromOnly $ DB.query db "SELECT group_id FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? AND group_id IS NOT NULL" (userId, ucLinkId) let viaGroupLink = isJust viaLinkGroupId @@ -222,7 +222,7 @@ createConnection_ db userId connType entityId acId connChatVersion peerChatVRang conn_chat_version, peer_chat_min_version, peer_chat_max_version, to_subscribe, pq_support, pq_encryption ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ( (userId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, customUserProfileId, ConnNew, connType) + ( (userId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, customUserProfileId, connStatus, connType) :. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs) :. (connChatVersion, minV, maxV, subMode == SMOnlyCreate, pqSup, pqSup) ) @@ -242,7 +242,7 @@ createConnection_ db userId connType entityId acId connChatVersion peerChatVRang groupLinkId = Nothing, customUserProfileId, connLevel, - connStatus = ConnNew, + connStatus, localAlias = "", createdAt = currentTs, connectionCode = Nothing, diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs index be8aa12cfe..40f14a10de 100644 --- a/src/Simplex/Chat/Terminal/Output.hs +++ b/src/Simplex/Chat/Terminal/Output.hs @@ -189,6 +189,8 @@ responseNotification t@ChatTerminal {sendNotification} cc = \case CRContactConnected u ct _ -> when (contactNtf u ct False) $ do whenCurrUser cc u $ setActiveContact t ct sendNtf (viewContactName ct <> "> ", "connected") + CRContactSndReady u ct -> + whenCurrUser cc u $ setActiveContact t ct CRContactAnotherClient u ct -> do whenCurrUser cc u $ unsetActiveContact t ct when (contactNtf u ct False) $ sendNtf (viewContactName ct <> "> ", "connected to another client") diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index 04cc2bcc6f..93820365b0 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1377,7 +1377,7 @@ data ConnStatus ConnRequested | -- | initiating party accepted connection with agent LET command (to be renamed to ACPT) (allowConnection) ConnAccepted - | -- | connection can be sent messages to (after joining party received INFO notification) + | -- | connection can be sent messages to (after joining party received INFO notification, or after securing snd queue on join) ConnSndReady | -- | connection is ready for both parties to send and receive messages ConnReady @@ -1588,9 +1588,9 @@ commandExpectedResponse = \case CFCreateConnGrpInv -> t INV_ CFCreateConnFileInvDirect -> t INV_ CFCreateConnFileInvGroup -> t INV_ - CFJoinConn -> t OK_ + CFJoinConn -> t JOINED_ CFAllowConn -> t OK_ - CFAcceptContact -> t OK_ + CFAcceptContact -> t JOINED_ CFAckMessage -> t OK_ CFDeleteConn -> t OK_ where diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index b0d3032ed3..a4c82b3b3f 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -176,7 +176,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactDeleted u c -> ttyUser u [ttyContact' c <> ": contact is deleted"] CRContactDeletedByContact u c -> ttyUser u [ttyFullContact c <> " deleted contact with you"] CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo - CRAcceptingContactRequest u c -> ttyUser u [ttyFullContact c <> ": accepting contact request..."] + CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c CRContactAlreadyExists u c -> ttyUser u [ttyFullContact c <> ": contact already exists"] CRContactRequestAlreadyAccepted u c -> ttyUser u [ttyFullContact c <> ": sent you a duplicate contact request, but you are already connected, no action needed"] CRUserContactLinkCreated u cReq -> ttyUser u $ connReqContact_ "Your new chat address is created!" cReq @@ -231,6 +231,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_ CRContactConnecting u _ -> ttyUser u [] CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView + CRContactSndReady u ct -> ttyUser u [ttyFullContact ct <> ": you can send messages to contact"] CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"] CRSubscriptionEnd u acEntity -> let Connection {connId} = entityConnection acEntity @@ -963,6 +964,11 @@ viewSentInvitation incognitoProfile testView = message = ["connection request sent incognito!"] Nothing -> ["connection request sent!"] +viewAcceptingContactRequest :: Contact -> [StyledString] +viewAcceptingContactRequest ct + | contactReady ct = [ttyFullContact ct <> ": accepting contact request, you can send messages to contact"] + | otherwise = [ttyFullContact ct <> ": accepting contact request..."] + viewReceivedContactRequest :: ContactName -> Profile -> [StyledString] viewReceivedContactRequest c Profile {fullName} = [ ttyFullName c fullName <> " wants to connect to you!", diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index ca5b92e04e..ffea6a3529 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -224,7 +224,10 @@ testCfgCreateGroupDirect = mkCfgCreateGroupDirect testCfg mkCfgCreateGroupDirect :: ChatConfig -> ChatConfig -mkCfgCreateGroupDirect cfg = cfg {chatVRange = groupCreateDirectVRange} +mkCfgCreateGroupDirect cfg = cfg { + chatVRange = groupCreateDirectVRange, + agentConfig = testAgentCfgSlow +} groupCreateDirectVRange :: VersionRangeChat groupCreateDirectVRange = mkVersionRange (VersionChat 1) (VersionChat 1) diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index aaf91af910..2bbcf87d5b 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1150,7 +1150,7 @@ testSubscribeAppNSE tmp = alice <## "to accept: /ac bob" alice <## "to reject: /rc bob (the sender will NOT be notified)" alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -1376,7 +1376,7 @@ testMultipleUserAddresses = alice <#? bob alice @@@ [("<@bob", "")] alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -1394,7 +1394,7 @@ testMultipleUserAddresses = alice <#? bob alice #$> ("/_get chats 2 pcc=on", chats, [("<@bob", ""), ("*", "")]) alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alisa: contact is connected") (alice <## "bob (Bob): contact is connected") @@ -1425,7 +1425,7 @@ testMultipleUserAddresses = showActiveUser alice "alisa" alice ##> "/ac cath" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" concurrently_ (cath <## "alisa: contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -2649,7 +2649,7 @@ testConnReqChatVRange ct1VRange ct2VRange tmp = bob ##> ("/c " <> cLink) alice <#? bob alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 6f1ba20246..67d1bfeae5 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -307,28 +307,6 @@ testGroupShared alice bob cath checkMessages directConnections = do alice ##> "/d bob" alice <## "bob: contact is deleted" bob <## "alice (Alice) deleted contact with you" - alice `send` "@bob hey" - if directConnections - then - alice - <### [ "@bob hey", - "member #team bob does not have direct connection, creating", - "peer chat protocol version range incompatible" - ] - else do - alice - <### [ WithTime "@bob hey", - "member #team bob does not have direct connection, creating", - "contact for member #team bob is created", - "sent invitation to connect directly to member #team bob", - "bob (Bob): contact is connected" - ] - bob - <### [ "#team alice is creating direct contact alice with you", - WithTime "alice> hey", - "alice: security code changed", - "alice (Alice): contact is connected" - ] when checkMessages $ threadDelay 1000000 alice #> "#team checking connection" bob <# "#team alice> checking connection" @@ -818,6 +796,7 @@ testGroupDeleteInvitedContact = WithTime "alice> hey", "alice: security code changed" ] + bob <## "alice (Alice): you can send messages to contact" concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alice (Alice): contact is connected") @@ -1907,7 +1886,7 @@ testGroupLink = cath ##> ("/c " <> cLink) alice <#? cath alice ##> "/ac cath" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -3965,6 +3944,7 @@ testMemberContactMessage = <### [ "#team alice is creating direct contact alice with you", WithTime "alice> hi" ] + bob <## "alice (Alice): you can send messages to contact" concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alice (Alice): contact is connected") @@ -3998,6 +3978,7 @@ testMemberContactMessage = <### [ "#team bob is creating direct contact bob with you", WithTime "bob> hi" ] + cath <## "bob (Bob): you can send messages to contact" concurrently_ (bob <## "cath (Catherine): contact is connected") (cath <## "bob (Bob): contact is connected") @@ -4018,6 +3999,7 @@ testMemberContactNoMessage = bob ##> "/_invite member contact @3" bob <## "sent invitation to connect directly to member #team cath" cath <## "#team bob is creating direct contact bob with you" + cath <## "bob (Bob): you can send messages to contact" concurrently_ (bob <## "cath (Catherine): contact is connected") (cath <## "bob (Bob): contact is connected") @@ -4058,6 +4040,7 @@ testMemberContactProhibitedRepeatInv = <### [ "#team bob is creating direct contact bob with you", WithTime "bob> hi" ] + cath <## "bob (Bob): you can send messages to contact" concurrently_ (bob <## "cath (Catherine): contact is connected") (cath <## "bob (Bob): contact is connected") @@ -4087,6 +4070,7 @@ testMemberContactInvitedConnectionReplaced tmp = do WithTime "alice> hi", "alice: security code changed" ] + bob <## "alice (Alice): you can send messages to contact" concurrently_ (alice <## "bob (Bob): contact is connected") (bob <## "alice (Alice): contact is connected") @@ -4196,6 +4180,7 @@ testMemberContactIncognito = <### [ ConsoleString ("#team " <> bobIncognito <> " is creating direct contact " <> bobIncognito <> " with you"), WithTime ("i " <> bobIncognito <> "> hi") ] + cath <## (bobIncognito <> ": you can send messages to contact") _ <- getTermLine bob _ <- getTermLine cath concurrentlyN_ @@ -4267,6 +4252,7 @@ testMemberContactProfileUpdate = <### [ "#team bob is creating direct contact bob with you", WithTime "bob> hi" ] + cath <## "bob (Bob): you can send messages to contact" concurrentlyN_ [ do bob <## "contact cath changed to kate (Kate)" diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index a3352c7f4f..6971898ddf 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -198,6 +198,7 @@ testMultiWordProfileNames = ] cath <## "#'Our Team' 'Alice Jones' is creating direct contact 'Alice Jones' with you" cath <# "'Alice Jones'> hello" + cath <## "'Alice Jones': you can send messages to contact" cath <## "'Alice Jones': contact is connected" alice <## "'Cath Johnson': contact is connected" cath ##> "/p 'Cath J'" @@ -224,7 +225,7 @@ testUserContactLink = alice <#? bob alice @@@ [("<@bob", "")] alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -236,7 +237,7 @@ testUserContactLink = alice <#? cath alice @@@ [("<@cath", ""), ("@bob", "hey")] alice ##> "/ac cath" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -254,7 +255,7 @@ testProfileLink = bob ##> ("/c " <> cLink) alice <#? bob alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -269,7 +270,7 @@ testProfileLink = cath ##> ("/c " <> cLink) alice <#? cath alice ##> "/ac cath" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -336,7 +337,7 @@ testUserContactLinkAutoAccept = alice <#? bob alice @@@ [("<@bob", "")] alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -350,6 +351,7 @@ testUserContactLinkAutoAccept = cath ##> ("/c " <> cLink) cath <## "connection request sent!" alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): you can send messages to contact" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -364,7 +366,7 @@ testUserContactLinkAutoAccept = alice <#? dan alice @@@ [("<@dan", ""), ("@cath", "hey"), ("@bob", "hey")] alice ##> "/ac dan" - alice <## "dan (Daniel): accepting contact request..." + alice <## "dan (Daniel): accepting contact request, you can send messages to contact" concurrently_ (dan <## "alice (Alice): contact is connected") (alice <## "dan (Daniel): contact is connected") @@ -391,7 +393,7 @@ testDeduplicateContactRequests = testChat3 aliceProfile bobProfile cathProfile $ bob @@@! [(":3", "", Just ConnJoined), (":2", "", Just ConnJoined), (":1", "", Just ConnJoined)] alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -423,7 +425,7 @@ testDeduplicateContactRequests = testChat3 aliceProfile bobProfile cathProfile $ alice <#? cath alice @@@ [("<@cath", ""), ("@bob", "hey")] alice ##> "/ac cath" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -465,7 +467,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile alice ##> "/ac bob" alice <## "no contact request from bob" alice ##> "/ac robert" - alice <## "robert (Robert): accepting contact request..." + alice <## "robert (Robert): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "robert (Robert): contact is connected") @@ -500,7 +502,7 @@ testDeduplicateContactRequestsProfileChange = testChat3 aliceProfile bobProfile alice <#? cath alice @@@ [("<@cath", ""), ("@robert", "hey")] alice ##> "/ac cath" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" concurrently_ (cath <## "alice (Alice): contact is connected") (alice <## "cath (Catherine): contact is connected") @@ -566,13 +568,13 @@ testAutoReplyMessage = testChat2 aliceProfile bobProfile $ bob ##> ("/c " <> cLink) bob <## "connection request sent!" alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): you can send messages to contact" + alice <# "@bob hello!" concurrentlyN_ [ do - bob <## "alice (Alice): contact is connected" - bob <# "alice> hello!", - do - alice <## "bob (Bob): contact is connected" - alice <# "@bob hello!" + bob <# "alice> hello!" + bob <## "alice (Alice): contact is connected", + alice <## "bob (Bob): contact is connected" ] testAutoReplyMessageInIncognito :: HasCallStack => FilePath -> IO () @@ -588,17 +590,16 @@ testAutoReplyMessageInIncognito = testChat2 aliceProfile bobProfile $ bob ##> ("/c " <> cLink) bob <## "connection request sent!" alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): you can send messages to contact" + alice <# "i @bob hello!" aliceIncognito <- getTermLine alice concurrentlyN_ [ do - bob <## (aliceIncognito <> ": contact is connected") - bob <# (aliceIncognito <> "> hello!"), + bob <# (aliceIncognito <> "> hello!") + bob <## (aliceIncognito <> ": contact is connected"), do alice <## ("bob (Bob): contact is connected, your incognito profile for this contact is " <> aliceIncognito) - alice - <### [ "use /i bob to print out this incognito profile again", - WithTime "i @bob hello!" - ] + alice <## "use /i bob to print out this incognito profile again" ] testPlanAddressOkKnown :: HasCallStack => FilePath -> IO () @@ -615,7 +616,7 @@ testPlanAddressOkKnown = alice <#? bob alice @@@ [("<@bob", "")] alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -654,7 +655,7 @@ testPlanAddressOwn tmp = alice <## "to reject: /rc alice_1 (the sender will NOT be notified)" alice @@@ [("<@alice_1", ""), (":2", "")] alice ##> "/ac alice_1" - alice <## "alice_1 (Alice): accepting contact request..." + alice <## "alice_1 (Alice): accepting contact request, you can send messages to contact" alice <### [ "alice_1 (Alice): contact is connected", "alice_2 (Alice): contact is connected" @@ -705,7 +706,7 @@ testPlanAddressConnecting tmp = do alice <## "to accept: /ac bob" alice <## "to reject: /rc bob (the sender will NOT be notified)" alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" withTestChat tmp "bob" $ \bob -> do threadDelay 500000 bob <## "alice (Alice): contact is connected" @@ -772,7 +773,7 @@ testPlanAddressContactDeletedReconnected = bob ##> ("/c " <> cLink) alice <#? bob alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -803,7 +804,7 @@ testPlanAddressContactDeletedReconnected = alice <## "to accept: /ac bob" alice <## "to reject: /rc bob (the sender will NOT be notified)" alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice_1 (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -877,7 +878,7 @@ testPlanAddressContactViaAddress = alice <## "to accept: /ac bob" alice <## "to reject: /rc bob (the sender will NOT be notified)" alice ##> "/ac bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" concurrently_ (bob <## "alice (Alice): contact is connected") (alice <## "bob (Bob): contact is connected") @@ -971,7 +972,7 @@ testConnectIncognitoContactAddress = testChat2 aliceProfile bobProfile $ alice <## ("to accept: /ac " <> bobIncognito) alice <## ("to reject: /rc " <> bobIncognito <> " (the sender will NOT be notified)") alice ##> ("/ac " <> bobIncognito) - alice <## (bobIncognito <> ": accepting contact request...") + alice <## (bobIncognito <> ": accepting contact request, you can send messages to contact") _ <- getTermLine bob concurrentlyN_ [ do @@ -1005,7 +1006,7 @@ testAcceptContactRequestIncognito = testChat3 aliceProfile bobProfile cathProfil bob ##> ("/c " <> cLink) alice <#? bob alice ##> "/accept incognito bob" - alice <## "bob (Bob): accepting contact request..." + alice <## "bob (Bob): accepting contact request, you can send messages to contact" aliceIncognitoBob <- getTermLine alice concurrentlyN_ [ bob <## (aliceIncognitoBob <> ": contact is connected"), @@ -1033,7 +1034,7 @@ testAcceptContactRequestIncognito = testChat3 aliceProfile bobProfile cathProfil cath ##> ("/c " <> cLink) alice <#? cath alice ##> "/_accept incognito=on 1" - alice <## "cath (Catherine): accepting contact request..." + alice <## "cath (Catherine): accepting contact request, you can send messages to contact" aliceIncognitoCath <- getTermLine alice concurrentlyN_ [ cath <## (aliceIncognitoCath <> ": contact is connected"), @@ -2036,6 +2037,7 @@ testGroupPrefsDirectForRole = testChat4 aliceProfile bobProfile cathProfile danP <### [ "#team alice is creating direct contact alice with you", WithTime "alice> hello dan" ] + dan <## "alice (Alice): you can send messages to contact" concurrently_ (alice <## "dan (Daniel): contact is connected") (dan <## "alice (Alice): contact is connected") From fa73e63a796e05a6691d22351828e0cadfabbd2f Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:31:43 +0400 Subject: [PATCH 42/57] ios: allow sending messages immediately on joinConnection, acceptContact (#4478) --- apps/ios/Shared/Model/SimpleXAPI.swift | 18 ++++++++- apps/ios/Shared/Views/Chat/ChatInfoView.swift | 24 ++++++----- apps/ios/Shared/Views/Chat/ChatView.swift | 2 +- .../Views/ChatList/ChatListNavLink.swift | 4 +- .../Views/ChatList/ChatPreviewView.swift | 2 +- .../bg.xcloc/Localized Contents/bg.xliff | 6 +-- .../cs.xcloc/Localized Contents/cs.xliff | 6 +-- .../de.xcloc/Localized Contents/de.xliff | 6 +-- .../en.xcloc/Localized Contents/en.xliff | 8 ++-- .../es.xcloc/Localized Contents/es.xliff | 6 +-- .../fi.xcloc/Localized Contents/fi.xliff | 6 +-- .../fr.xcloc/Localized Contents/fr.xliff | 6 +-- .../hu.xcloc/Localized Contents/hu.xliff | 6 +-- .../it.xcloc/Localized Contents/it.xliff | 6 +-- .../ja.xcloc/Localized Contents/ja.xliff | 6 +-- .../nl.xcloc/Localized Contents/nl.xliff | 6 +-- .../pl.xcloc/Localized Contents/pl.xliff | 6 +-- .../ru.xcloc/Localized Contents/ru.xliff | 8 ++-- .../th.xcloc/Localized Contents/th.xliff | 6 +-- .../tr.xcloc/Localized Contents/tr.xliff | 6 +-- .../uk.xcloc/Localized Contents/uk.xliff | 6 +-- .../Localized Contents/zh-Hans.xliff | 6 +-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 40 +++++++++---------- apps/ios/SimpleXChat/APITypes.swift | 3 ++ apps/ios/SimpleXChat/ChatTypes.swift | 5 ++- apps/ios/SimpleXChat/Notifications.swift | 2 +- apps/ios/bg.lproj/Localizable.strings | 4 +- apps/ios/cs.lproj/Localizable.strings | 2 +- apps/ios/de.lproj/Localizable.strings | 4 +- apps/ios/es.lproj/Localizable.strings | 4 +- apps/ios/fi.lproj/Localizable.strings | 2 +- apps/ios/fr.lproj/Localizable.strings | 4 +- apps/ios/hu.lproj/Localizable.strings | 4 +- apps/ios/it.lproj/Localizable.strings | 4 +- apps/ios/ja.lproj/Localizable.strings | 2 +- apps/ios/nl.lproj/Localizable.strings | 4 +- apps/ios/pl.lproj/Localizable.strings | 4 +- apps/ios/ru.lproj/Localizable.strings | 4 +- apps/ios/th.lproj/Localizable.strings | 2 +- apps/ios/tr.lproj/Localizable.strings | 4 +- apps/ios/uk.lproj/Localizable.strings | 4 +- apps/ios/zh-Hans.lproj/Localizable.strings | 4 +- 42 files changed, 142 insertions(+), 120 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 3fa219128b..ee5c217bda 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1143,7 +1143,10 @@ func networkErrorAlert(_ r: ChatResponse) -> Alert? { func acceptContactRequest(incognito: Bool, contactRequest: UserContactRequest) async { if let contact = await apiAcceptContactRequest(incognito: incognito, contactReqId: contactRequest.apiId) { let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: []) - DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) } + DispatchQueue.main.async { + ChatModel.shared.replaceChat(contactRequest.id, chat) + ChatModel.shared.setContactNetworkStatus(contact, .connected) + } } } @@ -1649,6 +1652,19 @@ func processReceivedMsg(_ res: ChatResponse) async { } } } + case let .contactSndReady(user, contact): + if active(user) && contact.directOrUsed { + await MainActor.run { + m.updateContact(contact) + if let conn = contact.activeConn { + m.dismissConnReqView(conn.id) + m.removeChat(conn.id) + } + } + } + await MainActor.run { + m.setContactNetworkStatus(contact, .connected) + } case let .receivedContactRequest(user, contactRequest): if active(user) { let cInfo = ChatInfo.contactRequest(contactRequest: contactRequest) diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index ed589ec083..f83fca8e2e 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -155,23 +155,25 @@ struct ChatInfoView: View { } Section { - if let code = connectionCode { verifyCodeButton(code) } - contactPreferencesButton() - sendReceiptsOption() - if let connStats = connectionStats, - connStats.ratchetSyncAllowed { - synchronizeConnectionButton() + Group { + if let code = connectionCode { verifyCodeButton(code) } + contactPreferencesButton() + sendReceiptsOption() + if let connStats = connectionStats, + connStats.ratchetSyncAllowed { + synchronizeConnectionButton() + } + // } else if developerTools { + // synchronizeConnectionButtonForce() + // } } + .disabled(!contact.ready || !contact.active) NavigationLink { ChatWallpaperEditorSheet(chat: chat) } label: { Label("Chat theme", systemImage: "photo") } -// } else if developerTools { -// synchronizeConnectionButtonForce() -// } } - .disabled(!contact.ready || !contact.active) if let conn = contact.activeConn { Section { @@ -271,7 +273,7 @@ struct ChatInfoView: View { } } .actionSheet(isPresented: $showDeleteContactActionSheet) { - if contact.ready && contact.active { + if contact.sndReady && contact.active { return ActionSheet( title: Text("Delete contact?\nThis cannot be undone!"), buttons: [ diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 1f3c04085b..0af460387b 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -378,7 +378,7 @@ struct ChatView: View { @ViewBuilder private func connectingText() -> some View { if case let .direct(contact) = chat.chatInfo, - !contact.ready, + !contact.sndReady, contact.active, !contact.nextSendGrpInv { Text("connecting…") diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index c0cc42bd8e..35cb5b3861 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -100,7 +100,7 @@ struct ChatListNavLink: View { clearChatButton() } Button { - if contact.ready || !contact.active { + if contact.sndReady || !contact.active { showDeleteContactActionSheet = true } else { AlertManager.shared.showAlert(deletePendingContactAlert(chat, contact)) @@ -114,7 +114,7 @@ struct ChatListNavLink: View { } } .actionSheet(isPresented: $showDeleteContactActionSheet) { - if contact.ready && contact.active { + if contact.sndReady && contact.active { return ActionSheet( title: Text("Delete contact?\nThis cannot be undone!"), buttons: [ diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index c1156225d8..f29e2e14e6 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -207,7 +207,7 @@ struct ChatPreviewView: View { if contact.activeConn == nil && contact.profile.contactLink != nil { chatPreviewInfoText("Tap to Connect") .foregroundColor(theme.colors.primary) - } else if !contact.ready && contact.activeConn != nil { + } else if !contact.sndReady && contact.activeConn != nil { if contact.nextSendGrpInv { chatPreviewInfoText("send direct message") } else if contact.active { diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index e005b44466..9ac4d8ced4 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -7064,8 +7064,8 @@ Repeat join request? Можете да го направите видим за вашите контакти в SimpleX чрез Настройки. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Вече можете да изпращате съобщения до %@ notification body @@ -7472,7 +7472,7 @@ SimpleX сървърите не могат да видят вашия профи blocked by admin блокиран от админ - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index 972d06f001..c37ee1038e 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -6797,8 +6797,8 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Nyní můžete posílat zprávy %@ notification body @@ -7191,7 +7191,7 @@ Servery SimpleX nevidí váš profil. blocked by admin - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index 24a40bfd1a..e3d36cf09e 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -7226,8 +7226,8 @@ Verbindungsanfrage wiederholen? Sie können sie über Einstellungen für Ihre SimpleX-Kontakte sichtbar machen. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Sie können nun Nachrichten an %@ versenden notification body @@ -7635,7 +7635,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. blocked by admin wurde vom Administrator blockiert - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index 73e6fb5a23..a0ec9aca4a 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -7236,9 +7236,9 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. No comment provided by engineer. - - You can now send messages to %@ - You can now send messages to %@ + + You can now chat with %@ + You can now chat with %@ notification body @@ -7645,7 +7645,7 @@ SimpleX servers cannot see your profile. blocked by admin blocked by admin - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index 455b74de5d..837ea06e96 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -7103,8 +7103,8 @@ Repeat join request? Puedes hacerlo visible para tus contactos de SimpleX en Configuración. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Ya puedes enviar mensajes a %@ notification body @@ -7511,7 +7511,7 @@ Los servidores SimpleX no pueden ver tu perfil. blocked by admin bloqueado por administrador - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 92070c316c..07673d911c 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -6782,8 +6782,8 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Voit nyt lähettää viestejä %@:lle notification body @@ -7176,7 +7176,7 @@ SimpleX-palvelimet eivät näe profiiliasi. blocked by admin - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index c2a801b0b5..5c23f58e9c 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -7103,8 +7103,8 @@ Répéter la demande d'adhésion ? Vous pouvez le rendre visible à vos contacts SimpleX via Paramètres. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Vous pouvez maintenant envoyer des messages à %@ notification body @@ -7511,7 +7511,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. blocked by admin bloqué par l'administrateur - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index 411317b715..2dbd206209 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -7226,8 +7226,8 @@ Csatlakozási kérés megismétlése? Láthatóvá teheti SimpleX ismerősök számára a Beállításokban. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Mostantól küldhet üzeneteket %@ számára notification body @@ -7635,7 +7635,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. blocked by admin letiltva az admin által - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index b0d9308716..bb2241c336 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -7226,8 +7226,8 @@ Ripetere la richiesta di ingresso? Puoi renderlo visibile ai tuoi contatti SimpleX nelle impostazioni. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Ora puoi inviare messaggi a %@ notification body @@ -7635,7 +7635,7 @@ I server di SimpleX non possono vedere il tuo profilo. blocked by admin bloccato dall'amministratore - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index 510766fa70..2f94e9c141 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -6800,8 +6800,8 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ %@ にメッセージを送信できるようになりました notification body @@ -7194,7 +7194,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 blocked by admin - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index f06fac9b97..1f7adaeca4 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -7103,8 +7103,8 @@ Deelnameverzoek herhalen? Je kunt het via Instellingen zichtbaar maken voor je SimpleX contacten. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Je kunt nu berichten sturen naar %@ notification body @@ -7511,7 +7511,7 @@ SimpleX servers kunnen uw profiel niet zien. blocked by admin geblokkeerd door beheerder - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index fd380d7e89..731095dea8 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -7103,8 +7103,8 @@ Powtórzyć prośbę dołączenia? Możesz ustawić go jako widoczny dla swoich kontaktów SimpleX w Ustawieniach. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Możesz teraz wysyłać wiadomości do %@ notification body @@ -7511,7 +7511,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. blocked by admin zablokowany przez admina - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index a31e6168e1..2fe659c6d8 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -7101,9 +7101,9 @@ Repeat join request? Вы можете сделать его видимым для ваших контактов в SimpleX через Настройки. No comment provided by engineer. - - You can now send messages to %@ - Вы теперь можете отправлять сообщения %@ + + You can now chat with %@ + Вы теперь можете общаться с %@ notification body @@ -7509,7 +7509,7 @@ SimpleX серверы не могут получить доступ к Ваше blocked by admin заблокировано администратором - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index caeefc37ed..2fb86f9a76 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -6754,8 +6754,8 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ ตอนนี้คุณสามารถส่งข้อความถึง %@ notification body @@ -7146,7 +7146,7 @@ SimpleX servers cannot see your profile. blocked by admin - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 5f1a48b381..4b4c804fa6 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -7103,8 +7103,8 @@ Katılma isteği tekrarlansın mı? Ayarlardan SimpleX kişilerinize görünür yapabilirsiniz. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Artık %@ adresine mesaj gönderebilirsin notification body @@ -7511,7 +7511,7 @@ SimpleX sunucuları profilinizi göremez. blocked by admin yönetici tarafından engellendi - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 318d8a877b..8056f0753f 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -7103,8 +7103,8 @@ Repeat join request? Ви можете зробити його видимим для ваших контактів у SimpleX за допомогою налаштувань. No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ Тепер ви можете надсилати повідомлення на адресу %@ notification body @@ -7511,7 +7511,7 @@ SimpleX servers cannot see your profile. blocked by admin заблоковано адміністратором - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index e53ab5b1a7..0a55e89f1a 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -7004,8 +7004,8 @@ Repeat join request? 你可以通过设置让它对你的 SimpleX 联系人可见。 No comment provided by engineer. - - You can now send messages to %@ + + You can now chat with %@ 您现在可以给 %@ 发送消息 notification body @@ -7407,7 +7407,7 @@ SimpleX 服务器无法看到您的资料。 blocked by admin 由管理员封禁 - blocked chat item + marked deleted chat item preview text bold diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5f209793f5..5ad01fa6a8 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -170,6 +170,11 @@ 649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; }; 64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; }; 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; }; + 64BAC45E2C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64BAC4592C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU.a */; }; + 64BAC45F2C495205008D3995 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64BAC45A2C495205008D3995 /* libffi.a */; }; + 64BAC4602C495205008D3995 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64BAC45B2C495205008D3995 /* libgmpxx.a */; }; + 64BAC4612C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64BAC45C2C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU-ghc9.6.3.a */; }; + 64BAC4622C495205008D3995 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64BAC45D2C495205008D3995 /* libgmp.a */; }; 64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; }; 64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; }; 64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; }; @@ -205,11 +210,6 @@ D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; }; D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; }; E50581062C3DDD9D009C3F71 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = E50581052C3DDD9D009C3F71 /* Yams */; }; - E505810C2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50581072C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a */; }; - E505810D2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50581082C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a */; }; - E505810E2C4656A4009C3F71 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E50581092C4656A4009C3F71 /* libgmpxx.a */; }; - E505810F2C4656A4009C3F71 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E505810A2C4656A4009C3F71 /* libffi.a */; }; - E50581102C4656A4009C3F71 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E505810B2C4656A4009C3F71 /* libgmp.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -480,6 +480,11 @@ 649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; }; 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; }; 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; }; + 64BAC4592C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU.a"; sourceTree = ""; }; + 64BAC45A2C495205008D3995 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 64BAC45B2C495205008D3995 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 64BAC45C2C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU-ghc9.6.3.a"; sourceTree = ""; }; + 64BAC45D2C495205008D3995 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; }; 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; }; 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; }; @@ -510,11 +515,6 @@ D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - E50581072C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a"; sourceTree = ""; }; - E50581082C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a"; sourceTree = ""; }; - E50581092C4656A4009C3F71 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - E505810A2C4656A4009C3F71 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - E505810B2C4656A4009C3F71 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -554,14 +554,14 @@ buildActionMask = 2147483647; files = ( 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - E50581102C4656A4009C3F71 /* libgmp.a in Frameworks */, + 64BAC4622C495205008D3995 /* libgmp.a in Frameworks */, + 64BAC45F2C495205008D3995 /* libffi.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - E505810C2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a in Frameworks */, - E505810F2C4656A4009C3F71 /* libffi.a in Frameworks */, E50581062C3DDD9D009C3F71 /* Yams in Frameworks */, - E505810D2C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a in Frameworks */, CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */, - E505810E2C4656A4009C3F71 /* libgmpxx.a in Frameworks */, + 64BAC4612C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU-ghc9.6.3.a in Frameworks */, + 64BAC45E2C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU.a in Frameworks */, + 64BAC4602C495205008D3995 /* libgmpxx.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -628,11 +628,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - E505810A2C4656A4009C3F71 /* libffi.a */, - E505810B2C4656A4009C3F71 /* libgmp.a */, - E50581092C4656A4009C3F71 /* libgmpxx.a */, - E50581072C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4-ghc9.6.3.a */, - E50581082C4656A4009C3F71 /* libHSsimplex-chat-6.0.0.1-6hE2sENjTY0J94Us5F3bT4.a */, + 64BAC45A2C495205008D3995 /* libffi.a */, + 64BAC45D2C495205008D3995 /* libgmp.a */, + 64BAC45B2C495205008D3995 /* libgmpxx.a */, + 64BAC45C2C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU-ghc9.6.3.a */, + 64BAC4592C495205008D3995 /* libHSsimplex-chat-6.0.0.1-J5MWx9pYOGnDBWRfMkQxFU.a */, ); path = Libraries; sourceTree = ""; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 7a33253c04..5163b02c39 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -575,6 +575,7 @@ public enum ChatResponse: Decodable, Error { case userContactLinkDeleted(user: User) case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?) case contactConnecting(user: UserRef, contact: Contact) + case contactSndReady(user: UserRef, contact: Contact) case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest) case acceptingContactRequest(user: UserRef, contact: Contact) case contactRequestRejected(user: UserRef) @@ -742,6 +743,7 @@ public enum ChatResponse: Decodable, Error { case .userContactLinkDeleted: return "userContactLinkDeleted" case .contactConnected: return "contactConnected" case .contactConnecting: return "contactConnecting" + case .contactSndReady: return "contactSndReady" case .receivedContactRequest: return "receivedContactRequest" case .acceptingContactRequest: return "acceptingContactRequest" case .contactRequestRejected: return "contactRequestRejected" @@ -907,6 +909,7 @@ public enum ChatResponse: Decodable, Error { case .userContactLinkDeleted: return noDetails case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact)) case let .contactConnecting(u, contact): return withUser(u, String(describing: contact)) + case let .contactSndReady(u, contact): return withUser(u, String(describing: contact)) case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest)) case let .acceptingContactRequest(u, contact): return withUser(u, String(describing: contact)) case .contactRequestRejected: return noDetails diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index b84e4bb3a0..7c113e37c3 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1512,10 +1512,11 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable { public var id: ChatId { get { "@\(contactId)" } } public var apiId: Int64 { get { contactId } } public var ready: Bool { get { activeConn?.connStatus == .ready } } + public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } } public var active: Bool { get { contactStatus == .active } } public var sendMsgEnabled: Bool { get { ( - ready + sndReady && active && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false) && !(activeConn?.connDisabled ?? true) @@ -1824,7 +1825,7 @@ public enum ConnStatus: String, Decodable, Hashable { case .joined: return false case .requested: return true case .accepted: return true - case .sndReady: return false + case .sndReady: return nil case .ready: return nil case .deleted: return nil } diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift index bc959cb34b..4b43595372 100644 --- a/apps/ios/SimpleXChat/Notifications.swift +++ b/apps/ios/SimpleXChat/Notifications.swift @@ -47,7 +47,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact) hideContent ? NSLocalizedString("A new contact", comment: "notification title") : contact.displayName ), body: String.localizedStringWithFormat( - NSLocalizedString("You can now send messages to %@", comment: "notification body"), + NSLocalizedString("You can now chat with %@", comment: "notification body"), hideContent ? NSLocalizedString("this contact", comment: "notification title") : contact.chatViewName ), targetContentIdentifier: contact.id, diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 0d5dbb7575..142baa5cbe 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -641,7 +641,7 @@ /* rcv group event chat item */ "blocked %@" = "блокиран %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "блокиран от админ"; /* No comment provided by engineer. */ @@ -4294,7 +4294,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Можете да го направите видим за вашите контакти в SimpleX чрез Настройки."; /* notification body */ -"You can now send messages to %@" = "Вече можете да изпращате съобщения до %@"; +"You can now chat with %@" = "Вече можете да изпращате съобщения до %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Можете да зададете визуализация на известията на заключен екран през настройките."; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index e12a12f474..1a83061c8c 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -3436,7 +3436,7 @@ "You can hide or mute a user profile - swipe it to the right." = "Profil uživatele můžete skrýt nebo ztlumit - přejeďte prstem doprava."; /* notification body */ -"You can now send messages to %@" = "Nyní můžete posílat zprávy %@"; +"You can now chat with %@" = "Nyní můžete posílat zprávy %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Náhled oznámení na zamykací obrazovce můžete změnit v nastavení."; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 79a7e8536d..d46f3243d4 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -686,7 +686,7 @@ /* rcv group event chat item */ "blocked %@" = "%@ wurde blockiert"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "wurde vom Administrator blockiert"; /* No comment provided by engineer. */ @@ -4801,7 +4801,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Sie können sie über Einstellungen für Ihre SimpleX-Kontakte sichtbar machen."; /* notification body */ -"You can now send messages to %@" = "Sie können nun Nachrichten an %@ versenden"; +"You can now chat with %@" = "Sie können nun Nachrichten an %@ versenden"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Über die Geräte-Einstellungen können Sie die Benachrichtigungsvorschau im Sperrbildschirm erlauben."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 6aca830a88..1a8835c976 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "ha bloqueado a %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "bloqueado por administrador"; /* No comment provided by engineer. */ @@ -4414,7 +4414,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Puedes hacerlo visible para tus contactos de SimpleX en Configuración."; /* notification body */ -"You can now send messages to %@" = "Ya puedes enviar mensajes a %@"; +"You can now chat with %@" = "Ya puedes enviar mensajes a %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puedes configurar las notificaciones de la pantalla de bloqueo desde Configuración."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 8fe0c07a13..84613e6a54 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -3394,7 +3394,7 @@ "You can hide or mute a user profile - swipe it to the right." = "Voit piilottaa tai mykistää käyttäjäprofiilin pyyhkäisemällä sitä oikealle."; /* notification body */ -"You can now send messages to %@" = "Voit nyt lähettää viestejä %@:lle"; +"You can now chat with %@" = "Voit nyt lähettää viestejä %@:lle"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Voit määrittää lukitusnäytön ilmoituksen esikatselun asetuksista."; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 6d19ccc6a9..c2777fac75 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "%@ bloqué"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "bloqué par l'administrateur"; /* No comment provided by engineer. */ @@ -4414,7 +4414,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Vous pouvez le rendre visible à vos contacts SimpleX via Paramètres."; /* notification body */ -"You can now send messages to %@" = "Vous pouvez maintenant envoyer des messages à %@"; +"You can now chat with %@" = "Vous pouvez maintenant envoyer des messages à %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Vous pouvez configurer l'aperçu des notifications sur l'écran de verrouillage via les paramètres."; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index 3feb67b764..ab732754c9 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -686,7 +686,7 @@ /* rcv group event chat item */ "blocked %@" = "letiltotta őt: %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "letiltva az admin által"; /* No comment provided by engineer. */ @@ -4801,7 +4801,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti SimpleX ismerősök számára a Beállításokban."; /* notification body */ -"You can now send messages to %@" = "Mostantól küldhet üzeneteket %@ számára"; +"You can now chat with %@" = "Mostantól küldhet üzeneteket %@ számára"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index ffd4df5ddf..24b604ecc0 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -686,7 +686,7 @@ /* rcv group event chat item */ "blocked %@" = "ha bloccato %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "bloccato dall'amministratore"; /* No comment provided by engineer. */ @@ -4801,7 +4801,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Puoi renderlo visibile ai tuoi contatti SimpleX nelle impostazioni."; /* notification body */ -"You can now send messages to %@" = "Ora puoi inviare messaggi a %@"; +"You can now chat with %@" = "Ora puoi inviare messaggi a %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Puoi impostare l'anteprima della notifica nella schermata di blocco tramite le impostazioni."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index bd33a36f0c..0f5ccc2b8c 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -3448,7 +3448,7 @@ "You can hide or mute a user profile - swipe it to the right." = "ユーザープロファイルを右にスワイプすると、非表示またはミュートにすることができます。"; /* notification body */ -"You can now send messages to %@" = "%@ にメッセージを送信できるようになりました"; +"You can now chat with %@" = "%@ にメッセージを送信できるようになりました"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "設定からロック画面の通知プレビューを設定できます。"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 89089ee726..9cd3c078b3 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "geblokkeerd %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "geblokkeerd door beheerder"; /* No comment provided by engineer. */ @@ -4414,7 +4414,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Je kunt het via Instellingen zichtbaar maken voor je SimpleX contacten."; /* notification body */ -"You can now send messages to %@" = "Je kunt nu berichten sturen naar %@"; +"You can now chat with %@" = "Je kunt nu berichten sturen naar %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "U kunt een voorbeeld van een melding op het vergrendeld scherm instellen via instellingen."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index cca88e9987..6807e4f240 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "zablokowany %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "zablokowany przez admina"; /* No comment provided by engineer. */ @@ -4414,7 +4414,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Możesz ustawić go jako widoczny dla swoich kontaktów SimpleX w Ustawieniach."; /* notification body */ -"You can now send messages to %@" = "Możesz teraz wysyłać wiadomości do %@"; +"You can now chat with %@" = "Możesz teraz wysyłać wiadomości do %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index fcab550e81..255ce8a6b5 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "%@ заблокирован"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "заблокировано администратором"; /* No comment provided by engineer. */ @@ -4405,7 +4405,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Вы можете сделать его видимым для ваших контактов в SimpleX через Настройки."; /* notification body */ -"You can now send messages to %@" = "Вы теперь можете отправлять сообщения %@"; +"You can now chat with %@" = "Вы теперь можете общаться с %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index ee379ec4a4..22b707f886 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -3301,7 +3301,7 @@ "You can hide or mute a user profile - swipe it to the right." = "คุณสามารถซ่อนหรือปิดเสียงโปรไฟล์ผู้ใช้ - ปัดไปทางขวา"; /* notification body */ -"You can now send messages to %@" = "ตอนนี้คุณสามารถส่งข้อความถึง %@"; +"You can now chat with %@" = "ตอนนี้คุณสามารถส่งข้อความถึง %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "คุณสามารถตั้งค่าแสดงตัวอย่างการแจ้งเตือนบนหน้าจอล็อคผ่านการตั้งค่า"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index d007f05406..644fa5fdbe 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "engellendi %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "yönetici tarafından engellendi"; /* No comment provided by engineer. */ @@ -4414,7 +4414,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Ayarlardan SimpleX kişilerinize görünür yapabilirsiniz."; /* notification body */ -"You can now send messages to %@" = "Artık %@ adresine mesaj gönderebilirsin"; +"You can now chat with %@" = "Artık %@ adresine mesaj gönderebilirsin"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Kilit ekranı bildirim önizlemesini ayarlar üzerinden ayarlayabilirsiniz."; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index eb29f07687..d41e32efc9 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -647,7 +647,7 @@ /* rcv group event chat item */ "blocked %@" = "заблоковано %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "заблоковано адміністратором"; /* No comment provided by engineer. */ @@ -4414,7 +4414,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "Ви можете зробити його видимим для ваших контактів у SimpleX за допомогою налаштувань."; /* notification body */ -"You can now send messages to %@" = "Тепер ви можете надсилати повідомлення на адресу %@"; +"You can now chat with %@" = "Тепер ви можете надсилати повідомлення на адресу %@"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "Ви можете налаштувати попередній перегляд сповіщень на екрані блокування за допомогою налаштувань."; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 5d9fd6bf80..9d990ab609 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -599,7 +599,7 @@ /* rcv group event chat item */ "blocked %@" = "已封禁 %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "由管理员封禁"; /* No comment provided by engineer. */ @@ -4111,7 +4111,7 @@ "You can make it visible to your SimpleX contacts via Settings." = "你可以通过设置让它对你的 SimpleX 联系人可见。"; /* notification body */ -"You can now send messages to %@" = "您现在可以给 %@ 发送消息"; +"You can now chat with %@" = "您现在可以给 %@ 发送消息"; /* No comment provided by engineer. */ "You can set lock screen notification preview via settings." = "您可以通过设置来设置锁屏通知预览。"; From bf9cdf3053c6a8164775784c02179d7bcbd82ab9 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:50:27 +0400 Subject: [PATCH 43/57] android: allow sending messages immediately on joinConnection, acceptContact (#4483) --- .../chat/simplex/common/model/ChatModel.kt | 5 ++-- .../chat/simplex/common/model/SimpleXAPI.kt | 14 ++++++++++ .../simplex/common/views/chat/ChatInfoView.kt | 28 +++++++++---------- .../simplex/common/views/chat/ChatView.kt | 2 +- .../views/chatlist/ChatListNavLinkView.kt | 1 + .../common/views/chatlist/ChatPreviewView.kt | 2 +- 6 files changed, 34 insertions(+), 18 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index e96b8a8eb1..025f722734 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1085,9 +1085,10 @@ data class Contact( override val id get() = "@$contactId" override val apiId get() = contactId override val ready get() = activeConn?.connStatus == ConnStatus.Ready + val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady val active get() = contactStatus == ContactStatus.Active override val sendMsgEnabled get() = ( - ready + sndReady && active && !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?: false) && !(activeConn?.connDisabled ?: true) @@ -1753,7 +1754,7 @@ enum class ConnStatus { Joined -> false Requested -> true Accepted -> true - SndReady -> false + SndReady -> null Ready -> null Deleted -> null } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 0b153ae7cf..964092ec29 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1935,6 +1935,17 @@ object ChatController { } } } + is CR.ContactSndReady -> { + if (active(r.user) && r.contact.directOrUsed) { + chatModel.updateContact(rhId, r.contact) + val conn = r.contact.activeConn + if (conn != null) { + chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}") + chatModel.removeChat(rhId, conn.id) + } + } + chatModel.setContactNetworkStatus(r.contact, NetworkStatus.Connected()) + } is CR.ReceivedContactRequest -> { val contactRequest = r.contactRequest val cInfo = ChatInfo.ContactRequest(contactRequest) @@ -4572,6 +4583,7 @@ sealed class CR { @Serializable @SerialName("userContactLinkDeleted") class UserContactLinkDeleted(val user: User): CR() @Serializable @SerialName("contactConnected") class ContactConnected(val user: UserRef, val contact: Contact, val userCustomProfile: Profile? = null): CR() @Serializable @SerialName("contactConnecting") class ContactConnecting(val user: UserRef, val contact: Contact): CR() + @Serializable @SerialName("contactSndReady") class ContactSndReady(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("receivedContactRequest") class ReceivedContactRequest(val user: UserRef, val contactRequest: UserContactRequest): CR() @Serializable @SerialName("acceptingContactRequest") class AcceptingContactRequest(val user: UserRef, val contact: Contact): CR() @Serializable @SerialName("contactRequestRejected") class ContactRequestRejected(val user: UserRef): CR() @@ -4747,6 +4759,7 @@ sealed class CR { is UserContactLinkDeleted -> "userContactLinkDeleted" is ContactConnected -> "contactConnected" is ContactConnecting -> "contactConnecting" + is ContactSndReady -> "contactSndReady" is ReceivedContactRequest -> "receivedContactRequest" is AcceptingContactRequest -> "acceptingContactRequest" is ContactRequestRejected -> "contactRequestRejected" @@ -4912,6 +4925,7 @@ sealed class CR { is UserContactLinkDeleted -> withUser(user, noDetails()) is ContactConnected -> withUser(user, json.encodeToString(contact)) is ContactConnecting -> withUser(user, json.encodeToString(contact)) + is ContactSndReady -> withUser(user, json.encodeToString(contact)) is ReceivedContactRequest -> withUser(user, json.encodeToString(contactRequest)) is AcceptingContactRequest -> withUser(user, json.encodeToString(contact)) is ContactRequestRejected -> withUser(user, noDetails()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 48ed0570a7..838225afb8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -206,7 +206,7 @@ fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = text = AnnotatedString(generalGetString(MR.strings.delete_contact_all_messages_deleted_cannot_undo_warning)), buttons = { Column { - if (chatInfo is ChatInfo.Direct && chatInfo.contact.ready && chatInfo.contact.active) { + if (chatInfo is ChatInfo.Direct && chatInfo.contact.sndReady && chatInfo.contact.active) { // Delete and notify contact SectionItemView({ AlertManager.shared.hideAlert() @@ -330,8 +330,8 @@ fun ChatInfoLayout( SectionDividerSpaced() } - if (contact.ready && contact.active) { - SectionView { + SectionView { + if (contact.ready && contact.active) { if (connectionCode != null) { VerifyCodeButton(contact.verified, verifyClicked) } @@ -340,22 +340,22 @@ fun ChatInfoLayout( if (cStats != null && cStats.ratchetSyncAllowed) { SynchronizeConnectionButton(syncContactConnection) } + // } else if (developerTools) { + // SynchronizeConnectionButtonForce(syncContactConnectionForce) + // } + } - WallpaperButton { - ModalManager.end.showModal { - val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } } - val c = chat.value - if (c != null) { - ChatWallpaperEditorModal(c) - } + WallpaperButton { + ModalManager.end.showModal { + val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } } + val c = chat.value + if (c != null) { + ChatWallpaperEditorModal(c) } } - // } else if (developerTools) { - // SynchronizeConnectionButtonForce(syncContactConnectionForce) - // } } - SectionDividerSpaced() } + SectionDividerSpaced() val conn = contact.activeConn if (conn != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 7af08107bb..cb563c4807 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -132,7 +132,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: ) { if ( chat.chatInfo is ChatInfo.Direct - && !chat.chatInfo.contact.ready + && !chat.chatInfo.contact.sndReady && chat.chatInfo.contact.active && !chat.chatInfo.contact.nextSendGrpInv ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index d06e2ae88e..dc32bb1318 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -630,6 +630,7 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe if (contact != null && isCurrentUser && contactRequest != null) { val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf()) chatModel.replaceChat(rhId, contactRequest.id, chat) + chatModel.setContactNetworkStatus(contact, NetworkStatus.Connected()) } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt index 3f66c7d7d8..2cf9008fc3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt @@ -209,7 +209,7 @@ fun ChatPreviewView( is ChatInfo.Direct -> if (cInfo.contact.activeConn == null && cInfo.contact.profile.contactLink != null) { Text(stringResource(MR.strings.contact_tap_to_connect), color = MaterialTheme.colors.primary) - } else if (!cInfo.ready && cInfo.contact.activeConn != null) { + } else if (!cInfo.contact.sndReady && cInfo.contact.activeConn != null) { if (cInfo.contact.nextSendGrpInv) { Text(stringResource(MR.strings.member_contact_send_direct_message), color = MaterialTheme.colors.secondary) } else if (cInfo.contact.active) { From 95776e0951a83eacbe5c4975a61948e3cb0d7d56 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:39:12 +0400 Subject: [PATCH 44/57] ios: improve proxy errors (#4485) --- apps/ios/Shared/Model/SimpleXAPI.swift | 35 ++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index ee5c217bda..65476fd93f 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1107,26 +1107,41 @@ func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? { case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))): return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).") case let .chatCmdError(_, .errorAgent(.SMP(serverAddress, .PROXY(proxyErr)))): - return proxyErrorAlert(proxyErr, serverAddress) - case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, _, .protocolError(.PROXY(proxyErr))))): - return proxyErrorAlert(proxyErr, proxyServer) + return smpProxyErrorAlert(proxyErr, serverAddress) + case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr))))): + return proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer) default: return nil } } -private func proxyErrorAlert(_ proxyErr: ProxyError, _ srvAddr: String) -> ErrorAlert? { +private func smpProxyErrorAlert(_ proxyErr: ProxyError, _ srvAddr: String) -> ErrorAlert? { switch proxyErr { case .BROKER(brokerErr: .TIMEOUT): - return ErrorAlert(title: "Private routing error", message: "Please try later.") + return ErrorAlert(title: "Private routing error", message: "Error connecting to forwarding server \(serverHostname(srvAddr)). Please try later.") case .BROKER(brokerErr: .NETWORK): - return ErrorAlert(title: "Private routing error", message: "Please try later.") - case .NO_SESSION: - return ErrorAlert(title: "Private routing error", message: "Please try later.") + return ErrorAlert(title: "Private routing error", message: "Error connecting to forwarding server \(serverHostname(srvAddr)). Please try later.") case .BROKER(brokerErr: .HOST): - return ErrorAlert(title: "Private routing error", message: "Server address is incompatible with network settings: \(serverHostname(srvAddr)).") + return ErrorAlert(title: "Private routing error", message: "Forwarding server address is incompatible with network settings: \(serverHostname(srvAddr)).") case .BROKER(brokerErr: .TRANSPORT(.version)): - return ErrorAlert(title: "Private routing error", message: "Server version is incompatible with network settings: \(serverHostname(srvAddr)).") + return ErrorAlert(title: "Private routing error", message: "Forwarding server version is incompatible with network settings: \(serverHostname(srvAddr)).") + default: + return nil + } +} + +private func proxyDestinationErrorAlert(_ proxyErr: ProxyError, _ proxyServer: String, _ relayServer: String) -> ErrorAlert? { + switch proxyErr { + case .BROKER(brokerErr: .TIMEOUT): + return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.") + case .BROKER(brokerErr: .NETWORK): + return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.") + case .NO_SESSION: + return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.") + case .BROKER(brokerErr: .HOST): + return ErrorAlert(title: "Private routing error", message: "Destination server address of \(serverHostname(relayServer)) is incompatible with forwarding server \(serverHostname(proxyServer)) settings.") + case .BROKER(brokerErr: .TRANSPORT(.version)): + return ErrorAlert(title: "Private routing error", message: "Destination server version of \(serverHostname(relayServer)) is incompatible with forwarding server \(serverHostname(proxyServer)).") default: return nil } From 6d488ba489290d00876f6534674acb119faf9584 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 19 Jul 2024 20:10:31 +0400 Subject: [PATCH 45/57] android: improve proxy errors (#4489) --- .../chat/simplex/common/model/SimpleXAPI.kt | 71 +++++++++++++++---- .../commonMain/resources/MR/base/strings.xml | 7 +- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 964092ec29..37baed40b2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1837,23 +1837,27 @@ object ChatController { r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.SMP && r.chatError.agentError.smpErr is SMPErrorType.PROXY -> - proxyErrorAlert(r.chatError.agentError.smpErr.proxyErr, r.chatError.agentError.serverAddress) + smpProxyErrorAlert(r.chatError.agentError.smpErr.proxyErr, r.chatError.agentError.serverAddress) r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.PROXY && r.chatError.agentError.proxyErr is ProxyClientError.ProxyProtocolError && r.chatError.agentError.proxyErr.protocolErr is SMPErrorType.PROXY -> - proxyErrorAlert(r.chatError.agentError.proxyErr.protocolErr.proxyErr, r.chatError.agentError.proxyServer) + proxyDestinationErrorAlert( + r.chatError.agentError.proxyErr.protocolErr.proxyErr, + r.chatError.agentError.proxyServer, + r.chatError.agentError.relayServer + ) else -> false } } - private fun proxyErrorAlert(pe: ProxyError, srvAddr: String): Boolean { + private fun smpProxyErrorAlert(pe: ProxyError, srvAddr: String): Boolean { return when { pe is ProxyError.BROKER && pe.brokerErr is BrokerErrorType.TIMEOUT -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - generalGetString(MR.strings.please_try_later) + String.format(generalGetString(MR.strings.smp_proxy_error_connecting), serverHostname(srvAddr)) ) true } @@ -1861,14 +1865,7 @@ object ChatController { && pe.brokerErr is BrokerErrorType.NETWORK -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - generalGetString(MR.strings.please_try_later) - ) - true - } - pe is ProxyError.NO_SESSION -> { - AlertManager.shared.showAlertMsg( - generalGetString(MR.strings.private_routing_error), - generalGetString(MR.strings.please_try_later) + String.format(generalGetString(MR.strings.smp_proxy_error_connecting), serverHostname(srvAddr)) ) true } @@ -1876,7 +1873,7 @@ object ChatController { && pe.brokerErr is BrokerErrorType.HOST -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - String.format(generalGetString(MR.strings.network_error_broker_host_desc), serverHostname(srvAddr)) + String.format(generalGetString(MR.strings.smp_proxy_error_broker_host), serverHostname(srvAddr)) ) true } @@ -1885,7 +1882,53 @@ object ChatController { && pe.brokerErr.transportErr is SMPTransportError.Version -> { AlertManager.shared.showAlertMsg( generalGetString(MR.strings.private_routing_error), - String.format(generalGetString(MR.strings.proxy_error_broker_version_desc), serverHostname(srvAddr)) + String.format(generalGetString(MR.strings.smp_proxy_error_broker_version), serverHostname(srvAddr)) + ) + true + } + else -> false + } + } + + private fun proxyDestinationErrorAlert(pe: ProxyError, proxyServer: String, relayServer: String): Boolean { + return when { + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.TIMEOUT -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(proxyServer), serverHostname(relayServer)) + ) + true + } + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.NETWORK -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(proxyServer), serverHostname(relayServer)) + ) + true + } + pe is ProxyError.NO_SESSION -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + String.format(generalGetString(MR.strings.proxy_destination_error_failed_to_connect), serverHostname(proxyServer), serverHostname(relayServer)) + ) + true + } + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.HOST -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + String.format(generalGetString(MR.strings.proxy_destination_error_broker_host), serverHostname(relayServer), serverHostname(proxyServer)) + ) + true + } + pe is ProxyError.BROKER + && pe.brokerErr is BrokerErrorType.TRANSPORT + && pe.brokerErr.transportErr is SMPTransportError.Version -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.private_routing_error), + String.format(generalGetString(MR.strings.proxy_destination_error_broker_version), serverHostname(relayServer), serverHostname(proxyServer)) ) true } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index d8fdfac45b..87c3cecd27 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -115,8 +115,13 @@ Server address is incompatible with network settings: %1$s. Server version is incompatible with your app: %1$s. Private routing error + Error connecting to forwarding server %1$s. Please try later. + Forwarding server address is incompatible with network settings: %1$s. + Forwarding server version is incompatible with network settings: %1$s. + Forwarding server %1$s failed to connect to destination server %2$s. Please try later. + Destination server address of %1$s is incompatible with forwarding server %2$s settings. + Destination server version of %1$s is incompatible with forwarding server %2$s. Please try later. - Server version is incompatible with network settings: %1$s. Error sending message Error creating message Error loading details From f10a0ce58e663c275a857933f807359ddbe63d6c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 22 Jul 2024 15:48:57 +0100 Subject: [PATCH 46/57] core: receive only one notification message on push notification (#4504) * core: receive only one notification message on push notification * ios: receive only one notification message on push * update stats to include notification server stats * Codable * update simplexmq --- .../Views/ChatList/ServersSummaryView.swift | 8 +- .../ios/SimpleX NSE/NotificationService.swift | 24 +-- apps/ios/SimpleXChat/APITypes.swift | 15 +- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 6 +- src/Simplex/Chat/Controller.hs | 2 +- src/Simplex/Chat/Stats.hs | 195 ++++++++++++------ 8 files changed, 164 insertions(+), 90 deletions(-) diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 30e32ba9d0..3639dcd5e4 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -589,7 +589,7 @@ struct DetailedSMPStatsView: View { infoRow(Text(verbatim: "NO_MSG errors"), numOrDash(stats._ackNoMsgErrs)).padding(.leading, 24) infoRow("other errors", numOrDash(stats._ackOtherErrs)).padding(.leading, 24) } - Section { + Section("Connections") { infoRow("Created", numOrDash(stats._connCreated)) infoRow("Secured", numOrDash(stats._connCreated)) infoRow("Completed", numOrDash(stats._connCompleted)) @@ -598,8 +598,12 @@ struct DetailedSMPStatsView: View { infoRowTwoValues("Subscribed", "attempts", stats._connSubscribed, stats._connSubAttempts) infoRow("Subscriptions ignored", numOrDash(stats._connSubIgnored)) infoRow("Subscription errors", numOrDash(stats._connSubErrs)) + } + Section { + infoRowTwoValues("Enabled", "attempts", stats._ntfKey, stats._ntfKeyAttempts) + infoRowTwoValues("Disabled", "attempts", stats._ntfKeyDeleted, stats._ntfKeyDeleteAttempts) } header: { - Text("Connections") + Text("Connection notifications") } footer: { Text("Starting from \(localTimestamp(statsStartedAt)).") } diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 3850664429..30c74fa120 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -119,7 +119,7 @@ class NotificationService: UNNotificationServiceExtension { var threadId: UUID? = NSEThreads.shared.newThread() var notificationInfo: NtfMessages? var receiveEntityId: String? - var expectedMessages: Set = [] + var expectedMessage: String? // return true if the message is taken - it prevents sending it to another NotificationService instance for processing var shouldProcessNtf = false var appSubscriber: AppSubscriber? @@ -191,7 +191,7 @@ class NotificationService: UNNotificationServiceExtension { let dbStatus = startChat() if case .ok = dbStatus, let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) { - logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count))") + logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessage_ == nil ? 0 : 1))") if let connEntity = ntfInfo.connEntity_ { setBestAttemptNtf( ntfInfo.ntfsEnabled @@ -201,7 +201,7 @@ class NotificationService: UNNotificationServiceExtension { if let id = connEntity.id, ntfInfo.msgTs != nil { notificationInfo = ntfInfo receiveEntityId = id - expectedMessages = Set(ntfInfo.ntfMessages.map { $0.msgId }) + expectedMessage = ntfInfo.ntfMessage_.flatMap { $0.msgId } shouldProcessNtf = true return } @@ -224,12 +224,10 @@ class NotificationService: UNNotificationServiceExtension { self.setBestAttemptNtf(.empty) } if case let .msgInfo(info) = ntf { - let found = expectedMessages.remove(info.msgId) - if found != nil { - logger.debug("NotificationService processNtf: msgInfo, last: \(self.expectedMessages.isEmpty)") - if expectedMessages.isEmpty { - self.deliverBestAttemptNtf() - } + if info.msgId == expectedMessage { + expectedMessage = nil + logger.debug("NotificationService processNtf: msgInfo") + self.deliverBestAttemptNtf() return true } else if info.msgTs > msgTs { logger.debug("NotificationService processNtf: unexpected msgInfo, let other instance to process it, stopping this one") @@ -677,9 +675,9 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? { return nil } let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo)) - if case let .ntfMessages(user, connEntity_, msgTs, ntfMessages) = r, let user = user { - logger.debug("apiGetNtfMessage response ntfMessages: \(ntfMessages.count)") - return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessages: ntfMessages) + if case let .ntfMessages(user, connEntity_, msgTs, ntfMessage_) = r, let user = user { + logger.debug("apiGetNtfMessage response ntfMessages: \(ntfMessage_ == nil ? 0 : 1)") + return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessage_: ntfMessage_) } else if case let .chatCmdError(_, error) = r { logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))") } else { @@ -726,7 +724,7 @@ struct NtfMessages { var user: User var connEntity_: ConnectionEntity? var msgTs: Date? - var ntfMessages: [NtfMsgInfo] + var ntfMessage_: NtfMsgInfo? var ntfsEnabled: Bool { user.showNotifications && (connEntity_?.ntfsEnabled ?? false) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 5163b02c39..443b2d0502 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -662,7 +662,7 @@ public enum ChatResponse: Decodable, Error { case callInvitations(callInvitations: [RcvCallInvitation]) case ntfTokenStatus(status: NtfTknStatus) case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) - case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo]) + case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessage_: NtfMsgInfo?) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgInfo) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) case contactDisabled(user: UserRef, contact: Contact) @@ -2377,6 +2377,10 @@ public struct AgentSMPServerStatsData: Codable { public var _connSubAttempts: Int public var _connSubIgnored: Int public var _connSubErrs: Int + public var _ntfKey: Int + public var _ntfKeyAttempts: Int + public var _ntfKeyDeleted: Int + public var _ntfKeyDeleteAttempts: Int } public struct XFTPServersSummary: Codable { @@ -2416,3 +2420,12 @@ public struct AgentXFTPServerStatsData: Codable { public var _deleteAttempts: Int public var _deleteErrs: Int } + +public struct AgentNtfServerStatsData: Codable { + public var _ntfCreated: Int + public var _ntfCreateAttempts: Int + public var _ntfChecked: Int + public var _ntfCheckAttempts: Int + public var _ntfDeleted: Int + public var _ntfDelAttempts: Int +} diff --git a/cabal.project b/cabal.project index 663bff2624..99ef5ddaeb 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 0be7ebed5c343e9f55a93224121f8a6df9333522 + tag: c605156302b78a8411a1a083c52e7d5506f11f9c source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 9a22ea9fe4..89984b4b74 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."0be7ebed5c343e9f55a93224121f8a6df9333522" = "1bx6p06nwrqsc5wc8hp86k8198vn36cs5zva2rb9zhixnjcx79ap"; + "https://github.com/simplex-chat/simplexmq.git"."c605156302b78a8411a1a083c52e7d5506f11f9c" = "064idjqf5cz2l2079gphj5gpc7pb7kc1kgg0fv5jx1ni5c4a3yzb"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 2983afa29a..cee3a78414 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1311,14 +1311,14 @@ processChatCommand' vr = \case APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) >> ok_ APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) >> ok_ APIGetNtfMessage nonce encNtfInfo -> withUser $ \_ -> do - (NotificationInfo {ntfConnId, ntfMsgMeta}, msgs) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo + (NotificationInfo {ntfConnId, ntfMsgMeta}, msg) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo let msgTs' = systemToUTCTime . (\SMP.NMsgMeta {msgTs} -> msgTs) <$> ntfMsgMeta agentConnId = AgentConnId ntfConnId user_ <- withStore' (`getUserByAConnId` agentConnId) connEntity_ <- pure user_ $>>= \user -> withStore (\db -> Just <$> getConnectionEntity db vr user agentConnId) `catchChatError` (\e -> toView (CRChatError (Just user) e) $> Nothing) - pure CRNtfMessages {user_, connEntity_, msgTs = msgTs', ntfMessages = map ntfMsgInfo msgs} + pure CRNtfMessages {user_, connEntity_, msgTs = msgTs', ntfMessage_ = ntfMsgInfo <$> msg} APIGetUserProtoServers userId (AProtocolType p) -> withUserId userId $ \user -> withServerProtocol p $ do cfg@ChatConfig {defaultServers} <- asks config servers <- withStore' (`getProtocolServers` user) @@ -2267,7 +2267,7 @@ processChatCommand' vr = \case cfg <- asks config (users, smpServers, xftpServers) <- withStore' $ \db -> (,,) <$> getUsers db <*> getServers db cfg user SPSMP <*> getServers db cfg user SPXFTP - let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers + let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers _defaultNtfServers pure $ CRAgentServersSummary user presentedServersSummary where getServers :: (ProtocolTypeI p, UserProtocol p) => DB.Connection -> ChatConfig -> User -> SProtocolType p -> IO (NonEmpty (ProtocolServer p)) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index fedfc463a3..922d3bef39 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -735,7 +735,7 @@ data ChatResponse | CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete | CRNtfTokenStatus {status :: NtfTknStatus} | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer} - | CRNtfMessages {user_ :: Maybe User, connEntity_ :: Maybe ConnectionEntity, msgTs :: Maybe UTCTime, ntfMessages :: [NtfMsgInfo]} + | CRNtfMessages {user_ :: Maybe User, connEntity_ :: Maybe ConnectionEntity, msgTs :: Maybe UTCTime, ntfMessage_ :: Maybe NtfMsgInfo} | CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgInfo} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} | CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} diff --git a/src/Simplex/Chat/Stats.hs b/src/Simplex/Chat/Stats.hs index e1d0372080..6dd5c79ab1 100644 --- a/src/Simplex/Chat/Stats.hs +++ b/src/Simplex/Chat/Stats.hs @@ -6,6 +6,7 @@ module Simplex.Chat.Stats where import qualified Data.Aeson.TH as J +import Data.List (partition) import Data.List.NonEmpty (NonEmpty) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M @@ -22,8 +23,10 @@ data PresentedServersSummary = PresentedServersSummary { statsStartedAt :: UTCTime, allUsersSMP :: SMPServersSummary, allUsersXFTP :: XFTPServersSummary, + allUsersNtf :: NtfServersSummary, currentUserSMP :: SMPServersSummary, - currentUserXFTP :: XFTPServersSummary + currentUserXFTP :: XFTPServersSummary, + currentUserNtf :: NtfServersSummary } deriving (Show) @@ -102,52 +105,86 @@ data XFTPServerSummary = XFTPServerSummary } deriving (Show) +data NtfServersSummary = NtfServersSummary + { ntfTotals :: NtfTotals, + currentlyUsedNtfServers :: [NtfServerSummary], + previouslyUsedNtfServers :: [NtfServerSummary] + } + deriving (Show) + +data NtfTotals = NtfTotals + { sessions :: ServerSessions, + stats :: AgentNtfServerStatsData + } + deriving (Show) + +data NtfServerSummary = NtfServerSummary + { ntfServer :: NtfServer, + known :: Maybe Bool, + sessions :: Maybe ServerSessions, + stats :: Maybe AgentNtfServerStatsData + } + deriving (Show) + -- Maps AgentServersSummary to PresentedServersSummary: -- - currentUserServers is for currentUser; -- - users are passed to exclude hidden users from totalServersSummary; -- - if currentUser is hidden, it should be accounted in totalServersSummary; -- - known is set only in user level summaries based on passed userSMPSrvs and userXFTPSrvs -toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> NonEmpty SMPServer -> NonEmpty XFTPServer -> PresentedServersSummary -toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrvs = do +toPresentedServersSummary :: AgentServersSummary -> [User] -> User -> NonEmpty SMPServer -> NonEmpty XFTPServer -> [NtfServer] -> PresentedServersSummary +toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrvs userNtfSrvs = do let (userSMPSrvsSumms, allSMPSrvsSumms) = accSMPSrvsSummaries - (userSMPTotals, allSMPTotals) = (accSMPTotals userSMPSrvsSumms, accSMPTotals allSMPSrvsSumms) (userSMPCurr, userSMPPrev, userSMPProx) = smpSummsIntoCategories userSMPSrvsSumms (allSMPCurr, allSMPPrev, allSMPProx) = smpSummsIntoCategories allSMPSrvsSumms - (userXFTPSrvsSumms, allXFTPSrvsSumms) = accXFTPSrvsSummaries - (userXFTPTotals, allXFTPTotals) = (accXFTPTotals userXFTPSrvsSumms, accXFTPTotals allXFTPSrvsSumms) + let (userXFTPSrvsSumms, allXFTPSrvsSumms) = accXFTPSrvsSummaries (userXFTPCurr, userXFTPPrev) = xftpSummsIntoCategories userXFTPSrvsSumms (allXFTPCurr, allXFTPPrev) = xftpSummsIntoCategories allXFTPSrvsSumms + let (userNtfSrvsSumms, allNtfSrvsSumms) = accNtfSrvsSummaries + (userNtfCurr, userNtfPrev) = ntfSummsIntoCategories userNtfSrvsSumms + (allNtfCurr, allNtfPrev) = ntfSummsIntoCategories allNtfSrvsSumms PresentedServersSummary { statsStartedAt, allUsersSMP = SMPServersSummary - { smpTotals = allSMPTotals, + { smpTotals = accSMPTotals allSMPSrvsSumms, currentlyUsedSMPServers = allSMPCurr, previouslyUsedSMPServers = allSMPPrev, onlyProxiedSMPServers = allSMPProx }, allUsersXFTP = XFTPServersSummary - { xftpTotals = allXFTPTotals, + { xftpTotals = accXFTPTotals allXFTPSrvsSumms, currentlyUsedXFTPServers = allXFTPCurr, previouslyUsedXFTPServers = allXFTPPrev }, + allUsersNtf = + NtfServersSummary + { ntfTotals = accNtfTotals allNtfSrvsSumms, + currentlyUsedNtfServers = allNtfCurr, + previouslyUsedNtfServers = allNtfPrev + }, currentUserSMP = SMPServersSummary - { smpTotals = userSMPTotals, + { smpTotals = accSMPTotals userSMPSrvsSumms, currentlyUsedSMPServers = userSMPCurr, previouslyUsedSMPServers = userSMPPrev, onlyProxiedSMPServers = userSMPProx }, currentUserXFTP = XFTPServersSummary - { xftpTotals = userXFTPTotals, + { xftpTotals = accXFTPTotals userXFTPSrvsSumms, currentlyUsedXFTPServers = userXFTPCurr, previouslyUsedXFTPServers = userXFTPPrev + }, + currentUserNtf = + NtfServersSummary + { ntfTotals = accNtfTotals userNtfSrvsSumms, + currentlyUsedNtfServers = userNtfCurr, + previouslyUsedNtfServers = userNtfPrev } } where - AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress} = agentSummary + AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress, ntfServersSessions, ntfServersStats} = agentSummary countUserInAll auId = countUserInAllStats (AgentUserId auId) currentUser users accSMPTotals :: Map SMPServer SMPServerSummary -> SMPTotals accSMPTotals = M.foldr' addTotals initialTotals @@ -168,10 +205,19 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv { sessions = maybe accSess (accSess `addServerSessions`) sessions, stats = maybe accStats (accStats `addXFTPStatsData`) stats } - smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary]) - smpSummsIntoCategories = M.foldr' partitionSummary ([], [], []) + accNtfTotals :: Map NtfServer NtfServerSummary -> NtfTotals + accNtfTotals = M.foldr' addTotals initialTotals where - partitionSummary srvSumm (curr, prev, prox) + initialTotals = NtfTotals {sessions = ServerSessions 0 0 0, stats = newAgentNtfServerStatsData} + addTotals NtfServerSummary {sessions, stats} NtfTotals {sessions = accSess, stats = accStats} = + NtfTotals + { sessions = maybe accSess (accSess `addServerSessions`) sessions, + stats = maybe accStats (accStats `addNtfStatsData`) stats + } + smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary]) + smpSummsIntoCategories = M.foldr' addSummary ([], [], []) + where + addSummary srvSumm (curr, prev, prox) | isCurrentlyUsed srvSumm = (srvSumm : curr, prev, prox) | isPreviouslyUsed srvSumm = (curr, srvSumm : prev, prox) | otherwise = (curr, prev, srvSumm : prox) @@ -183,42 +229,29 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv Just AgentSMPServerStatsData {_sentDirect, _sentProxied, _sentDirectAttempts, _sentProxiedAttempts, _recvMsgs, _connCreated, _connSecured, _connSubscribed, _connSubAttempts} -> _sentDirect > 0 || _sentProxied > 0 || _sentDirectAttempts > 0 || _sentProxiedAttempts > 0 || _recvMsgs > 0 || _connCreated > 0 || _connSecured > 0 || _connSubscribed > 0 || _connSubAttempts > 0 xftpSummsIntoCategories :: Map XFTPServer XFTPServerSummary -> ([XFTPServerSummary], [XFTPServerSummary]) - xftpSummsIntoCategories = M.foldr' partitionSummary ([], []) + xftpSummsIntoCategories = partition isCurrentlyUsed . M.elems where - partitionSummary srvSumm (curr, prev) - | isCurrentlyUsed srvSumm = (srvSumm : curr, prev) - | otherwise = (curr, srvSumm : prev) isCurrentlyUsed XFTPServerSummary {sessions, rcvInProgress, sndInProgress, delInProgress} = isJust sessions || rcvInProgress || sndInProgress || delInProgress + ntfSummsIntoCategories :: Map NtfServer NtfServerSummary -> ([NtfServerSummary], [NtfServerSummary]) + ntfSummsIntoCategories = partition isCurrentlyUsed . M.elems + where + isCurrentlyUsed NtfServerSummary {sessions} = isJust sessions accSMPSrvsSummaries :: (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) accSMPSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs2 smpServersStats where summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) smpServersSessions summs2 = M.foldrWithKey' (addServerData addSubs) summs1 smpServersSubs - addServerData :: - (a -> SMPServerSummary -> SMPServerSummary) -> - (UserId, SMPServer) -> - a -> - (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) -> - (Map SMPServer SMPServerSummary, Map SMPServer SMPServerSummary) - addServerData addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms') - where - userSumms' - | userId == aUserId currentUser = alterSumms newUserSummary userSumms - | otherwise = userSumms - allUsersSumms' - | countUserInAll userId = alterSumms newSummary allUsersSumms - | otherwise = allUsersSumms - alterSumms n = M.alter (Just . addData d . fromMaybe n) srv - newUserSummary = (newSummary :: SMPServerSummary) {known = Just $ srv `elem` userSMPSrvs} - newSummary = - SMPServerSummary - { smpServer = srv, - known = Nothing, - sessions = Nothing, - subs = Nothing, - stats = Nothing - } + addServerData = addServerData_ newSummary newUserSummary + newUserSummary srv = (newSummary srv :: SMPServerSummary) {known = Just $ srv `elem` userSMPSrvs} + newSummary srv = + SMPServerSummary + { smpServer = srv, + known = Nothing, + sessions = Nothing, + subs = Nothing, + stats = Nothing + } addSessions :: ServerSessions -> SMPServerSummary -> SMPServerSummary addSessions s summ@SMPServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions} addSubs :: SMPServerSubs -> SMPServerSummary -> SMPServerSummary @@ -229,36 +262,56 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv accXFTPSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs1 xftpServersStats where summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) xftpServersSessions - addServerData :: - (a -> XFTPServerSummary -> XFTPServerSummary) -> - (UserId, XFTPServer) -> - a -> - (Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary) -> - (Map XFTPServer XFTPServerSummary, Map XFTPServer XFTPServerSummary) - addServerData addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms') - where - userSumms' - | userId == aUserId currentUser = alterSumms newUserSummary userSumms - | otherwise = userSumms - allUsersSumms' - | countUserInAll userId = alterSumms newSummary allUsersSumms - | otherwise = allUsersSumms - alterSumms n = M.alter (Just . addData d . fromMaybe n) srv - newUserSummary = (newSummary :: XFTPServerSummary) {known = Just $ srv `elem` userXFTPSrvs} - newSummary = - XFTPServerSummary - { xftpServer = srv, - known = Nothing, - sessions = Nothing, - stats = Nothing, - rcvInProgress = srv `elem` xftpRcvInProgress, - sndInProgress = srv `elem` xftpSndInProgress, - delInProgress = srv `elem` xftpDelInProgress - } + addServerData = addServerData_ newSummary newUserSummary addSessions :: ServerSessions -> XFTPServerSummary -> XFTPServerSummary addSessions s summ@XFTPServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions} addStats :: AgentXFTPServerStatsData -> XFTPServerSummary -> XFTPServerSummary addStats s summ@XFTPServerSummary {stats} = summ {stats = Just $ maybe s (s `addXFTPStatsData`) stats} + newUserSummary srv = (newSummary srv :: XFTPServerSummary) {known = Just $ srv `elem` userXFTPSrvs} + newSummary srv = + XFTPServerSummary + { xftpServer = srv, + known = Nothing, + sessions = Nothing, + stats = Nothing, + rcvInProgress = srv `elem` xftpRcvInProgress, + sndInProgress = srv `elem` xftpSndInProgress, + delInProgress = srv `elem` xftpDelInProgress + } + accNtfSrvsSummaries :: (Map NtfServer NtfServerSummary, Map NtfServer NtfServerSummary) + accNtfSrvsSummaries = M.foldrWithKey' (addServerData addStats) summs1 ntfServersStats + where + summs1 = M.foldrWithKey' (addServerData addSessions) (M.empty, M.empty) ntfServersSessions + addServerData = addServerData_ newSummary newUserSummary + addSessions :: ServerSessions -> NtfServerSummary -> NtfServerSummary + addSessions s summ@NtfServerSummary {sessions} = summ {sessions = Just $ maybe s (s `addServerSessions`) sessions} + addStats :: AgentNtfServerStatsData -> NtfServerSummary -> NtfServerSummary + addStats s summ@NtfServerSummary {stats} = summ {stats = Just $ maybe s (s `addNtfStatsData`) stats} + newUserSummary srv = (newSummary srv :: NtfServerSummary) {known = Just $ srv `elem` userNtfSrvs} + newSummary srv = + NtfServerSummary + { ntfServer = srv, + known = Nothing, + sessions = Nothing, + stats = Nothing + } + addServerData_ :: + (ProtocolServer p -> s) -> + (ProtocolServer p -> s) -> + (a -> s -> s) -> + (UserId, ProtocolServer p) -> + a -> + (Map (ProtocolServer p) s, Map (ProtocolServer p) s) -> + (Map (ProtocolServer p) s, Map (ProtocolServer p) s) + addServerData_ newSummary newUserSummary addData (userId, srv) d (userSumms, allUsersSumms) = (userSumms', allUsersSumms') + where + userSumms' + | userId == aUserId currentUser = alterSumms (newUserSummary srv) userSumms + | otherwise = userSumms + allUsersSumms' + | countUserInAll userId = alterSumms (newSummary srv) allUsersSumms + | otherwise = allUsersSumms + alterSumms n = M.alter (Just . addData d . fromMaybe n) srv addServerSessions :: ServerSessions -> ServerSessions -> ServerSessions addServerSessions ss1 ss2 = ServerSessions @@ -292,4 +345,10 @@ $(J.deriveJSON defaultJSON ''XFTPServerSummary) $(J.deriveJSON defaultJSON ''XFTPServersSummary) +$(J.deriveJSON defaultJSON ''NtfTotals) + +$(J.deriveJSON defaultJSON ''NtfServerSummary) + +$(J.deriveJSON defaultJSON ''NtfServersSummary) + $(J.deriveJSON defaultJSON ''PresentedServersSummary) From 4a9b54fbafdeeba9777a236cf2d7ad5b598e67fc Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:06:53 +0400 Subject: [PATCH 47/57] core: get subs total api (#4500) --- src/Simplex/Chat.hs | 6 ++++++ src/Simplex/Chat/Controller.hs | 4 +++- src/Simplex/Chat/View.hs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index cee3a78414..bcf6856c4f 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2262,6 +2262,11 @@ processChatCommand' vr = \case CLUserContact ucId -> "UserContact " <> show ucId CLFile fId -> "File " <> show fId DebugEvent event -> toView event >> ok_ + GetAgentSubsTotal userId -> withUserId userId $ \user -> do + users <- withStore' $ \db -> getUsers db + let userIds = map aUserId $ filter (\u -> isNothing (viewPwdHash u) || aUserId u == aUserId user) users + (subsTotal, hasSession) <- lift $ withAgent' $ \a -> getAgentSubsTotal a userIds + pure $ CRAgentSubsTotal user subsTotal hasSession GetAgentServersSummary userId -> withUserId userId $ \user -> do agentServersSummary <- lift $ withAgent' getAgentServersSummary cfg <- asks config @@ -7672,6 +7677,7 @@ chatCommandP = ("/version" <|> "/v") $> ShowVersion, "/debug locks" $> DebugLocks, "/debug event " *> (DebugEvent <$> jsonP), + "/get subs total " *> (GetAgentSubsTotal <$> A.decimal), "/get servers summary " *> (GetAgentServersSummary <$> A.decimal), "/reset servers stats" $> ResetAgentServersStats, "/get subs" $> GetAgentSubs, diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 922d3bef39..66a4aca95d 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -69,7 +69,7 @@ import Simplex.Chat.Types.UITheme import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) -import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, ServerQueueInfo, UserNetworkInfo) +import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, ServerQueueInfo, SMPServerSubs, UserNetworkInfo) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol @@ -505,6 +505,7 @@ data ChatCommand | ShowVersion | DebugLocks | DebugEvent ChatResponse + | GetAgentSubsTotal UserId | GetAgentServersSummary UserId | ResetAgentServersStats | GetAgentSubs @@ -756,6 +757,7 @@ data ChatResponse | CRSQLResult {rows :: [Text]} | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRDebugLocks {chatLockName :: Maybe String, chatEntityLocks :: Map String String, agentLocks :: AgentLocks} + | CRAgentSubsTotal {user :: User, subsTotal :: SMPServerSubs, hasSession :: Bool} | CRAgentServersSummary {user :: User, serversSummary :: PresentedServersSummary} | CRAgentWorkersDetails {agentWorkersDetails :: AgentWorkersDetails} | CRAgentWorkersSummary {agentWorkersSummary :: AgentWorkersSummary} diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index a4c82b3b3f..8d5c782866 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -366,6 +366,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe "chat entity locks: " <> viewJSON chatEntityLocks, "agent locks: " <> viewJSON agentLocks ] + CRAgentSubsTotal u subsTotal _ -> ttyUser u ["total subscriptions: " <> sShow subsTotal] CRAgentServersSummary u serversSummary -> ttyUser u ["agent servers summary: " <> viewJSON serversSummary] CRAgentSubs {activeSubs, pendingSubs, removedSubs} -> [plain $ "Subscriptions: active = " <> show (sum activeSubs) <> ", pending = " <> show (sum pendingSubs) <> ", removed = " <> show (sum $ M.map length removedSubs)] From 2689d1e27bc02370f8ad1d1a4bcac48e0726f981 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:32:54 +0400 Subject: [PATCH 48/57] ios: optimize subscription indicator (#4501) --- apps/ios/Shared/Model/SimpleXAPI.swift | 8 +++ .../Shared/Views/ChatList/ChatListView.swift | 20 +++--- .../Views/ChatList/ServersSummaryView.swift | 64 +++++++++++++------ apps/ios/SimpleXChat/APITypes.swift | 8 +++ 4 files changed, 70 insertions(+), 30 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index 65476fd93f..3ffbe9a00d 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -1404,6 +1404,14 @@ func apiGetVersion() throws -> CoreVersionInfo { throw r } +func getAgentSubsTotal() throws -> (SMPServerSubs, Bool) { + let userId = try currentUserId("getAgentSubsTotal") + let r = chatSendCmdSync(.getAgentSubsTotal(userId: userId), log: false) + if case let .agentSubsTotal(_, subsTotal, hasSession) = r { return (subsTotal, hasSession) } + logger.error("getAgentSubsTotal error: \(String(describing: r))") + throw r +} + func getAgentServersSummary() throws -> PresentedServersSummary { let userId = try currentUserId("getAgentServersSummary") let r = chatSendCmdSync(.getAgentServersSummary(userId: userId), log: false) diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 16f87f0c13..69dcf7501a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -266,9 +266,9 @@ struct ChatListView: View { } struct SubsStatusIndicator: View { - @State private var serversSummary: PresentedServersSummary? + @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs + @State private var hasSess: Bool = false @State private var timer: Timer? = nil - @State private var timerCounter = 0 @State private var showServersSummary = false @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -277,12 +277,10 @@ struct SubsStatusIndicator: View { Button { showServersSummary = true } label: { - let subs = serversSummary?.allUsersSMP.smpTotals.subs ?? SMPServerSubs.newSMPServerSubs - let sess = serversSummary?.allUsersSMP.smpTotals.sessions ?? ServerSessions.newServerSessions HStack(spacing: 4) { - SubscriptionStatusIndicatorView(subs: subs, sess: sess) + SubscriptionStatusIndicatorView(subs: subs, hasSess: hasSess) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, sess: sess) + SubscriptionStatusPercentageView(subs: subs, hasSess: hasSess) } } } @@ -293,14 +291,14 @@ struct SubsStatusIndicator: View { stopTimer() } .sheet(isPresented: $showServersSummary) { - ServersSummaryView(serversSummary: $serversSummary) + ServersSummaryView() } } private func startTimer() { timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in if AppChatState.shared.value == .active { - getServersSummary() + getSubsTotal() } } } @@ -310,11 +308,11 @@ struct SubsStatusIndicator: View { timer = nil } - private func getServersSummary() { + private func getSubsTotal() { do { - serversSummary = try getAgentServersSummary() + (subs, hasSess) = try getAgentSubsTotal() } catch let error { - logger.error("getAgentServersSummary error: \(responseError(error))") + logger.error("getSubsTotal error: \(responseError(error))") } } } diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 3639dcd5e4..2e0dd9d9e4 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -12,11 +12,12 @@ import SimpleXChat struct ServersSummaryView: View { @EnvironmentObject var m: ChatModel @EnvironmentObject var theme: AppTheme - @Binding var serversSummary: PresentedServersSummary? + @State private var serversSummary: PresentedServersSummary? = nil @State private var selectedUserCategory: PresentedUserCategory = .allUsers @State private var selectedServerType: PresentedServerType = .smp @State private var selectedSMPServer: String? = nil @State private var selectedXFTPServer: String? = nil + @State private var timer: Timer? = nil @State private var alert: SomeAlert? @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -47,10 +48,36 @@ struct ServersSummaryView: View { if m.users.filter({ u in u.user.activeUser || !u.user.hidden }).count == 1 { selectedUserCategory = .currentUser } + getServersSummary() + startTimer() + } + .onDisappear { + stopTimer() } .alert(item: $alert) { $0.alert } } + private func startTimer() { + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if AppChatState.shared.value == .active { + getServersSummary() + } + } + } + + private func getServersSummary() { + do { + serversSummary = try getAgentServersSummary() + } catch let error { + logger.error("getAgentServersSummary error: \(responseError(error))") + } + } + + private func stopTimer() { + timer?.invalidate() + timer = nil + } + private func shareButton() -> some View { Button { if let serversSummary = serversSummary { @@ -180,9 +207,9 @@ struct ServersSummaryView: View { } header: { HStack { Text("Message reception") - SubscriptionStatusIndicatorView(subs: totals.subs, sess: totals.sessions) + SubscriptionStatusIndicatorView(subs: totals.subs, hasSess: totals.sessions.hasSess) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: totals.subs, sess: totals.sessions) + SubscriptionStatusPercentageView(subs: totals.subs, hasSess: totals.sessions.hasSess) } } } @@ -260,9 +287,9 @@ struct ServersSummaryView: View { if let subs = srvSumm.subs { Spacer() if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, sess: srvSumm.sessionsOrNew) + SubscriptionStatusPercentageView(subs: subs, hasSess: srvSumm.sessionsOrNew.hasSess) } - SubscriptionStatusIndicatorView(subs: subs, sess: srvSumm.sessionsOrNew) + SubscriptionStatusIndicatorView(subs: subs, hasSess: srvSumm.sessionsOrNew.hasSess) } else if let sess = srvSumm.sessions { Spacer() Image(systemName: "arrow.up.circle") @@ -356,6 +383,7 @@ struct ServersSummaryView: View { Task { do { try await resetAgentServersStats() + getServersSummary() } catch let error { alert = SomeAlert( alert: mkAlert( @@ -380,11 +408,11 @@ struct ServersSummaryView: View { struct SubscriptionStatusIndicatorView: View { @EnvironmentObject var m: ChatModel var subs: SMPServerSubs - var sess: ServerSessions + var hasSess: Bool var body: some View { let onionHosts = networkUseOnionHostsGroupDefault.get() - let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, sess) + let (color, variableValue, opacity, _) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) if #available(iOS 16.0, *) { Image(systemName: "dot.radiowaves.up.forward", variableValue: variableValue) .foregroundColor(color) @@ -398,18 +426,18 @@ struct SubscriptionStatusIndicatorView: View { struct SubscriptionStatusPercentageView: View { @EnvironmentObject var m: ChatModel var subs: SMPServerSubs - var sess: ServerSessions + var hasSess: Bool var body: some View { let onionHosts = networkUseOnionHostsGroupDefault.get() - let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, sess) + let (_, _, _, statusPercent) = subscriptionStatusColorAndPercentage(m.networkInfo.online, onionHosts, subs, hasSess) Text(verbatim: "\(Int(floor(statusPercent * 100)))%") .foregroundColor(.secondary) .font(.caption) } } -func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ sess: ServerSessions) -> (Color, Double, Double, Double) { +func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHosts, _ subs: SMPServerSubs, _ hasSess: Bool) -> (Color, Double, Double, Double) { func roundedToQuarter(_ n: Double) -> Double { n >= 1 ? 1 : n <= 0 ? 0 @@ -424,12 +452,12 @@ func subscriptionStatusColorAndPercentage(_ online: Bool, _ onionHosts: OnionHos ? ( subs.ssActive == 0 ? ( - sess.ssConnected == 0 ? noConnColorAndPercent : (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + hasSess ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) : noConnColorAndPercent ) : ( // ssActive > 0 - sess.ssConnected == 0 - ? (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error - : (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + hasSess + ? (activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + : (.orange, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) // This would mean implementation error ) ) : noConnColorAndPercent @@ -482,9 +510,9 @@ struct SMPServerSummaryView: View { } header: { HStack { Text("Message reception") - SubscriptionStatusIndicatorView(subs: subs, sess: summary.sessionsOrNew) + SubscriptionStatusIndicatorView(subs: subs, hasSess: summary.sessionsOrNew.hasSess) if showSubscriptionPercentage { - SubscriptionStatusPercentageView(subs: subs, sess: summary.sessionsOrNew) + SubscriptionStatusPercentageView(subs: subs, hasSess: summary.sessionsOrNew.hasSess) } } } @@ -716,7 +744,5 @@ struct DetailedXFTPStatsView: View { } #Preview { - ServersSummaryView( - serversSummary: Binding.constant(nil) - ) + ServersSummaryView() } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 443b2d0502..8ed3d63e53 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -146,6 +146,7 @@ public enum ChatCommand { case apiStandaloneFileInfo(url: String) // misc case showVersion + case getAgentSubsTotal(userId: Int64) case getAgentServersSummary(userId: Int64) case resetAgentServersStats case string(String) @@ -309,6 +310,7 @@ public enum ChatCommand { case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" case let .apiStandaloneFileInfo(link): return "/_download info \(link)" case .showVersion: return "/version" + case let .getAgentSubsTotal(userId): return "/get subs total \(userId)" case let .getAgentServersSummary(userId): return "/get servers summary \(userId)" case .resetAgentServersStats: return "/reset servers stats" case let .string(str): return str @@ -447,6 +449,7 @@ public enum ChatCommand { case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" case .showVersion: return "showVersion" + case .getAgentSubsTotal: return "getAgentSubsTotal" case .getAgentServersSummary: return "getAgentServersSummary" case .resetAgentServersStats: return "resetAgentServersStats" case .string: return "console command" @@ -678,6 +681,7 @@ public enum ChatResponse: Decodable, Error { // misc case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) case cmdOk(user: UserRef?) + case agentSubsTotal(user: UserRef, subsTotal: SMPServerSubs, hasSession: Bool) case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary) case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs) case chatCmdError(user_: UserRef?, chatError: ChatError) @@ -839,6 +843,7 @@ public enum ChatResponse: Decodable, Error { case .contactPQEnabled: return "contactPQEnabled" case .versionInfo: return "versionInfo" case .cmdOk: return "cmdOk" + case .agentSubsTotal: return "agentSubsTotal" case .agentServersSummary: return "agentServersSummary" case .agentSubsSummary: return "agentSubsSummary" case .chatCmdError: return "chatCmdError" @@ -1005,6 +1010,7 @@ public enum ChatResponse: Decodable, Error { case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))" case .cmdOk: return noDetails + case let .agentSubsTotal(u, subsTotal, hasSession): return withUser(u, "subsTotal: \(String(describing: subsTotal))\nhasSession: \(hasSession)") case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary)) case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary)) case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) @@ -2324,6 +2330,8 @@ public struct ServerSessions: Codable { ssErrors: 0, ssConnecting: 0 ) + + public var hasSess: Bool { ssConnected > 0 } } public struct SMPServerSubs: Codable { From 70a94d772ace87086ff50f4cc213a1afae93af64 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Mon, 22 Jul 2024 20:40:22 +0400 Subject: [PATCH 49/57] multiplatform: optimize subscription indicator (#4503) --- .../chat/simplex/common/model/SimpleXAPI.kt | 19 +++++ .../common/views/chatlist/ChatListView.kt | 22 +++--- .../views/chatlist/ServersSummaryView.kt | 78 +++++++++++-------- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 37baed40b2..71ea873ddd 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -423,6 +423,16 @@ object ChatController { fun hasChatCtrl() = ctrl != -1L && ctrl != null + suspend fun getAgentSubsTotal(rh: Long?): Pair? { + val userId = currentUserId("getAgentSubsTotal") + + val r = sendCmd(rh, CC.GetAgentSubsTotal(userId), log = false) + + if (r is CR.AgentSubsTotal) return r.subsTotal to r.hasSession + Log.e(TAG, "getAgentSubsTotal bad response: ${r.responseType} ${r.details}") + return null + } + suspend fun getAgentServersSummary(rh: Long?): PresentedServersSummary? { val userId = currentUserId("getAgentServersSummary") @@ -2815,6 +2825,7 @@ sealed class CC { // misc class ShowVersion(): CC() class ResetAgentServersStats(): CC() + class GetAgentSubsTotal(val userId: Long): CC() class GetAgentServersSummary(val userId: Long): CC() val cmdString: String get() = when (this) { @@ -2975,6 +2986,7 @@ sealed class CC { is ApiStandaloneFileInfo -> "/_download info $url" is ShowVersion -> "/version" is ResetAgentServersStats -> "/reset servers stats" + is GetAgentSubsTotal -> "/get subs total $userId" is GetAgentServersSummary -> "/get servers summary $userId" } @@ -3108,6 +3120,7 @@ sealed class CC { is ApiStandaloneFileInfo -> "apiStandaloneFileInfo" is ShowVersion -> "showVersion" is ResetAgentServersStats -> "resetAgentServersStats" + is GetAgentSubsTotal -> "getAgentSubsTotal" is GetAgentServersSummary -> "getAgentServersSummary" } @@ -3638,6 +3651,9 @@ data class ServerSessions( ssConnecting = 0 ) } + + val hasSess: Boolean + get() = ssConnected > 0 } @Serializable @@ -4743,6 +4759,7 @@ sealed class CR { @Serializable @SerialName("chatError") class ChatRespError(val user_: UserRef?, val chatError: ChatError): CR() @Serializable @SerialName("archiveImported") class ArchiveImported(val archiveErrors: List): CR() @Serializable @SerialName("appSettings") class AppSettingsR(val appSettings: AppSettings): CR() + @Serializable @SerialName("agentSubsTotal") class AgentSubsTotal(val user: UserRef, val subsTotal: SMPServerSubs, val hasSession: Boolean): CR() @Serializable @SerialName("agentServersSummary") class AgentServersSummary(val user: UserRef, val serversSummary: PresentedServersSummary): CR() // general @Serializable class Response(val type: String, val json: String): CR() @@ -4904,6 +4921,7 @@ sealed class CR { is ContactPQAllowed -> "contactPQAllowed" is ContactPQEnabled -> "contactPQEnabled" is VersionInfo -> "versionInfo" + is AgentSubsTotal -> "agentSubsTotal" is AgentServersSummary -> "agentServersSummary" is CmdOk -> "cmdOk" is ChatCmdError -> "chatCmdError" @@ -5084,6 +5102,7 @@ sealed class CR { is RemoteCtrlStopped -> "rcsState: $rcsState\nrcsStopReason: $rcStopReason" is ContactPQAllowed -> withUser(user, "contact: ${contact.id}\npqEncryption: $pqEncryption") is ContactPQEnabled -> withUser(user, "contact: ${contact.id}\npqEnabled: $pqEnabled") + is AgentSubsTotal -> withUser(user, "subsTotal: ${subsTotal}\nhasSession: $hasSession") is AgentServersSummary -> withUser(user, json.encodeToString(serversSummary)) is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" + "chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" + diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 5759877b07..43f0b7ef9b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -255,7 +255,6 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt fontWeight = FontWeight.SemiBold, ) SubscriptionStatusIndicator( - serversSummary = serversSummary, click = { ModalManager.start.closeModals() ModalManager.start.showModalCloseable( @@ -286,34 +285,33 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt } @Composable -fun SubscriptionStatusIndicator(serversSummary: MutableState, click: (() -> Unit)) { +fun SubscriptionStatusIndicator(click: (() -> Unit)) { var subs by remember { mutableStateOf(SMPServerSubs.newSMPServerSubs) } - var sess by remember { mutableStateOf(ServerSessions.newServerSessions) } + var hasSess by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() - suspend fun setServersSummary() { - serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) - - serversSummary.value?.let { - subs = it.allUsersSMP.smpTotals.subs - sess = it.allUsersSMP.smpTotals.sessions + suspend fun setSubsTotal() { + val r = chatModel.controller.getAgentSubsTotal(chatModel.remoteHostId()) + if (r != null) { + subs = r.first + hasSess = r.second } } LaunchedEffect(Unit) { - setServersSummary() + setSubsTotal() scope.launch { while (isActive) { delay(1.seconds) if ((appPlatform.isDesktop || chatModel.chatId.value == null) && !ModalManager.start.hasModalsOpen() && !ModalManager.fullscreen.hasModalsOpen() && isAppVisibleAndFocused()) { - setServersSummary() + setSubsTotal() } } } } SimpleButtonFrame(click = click) { - SubscriptionStatusIndicatorView(subs = subs, sess = sess) + SubscriptionStatusIndicatorView(subs = subs, hasSess = hasSess) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt index 74f4fcdcc3..8621c73ef3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ServersSummaryView.kt @@ -45,8 +45,7 @@ import chat.simplex.common.model.ServerProtocol import chat.simplex.common.model.ServerSessions import chat.simplex.common.model.XFTPServerSummary import chat.simplex.common.model.localTimestamp -import chat.simplex.common.platform.ColumnWithScrollBar -import chat.simplex.common.platform.appPlatform +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.ProtocolServersView @@ -54,12 +53,13 @@ import chat.simplex.common.views.usersettings.SettingsPreferenceItem import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import kotlinx.datetime.Instant import numOrDash import java.text.DecimalFormat import kotlin.math.floor import kotlin.math.roundToInt +import kotlin.time.Duration.Companion.seconds enum class SubscriptionColorType { ACTIVE, ACTIVE_SOCKS_PROXY, DISCONNECTED, ACTIVE_DISCONNECTED @@ -76,7 +76,7 @@ fun subscriptionStatusColorAndPercentage( online: Boolean, socksProxy: String?, subs: SMPServerSubs, - sess: ServerSessions + hasSess: Boolean ): SubscriptionStatus { fun roundedToQuarter(n: Float): Float = when { @@ -91,16 +91,16 @@ fun subscriptionStatusColorAndPercentage( return if (online && subs.total > 0) { if (subs.ssActive == 0) { - if (sess.ssConnected == 0) - noConnColorAndPercent - else + if (hasSess) SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + else + noConnColorAndPercent } else { // ssActive > 0 - if (sess.ssConnected == 0) + if (hasSess) + SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) + else // This would mean implementation error SubscriptionStatus(SubscriptionColorType.ACTIVE_DISCONNECTED, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) - else - SubscriptionStatus(activeColor, activeSubsRounded, subs.shareOfActive, subs.shareOfActive) } } else noConnColorAndPercent } @@ -116,9 +116,9 @@ private fun SubscriptionStatusIndicatorPercentage(percentageText: String) { } @Composable -fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, sess: ServerSessions, leadingPercentage: Boolean = false) { +fun SubscriptionStatusIndicatorView(subs: SMPServerSubs, hasSess: Boolean, leadingPercentage: Boolean = false) { val netCfg = rememberUpdatedState(chatModel.controller.getNetCfg()) - val statusColorAndPercentage = subscriptionStatusColorAndPercentage(chatModel.networkInfo.value.online, netCfg.value.socksProxy, subs, sess) + val statusColorAndPercentage = subscriptionStatusColorAndPercentage(chatModel.networkInfo.value.online, netCfg.value.socksProxy, subs, hasSess) val pref = remember { chatModel.controller.appPrefs.networkShowSubscriptionPercentage } val percentageText = "${(floor(statusColorAndPercentage.statusPercent * 100)).toInt()}%" @@ -193,7 +193,7 @@ private fun SMPServerView(srvSumm: SMPServerSummary, statsStartedAt: Instant, rh ) if (srvSumm.subs != null) { Spacer(Modifier.fillMaxWidth().weight(1f)) - SubscriptionStatusIndicatorView(subs = srvSumm.subs, sess = srvSumm.sessionsOrNew, leadingPercentage = true) + SubscriptionStatusIndicatorView(subs = srvSumm.subs, hasSess = srvSumm.sessionsOrNew.hasSess, leadingPercentage = true) } else if (srvSumm.sessions != null) { Spacer(Modifier.fillMaxWidth().weight(1f)) Icon(painterResource(MR.images.ic_arrow_upward), contentDescription = null, tint = SessIconColor(srvSumm.sessions)) @@ -334,7 +334,7 @@ private fun SMPSubscriptionsSection(totals: SMPTotals) { style = MaterialTheme.typography.body2, fontSize = 12.sp ) - SubscriptionStatusIndicatorView(totals.subs, totals.sessions) + SubscriptionStatusIndicatorView(totals.subs, totals.sessions.hasSess) } Column(Modifier.padding(PaddingValues()).fillMaxWidth()) { InfoRow( @@ -364,7 +364,7 @@ private fun SMPSubscriptionsSection(subs: SMPServerSubs, summary: SMPServerSumma style = MaterialTheme.typography.body2, fontSize = 12.sp ) - SubscriptionStatusIndicatorView(subs, summary.sessionsOrNew) + SubscriptionStatusIndicatorView(subs, summary.sessionsOrNew.hasSess) } Column(Modifier.padding(PaddingValues()).fillMaxWidth()) { InfoRow( @@ -718,6 +718,10 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta remember { stateGetOrPut("serverTypeSelection") { PresentedServerType.SMP } } val scope = rememberCoroutineScope() + suspend fun setServersSummary() { + serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) + } + LaunchedEffect(Unit) { if (chatModel.users.count { u -> u.user.activeUser || !u.user.hidden } == 1 ) { @@ -725,6 +729,29 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta } else { showUserSelection = true } + setServersSummary() + scope.launch { + while (isActive) { + delay(1.seconds) + if ((appPlatform.isDesktop || chat.simplex.common.platform.chatModel.chatId.value == null) && isAppVisibleAndFocused()) { + setServersSummary() + } + } + } + } + + fun resetStats() { + withBGApi { + val success = controller.resetAgentServersStats(rh?.remoteHostId) + if (success) { + setServersSummary() + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.servers_info_modal_error_title), + text = generalGetString(MR.strings.servers_info_reset_stats_alert_error_title) + ) + } + } } Column( @@ -908,7 +935,7 @@ fun ModalData.ServersSummaryView(rh: RemoteHostInfo?, serversSummary: MutableSta SectionView { ReconnectAllServersButton(rh) - ResetStatisticsButton(rh) + ResetStatisticsButton(rh, resetStats = { resetStats() }) } SectionBottomSpacer() @@ -949,8 +976,8 @@ private fun reconnectAllServersAlert(rh: RemoteHostInfo?) { } @Composable -private fun ResetStatisticsButton(rh: RemoteHostInfo?) { - SectionItemView(click = { resetStatisticsAlert(rh) }) { +private fun ResetStatisticsButton(rh: RemoteHostInfo?, resetStats: () -> Unit) { + SectionItemView(click = { resetStatisticsAlert(rh, resetStats) }) { Text( stringResource(MR.strings.servers_info_reset_stats), color = MaterialTheme.colors.primary @@ -958,23 +985,12 @@ private fun ResetStatisticsButton(rh: RemoteHostInfo?) { } } -private fun resetStatisticsAlert(rh: RemoteHostInfo?) { +private fun resetStatisticsAlert(rh: RemoteHostInfo?, resetStats: () -> Unit) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.servers_info_reset_stats_alert_title), text = generalGetString(MR.strings.servers_info_reset_stats_alert_message), confirmText = generalGetString(MR.strings.servers_info_reset_stats_alert_confirm), destructive = true, - onConfirm = { - withBGApi { - val success = controller.resetAgentServersStats(rh?.remoteHostId) - - if (!success) { - AlertManager.shared.showAlertMsg( - title = generalGetString(MR.strings.servers_info_modal_error_title), - text = generalGetString(MR.strings.servers_info_reset_stats_alert_error_title) - ) - } - } - } + onConfirm = resetStats ) } From e343dd017b9f0f230242e8521d115d31a5ced123 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Mon, 22 Jul 2024 19:35:36 +0100 Subject: [PATCH 50/57] core: fix nix build for iOS build with tagged JSON (e.g., Flutter apps) (#4484) --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 91cf43d0fe..e8ff779a87 100644 --- a/flake.nix +++ b/flake.nix @@ -562,7 +562,7 @@ packages.direct-sqlcipher.flags.commoncrypto = true; packages.entropy.flags.DoNotGetEntropy = true; packages.simplexmq.components.library.libs = pkgs.lib.mkForce [ - (pkgs.openssl.override { static = true; }) + ((pkgs.openssl.override { static = true; }).overrideDerivation (old: { CFLAGS = "-mcpu=apple-a7 -march=armv8-a+norcpc" ;})) ]; }]; }).simplex-chat.components.library.override ( From 6fca6c22c52ced1a8b2ff5a9b5e8bebe1b735687 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 24 Jul 2024 00:11:42 +0700 Subject: [PATCH 51/57] ios: interactive media and link previews in the list of chats (#4487) * ios: interactive media and link previews in the list of chats * commented out voice preview * voice message support and various fixes * changes to video * changes * playing voice in chat list with scrolling * revert This reverts commit 60f57403d117b208586c21b3f71d6aa942edad94. * prevent feedback loop * version of dependency * voice * fix param * working voice * reacting on messages and chat deletion * fix two videos in a row * video item layout * fix --------- Co-authored-by: Levitating Pineapple Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Model/ChatModel.swift | 4 + .../Chat/ChatItem/AnimatedImageView.swift | 14 +- .../Views/Chat/ChatItem/CIFileView.swift | 66 +++-- .../Views/Chat/ChatItem/CIImageView.swift | 27 +- .../Views/Chat/ChatItem/CIVideoView.swift | 164 ++++++++--- .../Views/Chat/ChatItem/CIVoiceView.swift | 261 ++++++++++++++---- .../Chat/ChatItem/FramedCIVoiceView.swift | 17 +- .../Views/Chat/ChatItem/FramedItemView.swift | 67 +++-- apps/ios/Shared/Views/Chat/ChatItemView.swift | 19 +- apps/ios/Shared/Views/Chat/ChatView.swift | 24 +- .../Shared/Views/ChatList/ChatListView.swift | 10 + .../Views/ChatList/ChatPreviewView.swift | 207 ++++++++++++-- apps/ios/SimpleX.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- apps/ios/SimpleXChat/ChatTypes.swift | 22 ++ 15 files changed, 679 insertions(+), 235 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 7dae94dfcd..a9d94be217 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -354,6 +354,9 @@ final class ChatModel: ObservableObject { addChat(Chat(chatInfo: cInfo, chatItems: [cItem])) res = true } + if cItem.isDeletedContent || cItem.meta.itemDeleted != nil { + VoiceItemState.stopVoiceInChatView(cInfo, cItem) + } // update current chat return chatId == cInfo.id ? _upsertChatItem(cInfo, cItem) : res } @@ -420,6 +423,7 @@ final class ChatModel: ObservableObject { } } } + VoiceItemState.stopVoiceInChatView(cInfo, cItem) } func nextChatItemData(_ chatItemId: Int64, previous: Bool, map: @escaping (ChatItem) -> T?) -> T? { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift index bcdeb7fd9c..30f5e7a589 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/AnimatedImageView.swift @@ -9,6 +9,7 @@ import SwiftUI class AnimatedImageView: UIView { var image: UIImage? = nil var imageView: UIImageView? = nil + var cMode: UIView.ContentMode = .scaleAspectFit override init(frame: CGRect) { super.init(frame: frame) @@ -18,11 +19,12 @@ class AnimatedImageView: UIView { fatalError("Not implemented") } - convenience init(image: UIImage) { + convenience init(image: UIImage, contentMode: UIView.ContentMode) { self.init() self.image = image + self.cMode = contentMode imageView = UIImageView(gifImage: image) - imageView!.contentMode = .scaleAspectFit + imageView!.contentMode = contentMode self.addSubview(imageView!) } @@ -35,7 +37,7 @@ class AnimatedImageView: UIView { if let subview = self.subviews.first as? UIImageView { if image.imageData != subview.gifImage?.imageData { imageView = UIImageView(gifImage: image) - imageView!.contentMode = .scaleAspectFit + imageView!.contentMode = contentMode self.addSubview(imageView!) subview.removeFromSuperview() } @@ -47,13 +49,15 @@ class AnimatedImageView: UIView { struct SwiftyGif: UIViewRepresentable { private let image: UIImage + private let contentMode: UIView.ContentMode - init(image: UIImage) { + init(image: UIImage, contentMode: UIView.ContentMode = .scaleAspectFit) { self.image = image + self.contentMode = contentMode } func makeUIView(context: Context) -> AnimatedImageView { - AnimatedImageView(image: image) + AnimatedImageView(image: image, contentMode: contentMode) } func updateUIView(_ imageView: AnimatedImageView, context: Context) { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index 414da5371a..57123d74ba 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -14,39 +14,45 @@ struct CIFileView: View { @EnvironmentObject var theme: AppTheme let file: CIFile? let edited: Bool + var smallView: Bool = false var body: some View { - let metaReserve = edited - ? " " - : " " - Button(action: fileAction) { - HStack(alignment: .bottom, spacing: 6) { - fileIndicator() - .padding(.top, 5) - .padding(.bottom, 3) - if let file = file { - let prettyFileSize = ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) - VStack(alignment: .leading, spacing: 2) { - Text(file.fileName) - .lineLimit(1) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.onBackground) - Text(prettyFileSize + metaReserve) - .font(.caption) - .lineLimit(1) - .multilineTextAlignment(.leading) - .foregroundColor(theme.colors.secondary) + if smallView { + fileIndicator() + .onTapGesture(perform: fileAction) + } else { + let metaReserve = edited + ? " " + : " " + Button(action: fileAction) { + HStack(alignment: .bottom, spacing: 6) { + fileIndicator() + .padding(.top, 5) + .padding(.bottom, 3) + if let file = file { + let prettyFileSize = ByteCountFormatter.string(fromByteCount: file.fileSize, countStyle: .binary) + VStack(alignment: .leading, spacing: 2) { + Text(file.fileName) + .lineLimit(1) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.onBackground) + Text(prettyFileSize + metaReserve) + .font(.caption) + .lineLimit(1) + .multilineTextAlignment(.leading) + .foregroundColor(theme.colors.secondary) + } + } else { + Text(metaReserve) } - } else { - Text(metaReserve) } + .padding(.top, 4) + .padding(.bottom, 6) + .padding(.leading, 10) + .padding(.trailing, 12) } - .padding(.top, 4) - .padding(.bottom, 6) - .padding(.leading, 10) - .padding(.trailing, 12) + .disabled(!itemInteractive) } - .disabled(!itemInteractive) } private var itemInteractive: Bool { @@ -199,17 +205,17 @@ struct CIFileView: View { Image(systemName: icon) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 30, height: 30) + .frame(width: smallView ? 36 : 30, height: smallView ? 36 : 30) .foregroundColor(color) if let innerIcon = innerIcon, - let innerIconSize = innerIconSize { + let innerIconSize = innerIconSize, (!smallView || file?.showStatusIconInSmallView == true) { Image(systemName: innerIcon) .resizable() .aspectRatio(contentMode: .fit) .frame(maxHeight: 16) .frame(width: innerIconSize, height: innerIconSize) .foregroundColor(.white) - .padding(.top, 12) + .padding(.top, smallView ? 15 : 12) } } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index 9c12653343..b247a33565 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -15,13 +15,14 @@ struct CIImageView: View { var preview: UIImage? let maxWidth: CGFloat var imgWidth: CGFloat? - @State private var showFullScreenImage = false + var smallView: Bool = false + @Binding var showFullScreenImage: Bool var body: some View { let file = chatItem.file VStack(alignment: .center, spacing: 6) { if let uiImage = getLoadedImage(file) { - imageView(uiImage) + Group { if smallView { smallViewImageView(uiImage) } else { imageView(uiImage) } } .fullScreenCover(isPresented: $showFullScreenImage) { FullScreenMediaView(chatItem: chatItem, image: uiImage, showView: $showFullScreenImage) } @@ -30,7 +31,7 @@ struct CIImageView: View { showFullScreenImage = false } } else if let preview { - imageView(preview) + Group { if smallView { smallViewImageView(preview) } else { imageView(preview) } } .onTapGesture { if let file = file { switch file.fileStatus { @@ -83,6 +84,9 @@ struct CIImageView: View { } } } + .onDisappear { + showFullScreenImage = false + } } private func imageView(_ img: UIImage) -> some View { @@ -102,6 +106,23 @@ struct CIImageView: View { } } + private func smallViewImageView(_ img: UIImage) -> some View { + ZStack(alignment: .topTrailing) { + if img.imageData == nil { + Image(uiImage: img) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: maxWidth, height: maxWidth) + } else { + SwiftyGif(image: img, contentMode: .scaleAspectFill) + .frame(width: maxWidth, height: maxWidth) + } + if chatItem.file?.showStatusIconInSmallView == true { + loadingIndicator() + } + } + } + @ViewBuilder private func loadingIndicator() -> some View { if let file = chatItem.file { switch file.fileStatus { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index c31c4a0da9..4cbbbbb689 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -20,45 +20,43 @@ struct CIVideoView: View { @State private var videoPlaying: Bool = false private let maxWidth: CGFloat private var videoWidth: CGFloat? + private let smallView: Bool @State private var player: AVPlayer? @State private var fullPlayer: AVPlayer? @State private var url: URL? @State private var urlDecrypted: URL? @State private var decryptionInProgress: Bool = false - @State private var showFullScreenPlayer = false + @Binding private var showFullScreenPlayer: Bool @State private var timeObserver: Any? = nil @State private var fullScreenTimeObserver: Any? = nil @State private var publisher: AnyCancellable? = nil + private var sizeMultiplier: CGFloat { smallView ? 0.38 : 1 } - init(chatItem: ChatItem, preview: UIImage?, duration: Int, maxWidth: CGFloat, videoWidth: CGFloat?) { + init(chatItem: ChatItem, preview: UIImage?, duration: Int, maxWidth: CGFloat, videoWidth: CGFloat?, smallView: Bool = false, showFullscreenPlayer: Binding) { self.chatItem = chatItem self.preview = preview self._duration = State(initialValue: duration) self.maxWidth = maxWidth self.videoWidth = videoWidth - if let url = getLoadedVideo(chatItem.file) { - let decrypted = chatItem.file?.fileSource?.cryptoArgs == nil ? url : chatItem.file?.fileSource?.decryptedGet() - self._urlDecrypted = State(initialValue: decrypted) - if let decrypted = decrypted { - self._player = State(initialValue: VideoPlayerView.getOrCreatePlayer(decrypted, false)) - self._fullPlayer = State(initialValue: AVPlayer(url: decrypted)) - } - self._url = State(initialValue: url) - } + self.smallView = smallView + self._showFullScreenPlayer = showFullscreenPlayer } var body: some View { let file = chatItem.file - ZStack { + ZStack(alignment: smallView ? .topLeading : .center) { ZStack(alignment: .topLeading) { - if let file = file, let preview = preview, let player = player, let decrypted = urlDecrypted { + if let file = file, let preview = preview, let decrypted = urlDecrypted, smallView { + smallVideoView(decrypted, file, preview) + } else if let file = file, let preview = preview, let player = player, let decrypted = urlDecrypted { videoView(player, decrypted, file, preview, duration) + } else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil, smallView { + smallVideoViewEncrypted(file, defaultPreview) } else if let file = file, let defaultPreview = preview, file.loaded && urlDecrypted == nil { videoViewEncrypted(file, defaultPreview, duration) - } else if let preview { - imageView(preview) - .onTapGesture { - if let file = file { + } else if let preview, let file { + Group { if smallView { smallViewImageView(preview, file) } else { imageView(preview) } } + .onTapGesture { switch file.fileStatus { case .rcvInvitation, .rcvAborted: receiveFileIfValidSize(file: file, receiveFile: receiveFile) @@ -82,18 +80,59 @@ struct CIVideoView: View { default: () } } - } } - durationProgress() + if !smallView { + durationProgress() + } } - if let file = file, showDownloadButton(file.fileStatus) { + if !smallView, let file = file, showDownloadButton(file.fileStatus) { Button { receiveFileIfValidSize(file: file, receiveFile: receiveFile) } label: { playPauseIcon("play.fill") } + } else if let file = file, showDownloadButton(file.fileStatus) && !file.showStatusIconInSmallView { + playPauseIcon("play.fill") + .onTapGesture { + receiveFileIfValidSize(file: file, receiveFile: receiveFile) + } } } + .fullScreenCover(isPresented: $showFullScreenPlayer) { + if let decrypted = urlDecrypted { + fullScreenPlayer(decrypted) + } + } + .onAppear { + setupPlayer(chatItem.file) + } + .onChange(of: chatItem.file) { file in + // ChatItem can be changed in small view on chat list screen + setupPlayer(file) + } + .onDisappear { + showFullScreenPlayer = false + } + } + + private func setupPlayer(_ file: CIFile?) { + let newUrl = getLoadedVideo(file) + if newUrl == url { + return + } + url = nil + urlDecrypted = nil + player = nil + fullPlayer = nil + if let newUrl { + let decrypted = file?.fileSource?.cryptoArgs == nil ? newUrl : file?.fileSource?.decryptedGet() + urlDecrypted = decrypted + if let decrypted = decrypted { + player = VideoPlayerView.getOrCreatePlayer(decrypted, false) + fullPlayer = AVPlayer(url: decrypted) + } + url = newUrl + } } private func showDownloadButton(_ fileStatus: CIFileStatus) -> Bool { @@ -109,11 +148,6 @@ struct CIVideoView: View { ZStack(alignment: .center) { let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local) imageView(defaultPreview) - .fullScreenCover(isPresented: $showFullScreenPlayer) { - if let decrypted = urlDecrypted { - fullScreenPlayer(decrypted) - } - } .onTapGesture { decrypt(file: file) { showFullScreenPlayer = urlDecrypted != nil @@ -154,9 +188,6 @@ struct CIVideoView: View { videoPlaying = false } } - .fullScreenCover(isPresented: $showFullScreenPlayer) { - fullScreenPlayer(url) - } .onTapGesture { switch player.timeControlStatus { case .playing: @@ -194,14 +225,53 @@ struct CIVideoView: View { } } + private func smallVideoViewEncrypted(_ file: CIFile, _ preview: UIImage) -> some View { + return ZStack(alignment: .topLeading) { + let canBePlayed = !chatItem.chatDir.sent || file.fileStatus == CIFileStatus.sndComplete || (file.fileStatus == .sndStored && file.fileProtocol == .local) + smallViewImageView(preview, file) + .onTapGesture { + decrypt(file: file) { + showFullScreenPlayer = urlDecrypted != nil + } + } + .onChange(of: m.activeCallViewIsCollapsed) { _ in + showFullScreenPlayer = false + } + if file.showStatusIconInSmallView { + // Show nothing + } else if !decryptionInProgress { + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") + } else { + videoDecryptionProgress() + } + } + } + + private func smallVideoView(_ url: URL, _ file: CIFile, _ preview: UIImage) -> some View { + return ZStack(alignment: .topLeading) { + smallViewImageView(preview, file) + .onTapGesture { + showFullScreenPlayer = true + } + .onChange(of: m.activeCallViewIsCollapsed) { _ in + showFullScreenPlayer = false + } + + if !file.showStatusIconInSmallView { + playPauseIcon("play.fill") + } + } + } + + private func playPauseIcon(_ image: String, _ color: Color = .white) -> some View { Image(systemName: image) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 12, height: 12) + .frame(width: smallView ? 12 * sizeMultiplier * 1.6 : 12, height: smallView ? 12 * sizeMultiplier * 1.6 : 12) .foregroundColor(color) - .padding(.leading, 4) - .frame(width: 40, height: 40) + .padding(.leading, smallView ? 0 : 4) + .frame(width: 40 * sizeMultiplier, height: 40 * sizeMultiplier) .background(Color.black.opacity(0.35)) .clipShape(Circle()) } @@ -209,9 +279,9 @@ struct CIVideoView: View { private func videoDecryptionProgress(_ color: Color = .white) -> some View { ProgressView() .progressViewStyle(.circular) - .frame(width: 12, height: 12) + .frame(width: smallView ? 12 * sizeMultiplier : 12, height: smallView ? 12 * sizeMultiplier : 12) .tint(color) - .frame(width: 40, height: 40) + .frame(width: smallView ? 40 * sizeMultiplier * 0.9 : 40, height: smallView ? 40 * sizeMultiplier * 0.9 : 40) .background(Color.black.opacity(0.35)) .clipShape(Circle()) } @@ -251,6 +321,19 @@ struct CIVideoView: View { } } + private func smallViewImageView(_ img: UIImage, _ file: CIFile) -> some View { + ZStack(alignment: .center) { + Image(uiImage: img) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: maxWidth, height: maxWidth) + if file.showStatusIconInSmallView { + fileStatusIcon() + .allowsHitTesting(false) + } + } + } + @ViewBuilder private func fileStatusIcon() -> some View { if let file = chatItem.file { switch file.fileStatus { @@ -322,7 +405,7 @@ struct CIVideoView: View { .aspectRatio(contentMode: .fit) .frame(width: size, height: size) .foregroundColor(.white) - .padding(padding) + .padding(smallView ? 0 : padding) } private func progressView() -> some View { @@ -330,7 +413,7 @@ struct CIVideoView: View { .progressViewStyle(.circular) .frame(width: 16, height: 16) .tint(.white) - .padding(11) + .padding(smallView ? 0 : 11) } private func progressCircle(_ progress: Int64, _ total: Int64) -> some View { @@ -342,7 +425,7 @@ struct CIVideoView: View { ) .rotationEffect(.degrees(-90)) .frame(width: 16, height: 16) - .padding([.trailing, .top], 11) + .padding([.trailing, .top], smallView ? 0 : 11) } // TODO encrypt: where file size is checked? @@ -382,7 +465,8 @@ struct CIVideoView: View { ) .onAppear { DispatchQueue.main.asyncAfter(deadline: .now()) { - m.stopPreviousRecPlay = url + // Prevent feedback loop - setting `ChatModel`s property causes `onAppear` to be called on iOS17+ + if m.stopPreviousRecPlay != url { m.stopPreviousRecPlay = url } if let player = fullPlayer { player.play() var played = false @@ -419,10 +503,12 @@ struct CIVideoView: View { urlDecrypted = await file.fileSource?.decryptedGetOrCreate(&ChatModel.shared.filesToDelete) await MainActor.run { if let decrypted = urlDecrypted { - player = VideoPlayerView.getOrCreatePlayer(decrypted, false) + if !smallView { + player = VideoPlayerView.getOrCreatePlayer(decrypted, false) + } fullPlayer = AVPlayer(url: decrypted) } - decryptionInProgress = true + decryptionInProgress = false completed?() } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index ad15d0d342..ce4d2a8181 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -15,15 +15,26 @@ struct CIVoiceView: View { var chatItem: ChatItem let recordingFile: CIFile? let duration: Int - @Binding var audioPlayer: AudioPlayer? - @Binding var playbackState: VoiceMessagePlaybackState - @Binding var playbackTime: TimeInterval? + @State var audioPlayer: AudioPlayer? = nil + @State var playbackState: VoiceMessagePlaybackState = .noPlayback + @State var playbackTime: TimeInterval? = nil + @Binding var allowMenu: Bool + var smallView: Bool = false @State private var seek: (TimeInterval) -> Void = { _ in } var body: some View { Group { - if chatItem.chatDir.sent { + if smallView { + HStack(spacing: 10) { + player() + playerTime() + .allowsHitTesting(false) + if .playing == playbackState || (playbackTime ?? 0) > 0 || !allowMenu { + playbackSlider() + } + } + } else if chatItem.chatDir.sent { VStack (alignment: .trailing, spacing: 6) { HStack { if .playing == playbackState || (playbackTime ?? 0) > 0 || !allowMenu { @@ -55,6 +66,7 @@ struct CIVoiceView: View { private func player() -> some View { VoiceMessagePlayer( + chat: chat, chatItem: chatItem, recordingFile: recordingFile, recordingTime: TimeInterval(duration), @@ -63,7 +75,8 @@ struct CIVoiceView: View { audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, - allowMenu: $allowMenu + allowMenu: $allowMenu, + sizeMultiplier: smallView ? voiceMessageSizeBasedOnSquareSize(36) / 56 : 1 ) } @@ -119,6 +132,7 @@ struct VoiceMessagePlayerTime: View { } struct VoiceMessagePlayer: View { + @ObservedObject var chat: Chat @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme var chatItem: ChatItem @@ -130,7 +144,9 @@ struct VoiceMessagePlayer: View { @Binding var audioPlayer: AudioPlayer? @Binding var playbackState: VoiceMessagePlaybackState @Binding var playbackTime: TimeInterval? + @Binding var allowMenu: Bool + var sizeMultiplier: CGFloat var body: some View { ZStack { @@ -190,49 +206,113 @@ struct VoiceMessagePlayer: View { } } .onAppear { + if audioPlayer == nil { + let small = sizeMultiplier != 1 + audioPlayer = small ? VoiceItemState.smallView[VoiceItemState.id(chat, chatItem)]?.audioPlayer : VoiceItemState.chatView[VoiceItemState.id(chat, chatItem)]?.audioPlayer + playbackState = (small ? VoiceItemState.smallView[VoiceItemState.id(chat, chatItem)]?.playbackState : VoiceItemState.chatView[VoiceItemState.id(chat, chatItem)]?.playbackState) ?? .noPlayback + playbackTime = small ? VoiceItemState.smallView[VoiceItemState.id(chat, chatItem)]?.playbackTime : VoiceItemState.chatView[VoiceItemState.id(chat, chatItem)]?.playbackTime + } seek = { to in audioPlayer?.seek(to) } - audioPlayer?.onTimer = { playbackTime = $0 } + let audioPath: URL? = if let recordingSource = getLoadedFileSource(recordingFile) { + getAppFilePath(recordingSource.filePath) + } else { + nil + } + let chatId = chatModel.chatId + let userId = chatModel.currentUser?.userId + audioPlayer?.onTimer = { + playbackTime = $0 + notifyStateChange() + // Manual check here is needed because when this view is not visible, SwiftUI don't react on stopPreviousRecPlay, chatId and current user changes and audio keeps playing when it should stop + if (audioPath != nil && chatModel.stopPreviousRecPlay != audioPath) || chatModel.chatId != chatId || chatModel.currentUser?.userId != userId { + stopPlayback() + } + } audioPlayer?.onFinishPlayback = { playbackState = .noPlayback playbackTime = TimeInterval(0) + notifyStateChange() + } + // One voice message was paused, then scrolled far from it, started to play another one, drop to stopped state + if let audioPath, chatModel.stopPreviousRecPlay != audioPath { + stopPlayback() } } .onChange(of: chatModel.stopPreviousRecPlay) { it in if let recordingFileName = getLoadedFileSource(recordingFile)?.filePath, chatModel.stopPreviousRecPlay != getAppFilePath(recordingFileName) { - audioPlayer?.stop() - playbackState = .noPlayback - playbackTime = TimeInterval(0) + stopPlayback() } } .onChange(of: playbackState) { state in allowMenu = state == .paused || state == .noPlayback + // Notify activeContentPreview in ChatPreviewView that playback is finished + if state == .noPlayback, let recordingFileName = getLoadedFileSource(recordingFile)?.filePath, + chatModel.stopPreviousRecPlay == getAppFilePath(recordingFileName) { + chatModel.stopPreviousRecPlay = nil + } + } + .onChange(of: chatModel.chatId) { _ in + stopPlayback() + } + .onDisappear { + if sizeMultiplier == 1 && chatModel.chatId == nil { + stopPlayback() + } } } @ViewBuilder private func playbackButton() -> some View { - switch playbackState { - case .noPlayback: - Button { - if let recordingSource = getLoadedFileSource(recordingFile) { - startPlayback(recordingSource) - } - } label: { + if sizeMultiplier != 1 { + switch playbackState { + case .noPlayback: playPauseIcon("play.fill", theme.colors.primary) - } - case .playing: - Button { - audioPlayer?.pause() - playbackState = .paused - } label: { + .onTapGesture { + if let recordingSource = getLoadedFileSource(recordingFile) { + startPlayback(recordingSource) + } + } + case .playing: playPauseIcon("pause.fill", theme.colors.primary) - } - case .paused: - Button { - audioPlayer?.play() - playbackState = .playing - } label: { + .onTapGesture { + audioPlayer?.pause() + playbackState = .paused + notifyStateChange() + } + case .paused: playPauseIcon("play.fill", theme.colors.primary) + .onTapGesture { + audioPlayer?.play() + playbackState = .playing + notifyStateChange() + } + } + } else { + switch playbackState { + case .noPlayback: + Button { + if let recordingSource = getLoadedFileSource(recordingFile) { + startPlayback(recordingSource) + } + } label: { + playPauseIcon("play.fill", theme.colors.primary) + } + case .playing: + Button { + audioPlayer?.pause() + playbackState = .paused + notifyStateChange() + } label: { + playPauseIcon("pause.fill", theme.colors.primary) + } + case .paused: + Button { + audioPlayer?.play() + playbackState = .playing + notifyStateChange() + } label: { + playPauseIcon("play.fill", theme.colors.primary) + } } } } @@ -242,28 +322,49 @@ struct VoiceMessagePlayer: View { Image(systemName: image) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) + .frame(width: 20 * sizeMultiplier, height: 20 * sizeMultiplier) .foregroundColor(color) .padding(.leading, image == "play.fill" ? 4 : 0) - .frame(width: 56, height: 56) + .frame(width: 56 * sizeMultiplier, height: 56 * sizeMultiplier) .background(showBackground ? chatItemFrameColor(chatItem, theme) : .clear) .clipShape(Circle()) if recordingTime > 0 { ProgressCircle(length: recordingTime, progress: $playbackTime) - .frame(width: 53, height: 53) // this + ProgressCircle lineWidth = background circle diameter + .frame(width: 53 * sizeMultiplier, height: 53 * sizeMultiplier) // this + ProgressCircle lineWidth = background circle diameter } } } private func downloadButton(_ recordingFile: CIFile, _ icon: String) -> some View { - Button { - Task { - if let user = chatModel.currentUser { - await receiveFile(user: user, fileId: recordingFile.fileId) + Group { + if sizeMultiplier != 1 { + playPauseIcon(icon, theme.colors.primary) + .onTapGesture { + Task { + if let user = chatModel.currentUser { + await receiveFile(user: user, fileId: recordingFile.fileId) + } + } + } + } else { + Button { + Task { + if let user = chatModel.currentUser { + await receiveFile(user: user, fileId: recordingFile.fileId) + } + } + } label: { + playPauseIcon(icon, theme.colors.primary) } } - } label: { - playPauseIcon(icon, theme.colors.primary) + } + } + + func notifyStateChange() { + if sizeMultiplier != 1 { + VoiceItemState.smallView[VoiceItemState.id(chat, chatItem)] = VoiceItemState(audioPlayer: audioPlayer, playbackState: playbackState, playbackTime: playbackTime) + } else { + VoiceItemState.chatView[VoiceItemState.id(chat, chatItem)] = VoiceItemState(audioPlayer: audioPlayer, playbackState: playbackState, playbackTime: playbackTime) } } @@ -288,33 +389,96 @@ struct VoiceMessagePlayer: View { Image(systemName: image) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: size, height: size) + .frame(width: size * sizeMultiplier, height: size * sizeMultiplier) .foregroundColor(Color(uiColor: .tertiaryLabel)) - .frame(width: 56, height: 56) + .frame(width: 56 * sizeMultiplier, height: 56 * sizeMultiplier) .background(showBackground ? chatItemFrameColor(chatItem, theme) : .clear) .clipShape(Circle()) } private func loadingIcon() -> some View { ProgressView() - .frame(width: 30, height: 30) - .frame(width: 56, height: 56) + .frame(width: 30 * sizeMultiplier, height: 30 * sizeMultiplier) + .frame(width: 56 * sizeMultiplier, height: 56 * sizeMultiplier) .background(showBackground ? chatItemFrameColor(chatItem, theme) : .clear) .clipShape(Circle()) } private func startPlayback(_ recordingSource: CryptoFile) { - chatModel.stopPreviousRecPlay = getAppFilePath(recordingSource.filePath) + let audioPath = getAppFilePath(recordingSource.filePath) + let chatId = chatModel.chatId + let userId = chatModel.currentUser?.userId + chatModel.stopPreviousRecPlay = audioPath audioPlayer = AudioPlayer( - onTimer: { playbackTime = $0 }, + onTimer: { + playbackTime = $0 + notifyStateChange() + // Manual check here is needed because when this view is not visible, SwiftUI don't react on stopPreviousRecPlay, chatId and current user changes and audio keeps playing when it should stop + if chatModel.stopPreviousRecPlay != audioPath || chatModel.chatId != chatId || chatModel.currentUser?.userId != userId { + stopPlayback() + } + }, onFinishPlayback: { playbackState = .noPlayback playbackTime = TimeInterval(0) + notifyStateChange() } ) audioPlayer?.start(fileSource: recordingSource, at: playbackTime) playbackState = .playing + notifyStateChange() } + + private func stopPlayback() { + audioPlayer?.stop() + playbackState = .noPlayback + playbackTime = TimeInterval(0) + notifyStateChange() + } +} + +func voiceMessageSizeBasedOnSquareSize(_ squareSize: CGFloat) -> CGFloat { + let squareToCircleRatio = 0.935 + return squareSize + squareSize * (1 - squareToCircleRatio) +} + +class VoiceItemState { + var audioPlayer: AudioPlayer? + var playbackState: VoiceMessagePlaybackState + var playbackTime: TimeInterval? + + init(audioPlayer: AudioPlayer? = nil, playbackState: VoiceMessagePlaybackState, playbackTime: TimeInterval? = nil) { + self.audioPlayer = audioPlayer + self.playbackState = playbackState + self.playbackTime = playbackTime + } + + static func id(_ chat: Chat, _ chatItem: ChatItem) -> String { + "\(chat.id) \(chatItem.id)" + } + + static func id(_ chatInfo: ChatInfo, _ chatItem: ChatItem) -> String { + "\(chatInfo.id) \(chatItem.id)" + } + + static func stopVoiceInSmallView(_ chatInfo: ChatInfo, _ chatItem: ChatItem) { + let id = id(chatInfo, chatItem) + if let item = smallView[id] { + item.audioPlayer?.stop() + ChatModel.shared.stopPreviousRecPlay = nil + } + } + + static func stopVoiceInChatView(_ chatInfo: ChatInfo, _ chatItem: ChatItem) { + let id = id(chatInfo, chatItem) + if let item = chatView[id] { + item.audioPlayer?.stop() + ChatModel.shared.stopPreviousRecPlay = nil + } + } + + static var smallView: [String: VoiceItemState] = [:] + static var chatView: [String: VoiceItemState] = [:] } struct CIVoiceView_Previews: PreviewProvider { @@ -339,15 +503,12 @@ struct CIVoiceView_Previews: PreviewProvider { chatItem: ChatItem.getVoiceMsgContentSample(), recordingFile: CIFile.getSample(fileName: "voice.m4a", fileSize: 65536, fileStatus: .rcvComplete), duration: 30, - audioPlayer: .constant(nil), - playbackState: .constant(.playing), - playbackTime: .constant(TimeInterval(20)), allowMenu: Binding.constant(true) ) - ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) + ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, revealed: Binding.constant(false), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), revealed: Binding.constant(false), allowMenu: .constant(true)) + ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, revealed: Binding.constant(false), allowMenu: .constant(true)) } .previewLayout(.fixed(width: 360, height: 360)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift index 59fabb3901..64a7f29a25 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift @@ -13,21 +13,23 @@ import SimpleXChat struct FramedCIVoiceView: View { @EnvironmentObject var theme: AppTheme + @ObservedObject var chat: Chat var chatItem: ChatItem let recordingFile: CIFile? let duration: Int - + + @State var audioPlayer: AudioPlayer? = nil + @State var playbackState: VoiceMessagePlaybackState = .noPlayback + @State var playbackTime: TimeInterval? = nil + @Binding var allowMenu: Bool - - @Binding var audioPlayer: AudioPlayer? - @Binding var playbackState: VoiceMessagePlaybackState - @Binding var playbackTime: TimeInterval? - + @State private var seek: (TimeInterval) -> Void = { _ in } var body: some View { HStack { VoiceMessagePlayer( + chat: chat, chatItem: chatItem, recordingFile: recordingFile, recordingTime: TimeInterval(duration), @@ -36,7 +38,8 @@ struct FramedCIVoiceView: View { audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, - allowMenu: $allowMenu + allowMenu: $allowMenu, + sizeMultiplier: 1 ) VoiceMessagePlayerTime( recordingTime: TimeInterval(duration), diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index 595d9bf2fc..2fdd708fdb 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -26,10 +26,7 @@ struct FramedItemView: View { @Binding var allowMenu: Bool @State private var showSecrets = false @State private var showQuoteSecrets = false - - @Binding var audioPlayer: AudioPlayer? - @Binding var playbackState: VoiceMessagePlaybackState - @Binding var playbackTime: TimeInterval? + @State private var showFullscreenGallery: Bool = false var body: some View { let v = ZStack(alignment: .bottomTrailing) { @@ -106,7 +103,7 @@ struct FramedItemView: View { } else { switch (chatItem.content.msgContent) { case let .image(text, _): - CIImageView(chatItem: chatItem, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth) + CIImageView(chatItem: chatItem, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery) .overlay(DetermineWidth()) if text == "" && !chatItem.meta.isLive { Color.clear @@ -121,7 +118,7 @@ struct FramedItemView: View { ciMsgContentView(chatItem) } case let .video(text, _, duration): - CIVideoView(chatItem: chatItem, preview: preview, duration: duration, maxWidth: maxWidth, videoWidth: videoWidth) + CIVideoView(chatItem: chatItem, preview: preview, duration: duration, maxWidth: maxWidth, videoWidth: videoWidth, showFullscreenPlayer: $showFullscreenGallery) .overlay(DetermineWidth()) if text == "" && !chatItem.meta.isLive { Color.clear @@ -136,7 +133,7 @@ struct FramedItemView: View { ciMsgContentView(chatItem) } case let .voice(text, duration): - FramedCIVoiceView(chatItem: chatItem, recordingFile: chatItem.file, duration: duration, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime) + FramedCIVoiceView(chat: chat, chatItem: chatItem, recordingFile: chatItem.file, duration: duration, allowMenu: $allowMenu) .overlay(DetermineWidth()) if text != "" { ciMsgContentView(chatItem) @@ -370,14 +367,14 @@ func chatItemFrameContextColor(_ ci: ChatItem, _ theme: AppTheme) -> Color { struct FramedItemView_Previews: PreviewProvider { static var previews: some View { Group{ - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) } .previewLayout(.fixed(width: 360, height: 200)) } @@ -386,16 +383,16 @@ struct FramedItemView_Previews: PreviewProvider { struct FramedItemView_Edited_Previews: PreviewProvider { static var previews: some View { Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQAAAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAuKADAAQAAAABAAAAYAAAAAD/7QA4UGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAA4QklNBCUAAAAAABDUHYzZjwCyBOmACZjs+EJ+/8AAEQgAYAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/bAEMAAQEBAQEBAgEBAgMCAgIDBAMDAwMEBgQEBAQEBgcGBgYGBgYHBwcHBwcHBwgICAgICAkJCQkJCwsLCwsLCwsLC//bAEMBAgICAwMDBQMDBQsIBggLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC//dAAQADP/aAAwDAQACEQMRAD8A/v4ooooAKKKKACiiigAooooAKK+CP2vP+ChXwZ/ZPibw7dMfEHi2VAYdGs3G9N33TO/IiU9hgu3ZSOa/NzXNL/4KJ/td6JJ49+NXiq2+Cvw7kG/ZNKbDMLcjKblmfI/57SRqewrwMdxBRo1HQoRdWqt1HaP+KT0j838j7XKOCMXiqEcbjKkcPh5bSne8/wDr3BXlN+is+5+43jb45/Bf4bs0fj/xZpGjSL1jvL2KF/8AvlmDfpXjH/DfH7GQuPsv/CydD35x/wAfIx+fT9a/AO58D/8ABJj4UzvF4v8AFfif4l6mp/evpkfkWzP3w2Isg+omb61X/wCF0/8ABJr/AI9f+FQeJPL6ed9vbzPrj7ZivnavFuIT+KhHyc5Sf3wjY+7w/hlgZQv7PF1P70aUKa+SqTUvwP6afBXx2+CnxIZYvAHi3R9ZkfpHZ3sUz/8AfKsW/SvVq/lItvBf/BJX4rTLF4V8UeJ/hpqTH91JqUfn2yv2y2JcD3MqfUV9OaFon/BRH9krQ4vH3wI8XW3xq+HkY3+XDKb/ABCvJxHuaZMDr5Ergd1ruwvFNVrmq0VOK3lSkp29Y6SS+R5GY+HGGi1DD4qVKo9oYmm6XN5RqK9Nvsro/obor4A/ZC/4KH/Bv9qxV8MLnw54vjU+bo9443SFPvG3k4EoHdcB17rjmvv+vqcHjaGKpKth5qUX1X9aPyZ+b5rlOMy3ESwmOpOFRdH+aezT6NXTCiiiuo84KKKKACiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/Q/v4ooooAKKKKACiiigAr8tf+ChP7cWs/BEWfwD+A8R1P4k+JQkUCQr5rWUc52o+zndNIf9Up4H324wD9x/tDfGjw/wDs9fBnX/i/4jAeHRrZpI4c4M87YWKIe7yFV9gc9q/n6+B3iOb4GfCLxL/wU1+Oypq3jzxndT2nhK2uBwZptyvcBeoQBSq4xthjwPvivluIs0lSthKM+WUk5Sl/JBbtebekfM/R+BOHaeIcszxVL2kISUKdP/n7WlrGL/uxXvT8u6uizc6b8I/+CbmmRePPi9HD8Q/j7rifbktLmTz7bSGm582ZzktITyX++5+5tX5z5L8LPgv+0X/wVH12+8ZfEbxneW/2SRxB9o02eTSosdY4XRlgjYZGV++e5Jr8xvF3i7xN4+8UX/jXxney6jquqTNcXVzMcvJI5ySfQdgBwBgDgV+sP/BPX9jj9oL9oXw9H4tuvG2s+DfAVlM8VsthcyJLdSBsyCBNwREDZ3SEHLcBTgkfmuX4j+0MXHB06LdBXagna/8AenK6u+7el9Ej9+zvA/2Jls81r4uMcY7J1px5lHf93ShaVo9FFJNq8pMyPil/wRs/aj8D6dLq3gq70vxdHECxgtZGtrogf3UmAQn2EmT2r8rPEPh3xB4R1u58M+KrGfTdRsnMdxa3MbRTROOzKwBBr+674VfCnTfhNoI0DTtX1jWFAGZtYvpL2U4934X/AICAK8V/aW/Yf/Z9/areHUvibpkkerWsRhg1KxkMFyqHkBiMrIAeQJFYDJxjJr6bNPD+nOkqmAfLP+WTuvk7XX4/I/PeHvG6tSxDo5zH2lLpUhHll6uN7NelmvPY/iir2T4KftA/GD9njxMvir4Q65caTPkGWFTutrgD+GaE/I4+oyOxB5r2n9tb9jTxj+x18RYvD+pTtqmgaqrS6VqezZ5qpjfHIBwsseRuA4IIYdcD4yr80q0sRgcQ4SvCpB+jT8mvzP6Bw2JwOcYGNany1aFRdVdNdmn22aauno9T9tLO0+D/APwUr02Txd8NI4Ph38ftGT7b5NtIYLXWGh58yJwQVkBGd/8ArEP3i6fMP0R/4J7ftw6/8YZ7z9nb9oGJtN+JPhoPFIJ18p75IPlclegnj/5aKOGHzrxnH8rPhXxT4j8D+JbHxj4QvZdO1TTJkuLW5hba8UqHIIP8x0I4PFfsZ8bPEdx+0N8FvDv/AAUl+CgXSfiJ4EuYLXxZBbDALw4CXO0clMEZznMLlSf3Zr7PJM+nzyxUF+9ir1IrRVILeVtlOO+lrr5n5RxfwbRdKGXVXfDzfLRm9ZUKr+GDlq3RqP3UnfllZfy2/ptorw/9m/43aF+0X8FNA+L+gARpq1uGnhByYLlCUmiP+44IHqMHvXuFfsNGtCrTjVpu8ZJNPyZ/LWKwtXDVp4evG04Nxa7NOzX3hRRRWhzhRRRQBBdf8e0n+6f5Vx1djdf8e0n+6f5Vx1AH/9H+/iiiigAooooAKKKKAPw9/wCCvXiPWviH4q+F/wCyN4XlKT+K9TS6uQvoXFvAT7AvI3/AQe1fnF/wVO+IOnXfxx034AeDj5Xhv4ZaXb6TawKfkE7Ro0rY6bgvlofdT61+h3xNj/4Tv/gtd4Q0W/8Anh8P6THLGp6Ax21xOD/324Nfg3+0T4kufGH7QHjjxRdtukvte1GXJ9PPcKPwAAr8a4pxUpLEz6zq8n/btOK0+cpX9Uf1d4c5bCDy+lbSlh3W/wC38RNq/qoQcV5M8fjiaeRYEOGchR9TxX9svw9+GHijSvgB4I+Gnwr1ceGbGztYY728gijluhbohLLAJVeJZJJCN0jo+0Zwu4gj+JgO8REsf3l+YfUV/bf8DNVm+Mv7KtkNF1CTTZ9Z0d4Ir2D/AFls9zF8sidPmj3hhz1Fel4YyhGtiHpzWjur6e9f9Dw/H9VXQwFvgvUv62hb8Oa3zPoDwfp6aPoiaONXuNaa1Zo3ubp43nLDqrmJEXI/3QfWukmjMsTRBihYEbl6jPcZ7ivxk/4JMf8ABOv9ob9hBvFdr8ZvGOma9Yak22wttLiYGV2kMkl1dzSIkkkzcKisX8tSwDYNfs/X7Bj6NOlXlCjUU4/zJWv8j+ZsNUnOmpThyvtufj/+1Z8Hf2bPi58PviF8Avh/4wl1j4iaBZjXG0m71qfU7i3u4FMqt5VxLL5LzR70Kx7AVfJXAXH8sysGUMOh5r+vzwl+wD+y78KP2wPEX7bGn6xqFv4g8QmWa70+fUFGlrdTRmGS4EGATIY2dRvdlXe+0DPH83Nh+x58bPFev3kljpSaVYPcymGS+kEX7oudp2DL/dx/DX4Z4xZxkmCxGHxdTGRTlG0ueUU7q3S93a7S69Oh/SngTnNSjgcZhMc1CnCSlC70966dr/4U7Lq79T5Kr9MP+CWfxHsNH+P138EPF2JvDfxL0640a9gc/I0vls0Rx6kb4x/v1x3iz9hmHwV4KuPFHiLxlaWkltGzt5sBSAsBkIHL7iT0GFJJ7V8qfAnxLc+D/jd4N8V2bFJdP1vT5wR/szoT+YyK/NeD+Lcvx+Ijisuq88ackpPlklruveSvdX2ufsmavC5zlWKw9CV7xaTs1aSV4tXS1Ukmrdj9/P8Agkfrus/DD4ifFP8AY/8AEkrPJ4Z1F7y1DeiSG3mI9m2wv/wI1+5Ffhd4Ki/4Qf8A4Lb+INM0/wCSHxDpDySqOhL2cMx/8fizX7o1/RnC7ccLPDP/AJdTnBeid1+DP5M8RkqmZUselZ4ijSqv1lG0vvcWwooor6Q+BCiiigCC6/49pP8AdP8AKuOrsbr/AI9pP90/yrjqAP/S/v4ooooAKKKKACiiigD8LfiNIfBP/BbLwpq9/wDJDr2kJHGTwCZLS4gH/j0eK/Bj9oPw7c+Evj3428M3ilZLHXtRiIPoJ3x+Ywa/fL/grnoWsfDPx98K/wBrzw5EzyeGNSS0uSvokguYQfZtsy/8CFfnB/wVP+HNho/7QFp8bvCeJvDnxK0231mznQfI0vlqsoz6kbJD/v1+M8U4WUViYW1hV5/+3akVr/4FG3qz+r/DnMYTeX1b6VcP7L/t/Dzenq4Tcl5I/M2v6yP+CR3j4eLP2XbLRZZN0uku9sRnp5bMB/45sr+Tev3u/wCCJXj7yNW8T/DyZ+C6XUak9pUw36xD865uAcV7LNFTf24tfd736Hd405d9Y4cddLWlOMvk7wf/AKUvuP6Kq/P/APaa+InjJfF8vge3lez06KONgIyVM+8ZJYjkgHIx045r9AK/Gr/gsB8UPHXwg8N+AvFfgV4oWmv7u3uTJEsiyL5SsiNkZxkMeCDmvU8bsgzPN+Fa+FyrEujUUot6tKcdnBtapO6fny2ejZ/OnAOFWJzqjheVOU+ZK+yaTlfr2t8z85td/b18H6D4n1DQLrw5fSLY3Elv5okRWcxsVJKMAVyR0yTivEPHf7f3jjVFe18BaXb6PGeBPcH7RN9QMBAfqGrFP7UPwj8c3f2/4y/DuzvbxgA93ZNtd8dyGwT+Lmuvh/aP/ZT8IxC58EfD0y3Y5UzwxKAf99mlP5Cv49wvCeBwUoc3D9Sday3qRlTb73c7Wf8Aej8j+rKWVUKLV8vlKf8AiTj/AOlW+9Hw74w8ceNvHl8NX8bajc6jK2SjTsSo/wBxeFUf7orovgf4dufF3xp8H+F7NS0uoa3p8Cgf7c6A/pW98avjx4q+NmoW0mswW9jY2G/7LaWy4WPfjJLHlicD0HoBX13/AMEtPhrZeI/2jH+L3inEPh34cWE+t31w/wBxJFRliBPqPmkH/XOv3fhXCVa/1ahUoRoybV4RacYq/dKK0jq7Ky1s3uezm+PeByeviqkFBxhK0U767RirJattLTqz9H/CMg8af8Futd1DT/ni8P6OySsOxSyiiP8A49Niv3Qr8NP+CS+j6t8V/iv8V/2wdfiZD4i1B7K0LDtLJ9olUf7imFfwr9y6/oLhe88LUxPSrUnNejdl+CP5G8RWqeY0cAnd4ejSpP8AxRjd/c5NBRRRX0h8CFFFFAEF1/x7Sf7p/lXHV2N1/wAe0n+6f5Vx1AH/0/7+KKKKACiiigAooooA8M/aT+B+iftGfBLxB8INcIjGrWxFvORnyLmMh4ZB/uSAE46jI71+AfwU8N3H7SXwL8Qf8E5fjFt0r4kfD65nuvCstycbmhz5ltuPVcE4x1idWHEdf031+UX/AAUL/Yj8T/FG/sv2mP2c5H074keGtkoFufLe+jg5Taennx9Ezw6/Ie2PleI8slUtjKUOZpOM4/zwe6X96L1j5/cfpPAXEMKF8rxNX2cZSU6VR7Uq0dE3/cmvcn5dldn8r/iXw3r/AIN8Q3vhPxXZy6fqemzPb3VtMNskUsZwysPY/n1HFfe3/BL3x/8A8IP+1bptvK+2HVbeSBvdoyso/RWH419SX8fwg/4Kc6QmleIpLfwB8f8ASI/ssiXCGC11kwfLtZSNwkGMbceZH0w6Dj88tM+HvxW/ZK/aO8OQ/FvR7nQ7uw1OElpV/czQs+x2ilGUkUqTypPvivy3DYWWX46hjaT56HOrSXa+ql/LK26fy0P6LzDMYZ3lGMynEx9ni/ZyvTfV2bjKD+3BtJqS9HZn9gnxB/aM+Cvwp8XWXgj4ja/Bo+o6hB9ogW5DrG0ZYoCZNvlr8wI+Zh0r48/4KkfDey+NP7GOqeIPDUsV7L4elh1u0khYOskcOVl2MCQcwu5GDyRXwx/wVBnbVPH3gjxGeVvPDwUt2LxzOW/9Cr87tO8PfFXVdPisbDS9avNImbzLNILa4mtXfo5j2KULZwDjmvqs+4srKvi8rqYfnjays2nqlq9JX3v0P4FwfiDisjzqNanQU3RnGUbNq9rOz0ej207nxZovhrV9enMNhHwpwztwq/U+vt1qrrWlT6JqUumXBDNHj5l6EEZr7U+IHhHxF8JvEUHhL4j2Umiald2sV/Hb3Q8t2hnztbB75BDKfmVgQQCK8e0f4N/E349/FRvBvwh0a41y+YRq/kD91ECPvSyHCRqPVmFfl8aNZ1vYcj59rWd79rbn9T+HPjFnnEPE1WhmmEWEwKw8qkVJNbSppTdSSimmpO1ko2a3aueH+H/D+ueLNds/DHhi0lv9R1CZLe2toV3SSyyHCqoHUk1+yfxl8N3X7Ln7P+h/8E9/hOF1X4nfEm4gufFDWp3FBMR5dqGHRTgLzx5au5wJKtaZZ/B7/gmFpBhsJLbx78fdVi+zwQWyma00UzjbgAfMZDnGMCSToAiElvv/AP4J7fsS+LPh5q15+1H+0q76h8R/Em+ZUuSHksI5/vFj0E8g4YDiNPkH8VfeZJkVTnlhYfxpK02tqUHur7c8trdFfzt9dxdxjQ9lDMKi/wBlpvmpRejxFVfDK26o03713bmla2yv90/sw/ArRv2bvgboHwh0crK2mQZup1GPPu5Tvmk9fmcnGei4HavfKKK/YaFGFGnGlTVoxSSXkj+WMXi6uKr1MTXlec25N923dsKKKK1OcKKKKAILr/j2k/3T/KuOrsbr/j2k/wB0/wAq46gD/9T+/iiiigAooooAKKKKACiiigD87P2wf+Ccnwm/ahmbxvosh8K+NY8NHq1onyzOn3ftEYK7yMcSKVkX1IAFfnT4m8f/ALdv7L+gyfDn9rjwFb/GLwFD8q3ssf2srGOjfaAjspA6GeMMOzV/RTRXz+N4eo1akq+Hm6VR7uNrS/xRekvzPuMo45xOGoQweOpRxFCPwqd1KH/XuorSh8m0uiPwz0L/AIKEf8E3vi6miH4saHd6Xc6B5gs4tWs3vYIPNILAGFpA65UcSLxjgCvtS1/4KT/sLWVlHFZePrCGCJAqRJa3K7VHQBRFxj0xXv8A48/Zc/Zx+J0z3Xj3wPoupzyHLTS2cfnE+8iqH/WvGP8Ah23+w953n/8ACu9PznOPMn2/98+bj9K5oYTOqMpSpyoyb3k4yjJ2015Xqac/BNSbrPD4mlKW6hKlJf8AgUkpP5n5zfta/tof8Ex/jPq+k+IPHelan491HQlljtI7KGWyikWUqSkryNCzJlcgc4JPHNcZ4V+Iv7c37TGgJ8N/2Ovh7bfB7wHN8pvoo/shMZ4LfaSiMxx1MERf/ar9sPAn7LH7N3wxmS68B+BtF02eM5WaOzjMwI9JGBf9a98AAGBWSyDF16kquKrqPN8Xso8rfrN3lY9SXG+WYPDww2W4SdRQ+B4io5xjre6pRtTvfW+up+cv7H//AATg+FX7MdynjzxHMfFnjeTLvqt2vyQO/wB77OjFtpOeZGLSH1AOK/Rqiivo8FgaGEpKjh4KMV/V33fmz4LNs5xuZ4h4rHVXOb6vouyWyS6JJIKKKK6zzAooooAKKKKAILr/AI9pP90/yrjq7G6/49pP90/yrjqAP//Z"), itemEdited: true), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) } .previewLayout(.fixed(width: 360, height: 200)) } @@ -404,16 +401,16 @@ struct FramedItemView_Edited_Previews: PreviewProvider { struct FramedItemView_Deleted_Previews: PreviewProvider { static var previews: some View { Group { - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) - FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true), audioPlayer: .constant(nil), playbackState: .constant(.noPlayback), playbackTime: .constant(nil)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) + FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(true), allowMenu: Binding.constant(true)) } .previewLayout(.fixed(width: 360, height: 200)) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index d06e67e2e6..870fe30108 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -16,28 +16,20 @@ struct ChatItemView: View { var maxWidth: CGFloat = .infinity @Binding var revealed: Bool @Binding var allowMenu: Bool - @Binding var audioPlayer: AudioPlayer? - @Binding var playbackState: VoiceMessagePlaybackState - @Binding var playbackTime: TimeInterval? + init( chat: Chat, chatItem: ChatItem, showMember: Bool = false, maxWidth: CGFloat = .infinity, revealed: Binding, - allowMenu: Binding = .constant(false), - audioPlayer: Binding = .constant(nil), - playbackState: Binding = .constant(.noPlayback), - playbackTime: Binding = .constant(nil) + allowMenu: Binding = .constant(false) ) { self.chat = chat self.chatItem = chatItem self.maxWidth = maxWidth _revealed = revealed _allowMenu = allowMenu - _audioPlayer = audioPlayer - _playbackState = playbackState - _playbackTime = playbackTime } var body: some View { @@ -48,7 +40,7 @@ struct ChatItemView: View { if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) { EmojiItemView(chat: chat, chatItem: ci) } else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent { - CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime, allowMenu: $allowMenu) + CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu) } else if ci.content.msgContent == nil { ChatItemContentView(chat: chat, chatItem: chatItem, revealed: $revealed, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case } else { @@ -84,10 +76,7 @@ struct ChatItemView: View { maxWidth: maxWidth, imgWidth: adjustedMaxWidth, videoWidth: adjustedMaxWidth, - allowMenu: $allowMenu, - audioPlayer: $audioPlayer, - playbackState: $playbackState, - playbackTime: $playbackTime + allowMenu: $allowMenu ) } } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 0af460387b..5eb7861bd2 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -96,6 +96,7 @@ struct ChatView: View { } .onChange(of: chatModel.chatId) { cId in showChatInfoSheet = false + stopAudioPlayer() if let cId { if let c = chatModel.getChat(cId) { chat = c @@ -117,6 +118,7 @@ struct ChatView: View { .environmentObject(scrollModel) .onDisappear { VideoPlayerView.players.removeAll() + stopAudioPlayer() if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented { chatModel.chatId = nil DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { @@ -589,6 +591,11 @@ struct ChatView: View { } } + func stopAudioPlayer() { + VoiceItemState.chatView.values.forEach { $0.audioPlayer?.stop() } + VoiceItemState.chatView = [:] + } + @ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View { ChatItemWithMenu( chat: chat, @@ -620,10 +627,6 @@ struct ChatView: View { @State private var allowMenu: Bool = true - @State private var audioPlayer: AudioPlayer? - @State private var playbackState: VoiceMessagePlaybackState = .noPlayback - @State private var playbackTime: TimeInterval? - var revealed: Bool { chatItem == revealedChatItem } var body: some View { @@ -742,10 +745,7 @@ struct ChatView: View { chatItem: ci, maxWidth: maxWidth, revealed: .constant(revealed), - allowMenu: $allowMenu, - audioPlayer: $audioPlayer, - playbackState: $playbackState, - playbackTime: $playbackTime + allowMenu: $allowMenu ) .modifier(ChatItemClipped(ci)) .contextMenu { menu(ci, range, live: composeState.liveMessage != nil) } @@ -772,14 +772,6 @@ struct ChatView: View { } .frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment) .frame(minWidth: 0, maxWidth: .infinity, alignment: alignment) - .onDisappear { - if ci.content.msgContent?.isVoice == true { - allowMenu = true - audioPlayer?.stop() - playbackState = .noPlayback - playbackTime = TimeInterval(0) - } - } .sheet(isPresented: $showChatItemInfoSheet, onDismiss: { chatItemInfo = nil }) { diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 69dcf7501a..82d322d6fd 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -21,6 +21,7 @@ struct ChatListView: View { @State private var newChatMenuOption: NewChatMenuOption? = nil @State private var userPickerVisible = false @State private var showConnectDesktop = false + @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false var body: some View { @@ -162,6 +163,10 @@ struct ChatListView: View { chatModel.chatToTop = nil chatModel.popChat(chatId) } + stopAudioPlayer() + } + .onChange(of: chatModel.currentUser?.userId) { _ in + stopAudioPlayer() } if cs.isEmpty && !chatModel.chats.isEmpty { Text("No filtered chats").foregroundColor(theme.colors.secondary) @@ -217,6 +222,11 @@ struct ChatListView: View { } } + func stopAudioPlayer() { + VoiceItemState.smallView.values.forEach { $0.audioPlayer?.stop() } + VoiceItemState.smallView = [:] + } + private func filteredChats() -> [Chat] { if let linkChatId = searchChatFilteredBySimplexLink { return chatModel.chats.filter { $0.id == linkChatId } diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index f29e2e14e6..511c735895 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -16,6 +16,8 @@ struct ChatPreviewView: View { @Binding var progressByTimeout: Bool @State var deleting: Bool = false var darkGreen = Color(red: 0, green: 0.5, blue: 0) + @State private var activeContentPreview: ActiveContentPreview? = nil + @State private var showFullscreenGallery: Bool = false @AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true @@ -43,11 +45,38 @@ struct ChatPreviewView: View { .padding(.horizontal, 8) ZStack(alignment: .topTrailing) { - chatMessagePreview(cItem) + let chat = activeContentPreview?.chat ?? chat + let ci = activeContentPreview?.ci ?? chat.chatItems.last + let mc = ci?.content.msgContent + HStack(alignment: .top) { + let deleted = ci?.isDeletedContent == true || ci?.meta.itemDeleted != nil + let showContentPreview = (showChatPreviews && chatModel.draftChatId != chat.id && !deleted) || activeContentPreview != nil + if let ci, showContentPreview { + chatItemContentPreview(chat, ci) + } + let mcIsVoice = switch mc { case .voice: true; default: false } + if !mcIsVoice || !showContentPreview || mc?.text != "" || chatModel.draftChatId == chat.id { + let hasFilePreview = if case .file = mc { true } else { false } + chatMessagePreview(cItem, hasFilePreview) + } else { + Spacer() + chatInfoIcon(chat).frame(minWidth: 37, alignment: .trailing) + } + } + .onChange(of: chatModel.stopPreviousRecPlay?.path) { _ in + checkActiveContentPreview(chat, ci, mc) + } + .onChange(of: activeContentPreview) { _ in + checkActiveContentPreview(chat, ci, mc) + } + .onChange(of: showFullscreenGallery) { _ in + checkActiveContentPreview(chat, ci, mc) + } chatStatusImage() .padding(.top, 26) .frame(maxWidth: .infinity, alignment: .trailing) } + .frame(maxWidth: .infinity, alignment: .leading) .padding(.trailing, 8) Spacer() @@ -57,6 +86,33 @@ struct ChatPreviewView: View { .padding(.bottom, -8) .onChange(of: chatModel.deletedChats.contains(chat.chatInfo.id)) { contains in deleting = contains + // Stop voice when deleting the chat + if contains, let ci = activeContentPreview?.ci { + VoiceItemState.stopVoiceInSmallView(chat.chatInfo, ci) + } + } + + func checkActiveContentPreview(_ chat: Chat, _ ci: ChatItem?, _ mc: MsgContent?) { + let playing = chatModel.stopPreviousRecPlay + if case .voice = activeContentPreview?.mc, playing == nil { + activeContentPreview = nil + } else if activeContentPreview == nil { + if case .image = mc, let ci, let mc, showFullscreenGallery { + activeContentPreview = ActiveContentPreview(chat: chat, ci: ci, mc: mc) + } + if case .video = mc, let ci, let mc, showFullscreenGallery { + activeContentPreview = ActiveContentPreview(chat: chat, ci: ci, mc: mc) + } + if case .voice = mc, let ci, let mc, let fileSource = ci.file?.fileSource, playing?.path.hasSuffix(fileSource.filePath) == true { + activeContentPreview = ActiveContentPreview(chat: chat, ci: ci, mc: mc) + } + } else if case .voice = activeContentPreview?.mc { + if let playing, let fileSource = ci?.file?.fileSource, !playing.path.hasSuffix(fileSource.filePath) { + activeContentPreview = nil + } + } else if !showFullscreenGallery { + activeContentPreview = nil + } } } @@ -113,39 +169,46 @@ struct ChatPreviewView: View { .kerning(-2) } - private func chatPreviewLayout(_ text: Text, draft: Bool = false) -> some View { + private func chatPreviewLayout(_ text: Text?, draft: Bool = false, _ hasFilePreview: Bool = false) -> some View { ZStack(alignment: .topTrailing) { let t = text .lineLimit(2) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .topLeading) - .padding(.leading, 8) - .padding(.trailing, 36) + .padding(.leading, hasFilePreview ? 0 : 8) + .padding(.trailing, hasFilePreview ? 38 : 36) + .offset(x: hasFilePreview ? -2 : 0) if !showChatPreviews && !draft { t.privacySensitive(true).redacted(reason: .privacy) } else { t } - let s = chat.chatStats - if s.unreadCount > 0 || s.unreadChat { - unreadCountText(s.unreadCount) - .font(.caption) - .foregroundColor(.white) - .padding(.horizontal, 4) - .frame(minWidth: 18, minHeight: 18) - .background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary) - .cornerRadius(10) - } else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local { - Image(systemName: "speaker.slash.fill") - .foregroundColor(theme.colors.secondary) - } else if chat.chatInfo.chatSettings?.favorite ?? false { - Image(systemName: "star.fill") - .resizable() - .scaledToFill() - .frame(width: 18, height: 18) - .padding(.trailing, 1) - .foregroundColor(.secondary.opacity(0.65)) - } + chatInfoIcon(chat).frame(minWidth: 37, alignment: .trailing) + } + } + + @ViewBuilder private func chatInfoIcon(_ chat: Chat) -> some View { + let s = chat.chatStats + if s.unreadCount > 0 || s.unreadChat { + unreadCountText(s.unreadCount) + .font(.caption) + .foregroundColor(.white) + .padding(.horizontal, 4) + .frame(minWidth: 18, minHeight: 18) + .background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary) + .cornerRadius(10) + } else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local { + Image(systemName: "speaker.slash.fill") + .foregroundColor(theme.colors.secondary) + } else if chat.chatInfo.chatSettings?.favorite ?? false { + Image(systemName: "star.fill") + .resizable() + .scaledToFill() + .frame(width: 18, height: 18) + .padding(.trailing, 1) + .foregroundColor(.secondary.opacity(0.65)) + } else { + Color.clear.frame(width: 0) } } @@ -172,7 +235,7 @@ struct ChatPreviewView: View { func chatItemPreview(_ cItem: ChatItem) -> Text { let itemText = cItem.meta.itemDeleted == nil ? cItem.text : markedDeletedText() let itemFormattedText = cItem.meta.itemDeleted == nil ? cItem.formattedText : nil - return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: attachment(), preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) + return messageText(itemText, itemFormattedText, cItem.memberDisplayName, icon: nil, preview: true, showSecrets: false, secondaryColor: theme.colors.secondary) // same texts are in markedDeletedText in MarkedDeletedItemView, but it returns LocalizedStringKey; // can be refactored into a single function if functions calling these are changed to return same type @@ -196,11 +259,11 @@ struct ChatPreviewView: View { } } - @ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?) -> some View { + @ViewBuilder private func chatMessagePreview(_ cItem: ChatItem?, _ hasFilePreview: Bool = false) -> some View { if chatModel.draftChatId == chat.id, let draft = chatModel.draft { - chatPreviewLayout(messageDraft(draft), draft: true) + chatPreviewLayout(messageDraft(draft), draft: true, hasFilePreview) } else if let cItem = cItem { - chatPreviewLayout(itemStatusMark(cItem) + chatItemPreview(cItem)) + chatPreviewLayout(itemStatusMark(cItem) + chatItemPreview(cItem), hasFilePreview) } else { switch (chat.chatInfo) { case let .direct(contact): @@ -225,6 +288,54 @@ struct ChatPreviewView: View { } } + @ViewBuilder func chatItemContentPreview(_ chat: Chat, _ ci: ChatItem) -> some View { + let mc = ci.content.msgContent + switch mc { + case let .link(_, preview): + smallContentPreview( + ZStack(alignment: .topTrailing) { + Image(uiImage: UIImage(base64Encoded: preview.image) ?? UIImage(systemName: "arrow.up.right")!) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 36, height: 36) + ZStack { + Image(systemName: "arrow.up.right") + .resizable() + .foregroundColor(Color.white) + .font(.system(size: 15, weight: .black)) + .frame(width: 8, height: 8) + } + .frame(width: 16, height: 16) + .background(Color.black.opacity(0.25)) + .cornerRadius(8) + } + .onTapGesture { + UIApplication.shared.open(preview.uri) + } + ) + case let .image(_, image): + smallContentPreview( + CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: 36, smallView: true, showFullScreenImage: $showFullscreenGallery) + .environmentObject(ReverseListScrollModel()) + ) + case let .video(_,image, duration): + smallContentPreview( + CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: 36, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) + .environmentObject(ReverseListScrollModel()) + ) + case let .voice(_, duration): + smallContentPreviewVoice( + CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: Binding.constant(true), smallView: true) + ) + case .file: + smallContentPreviewFile( + CIFileView(file: ci.file, edited: ci.meta.itemEdited, smallView: true) + ) + default: EmptyView() + } + } + + @ViewBuilder private func groupInvitationPreviewText(_ groupInfo: GroupInfo) -> some View { groupInfo.membership.memberIncognito ? chatPreviewInfoText("join as \(groupInfo.membership.memberProfile.displayName)") @@ -294,10 +405,50 @@ struct ChatPreviewView: View { } } +func smallContentPreview(_ view: some View) -> some View { + ZStack { + view + .frame(width: 36, height: 36) + } + .cornerRadius(8) + .overlay(RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) + .strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)) + .padding([.top, .leading], 3) + .offset(x: 6) +} + +func smallContentPreviewVoice(_ view: some View) -> some View { + ZStack { + view + .frame(height: voiceMessageSizeBasedOnSquareSize(36)) + } + .padding(.leading, 8) + .padding(.top, 6) +} + +func smallContentPreviewFile(_ view: some View) -> some View { + ZStack { + view + .frame(width: 36, height: 36) + } + .padding(.top, 2) + .padding(.leading, 5) +} + func unreadCountText(_ n: Int) -> Text { Text(n > 999 ? "\(n / 1000)k" : n > 0 ? "\(n)" : "") } +private struct ActiveContentPreview: Equatable { + var chat: Chat + var ci: ChatItem + var mc: MsgContent + + static func == (lhs: ActiveContentPreview, rhs: ActiveContentPreview) -> Bool { + lhs.chat.id == rhs.chat.id && lhs.ci.id == rhs.ci.id && lhs.mc == rhs.mc + } +} + struct ChatPreviewView_Previews: PreviewProvider { static var previews: some View { Group { diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 5ad01fa6a8..0ec33d9899 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -1977,8 +1977,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/twostraws/CodeScanner"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; + kind = exactVersion; + version = 2.1.1; }; }; 8C73C1162C21E17B00892670 /* XCRemoteSwiftPackageReference "Yams" */ = { @@ -2001,8 +2001,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/kirualex/SwiftyGif"; requirement = { - branch = master; - kind = branch; + kind = revision; + revision = 5e8619335d394901379c9add5c4c1c2f420b3800; }; }; D7F0E33729964E7D0068AF69 /* XCRemoteSwiftPackageReference "lzstring-swift" */ = { diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d3e61c88f9..6d302754d3 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,4 @@ { - "originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115", "pins" : [ { "identity" : "codescanner", @@ -23,7 +22,6 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { - "branch" : "master", "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" } }, @@ -45,5 +43,5 @@ } } ], - "version" : 3 + "version" : 2 } diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 7c113e37c3..3898d1e4ea 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -3294,6 +3294,28 @@ public struct CIFile: Decodable, Hashable { } } } + + public var showStatusIconInSmallView: Bool { + get { + switch fileStatus { + case .sndStored: fileProtocol != .local + case .sndTransfer: true + case .sndComplete: false + case .sndCancelled: true + case .sndError: true + case .sndWarning: true + case .rcvInvitation: false + case .rcvAccepted: true + case .rcvTransfer: true + case .rcvAborted: true + case .rcvCancelled: true + case .rcvComplete: false + case .rcvError: true + case .rcvWarning: true + case .invalid: true + } + } + } } public struct CryptoFile: Codable, Hashable { From ea1298278853188d0867dff92c23c8adcd603471 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:53:10 +0700 Subject: [PATCH 52/57] ios: fix two-line preview on IOS 17+ (#4514) --- apps/ios/Shared/Views/ChatList/ChatPreviewView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index 511c735895..ce638e8a0a 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -178,6 +178,7 @@ struct ChatPreviewView: View { .padding(.leading, hasFilePreview ? 0 : 8) .padding(.trailing, hasFilePreview ? 38 : 36) .offset(x: hasFilePreview ? -2 : 0) + .fixedSize(horizontal: false, vertical: true) if !showChatPreviews && !draft { t.privacySensitive(true).redacted(reason: .privacy) } else { From 70d577260b5aca9e996480f6caaa44bfe1363b9d Mon Sep 17 00:00:00 2001 From: Arturs Krumins Date: Thu, 25 Jul 2024 22:19:44 +0300 Subject: [PATCH 53/57] ios: fix crash when changing screen orientation with tiled wallpaper (#4511) * ios: fix crash, when displaying wallpaper * simpler --------- Co-authored-by: Evgeny Poberezkin --- apps/ios/Shared/Views/Helpers/ChatWallpaper.swift | 2 ++ .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift b/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift index 6eef843d37..76b0102a6c 100644 --- a/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift +++ b/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift @@ -23,6 +23,8 @@ struct ChatViewBackground: ViewModifier { var image = context.resolve(image) let rect = CGRectMake(0, 0, size.width, size.height) func repeatDraw(_ imageScale: CGFloat) { + // Prevent range bounds crash and dividing by zero + if size.height == 0 || size.width == 0 || image.size.height == 0 || image.size.width == 0 { return } image.shading = .color(tint) let scale = imageScale * 1.57 // for some reason a wallpaper on iOS looks smaller than on Android for h in 0 ... Int(size.height / image.size.height / scale) { diff --git a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6d302754d3..d3e61c88f9 100644 --- a/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/ios/SimpleX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "e2611d1e91fd8071abc106776ba14ee2e395d2ad08a78e073381294abc10f115", "pins" : [ { "identity" : "codescanner", @@ -22,6 +23,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/kirualex/SwiftyGif", "state" : { + "branch" : "master", "revision" : "5e8619335d394901379c9add5c4c1c2f420b3800" } }, @@ -43,5 +45,5 @@ } } ], - "version" : 2 + "version" : 3 } From a966f6b19db4e051578b621aefcc79dad0ffa9d1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 26 Jul 2024 14:38:22 +0700 Subject: [PATCH 54/57] ios: blur for media (#4512) * ios: blur for media * line * one more place * changes for video * using notification center * change * unused code * string * simplify * refactor ifs * fix --------- Co-authored-by: Evgeny Poberezkin --- .../Views/Chat/ChatItem/CIImageView.swift | 24 +++++++- .../Views/Chat/ChatItem/CILinkView.swift | 2 + .../Views/Chat/ChatItem/CIVideoView.swift | 55 +++++++++++-------- apps/ios/Shared/Views/Chat/ReverseList.swift | 3 +- .../Shared/Views/Helpers/ViewModifiers.swift | 34 ++++++++++++ .../Views/UserSettings/AppSettings.swift | 2 + .../Views/UserSettings/PrivacySettings.swift | 17 ++++++ .../Views/UserSettings/SettingsView.swift | 2 + apps/ios/SimpleXChat/APITypes.swift | 3 + 9 files changed, 116 insertions(+), 26 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index b247a33565..3966d7e258 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -17,6 +17,7 @@ struct CIImageView: View { var imgWidth: CGFloat? var smallView: Bool = false @Binding var showFullScreenImage: Bool + @State private var blurred: Bool = UserDefaults.standard.integer(forKey: DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) > 0 var body: some View { let file = chatItem.file @@ -26,12 +27,21 @@ struct CIImageView: View { .fullScreenCover(isPresented: $showFullScreenImage) { FullScreenMediaView(chatItem: chatItem, image: uiImage, showView: $showFullScreenImage) } + .if(!smallView) { view in + view.modifier(PrivacyBlur(blurred: $blurred)) + } .onTapGesture { showFullScreenImage = true } .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenImage = false } } else if let preview { - Group { if smallView { smallViewImageView(preview) } else { imageView(preview) } } + Group { + if smallView { + smallViewImageView(preview) + } else { + imageView(preview).modifier(PrivacyBlur(blurred: $blurred)) + } + } .onTapGesture { if let file = file { switch file.fileStatus { @@ -102,7 +112,9 @@ struct CIImageView: View { .frame(width: w, height: w * img.size.height / img.size.width) .scaledToFit() } - loadingIndicator() + if !blurred || !showDownloadButton(chatItem.file?.fileStatus) { + loadingIndicator() + } } } @@ -166,4 +178,12 @@ struct CIImageView: View { .tint(.white) .padding(8) } + + private func showDownloadButton(_ fileStatus: CIFileStatus?) -> Bool { + switch fileStatus { + case .rcvInvitation: true + case .rcvAborted: true + default: false + } + } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift index 3e6ef4abff..3c864ab172 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CILinkView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct CILinkView: View { @EnvironmentObject var theme: AppTheme let linkPreview: LinkPreview + @State private var blurred: Bool = UserDefaults.standard.integer(forKey: DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) > 0 var body: some View { VStack(alignment: .center, spacing: 6) { @@ -19,6 +20,7 @@ struct CILinkView: View { Image(uiImage: uiImage) .resizable() .scaledToFit() + .modifier(PrivacyBlur(blurred: $blurred)) } VStack(alignment: .leading, spacing: 6) { Text(linkPreview.title) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index 4cbbbbb689..4670fc685f 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -31,6 +31,7 @@ struct CIVideoView: View { @State private var fullScreenTimeObserver: Any? = nil @State private var publisher: AnyCancellable? = nil private var sizeMultiplier: CGFloat { smallView ? 0.38 : 1 } + @State private var blurred: Bool = UserDefaults.standard.integer(forKey: DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) > 0 init(chatItem: ChatItem, preview: UIImage?, duration: Int, maxWidth: CGFloat, videoWidth: CGFloat?, smallView: Bool = false, showFullscreenPlayer: Binding) { self.chatItem = chatItem @@ -85,17 +86,19 @@ struct CIVideoView: View { durationProgress() } } - if !smallView, let file = file, showDownloadButton(file.fileStatus) { - Button { - receiveFileIfValidSize(file: file, receiveFile: receiveFile) - } label: { - playPauseIcon("play.fill") - } - } else if let file = file, showDownloadButton(file.fileStatus) && !file.showStatusIconInSmallView { - playPauseIcon("play.fill") - .onTapGesture { + if !blurred, let file, showDownloadButton(file.fileStatus) { + if !smallView { + Button { receiveFileIfValidSize(file: file, receiveFile: receiveFile) + } label: { + playPauseIcon("play.fill") } + } else if !file.showStatusIconInSmallView { + playPauseIcon("play.fill") + .onTapGesture { + receiveFileIfValidSize(file: file, receiveFile: receiveFile) + } + } } } .fullScreenCover(isPresented: $showFullScreenPlayer) { @@ -135,7 +138,7 @@ struct CIVideoView: View { } } - private func showDownloadButton(_ fileStatus: CIFileStatus) -> Bool { + private func showDownloadButton(_ fileStatus: CIFileStatus?) -> Bool { switch fileStatus { case .rcvInvitation: true case .rcvAborted: true @@ -156,20 +159,22 @@ struct CIVideoView: View { .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } - if !decryptionInProgress { - Button { - decrypt(file: file) { - if urlDecrypted != nil { - videoPlaying = true - player?.play() + if !blurred { + if !decryptionInProgress { + Button { + decrypt(file: file) { + if urlDecrypted != nil { + videoPlaying = true + player?.play() + } } + } label: { + playPauseIcon(canBePlayed ? "play.fill" : "play.slash") } - } label: { - playPauseIcon(canBePlayed ? "play.fill" : "play.slash") + .disabled(!canBePlayed) + } else { + videoDecryptionProgress() } - .disabled(!canBePlayed) - } else { - videoDecryptionProgress() } } } @@ -188,6 +193,7 @@ struct CIVideoView: View { videoPlaying = false } } + .modifier(PrivacyBlur(enabled: !videoPlaying, blurred: $blurred)) .onTapGesture { switch player.timeControlStatus { case .playing: @@ -203,7 +209,7 @@ struct CIVideoView: View { .onChange(of: m.activeCallViewIsCollapsed) { _ in showFullScreenPlayer = false } - if !videoPlaying { + if !videoPlaying && !blurred { Button { m.stopPreviousRecPlay = url player.play() @@ -317,7 +323,10 @@ struct CIVideoView: View { .resizable() .scaledToFit() .frame(width: w) - fileStatusIcon() + .modifier(PrivacyBlur(blurred: $blurred)) + if !blurred || !showDownloadButton(chatItem.file?.fileStatus) { + fileStatusIcon() + } } } diff --git a/apps/ios/Shared/Views/Chat/ReverseList.swift b/apps/ios/Shared/Views/Chat/ReverseList.swift index ae6d900eb6..a3f485cb5e 100644 --- a/apps/ios/Shared/Views/Chat/ReverseList.swift +++ b/apps/ios/Shared/Views/Chat/ReverseList.swift @@ -129,8 +129,9 @@ struct ReverseList: UIV from: nil, for: nil ) + NotificationCenter.default.post(name: .chatViewWillBeginScrolling, object: nil) } - + /// Scrolls up func scrollToNextPage() { tableView.setContentOffset( diff --git a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift index 7e2655f4f7..fee0f262cb 100644 --- a/apps/ios/Shared/Views/Helpers/ViewModifiers.swift +++ b/apps/ios/Shared/Views/Helpers/ViewModifiers.swift @@ -17,3 +17,37 @@ extension View { } } } + +extension Notification.Name { + static let chatViewWillBeginScrolling = Notification.Name("chatWillBeginScrolling") +} + +struct PrivacyBlur: ViewModifier { + var enabled: Bool = true + @Binding var blurred: Bool + @AppStorage(DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) private var blurRadius: Int = 0 + + func body(content: Content) -> some View { + if blurRadius > 0 { + // parallel ifs are necessary here because otherwise some views flicker, + // e.g. when playing video + content + .blur(radius: blurred && enabled ? CGFloat(blurRadius) * 0.5 : 0) + .overlay { + if (blurred && enabled) { + Color.clear.contentShape(Rectangle()) + .onTapGesture { + blurred = false + } + } + } + .onReceive(NotificationCenter.default.publisher(for: .chatViewWillBeginScrolling)) { _ in + if !blurred { + blurred = true + } + } + } else { + content + } + } +} diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift index 8c68d70526..ac81c42b2e 100644 --- a/apps/ios/Shared/Views/UserSettings/AppSettings.swift +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -32,6 +32,7 @@ extension AppSettings { if let val = privacyShowChatPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) } if let val = privacySaveLastDraft { def.setValue(val, forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) } if let val = privacyProtectScreen { def.setValue(val, forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) } + if let val = privacyMediaBlurRadius { def.setValue(val, forKey: DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) } if let val = notificationMode { ChatModel.shared.notificationMode = val.toNotificationsMode() } if let val = notificationPreviewMode { ntfPreviewModeGroupDefault.set(val) } if let val = webrtcPolicyRelay { def.setValue(val, forKey: DEFAULT_WEBRTC_POLICY_RELAY) } @@ -62,6 +63,7 @@ extension AppSettings { c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) c.privacySaveLastDraft = def.bool(forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) c.privacyProtectScreen = def.bool(forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) + c.privacyMediaBlurRadius = def.integer(forKey: DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) c.notificationMode = AppSettingsNotificationMode.from(ChatModel.shared.notificationMode) c.notificationPreviewMode = ntfPreviewModeGroupDefault.get() c.webrtcPolicyRelay = def.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY) diff --git a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift index 879ee301f2..6b1a619c18 100644 --- a/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift +++ b/apps/ios/Shared/Views/UserSettings/PrivacySettings.swift @@ -22,6 +22,7 @@ struct PrivacySettings: View { @AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @State private var currentLAMode = privacyLocalAuthModeDefault.get() + @AppStorage(DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS) private var privacyMediaBlurRadius: Int = 0 @State private var contactReceipts = false @State private var contactReceiptsReset = false @State private var contactReceiptsOverrides = 0 @@ -113,6 +114,22 @@ struct PrivacySettings: View { privacyAcceptImagesGroupDefault.set($0) } } + settingsRow("circle.rectangle.filled.pattern.diagonalline", color: theme.colors.secondary) { + Picker("Blur media", selection: $privacyMediaBlurRadius) { + let values = [0, 12, 24, 48] + ([0, 12, 24, 48].contains(privacyMediaBlurRadius) ? [] : [privacyMediaBlurRadius]) + ForEach(values, id: \.self) { radius in + let text: String = switch radius { + case 0: NSLocalizedString("Off", comment: "blur media") + case 12: NSLocalizedString("Soft", comment: "blur media") + case 24: NSLocalizedString("Medium", comment: "blur media") + case 48: NSLocalizedString("Strong", comment: "blur media") + default: "\(radius)" + } + Text(text) + } + } + } + .frame(height: 36) settingsRow("network.badge.shield.half.filled", color: theme.colors.secondary) { Toggle("Protect IP address", isOn: $askToApproveRelays) } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index b195efb985..0a83db1e5c 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -34,6 +34,7 @@ let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews" let DEFAULT_PRIVACY_SAVE_LAST_DRAFT = "privacySaveLastDraft" let DEFAULT_PRIVACY_PROTECT_SCREEN = "privacyProtectScreen" let DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET = "privacyDeliveryReceiptsSet" +let DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS = "privacyMediaBlurRadius" let DEFAULT_EXPERIMENTAL_CALLS = "experimentalCalls" let DEFAULT_CHAT_ARCHIVE_NAME = "chatArchiveName" let DEFAULT_CHAT_ARCHIVE_TIME = "chatArchiveTime" @@ -87,6 +88,7 @@ let appDefaults: [String: Any] = [ DEFAULT_PRIVACY_SAVE_LAST_DRAFT: true, DEFAULT_PRIVACY_PROTECT_SCREEN: false, DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET: false, + DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS: 0, DEFAULT_EXPERIMENTAL_CALLS: false, DEFAULT_CHAT_V3_DB_MIGRATION: V3DBMigrationState.offer.rawValue, DEFAULT_DEVELOPER_TOOLS: false, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 8ed3d63e53..c1263f26e2 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -2096,6 +2096,7 @@ public struct AppSettings: Codable, Equatable, Hashable { public var privacyShowChatPreviews: Bool? = nil public var privacySaveLastDraft: Bool? = nil public var privacyProtectScreen: Bool? = nil + public var privacyMediaBlurRadius: Int? = nil public var notificationMode: AppSettingsNotificationMode? = nil public var notificationPreviewMode: NotificationPreviewMode? = nil public var webrtcPolicyRelay: Bool? = nil @@ -2125,6 +2126,7 @@ public struct AppSettings: Codable, Equatable, Hashable { if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } + if privacyMediaBlurRadius != def.privacyMediaBlurRadius { empty.privacyMediaBlurRadius = privacyMediaBlurRadius } if notificationMode != def.notificationMode { empty.notificationMode = notificationMode } if notificationPreviewMode != def.notificationPreviewMode { empty.notificationPreviewMode = notificationPreviewMode } if webrtcPolicyRelay != def.webrtcPolicyRelay { empty.webrtcPolicyRelay = webrtcPolicyRelay } @@ -2155,6 +2157,7 @@ public struct AppSettings: Codable, Equatable, Hashable { privacyShowChatPreviews: true, privacySaveLastDraft: true, privacyProtectScreen: false, + privacyMediaBlurRadius: 0, notificationMode: AppSettingsNotificationMode.instant, notificationPreviewMode: NotificationPreviewMode.message, webrtcPolicyRelay: true, From a53333be20ed9b784b948e61f1b6ff49dc021ffd Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 26 Jul 2024 09:11:42 +0100 Subject: [PATCH 55/57] ios: increase wallpaper scale (#4517) --- apps/ios/Shared/Views/Helpers/ChatWallpaper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift b/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift index 76b0102a6c..d8ddc1c306 100644 --- a/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift +++ b/apps/ios/Shared/Views/Helpers/ChatWallpaper.swift @@ -26,7 +26,7 @@ struct ChatViewBackground: ViewModifier { // Prevent range bounds crash and dividing by zero if size.height == 0 || size.width == 0 || image.size.height == 0 || image.size.width == 0 { return } image.shading = .color(tint) - let scale = imageScale * 1.57 // for some reason a wallpaper on iOS looks smaller than on Android + let scale = imageScale * 2.5 // scale wallpaper for iOS for h in 0 ... Int(size.height / image.size.height / scale) { for w in 0 ... Int(size.width / image.size.width / scale) { let rect = CGRectMake(CGFloat(w) * image.size.width * scale, CGFloat(h) * image.size.height * scale, image.size.width * scale, image.size.height * scale) From 032c5d3a5b56bf78cf896b5121e56112b006afa1 Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 26 Jul 2024 15:29:13 +0700 Subject: [PATCH 56/57] android, desktop: blur for media (#4508) * android, desktop: blur for media * change * new option and applied blur to other elements * new line * added to migration * long click handling * hover on desktop * changes * change * showDownloadButton function * file rename * don't blur when menu is visible * rename --- .../common/platform/Modifier.android.kt | 2 + .../chat/simplex/common/model/SimpleXAPI.kt | 7 ++ .../chat/simplex/common/platform/Modifier.kt | 76 ++++++++++++++++++- .../simplex/common/views/chat/ChatView.kt | 8 ++ .../common/views/chat/item/CIImageView.kt | 25 ++++-- .../item/{CIVIdeoView.kt => CIVideoView.kt} | 67 +++++++++++----- .../common/views/chat/item/FramedItemView.kt | 2 +- .../common/views/helpers/LinkPreviews.kt | 19 +++-- .../views/usersettings/PrivacySettings.kt | 30 ++++++++ .../commonMain/resources/MR/base/strings.xml | 5 ++ .../resources/MR/images/ic_blur_on.svg | 1 + .../common/platform/Modifier.desktop.kt | 8 +- 12 files changed, 216 insertions(+), 34 deletions(-) rename apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/{CIVIdeoView.kt => CIVideoView.kt} (88%) create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt index b103367fe8..2ff2a3e021 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Modifier.android.kt @@ -28,3 +28,5 @@ actual fun Modifier.desktopOnExternalDrag( actual fun Modifier.onRightClick(action: () -> Unit): Modifier = this actual fun Modifier.desktopPointerHoverIconHand(): Modifier = this + +actual fun Modifier.desktopOnHovered(action: (Boolean) -> Unit): Modifier = Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 71ea873ddd..cac6a7082d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -116,6 +116,7 @@ class AppPreferences { val privacyDeliveryReceiptsSet = mkBoolPreference(SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET, false) val privacyEncryptLocalFiles = mkBoolPreference(SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES, true) val privacyAskToApproveRelays = mkBoolPreference(SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS, true) + val privacyMediaBlurRadius = mkIntPreference(SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS, 0) val experimentalCalls = mkBoolPreference(SHARED_PREFS_EXPERIMENTAL_CALLS, false) val showUnreadAndFavorites = mkBoolPreference(SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES, false) val chatArchiveName = mkStrPreference(SHARED_PREFS_CHAT_ARCHIVE_NAME, null) @@ -328,6 +329,7 @@ class AppPreferences { private const val SHARED_PREFS_PRIVACY_DELIVERY_RECEIPTS_SET = "PrivacyDeliveryReceiptsSet" private const val SHARED_PREFS_PRIVACY_ENCRYPT_LOCAL_FILES = "PrivacyEncryptLocalFiles" private const val SHARED_PREFS_PRIVACY_ASK_TO_APPROVE_RELAYS = "PrivacyAskToApproveRelays" + private const val SHARED_PREFS_PRIVACY_MEDIA_BLUR_RADIUS = "PrivacyMediaBlurRadius" const val SHARED_PREFS_PRIVACY_FULL_BACKUP = "FullBackup" private const val SHARED_PREFS_EXPERIMENTAL_CALLS = "ExperimentalCalls" private const val SHARED_PREFS_SHOW_UNREAD_AND_FAVORITES = "ShowUnreadAndFavorites" @@ -5998,6 +6000,7 @@ data class AppSettings( var privacyShowChatPreviews: Boolean? = null, var privacySaveLastDraft: Boolean? = null, var privacyProtectScreen: Boolean? = null, + var privacyMediaBlurRadius: Int? = null, var notificationMode: AppSettingsNotificationMode? = null, var notificationPreviewMode: AppSettingsNotificationPreviewMode? = null, var webrtcPolicyRelay: Boolean? = null, @@ -6027,6 +6030,7 @@ data class AppSettings( if (privacyShowChatPreviews != def.privacyShowChatPreviews) { empty.privacyShowChatPreviews = privacyShowChatPreviews } if (privacySaveLastDraft != def.privacySaveLastDraft) { empty.privacySaveLastDraft = privacySaveLastDraft } if (privacyProtectScreen != def.privacyProtectScreen) { empty.privacyProtectScreen = privacyProtectScreen } + if (privacyMediaBlurRadius != def.privacyMediaBlurRadius) { empty.privacyMediaBlurRadius = privacyMediaBlurRadius } if (notificationMode != def.notificationMode) { empty.notificationMode = notificationMode } if (notificationPreviewMode != def.notificationPreviewMode) { empty.notificationPreviewMode = notificationPreviewMode } if (webrtcPolicyRelay != def.webrtcPolicyRelay) { empty.webrtcPolicyRelay = webrtcPolicyRelay } @@ -6064,6 +6068,7 @@ data class AppSettings( privacyShowChatPreviews?.let { def.privacyShowChatPreviews.set(it) } privacySaveLastDraft?.let { def.privacySaveLastDraft.set(it) } privacyProtectScreen?.let { def.privacyProtectScreen.set(it) } + privacyMediaBlurRadius?.let { def.privacyMediaBlurRadius.set(it) } notificationMode?.let { def.notificationsMode.set(it.toNotificationsMode()) } notificationPreviewMode?.let { def.notificationPreviewMode.set(it.toNotificationPreviewMode().name) } webrtcPolicyRelay?.let { def.webrtcPolicyRelay.set(it) } @@ -6094,6 +6099,7 @@ data class AppSettings( privacyShowChatPreviews = true, privacySaveLastDraft = true, privacyProtectScreen = false, + privacyMediaBlurRadius = 0, notificationMode = AppSettingsNotificationMode.INSTANT, notificationPreviewMode = AppSettingsNotificationPreviewMode.MESSAGE, webrtcPolicyRelay = true, @@ -6125,6 +6131,7 @@ data class AppSettings( privacyShowChatPreviews = def.privacyShowChatPreviews.get(), privacySaveLastDraft = def.privacySaveLastDraft.get(), privacyProtectScreen = def.privacyProtectScreen.get(), + privacyMediaBlurRadius = def.privacyMediaBlurRadius.get(), notificationMode = AppSettingsNotificationMode.from(def.notificationsMode.get()), notificationPreviewMode = AppSettingsNotificationPreviewMode.from(NotificationPreviewMode.valueOf(def.notificationPreviewMode.get()!!)), webrtcPolicyRelay = def.webrtcPolicyRelay.get(), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt index 4a10027746..6683ea7d33 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Modifier.kt @@ -1,8 +1,17 @@ package chat.simplex.common.platform -import androidx.compose.runtime.Composable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.BlurredEdgeTreatment +import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.dp +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.views.helpers.KeyChangeEffect +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.filter import java.io.File expect fun Modifier.navigationBarsWithImePadding(): Modifier @@ -25,3 +34,68 @@ expect fun Modifier.desktopOnExternalDrag( expect fun Modifier.onRightClick(action: () -> Unit): Modifier expect fun Modifier.desktopPointerHoverIconHand(): Modifier + +expect fun Modifier.desktopOnHovered(action: (Boolean) -> Unit): Modifier + +@Composable +fun Modifier.desktopModifyBlurredState(enabled: Boolean, blurred: MutableState, showMenu: State,): Modifier { + val blurRadius = remember { appPrefs.privacyMediaBlurRadius.state } + if (appPlatform.isDesktop) { + KeyChangeEffect(blurRadius.value) { + blurred.value = enabled && blurRadius.value > 0 + } + } + return if (appPlatform.isDesktop && enabled && blurRadius.value > 0 && !showMenu.value) { + var job: Job = remember { Job() } + LaunchedEffect(Unit) { + // The approach here is to allow menu to show up and to not blur the view. When menu is shown and mouse is hovering, + // unhovered action is still received, but we don't need to handle it until menu closes. When it closes, it takes one frame to catch a + // hover action again and if: + // 1. mouse is still on the view, the hover action will cancel this coroutine and the view will stay unblurred + // 2. mouse is not on the view, the view will become blurred after 100 ms + job = launch { + delay(100) + blurred.value = true + } + } + this then Modifier.desktopOnHovered { hovered -> + job.cancel() + blurred.value = !hovered && !showMenu.value + } + } else { + this + } +} + +@Composable +fun Modifier.privacyBlur( + enabled: Boolean, + blurred: MutableState = remember { mutableStateOf(appPrefs.privacyMediaBlurRadius.get() > 0) }, + scrollState: State, + onLongClick: () -> Unit = {} +): Modifier { + val blurRadius = remember { appPrefs.privacyMediaBlurRadius.state } + return if (enabled && blurred.value) { + this then Modifier.blur( + radiusX = remember { appPrefs.privacyMediaBlurRadius.state }.value.dp, + radiusY = remember { appPrefs.privacyMediaBlurRadius.state }.value.dp, + edgeTreatment = BlurredEdgeTreatment(RoundedCornerShape(0.dp)) + ) + .combinedClickable( + onLongClick = onLongClick, + onClick = { + blurred.value = false + } + ) + } else if (enabled && blurRadius.value > 0 && appPlatform.isAndroid) { + LaunchedEffect(Unit) { + snapshotFlow { scrollState.value } + .filter { it } + .filter { !blurred.value } + .collect { blurred.value = true } + } + this + } else { + this + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index cb563c4807..610d8d95e9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1113,6 +1113,12 @@ fun BoxWithConstraintsScope.ChatItemsList( } } FloatingButtons(chatModel.chatItems, unreadCount, chat.chatStats.minUnreadItemId, searchValue, markRead, setFloatingButton, listState) + LaunchedEffect(Unit) { + snapshotFlow { listState.isScrollInProgress } + .collect { + chatViewScrollState.value = it + } + } } @Composable @@ -1326,6 +1332,8 @@ private fun TopEndFloatingButton( } } +val chatViewScrollState = MutableStateFlow(false) + private fun bottomEndFloatingButton( unreadCount: Int, showButtonWithCounter: Boolean, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 59740711fe..e234a73136 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -1,6 +1,8 @@ package chat.simplex.common.views.chat.item import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* @@ -17,8 +19,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.DEFAULT_MAX_IMAGE_WIDTH +import chat.simplex.common.views.chat.chatViewScrollState import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.runBlocking @@ -32,6 +36,7 @@ fun CIImageView( smallView: Boolean, receiveFile: (Long) -> Unit ) { + val blurred = remember { mutableStateOf(appPrefs.privacyMediaBlurRadius.get() > 0) } @Composable fun progressIndicator() { CircularProgressIndicator( @@ -105,7 +110,8 @@ fun CIImageView( onLongClick = { showMenu.value = true }, onClick = onClick ) - .onRightClick { showMenu.value = true }, + .onRightClick { showMenu.value = true } + .privacyBlur(!smallView, blurred, scrollState = chatViewScrollState.collectAsState(), onLongClick = { showMenu.value = true }), contentScale = if (smallView) ContentScale.Crop else ContentScale.FillWidth, ) } @@ -128,7 +134,8 @@ fun CIImageView( onLongClick = { showMenu.value = true }, onClick = onClick ) - .onRightClick { showMenu.value = true }, + .onRightClick { showMenu.value = true } + .privacyBlur(!smallView, blurred, scrollState = chatViewScrollState.collectAsState(), onLongClick = { showMenu.value = true }), contentScale = if (smallView) ContentScale.Crop else ContentScale.FillWidth, ) } else { @@ -138,7 +145,8 @@ fun CIImageView( onLongClick = { showMenu.value = true }, onClick = {} ) - .onRightClick { showMenu.value = true }, + .onRightClick { showMenu.value = true } + .privacyBlur(!smallView, blurred, scrollState = chatViewScrollState.collectAsState(), onLongClick = { showMenu.value = true }), contentAlignment = Alignment.Center ) { imageView(base64ToBitmap(image), onClick = { @@ -174,7 +182,8 @@ fun CIImageView( } Box( - Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), + Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID) + .desktopModifyBlurredState(!smallView, blurred, showMenu), contentAlignment = Alignment.TopEnd ) { val res: MutableState?> = remember { @@ -256,9 +265,10 @@ fun CIImageView( } }) } - if (!smallView) { + // Do not show download icon when the view is blurred + if (!smallView && (!showDownloadButton(file?.fileStatus) || !blurred.value)) { loadingIndicator() - } else if (file?.showStatusIconInSmallView == true) { + } else if (smallView && file?.showStatusIconInSmallView == true) { Box(Modifier.align(Alignment.Center)) { loadingIndicator() } @@ -266,6 +276,9 @@ fun CIImageView( } } +private fun showDownloadButton(status: CIFileStatus?): Boolean = + status is CIFileStatus.RcvInvitation || status is CIFileStatus.RcvAborted + @Composable expect fun SimpleAndAnimatedImageView( data: ByteArray, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt similarity index 88% rename from apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt rename to apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt index dd6e8a28fd..ca93349092 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVideoView.kt @@ -19,7 +19,9 @@ import chat.simplex.res.MR import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.chatViewScrollState import dev.icerock.moko.resources.StringResource import java.io.File import java.net.URI @@ -34,8 +36,10 @@ fun CIVideoView( smallView: Boolean = false, receiveFile: (Long) -> Unit ) { + val blurred = remember { mutableStateOf(appPrefs.privacyMediaBlurRadius.get() > 0) } Box( - Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID), + Modifier.layoutId(CHAT_IMAGE_LAYOUT_ID) + .desktopModifyBlurredState(!smallView, blurred, showMenu), contentAlignment = Alignment.TopEnd ) { val preview = remember(image) { base64ToBitmap(image) } @@ -68,15 +72,15 @@ fun CIVideoView( if (decrypted != null && smallView) { SmallVideoView(decrypted, file, preview, duration * 1000L, autoPlay, sizeMultiplier, openFullscreen = openFullscreen) } else if (decrypted != null) { - VideoView(decrypted, file, preview, duration * 1000L, autoPlay, showMenu, openFullscreen = openFullscreen) + VideoView(decrypted, file, preview, duration * 1000L, autoPlay, showMenu, blurred, openFullscreen = openFullscreen) } else if (smallView) { SmallVideoViewEncrypted(uriDecrypted, file, preview, autoPlay, showMenu, sizeMultiplier, openFullscreen = openFullscreen) } else { - VideoViewEncrypted(uriDecrypted, file, preview, duration * 1000L, autoPlay, showMenu, openFullscreen = openFullscreen) + VideoViewEncrypted(uriDecrypted, file, preview, duration * 1000L, autoPlay, showMenu, blurred, openFullscreen = openFullscreen) } } else { Box { - VideoPreviewImageView(preview, onClick = { + VideoPreviewImageView(preview, blurred = blurred, onClick = { if (file != null) { when (file.fileStatus) { CIFileStatus.RcvInvitation, CIFileStatus.RcvAborted -> @@ -109,14 +113,15 @@ fun CIVideoView( if (file != null && !smallView) { DurationProgress(file, remember { mutableStateOf(false) }, remember { mutableStateOf(duration * 1000L) }, remember { mutableStateOf(0L) }/*, soundEnabled*/) } - if (file?.fileStatus is CIFileStatus.RcvInvitation || file?.fileStatus is CIFileStatus.RcvAborted) { + if (showDownloadButton(file?.fileStatus) && !blurred.value && file != null) { PlayButton(error = false, sizeMultiplier, { showMenu.value = true }) { receiveFileIfValidSize(file, receiveFile) } } } } - if (!smallView) { + // Do not show download icon when the view is blurred + if (!smallView && (!showDownloadButton(file?.fileStatus) || !blurred.value)) { fileStatusIcon(file, false) - } else if (file?.showStatusIconInSmallView == true) { + } else if (smallView && file?.showStatusIconInSmallView == true) { Box(Modifier.align(Alignment.Center)) { fileStatusIcon(file, true) } @@ -132,15 +137,16 @@ private fun VideoViewEncrypted( defaultDuration: Long, autoPlay: MutableState, showMenu: MutableState, + blurred: MutableState, openFullscreen: () -> Unit, ) { var decryptionInProgress by rememberSaveable(file.fileName) { mutableStateOf(false) } val onLongClick = { showMenu.value = true } Box { - VideoPreviewImageView(defaultPreview, smallView = false, if (decryptionInProgress) {{}} else openFullscreen, onLongClick) + VideoPreviewImageView(defaultPreview, smallView = false, blurred = blurred, if (decryptionInProgress) {{}} else openFullscreen, onLongClick) if (decryptionInProgress) { VideoDecryptionProgress(1f, onLongClick = onLongClick) - } else { + } else if (!blurred.value) { PlayButton(false, 1f, onLongClick = onLongClick) { decryptionInProgress = true withBGApi { @@ -170,7 +176,7 @@ private fun SmallVideoViewEncrypted( var decryptionInProgress by rememberSaveable(file.fileName) { mutableStateOf(false) } val onLongClick = { showMenu.value = true } Box { - VideoPreviewImageView(defaultPreview, smallView = true, if (decryptionInProgress) {{}} else openFullscreen, onLongClick) + VideoPreviewImageView(defaultPreview, smallView = true, blurred = remember { mutableStateOf(false) }, onClick = if (decryptionInProgress) {{}} else openFullscreen, onLongClick = onLongClick) if (decryptionInProgress) { VideoDecryptionProgress(sizeMultiplier, onLongClick = onLongClick) } else if (!file.showStatusIconInSmallView) { @@ -190,7 +196,15 @@ private fun SmallVideoViewEncrypted( } @Composable -private fun SmallVideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defaultDuration: Long, autoPlay: MutableState, sizeMultiplier: Float, openFullscreen: () -> Unit) { +private fun SmallVideoView( + uri: URI, + file: CIFile, + defaultPreview: ImageBitmap, + defaultDuration: Long, + autoPlay: MutableState, + sizeMultiplier: Float, + openFullscreen: () -> Unit +) { val player = remember(uri) { VideoPlayerHolder.getOrCreate(uri, true, defaultPreview, defaultDuration, true) } val preview by remember { player.preview } // val soundEnabled by rememberSaveable(uri.path) { player.soundEnabled } @@ -205,7 +219,7 @@ private fun SmallVideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, onLongClick = {}, {} ) - VideoPreviewImageView(preview, smallView = true, openFullscreen, onLongClick = {}) + VideoPreviewImageView(preview, smallView = true, blurred = remember { mutableStateOf(false) }, onClick = openFullscreen, onLongClick = {}) if (!file.showStatusIconInSmallView) { PlayButton(brokenVideo, sizeMultiplier, onLongClick = {}, onClick = openFullscreen) } @@ -216,7 +230,16 @@ private fun SmallVideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, } @Composable -private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defaultDuration: Long, autoPlay: MutableState, showMenu: MutableState, openFullscreen: () -> Unit) { +private fun VideoView( + uri: URI, + file: CIFile, + defaultPreview: ImageBitmap, + defaultDuration: Long, + autoPlay: MutableState, + showMenu: MutableState, + blurred: MutableState, + openFullscreen: () -> Unit +) { val player = remember(uri) { VideoPlayerHolder.getOrCreate(uri, false, defaultPreview, defaultDuration, true) } val videoPlaying = remember(uri.path) { player.videoPlaying } val progress = remember(uri.path) { player.progress } @@ -257,8 +280,8 @@ private fun VideoView(uri: URI, file: CIFile, defaultPreview: ImageBitmap, defau stop ) if (showPreview.value) { - VideoPreviewImageView(preview, smallView = false, openFullscreen, onLongClick) - if (!autoPlay.value) { + VideoPreviewImageView(preview, smallView = false, blurred = blurred, openFullscreen, onLongClick) + if (!autoPlay.value && !blurred.value) { PlayButton(brokenVideo, onLongClick = onLongClick, onClick = play) } } @@ -365,7 +388,13 @@ private fun DurationProgress(file: CIFile, playing: MutableState, durat } @Composable -fun VideoPreviewImageView(preview: ImageBitmap, smallView: Boolean, onClick: () -> Unit, onLongClick: () -> Unit) { +fun VideoPreviewImageView( + preview: ImageBitmap, + smallView: Boolean, + blurred: MutableState, + onClick: () -> Unit, + onLongClick: () -> Unit +) { val windowWidth = LocalWindowWidth() val width = remember(preview) { if (preview.width * 0.97 <= preview.height) videoViewFullWidth(windowWidth) * 0.75f else DEFAULT_MAX_IMAGE_WIDTH } Image( @@ -377,7 +406,8 @@ fun VideoPreviewImageView(preview: ImageBitmap, smallView: Boolean, onClick: () onLongClick = onLongClick, onClick = onClick ) - .onRightClick(onLongClick), + .onRightClick(onLongClick) + .privacyBlur(!smallView, blurred, scrollState = chatViewScrollState.collectAsState(), onLongClick = onLongClick), contentScale = if (smallView) ContentScale.Crop else ContentScale.FillWidth, ) } @@ -522,6 +552,9 @@ private fun fileStatusIcon(file: CIFile?, smallView: Boolean) { } } +private fun showDownloadButton(status: CIFileStatus?): Boolean = + status is CIFileStatus.RcvInvitation || status is CIFileStatus.RcvAborted + private fun fileSizeValid(file: CIFile?): Boolean { if (file != null) { return file.fileSize <= getMaxFileSize(file.fileProtocol) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 16d2a2dad2..8a579d5289 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -267,7 +267,7 @@ fun FramedItemView( ciFileView(ci, mc.text) } is MsgContent.MCLink -> { - ChatItemLinkView(mc.preview) + ChatItemLinkView(mc.preview, showMenu, onLongClick = { showMenu.value = true }) Box(Modifier.widthIn(max = DEFAULT_MAX_IMAGE_WIDTH)) { CIMarkdownText(ci, chatTTL, linkMode, uriHandler, onLinkLongClick, showViaProxy = showViaProxy) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt index 20b8f7c09f..cce7cf17a5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt @@ -1,12 +1,11 @@ package chat.simplex.common.views.helpers import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.Image -import androidx.compose.foundation.background +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.material.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -15,9 +14,11 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.LinkPreview import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.chat.chatViewScrollState import chat.simplex.res.MR import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -121,12 +122,16 @@ fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancel } @Composable -fun ChatItemLinkView(linkPreview: LinkPreview) { +fun ChatItemLinkView(linkPreview: LinkPreview, showMenu: State, onLongClick: () -> Unit) { Column(Modifier.widthIn(max = DEFAULT_MAX_IMAGE_WIDTH)) { + val blurred = remember { mutableStateOf(appPrefs.privacyMediaBlurRadius.get() > 0) } Image( base64ToBitmap(linkPreview.image), stringResource(MR.strings.image_descr_link_preview), - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .desktopModifyBlurredState(true, blurred, showMenu) + .privacyBlur(true, blurred, chatViewScrollState.collectAsState(), onLongClick = onLongClick), contentScale = ContentScale.FillWidth, ) Column(Modifier.padding(top = 6.dp).padding(horizontal = 12.dp)) { @@ -179,7 +184,7 @@ private fun normalizeImageUri(u: URL, imageUri: String) = when { @Composable fun PreviewChatItemLinkView() { SimpleXTheme { - ChatItemLinkView(LinkPreview.sampleData) + ChatItemLinkView(LinkPreview.sampleData, remember { mutableStateOf(false) }) {} } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index dc0760193d..88b14b6a66 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.res.MR import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* import chat.simplex.common.views.ProfileNameField import chat.simplex.common.views.helpers.* @@ -32,6 +33,8 @@ import chat.simplex.common.views.localauth.SetAppPasscodeView import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* +import kotlin.math.min +import kotlin.math.roundToInt enum class LAMode { SYSTEM, @@ -95,6 +98,9 @@ fun PrivacySettingsView( withBGApi { chatModel.controller.apiSetEncryptLocalFiles(enable) } }) SettingsPreferenceItem(painterResource(MR.images.ic_image), stringResource(MR.strings.auto_accept_images), chatModel.controller.appPrefs.privacyAcceptImages) + BlurRadiusOptions(remember { appPrefs.privacyMediaBlurRadius.state }) { + appPrefs.privacyMediaBlurRadius.set(it) + } SettingsPreferenceItem(painterResource(MR.images.ic_security), stringResource(MR.strings.protect_ip_address), chatModel.controller.appPrefs.privacyAskToApproveRelays) } SectionCustomFooter { @@ -217,6 +223,30 @@ private fun SimpleXLinkOptions(simplexLinkModeState: State, onS ) } +@Composable +private fun BlurRadiusOptions(state: State, onSelected: (Int) -> Unit) { + val choices = listOf(0, 12, 24, 48) + val pickerValues = choices + if (choices.contains(state.value)) emptyList() else listOf(state.value) + val values = remember { + pickerValues.map { + when (it) { + 0 -> it to generalGetString(MR.strings.privacy_media_blur_radius_off) + 12 -> it to generalGetString(MR.strings.privacy_media_blur_radius_soft) + 24 -> it to generalGetString(MR.strings.privacy_media_blur_radius_medium) + 48 -> it to generalGetString(MR.strings.privacy_media_blur_radius_strong) + else -> it to "$it" + } + } + } + ExposedDropDownSettingRow( + generalGetString(MR.strings.privacy_media_blur_radius), + values, + state, + icon = painterResource(MR.images.ic_blur_on), + onSelected = onSelected + ) +} + @Composable expect fun PrivacyDeviceSection( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 87c3cecd27..0564d87cd3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1102,6 +1102,11 @@ Disable (keep group overrides) Enable for all groups Disable for all groups + Blur media + Off + Soft + Medium + Strong YOU diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg new file mode 100644 index 0000000000..e8767ccc86 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_blur_on.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt index 9245f2b950..97f8bc129a 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Modifier.desktop.kt @@ -4,8 +4,7 @@ import androidx.compose.foundation.contextMenuOpenDetector import androidx.compose.runtime.Composable import androidx.compose.ui.* import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.input.pointer.PointerIcon -import androidx.compose.ui.input.pointer.pointerHoverIcon +import androidx.compose.ui.input.pointer.* import java.io.File import java.net.URI @@ -40,3 +39,8 @@ onExternalDrag(enabled) { actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpenDetector { action() } actual fun Modifier.desktopPointerHoverIconHand(): Modifier = Modifier.pointerHoverIcon(PointerIcon.Hand) + +actual fun Modifier.desktopOnHovered(action: (Boolean) -> Unit): Modifier = + this then Modifier + .onPointerEvent(PointerEventType.Enter) { action(true) } + .onPointerEvent(PointerEventType.Exit) { action(false) } From 13bfc9e92b22fb34785855a75c7c070b962d8d10 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 26 Jul 2024 10:30:49 +0100 Subject: [PATCH 57/57] core: update app settings for migration (#4518) --- src/Simplex/Chat/AppSettings.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs index 2b8b531dc3..6d23a19ba1 100644 --- a/src/Simplex/Chat/AppSettings.hs +++ b/src/Simplex/Chat/AppSettings.hs @@ -35,6 +35,7 @@ data AppSettings = AppSettings privacyShowChatPreviews :: Maybe Bool, privacySaveLastDraft :: Maybe Bool, privacyProtectScreen :: Maybe Bool, + privacyMediaBlurRadius :: Maybe Int, notificationMode :: Maybe NotificationMode, notificationPreviewMode :: Maybe NotificationPreviewMode, webrtcPolicyRelay :: Maybe Bool, @@ -68,6 +69,7 @@ defaultAppSettings = privacyShowChatPreviews = Just True, privacySaveLastDraft = Just True, privacyProtectScreen = Just False, + privacyMediaBlurRadius = Just 0, notificationMode = Just NMInstant, notificationPreviewMode = Just NPMMessage, webrtcPolicyRelay = Just True, @@ -100,6 +102,7 @@ defaultParseAppSettings = privacyShowChatPreviews = Nothing, privacySaveLastDraft = Nothing, privacyProtectScreen = Nothing, + privacyMediaBlurRadius = Nothing, notificationMode = Nothing, notificationPreviewMode = Nothing, webrtcPolicyRelay = Nothing, @@ -132,6 +135,7 @@ combineAppSettings platformDefaults storedSettings = privacyShowChatPreviews = p privacyShowChatPreviews, privacySaveLastDraft = p privacySaveLastDraft, privacyProtectScreen = p privacyProtectScreen, + privacyMediaBlurRadius = p privacyMediaBlurRadius, notificationMode = p notificationMode, notificationPreviewMode = p notificationPreviewMode, webrtcPolicyRelay = p webrtcPolicyRelay, @@ -176,6 +180,7 @@ instance FromJSON AppSettings where privacyShowChatPreviews <- p "privacyShowChatPreviews" privacySaveLastDraft <- p "privacySaveLastDraft" privacyProtectScreen <- p "privacyProtectScreen" + privacyMediaBlurRadius <- p "privacyMediaBlurRadius" notificationMode <- p "notificationMode" notificationPreviewMode <- p "notificationPreviewMode" webrtcPolicyRelay <- p "webrtcPolicyRelay" @@ -205,6 +210,7 @@ instance FromJSON AppSettings where privacyShowChatPreviews, privacySaveLastDraft, privacyProtectScreen, + privacyMediaBlurRadius, notificationMode, notificationPreviewMode, webrtcPolicyRelay,