Merge branch 'master' into master-ghc8107

This commit is contained in:
Evgeny Poberezkin
2023-12-30 19:19:39 +00:00
191 changed files with 9964 additions and 5739 deletions
@@ -26,3 +26,5 @@ actual fun Modifier.desktopOnExternalDrag(
): Modifier = this
actual fun Modifier.onRightClick(action: () -> Unit): Modifier = this
actual fun Modifier.desktopPointerHoverIconHand(): Modifier = this
@@ -17,7 +17,8 @@ actual fun ChatListNavLinkLayout(
dropdownMenuItems: (@Composable () -> Unit)?,
showMenu: MutableState<Boolean>,
stopped: Boolean,
selectedChat: State<Boolean>
selectedChat: State<Boolean>,
nextChatSelected: State<Boolean>,
) {
var modifier = Modifier.fillMaxWidth()
if (!stopped) modifier = modifier
@@ -1,5 +1,7 @@
package chat.simplex.common.views.helpers
import android.content.ClipboardManager
import android.content.Context
import android.content.res.Resources
import android.graphics.*
import android.graphics.Typeface
@@ -12,6 +14,7 @@ import android.text.SpannedString
import android.text.style.*
import android.util.Base64
import android.view.WindowManager
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.*
@@ -24,7 +27,6 @@ import androidx.core.text.HtmlCompat
import chat.simplex.common.helpers.*
import chat.simplex.common.model.*
import chat.simplex.common.platform.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
import java.io.*
import java.net.URI
@@ -55,6 +57,19 @@ fun keepScreenOn(on: Boolean) {
}
}
@Composable
actual fun SetupClipboardListener() {
DisposableEffect(Unit) {
val service = androidAppContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val listener = { chatModel.clipboardHasText.value = service.hasPrimaryClip() }
chatModel.clipboardHasText.value = service.hasPrimaryClip()
service.addPrimaryClipChangedListener(listener)
onDispose {
service.removePrimaryClipChangedListener(listener)
}
}
}
actual fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString {
return spannableStringToAnnotatedString(HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY), density)
}
@@ -1,68 +0,0 @@
package chat.simplex.common.views.newchat
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.RemoteHostInfo
import chat.simplex.res.MR
@Composable
actual fun ConnectViaLinkView(m: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
// TODO this should close if remote host changes in model
val selection = remember {
mutableStateOf(
runCatching { ConnectViaLinkTab.valueOf(m.controller.appPrefs.connectViaLinkTab.get()!!) }.getOrDefault(ConnectViaLinkTab.SCAN)
)
}
val tabTitles = ConnectViaLinkTab.values().map {
when (it) {
ConnectViaLinkTab.SCAN -> stringResource(MR.strings.scan_QR_code)
ConnectViaLinkTab.PASTE -> stringResource(MR.strings.paste_the_link_you_received)
}
}
Column(
Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) {
Column(Modifier.weight(1f)) {
when (selection.value) {
ConnectViaLinkTab.SCAN -> {
ScanToConnectView(m, rh, close)
}
ConnectViaLinkTab.PASTE -> {
PasteToConnectView(m, rh, close)
}
}
}
TabRow(
selectedTabIndex = selection.value.ordinal,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
Tab(
selected = selection.value.ordinal == index,
onClick = {
selection.value = ConnectViaLinkTab.values()[index]
m.controller.appPrefs.connectViaLinkTab.set(selection.value .name)
},
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (ConnectViaLinkTab.SCAN.ordinal == index) painterResource(MR.images.ic_qr_code) else painterResource(MR.images.ic_article),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}
}
}
@@ -2,16 +2,20 @@ package chat.simplex.common.views.newchat
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.util.Log
import android.view.ViewGroup
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.*
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import boofcv.abst.fiducial.QrCodeDetector
@@ -20,18 +24,23 @@ import boofcv.android.ConvertCameraImage
import boofcv.factory.fiducial.FactoryFiducial
import boofcv.struct.image.GrayU8
import chat.simplex.common.platform.TAG
import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.google.accompanist.permissions.rememberPermissionState
import com.google.common.util.concurrent.ListenableFuture
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import java.util.concurrent.*
// Adapted from learntodroid - https://gist.github.com/learntodroid/8f839be0b29d0378f843af70607bd7f5
@Composable
actual fun QRCodeScanner(onBarcode: (String) -> Unit) {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
actual fun QRCodeScanner(
showQRCodeScanner: MutableState<Boolean>,
padding: PaddingValues,
onBarcode: (String) -> Unit
) {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
var preview by remember { mutableStateOf<Preview?>(null) }
@@ -48,57 +57,102 @@ actual fun QRCodeScanner(onBarcode: (String) -> Unit) {
}
}
AndroidView(
factory = { AndroidViewContext ->
PreviewView(AndroidViewContext).apply {
this.scaleType = PreviewView.ScaleType.FILL_CENTER
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
}
},
modifier = Modifier.clipToBounds()
) { previewView ->
val cameraSelector: CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
cameraProviderFuture?.addListener({
preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val detector: QrCodeDetector<GrayU8> = FactoryFiducial.qrcode(null, GrayU8::class.java)
fun getQR(imageProxy: ImageProxy) {
val currentTimeStamp = System.currentTimeMillis()
if (currentTimeStamp - lastAnalyzedTimeStamp >= TimeUnit.SECONDS.toMillis(1)) {
detector.process(imageProxyToGrayU8(imageProxy))
val found = detector.detections
val qr = found.firstOrNull()
if (qr != null) {
if (qr.message != contactLink) {
// Make sure link is new and not a repeat
contactLink = qr.message
onBarcode(contactLink)
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
val modifier = Modifier
.padding(padding)
.clipToBounds()
.widthIn(max = 400.dp)
.aspectRatio(1f)
val showScanner = remember { showQRCodeScanner }
if (showScanner.value && cameraPermissionState.hasPermission) {
AndroidView(
factory = { AndroidViewContext ->
PreviewView(AndroidViewContext).apply {
this.scaleType = PreviewView.ScaleType.FILL_CENTER
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
)
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
}
},
modifier = modifier
) { previewView ->
val cameraSelector: CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
cameraProviderFuture?.addListener({
preview = Preview.Builder().build().also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
val detector: QrCodeDetector<GrayU8> = FactoryFiducial.qrcode(null, GrayU8::class.java)
fun getQR(imageProxy: ImageProxy) {
val currentTimeStamp = System.currentTimeMillis()
if (currentTimeStamp - lastAnalyzedTimeStamp >= TimeUnit.SECONDS.toMillis(1)) {
detector.process(imageProxyToGrayU8(imageProxy))
val found = detector.detections
val qr = found.firstOrNull()
if (qr != null) {
if (qr.message != contactLink) {
// Make sure link is new and not a repeat
contactLink = qr.message
onBarcode(contactLink)
}
}
}
imageProxy.close()
}
val imageAnalyzer = ImageAnalysis.Analyzer { proxy -> getQR(proxy) }
val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setImageQueueDepth(1)
.build()
.also { it.setAnalyzer(cameraExecutor, imageAnalyzer) }
try {
cameraProviderFuture?.get()?.unbindAll()
cameraProviderFuture?.get()?.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis)
} catch (e: Exception) {
Log.d(TAG, "CameraPreview: ${e.localizedMessage}")
}
}, ContextCompat.getMainExecutor(context))
}
} else {
val buttonColors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f),
contentColor = MaterialTheme.colors.primary,
disabledBackgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.9f),
disabledContentColor = MaterialTheme.colors.primary,
)
when {
!cameraPermissionState.hasPermission && !cameraPermissionState.permissionRequested && showScanner.value -> {
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
}
!cameraPermissionState.hasPermission -> {
Button({ withBGApi { cameraPermissionState.launchPermissionRequest() } }, modifier = modifier, colors = buttonColors) {
Icon(painterResource(MR.images.ic_camera_enhance), null)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Text(stringResource(MR.strings.enable_camera_access))
}
}
cameraPermissionState.hasPermission -> {
Button({ showQRCodeScanner.value = true }, modifier = modifier, colors = buttonColors) {
Icon(painterResource(MR.images.ic_qr_code), null)
Spacer(Modifier.width(DEFAULT_PADDING_HALF))
Text(stringResource(MR.strings.tap_to_scan))
}
}
!LocalContext.current.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) -> {
Button({ }, enabled = false, modifier = modifier, colors = buttonColors) {
Text(stringResource(MR.strings.camera_not_available))
}
}
imageProxy.close()
}
val imageAnalyzer = ImageAnalysis.Analyzer { proxy -> getQR(proxy) }
val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setImageQueueDepth(1)
.build()
.also { it.setAnalyzer(cameraExecutor, imageAnalyzer) }
try {
cameraProviderFuture?.get()?.unbindAll()
cameraProviderFuture?.get()?.bindToLifecycle(lifecycleOwner, cameraSelector, preview, imageAnalysis)
} catch (e: Exception) {
Log.d(TAG, "CameraPreview: ${e.localizedMessage}")
}
}, ContextCompat.getMainExecutor(context))
}
}
}
@@ -1,22 +0,0 @@
package chat.simplex.common.views.newchat
import android.Manifest
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.RemoteHostInfo
import com.google.accompanist.permissions.rememberPermissionState
@Composable
actual fun ScanToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
val cameraPermissionState = rememberPermissionState(permission = Manifest.permission.CAMERA)
LaunchedEffect(Unit) {
cameraPermissionState.launchPermissionRequest()
}
ConnectContactLayout(
chatModel = chatModel,
rh = rh,
incognitoPref = chatModel.controller.appPrefs.incognito,
close = close
)
}
@@ -57,6 +57,7 @@ typedef long* chat_ctrl;
*/
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_close_store(chat_ctrl ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd);
extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
@@ -93,6 +94,12 @@ Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, __unused j
return ret;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, __unused jclass clazz, jlong controller) {
jstring res = (*env)->NewStringUTF(env, chat_close_store((void*)controller));
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, __unused jclass clazz, jlong controller, jstring msg) {
const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE);
@@ -30,6 +30,7 @@ typedef long* chat_ctrl;
*/
extern char *chat_migrate_init(const char *path, const char *key, const char *confirm, chat_ctrl *ctrl);
extern char *chat_close_store(chat_ctrl ctrl);
extern char *chat_send_cmd(chat_ctrl ctrl, const char *cmd);
extern char *chat_send_remote_cmd(chat_ctrl ctrl, const int rhId, const char *cmd);
extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
@@ -106,6 +107,12 @@ Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, jclass cla
return ret;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatCloseStore(JNIEnv *env, jclass clazz, jlong controller) {
jstring res = decode_to_utf8_string(env, chat_close_store((void*)controller));
return res;
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) {
const char *_msg = encode_to_utf8_chars(env, msg);
@@ -103,13 +103,15 @@ fun MainScreen() {
}
Box {
val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } }
val onboarding by remember { chatModel.controller.appPrefs.onboardingStage.state }
val localUserCreated = chatModel.localUserCreated.value
var showInitializationView by remember { mutableStateOf(false) }
when {
chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView()
chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database))
showChatDatabaseError -> {
chatModel.chatDbStatus.value?.let {
// Prevent showing keyboard on Android when: passcode enabled and database password not saved
if (!unauthorized.value && chatModel.chatDbStatus.value != null) {
DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs)
}
}
@@ -125,6 +127,7 @@ fun MainScreen() {
}
val scaffoldState = rememberScaffoldState()
val settingsState = remember { SettingsViewState(userPickerState, scaffoldState) }
SetupClipboardListener()
if (appPlatform.isAndroid) {
AndroidScreen(settingsState)
} else {
@@ -149,7 +152,6 @@ fun MainScreen() {
SwitchingUsersView()
}
val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } }
if (unauthorized.value && !(chatModel.activeCallViewIsVisible.value && chatModel.showCallView.value)) {
LaunchedEffect(Unit) {
// With these constrains when user presses back button while on ChatList, activity destroys and shows auth request
@@ -342,22 +344,6 @@ fun DesktopScreen(settingsState: SettingsViewState) {
}
}
@Composable
fun InitializationView() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(
Modifier
.padding(bottom = DEFAULT_PADDING)
.size(30.dp),
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
Text(stringResource(MR.strings.opening_database))
}
}
}
@Composable
private fun SwitchingUsersView() {
if (remember { chatModel.switchingUsersAndHosts }.value) {
@@ -47,6 +47,7 @@ object ChatModel {
val chatDbChanged = mutableStateOf<Boolean>(false)
val chatDbEncrypted = mutableStateOf<Boolean?>(false)
val chatDbStatus = mutableStateOf<DBMigrationResult?>(null)
val ctrlInitInProgress = mutableStateOf(false)
val chats = mutableStateListOf<Chat>()
// map of connections network statuses, key is agent connection id
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
@@ -97,8 +98,8 @@ object ChatModel {
val showCallView = mutableStateOf(false)
val switchingCall = mutableStateOf(false)
// currently showing QR code
val connReqInv = mutableStateOf(null as String?)
// currently showing invitation
val showingInvitation = mutableStateOf(null as ShowingInvitation?)
var draft = mutableStateOf(null as ComposeState?)
var draftChatId = mutableStateOf(null as String?)
@@ -109,6 +110,8 @@ object ChatModel {
val filesToDelete = mutableSetOf<File>()
val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) }
val clipboardHasText = mutableStateOf(false)
var updatingChatsMutex: Mutex = Mutex()
val desktopNoUserNoRemote: Boolean @Composable get() = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
@@ -561,15 +564,30 @@ object ChatModel {
chats.add(index = 0, chat)
}
fun dismissConnReqView(id: String) {
if (connReqInv.value == null) return
val info = getChat(id)?.chatInfo as? ChatInfo.ContactConnection ?: return
if (info.contactConnection.connReqInv == connReqInv.value) {
connReqInv.value = null
ModalManager.center.closeModals()
fun replaceConnReqView(id: String, withId: String) {
if (id == showingInvitation.value?.connId) {
showingInvitation.value = null
chatModel.chatItems.clear()
chatModel.chatId.value = withId
ModalManager.end.closeModals()
}
}
fun dismissConnReqView(id: String) {
if (id == showingInvitation.value?.connId) {
showingInvitation.value = null
chatModel.chatItems.clear()
chatModel.chatId.value = null
// Close NewChatView
ModalManager.center.closeModals()
ModalManager.end.closeModals()
}
}
fun markShowingInvitationUsed() {
showingInvitation.value = showingInvitation.value?.copy(connChatUsed = true)
}
fun removeChat(rhId: Long?, id: String) {
chats.removeAll { it.id == id && it.remoteHostId == rhId }
}
@@ -630,6 +648,12 @@ object ChatModel {
fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true
}
data class ShowingInvitation(
val connId: String,
val connReq: String,
val connChatUsed: Boolean
)
enum class ChatType(val type: String) {
Direct("@"),
Group("#"),
@@ -2664,6 +2688,8 @@ sealed class Format {
is Phone -> linkStyle
}
val isSimplexLink = this is SimplexLink
companion object {
val linkStyle @Composable get() = SpanStyle(color = MaterialTheme.colors.primary, textDecoration = TextDecoration.Underline)
}
@@ -9,7 +9,6 @@ import dev.icerock.moko.resources.compose.painterResource
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.*
import chat.simplex.common.views.newchat.ConnectViaLinkTab
import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.views.usersettings.*
import com.charleskorn.kaml.Yaml
@@ -135,7 +134,6 @@ class AppPreferences {
val networkTCPKeepIntvl = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_INTVL, KeepAliveOpts.defaults.keepIntvl)
val networkTCPKeepCnt = mkIntPreference(SHARED_PREFS_NETWORK_TCP_KEEP_CNT, KeepAliveOpts.defaults.keepCnt)
val incognito = mkBoolPreference(SHARED_PREFS_INCOGNITO, false)
val connectViaLinkTab = mkStrPreference(SHARED_PREFS_CONNECT_VIA_LINK_TAB, ConnectViaLinkTab.SCAN.name)
val liveMessageAlertShown = mkBoolPreference(SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN, false)
val showHiddenProfilesNotice = mkBoolPreference(SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE, true)
val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true)
@@ -292,7 +290,6 @@ class AppPreferences {
private const val SHARED_PREFS_NETWORK_TCP_KEEP_INTVL = "NetworkTCPKeepIntvl"
private const val SHARED_PREFS_NETWORK_TCP_KEEP_CNT = "NetworkTCPKeepCnt"
private const val SHARED_PREFS_INCOGNITO = "Incognito"
private const val SHARED_PREFS_CONNECT_VIA_LINK_TAB = "ConnectViaLinkTab"
private const val SHARED_PREFS_LIVE_MESSAGE_ALERT_SHOWN = "LiveMessageAlertShown"
private const val SHARED_PREFS_SHOW_HIDDEN_PROFILES_NOTICE = "ShowHiddenProfilesNotice"
private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert"
@@ -890,19 +887,19 @@ object ChatController {
suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair<String, PendingContactConnection>? {
suspend fun apiAddContact(rh: Long?, incognito: Boolean): Pair<Pair<String, PendingContactConnection>?, (() -> Unit)?> {
val userId = chatModel.currentUser.value?.userId ?: run {
Log.e(TAG, "apiAddContact: no current user")
return null
return null to null
}
val r = sendCmd(rh, CC.APIAddContact(userId, incognito))
return when (r) {
is CR.Invitation -> r.connReqInvitation to r.connection
is CR.Invitation -> (r.connReqInvitation to r.connection) to null
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r)
return null to { apiErrorAlert("apiAddContact", generalGetString(MR.strings.connection_error), r) }
}
null
null to null
}
}
}
@@ -981,6 +978,13 @@ object ChatController {
}
}
suspend fun deleteChat(chat: Chat, notify: Boolean? = null) {
val cInfo = chat.chatInfo
if (apiDeleteChat(rh = chat.remoteHostId, type = cInfo.chatType, id = cInfo.apiId, notify = notify)) {
chatModel.removeChat(chat.remoteHostId, cInfo.id)
}
}
suspend fun apiDeleteChat(rh: Long?, type: ChatType, id: Long, notify: Boolean? = null): Boolean {
val r = sendCmd(rh, CC.ApiDeleteChat(type, id, notify))
when {
@@ -1568,7 +1572,7 @@ object ChatController {
chatModel.updateContact(rhId, r.contact)
val conn = r.contact.activeConn
if (conn != null) {
chatModel.dismissConnReqView(conn.id)
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
chatModel.removeChat(rhId, conn.id)
}
}
@@ -1582,7 +1586,7 @@ object ChatController {
chatModel.updateContact(rhId, r.contact)
val conn = r.contact.activeConn
if (conn != null) {
chatModel.dismissConnReqView(conn.id)
chatModel.replaceConnReqView(conn.id, "@${r.contact.contactId}")
chatModel.removeChat(rhId, conn.id)
}
}
@@ -1717,7 +1721,7 @@ object ChatController {
chatModel.updateGroup(rhId, r.groupInfo)
val conn = r.hostContact?.activeConn
if (conn != null) {
chatModel.dismissConnReqView(conn.id)
chatModel.replaceConnReqView(conn.id, "#${r.groupInfo.groupId}")
chatModel.removeChat(rhId, conn.id)
}
}
@@ -1727,7 +1731,7 @@ object ChatController {
chatModel.updateGroup(rhId, r.groupInfo)
val hostConn = r.hostMember.activeConn
if (hostConn != null) {
chatModel.dismissConnReqView(hostConn.id)
chatModel.replaceConnReqView(hostConn.id, "#${r.groupInfo.groupId}")
chatModel.removeChat(rhId, hostConn.id)
}
}
@@ -2141,7 +2145,15 @@ class SharedPreference<T>(val get: () -> T, set: (T) -> Unit) {
init {
this.set = { value ->
set(value)
_state.value = value
try {
_state.value = value
} catch (e: IllegalStateException) {
// Can be `Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied`
Log.i(TAG, e.stackTraceToString())
withApi {
_state.value = value
}
}
}
}
}
@@ -2385,7 +2397,7 @@ sealed class CC {
is CancelFile -> "/fcancel $fileId"
is SetLocalDeviceName -> "/set device name $displayName"
is ListRemoteHosts -> "/list remote hosts"
is StartRemoteHost -> "/start remote host " + (if (remoteHostId == null) "new" else "$remoteHostId multicast=${onOff(multicast)}") + (if (address != null) " addr=${address.address} iface=${address.`interface`}" else "") + (if (port != null) " port=$port" else "")
is StartRemoteHost -> "/start remote host " + (if (remoteHostId == null) "new" else "$remoteHostId multicast=${onOff(multicast)}") + (if (address != null) " addr=${address.address} iface=${json.encodeToString(address.`interface`)}" else "") + (if (port != null) " port=$port" else "")
is SwitchRemoteHost -> "/switch remote host " + if (remoteHostId == null) "local" else "$remoteHostId"
is StopRemoteHost -> "/stop remote host " + if (remoteHostKey == null) "new" else "$remoteHostKey"
is DeleteRemoteHost -> "/delete remote host $remoteHostId"
@@ -3304,7 +3316,8 @@ enum class GroupFeature: Feature {
@SerialName("fullDelete") FullDelete,
@SerialName("reactions") Reactions,
@SerialName("voice") Voice,
@SerialName("files") Files;
@SerialName("files") Files,
@SerialName("history") History;
override val hasParam: Boolean get() = when(this) {
TimedMessages -> true
@@ -3319,6 +3332,7 @@ enum class GroupFeature: Feature {
Reactions -> generalGetString(MR.strings.message_reactions)
Voice -> generalGetString(MR.strings.voice_messages)
Files -> generalGetString(MR.strings.files_and_media)
History -> generalGetString(MR.strings.recent_history)
}
val icon: Painter
@@ -3329,6 +3343,7 @@ enum class GroupFeature: Feature {
Reactions -> painterResource(MR.images.ic_add_reaction)
Voice -> painterResource(MR.images.ic_keyboard_voice)
Files -> painterResource(MR.images.ic_draft)
History -> painterResource(MR.images.ic_schedule)
}
@Composable
@@ -3339,6 +3354,7 @@ enum class GroupFeature: Feature {
Reactions -> painterResource(MR.images.ic_add_reaction_filled)
Voice -> painterResource(MR.images.ic_keyboard_voice_filled)
Files -> painterResource(MR.images.ic_draft_filled)
History -> painterResource(MR.images.ic_schedule_filled)
}
fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String =
@@ -3368,6 +3384,10 @@ enum class GroupFeature: Feature {
GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_send_files)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_files)
}
History -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history)
}
}
} else {
when(this) {
@@ -3395,6 +3415,10 @@ enum class GroupFeature: Feature {
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_files)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.files_are_prohibited_in_group)
}
History -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members)
}
}
}
}
@@ -3509,6 +3533,7 @@ data class FullGroupPreferences(
val reactions: GroupPreference,
val voice: GroupPreference,
val files: GroupPreference,
val history: GroupPreference,
) {
fun toGroupPreferences(): GroupPreferences =
GroupPreferences(
@@ -3518,6 +3543,7 @@ data class FullGroupPreferences(
reactions = reactions,
voice = voice,
files = files,
history = history
)
companion object {
@@ -3528,18 +3554,20 @@ data class FullGroupPreferences(
reactions = GroupPreference(GroupFeatureEnabled.ON),
voice = GroupPreference(GroupFeatureEnabled.ON),
files = GroupPreference(GroupFeatureEnabled.ON),
history = GroupPreference(GroupFeatureEnabled.ON),
)
}
}
@Serializable
data class GroupPreferences(
val timedMessages: TimedMessagesGroupPreference?,
val directMessages: GroupPreference?,
val fullDelete: GroupPreference?,
val reactions: GroupPreference?,
val voice: GroupPreference?,
val files: GroupPreference?,
val timedMessages: TimedMessagesGroupPreference? = null,
val directMessages: GroupPreference? = null,
val fullDelete: GroupPreference? = null,
val reactions: GroupPreference? = null,
val voice: GroupPreference? = null,
val files: GroupPreference? = null,
val history: GroupPreference? = null,
) {
companion object {
val sampleData = GroupPreferences(
@@ -3549,6 +3577,7 @@ data class GroupPreferences(
reactions = GroupPreference(GroupFeatureEnabled.ON),
voice = GroupPreference(GroupFeatureEnabled.ON),
files = GroupPreference(GroupFeatureEnabled.ON),
history = GroupPreference(GroupFeatureEnabled.ON),
)
}
}
@@ -14,6 +14,7 @@ external fun pipeStdOutToSocket(socketName: String) : Int
// SimpleX API
typealias ChatCtrl = Long
external fun chatMigrateInit(dbPath: String, dbKey: String, confirm: String): Array<Any>
external fun chatCloseStore(ctrl: ChatCtrl): String
external fun chatSendCmd(ctrl: ChatCtrl, msg: String): String
external fun chatSendRemoteCmd(ctrl: ChatCtrl, rhId: Int, msg: String): String
external fun chatRecvMsg(ctrl: ChatCtrl): String
@@ -35,54 +36,71 @@ val appPreferences: AppPreferences
val chatController: ChatController = ChatController
suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) {
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp
val migrated: Array<Any> = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value)
val res: DBMigrationResult = kotlin.runCatching {
json.decodeFromString<DBMigrationResult>(migrated[0] as String)
}.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) }
val ctrl = if (res is DBMigrationResult.OK) {
migrated[1] as Long
} else null
chatController.ctrl = ctrl
chatModel.chatDbEncrypted.value = dbKey != ""
chatModel.chatDbStatus.value = res
if (res != DBMigrationResult.OK) {
Log.d(TAG, "Unable to migrate successfully: $res")
} else if (startChat) {
// If we migrated successfully means previous re-encryption process on database level finished successfully too
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
val user = chatController.apiGetActiveUser(null)
if (user == null) {
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
chatModel.currentUser.value = null
chatModel.users.clear()
if (appPlatform.isDesktop) {
/**
* Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start
* because of default value of [OnboardingStage.OnboardingComplete]
* */
chatModel.localUserCreated.value = null
if (chatController.listRemoteHosts()?.isEmpty() == true) {
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
}
chatController.startChatWithoutUser()
} else {
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
}
} else {
val savedOnboardingStage = appPreferences.onboardingStage.get()
appPreferences.onboardingStage.set(if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
OnboardingStage.Step3_CreateSimpleXAddress
} else {
savedOnboardingStage
})
if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) {
chatModel.setDeliveryReceipts.value = true
}
chatController.startChat(user)
platform.androidChatInitializedAndStarted()
fun initChatControllerAndRunMigrations(ignoreSelfDestruct: Boolean) {
if (ignoreSelfDestruct || DatabaseUtils.ksSelfDestructPassword.get() == null) {
withBGApi {
initChatController()
runMigrations()
}
}
}
suspend fun initChatController(useKey: String? = null, confirmMigrations: MigrationConfirmation? = null, startChat: Boolean = true) {
try {
chatModel.ctrlInitInProgress.value = true
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp
val migrated: Array<Any> = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value)
val res: DBMigrationResult = kotlin.runCatching {
json.decodeFromString<DBMigrationResult>(migrated[0] as String)
}.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) }
val ctrl = if (res is DBMigrationResult.OK) {
migrated[1] as Long
} else null
chatController.ctrl = ctrl
chatModel.chatDbEncrypted.value = dbKey != ""
chatModel.chatDbStatus.value = res
if (res != DBMigrationResult.OK) {
Log.d(TAG, "Unable to migrate successfully: $res")
} else if (startChat) {
// If we migrated successfully means previous re-encryption process on database level finished successfully too
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
val user = chatController.apiGetActiveUser(null)
if (user == null) {
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
chatModel.currentUser.value = null
chatModel.users.clear()
if (appPlatform.isDesktop) {
/**
* Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start
* because of default value of [OnboardingStage.OnboardingComplete]
* */
chatModel.localUserCreated.value = null
if (chatController.listRemoteHosts()?.isEmpty() == true) {
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
}
chatController.startChatWithoutUser()
} else {
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
}
} else {
val savedOnboardingStage = appPreferences.onboardingStage.get()
val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
OnboardingStage.Step3_CreateSimpleXAddress
} else {
savedOnboardingStage
}
if (appPreferences.onboardingStage.get() != newStage) {
appPreferences.onboardingStage.set(newStage)
}
if (appPreferences.onboardingStage.get() == OnboardingStage.OnboardingComplete && !chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.get()) {
chatModel.setDeliveryReceipts.value = true
}
chatController.startChat(user)
platform.androidChatInitializedAndStarted()
}
}
} finally {
chatModel.ctrlInitInProgress.value = false
}
}
@@ -23,3 +23,5 @@ expect fun Modifier.desktopOnExternalDrag(
): Modifier
expect fun Modifier.onRightClick(action: () -> Unit): Modifier
expect fun Modifier.desktopPointerHoverIconHand(): Modifier
@@ -355,7 +355,7 @@ fun ChatInfoLayout(
if (contact.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
SimpleXLinkQRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
SimpleXLinkQRCode(contact.contactLink)
val clipboard = LocalClipboardManager.current
ShareAddressButton { clipboard.shareText(simplexChatLink(contact.contactLink)) }
SectionTextFooter(stringResource(MR.strings.you_can_share_this_address_with_your_contacts).format(contact.displayName))
@@ -53,6 +53,7 @@ fun ChatItemInfoView(chatModel: ChatModel, ci: ChatItem, ciInfo: ChatItemInfo, d
text, if (text.isEmpty()) emptyList() else formattedText,
sender = sender,
senderBold = true,
toggleSecrets = true,
linkMode = SimplexLinkMode.DESCRIPTION, uriHandler = uriHandler,
onLinkLongClick = { showMenu.value = true }
)
@@ -33,6 +33,7 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.common.model.GroupInfo
import chat.simplex.common.platform.*
import chat.simplex.common.platform.AudioPlayer
import chat.simplex.common.views.newchat.ContactConnectionInfoView
import chat.simplex.res.MR
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
@@ -114,343 +115,371 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
}
}
val clipboard = LocalClipboardManager.current
ChatLayout(
chat,
unreadCount,
composeState,
composeView = {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (
chat.chatInfo is ChatInfo.Direct
&& !chat.chatInfo.contact.ready
&& chat.chatInfo.contact.active
&& !chat.chatInfo.contact.nextSendGrpInv
) {
Text(
generalGetString(MR.strings.contact_connection_pending),
Modifier.padding(top = 4.dp),
fontSize = 14.sp,
color = MaterialTheme.colors.secondary
)
}
ComposeView(
chatModel, chat, composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
)
}
},
attachmentOption,
attachmentBottomSheetState,
chatModel.chatItems,
searchText,
useLinkPreviews = useLinkPreviews,
linkMode = chatModel.simplexLinkMode.value,
back = {
hideKeyboard(view)
AudioPlayer.stop()
chatModel.chatId.value = null
chatModel.groupMembers.clear()
},
info = {
if (ModalManager.end.hasModalsOpen()) {
ModalManager.end.closeModals()
return@ChatLayout
}
hideKeyboard(view)
withApi {
// The idea is to preload information before showing a modal because large groups can take time to load all members
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
var preloadedCode: String? = null
var preloadedLink: Pair<String, GroupMemberRole>? = null
if (chat.chatInfo is ChatInfo.Direct) {
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
} else if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
}
ModalManager.end.showModalCloseable(true) { close ->
val chat = remember { activeChat }.value
if (chat?.chatInfo is ChatInfo.Direct) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
var code: String? by remember { mutableStateOf(preloadedCode) }
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
preloadedCode = code
when (chat.chatInfo) {
is ChatInfo.Direct, is ChatInfo.Group -> {
ChatLayout(
chat,
unreadCount,
composeState,
composeView = {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (
chat.chatInfo is ChatInfo.Direct
&& !chat.chatInfo.contact.ready
&& chat.chatInfo.contact.active
&& !chat.chatInfo.contact.nextSendGrpInv
) {
Text(
generalGetString(MR.strings.contact_connection_pending),
Modifier.padding(top = 4.dp),
fontSize = 14.sp,
color = MaterialTheme.colors.secondary
)
}
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
} else if (chat?.chatInfo is ChatInfo.Group) {
var link: Pair<String, GroupMemberRole>? by remember(chat.id) { mutableStateOf(preloadedLink) }
KeyChangeEffect(chat.id) {
setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel)
link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
preloadedLink = link
ComposeView(
chatModel, chat, composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
)
}
},
attachmentOption,
attachmentBottomSheetState,
chatModel.chatItems,
searchText,
useLinkPreviews = useLinkPreviews,
linkMode = chatModel.simplexLinkMode.value,
back = {
hideKeyboard(view)
AudioPlayer.stop()
chatModel.chatId.value = null
chatModel.groupMembers.clear()
},
info = {
if (ModalManager.end.hasModalsOpen()) {
ModalManager.end.closeModals()
return@ChatLayout
}
hideKeyboard(view)
withApi {
// The idea is to preload information before showing a modal because large groups can take time to load all members
var preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
var preloadedCode: String? = null
var preloadedLink: Pair<String, GroupMemberRole>? = null
if (chat.chatInfo is ChatInfo.Direct) {
preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
} else if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
}
GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, {
link = it
preloadedLink = it
}, close)
}
}
}
},
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
hideKeyboard(view)
withApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
val stats = r?.second
val (_, code) = if (member.memberActive) {
val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
member to memCode?.second
} else {
member to null
}
setGroupMembers(chatRh, groupInfo, chatModel)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) { close ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
}
}
}
},
loadPrevMessages = {
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
val firstId = chatModel.chatItems.firstOrNull()?.id
if (c != null && firstId != null) {
withApi {
Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}")
apiLoadPrevMessages(c, chatModel, firstId, searchText.value)
Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}")
}
}
},
deleteMessage = { itemId, mode ->
withApi {
val cInfo = chat.chatInfo
val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId }
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
val groupInfo = toModerate?.first
val groupMember = toModerate?.second
val deletedChatItem: ChatItem?
val toChatItem: ChatItem?
if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) {
val r = chatModel.controller.apiDeleteMemberChatItem(
chatRh,
groupId = groupInfo.groupId,
groupMemberId = groupMember.groupMemberId,
itemId = itemId
)
deletedChatItem = r?.first
toChatItem = r?.second
} else {
val r = chatModel.controller.apiDeleteChatItem(
chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
itemId = itemId,
mode = mode
)
deletedChatItem = r?.deletedChatItem?.chatItem
toChatItem = r?.toChatItem?.chatItem
}
if (toChatItem == null && deletedChatItem != null) {
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem)
} else if (toChatItem != null) {
chatModel.upsertChatItem(chatRh, cInfo, toChatItem)
}
}
},
deleteMessages = { itemIds ->
if (itemIds.isNotEmpty()) {
val chatInfo = chat.chatInfo
withBGApi {
val deletedItems: ArrayList<ChatItem> = arrayListOf()
for (itemId in itemIds) {
val di = chatModel.controller.apiDeleteChatItem(
chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal
)?.deletedChatItem?.chatItem
if (di != null) {
deletedItems.add(di)
ModalManager.end.showModalCloseable(true) { close ->
val chat = remember { activeChat }.value
if (chat?.chatInfo is ChatInfo.Direct) {
var contactInfo: Pair<ConnectionStats?, Profile?>? by remember { mutableStateOf(preloadedContactInfo) }
var code: String? by remember { mutableStateOf(preloadedCode) }
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second
preloadedCode = code
}
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
} else if (chat?.chatInfo is ChatInfo.Group) {
var link: Pair<String, GroupMemberRole>? by remember(chat.id) { mutableStateOf(preloadedLink) }
KeyChangeEffect(chat.id) {
setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel)
link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId)
preloadedLink = link
}
GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, {
link = it
preloadedLink = it
}, close)
}
}
}
for (di in deletedItems) {
chatModel.removeChatItem(chatRh, chatInfo, di)
},
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
hideKeyboard(view)
withApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
val stats = r?.second
val (_, code) = if (member.memberActive) {
val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId)
member to memCode?.second
} else {
member to null
}
setGroupMembers(chatRh, groupInfo, chatModel)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) { close ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
}
}
}
}
}
},
receiveFile = { fileId, encrypted ->
withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) }
},
cancelFile = { fileId ->
withApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
},
joinGroup = { groupId, onComplete ->
withApi {
chatModel.controller.apiJoinGroup(chatRh, groupId)
onComplete.invoke()
}
},
startCall = out@ { media ->
withBGApi {
val cInfo = chat.chatInfo
if (cInfo is ChatInfo.Direct) {
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media)
chatModel.showCallView.value = true
chatModel.callCommand.add(WCallCommand.Capabilities(media))
}
}
},
endCall = {
val call = chatModel.activeCall.value
if (call != null) withApi { chatModel.callManager.endCall(call) }
},
acceptCall = { contact ->
hideKeyboard(view)
val invitation = chatModel.callInvitations.remove(contact.id)
if (invitation == null) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
} else {
chatModel.callManager.acceptIncomingCall(invitation = invitation)
}
},
acceptFeature = { contact, feature, param ->
withApi {
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
}
},
openDirectChat = { contactId ->
withApi {
openDirectChat(chatRh, contactId, chatModel)
}
},
updateContactStats = { contact ->
withApi {
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
if (r != null) {
val contactStats = r.first
if (contactStats != null)
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
}
}
},
updateMemberStats = { groupInfo, member ->
withApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
if (r != null) {
val memStats = r.second
if (memStats != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
},
loadPrevMessages = {
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
val firstId = chatModel.chatItems.firstOrNull()?.id
if (c != null && firstId != null) {
withApi {
Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}")
apiLoadPrevMessages(c, chatModel, firstId, searchText.value)
Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}")
}
}
}
}
},
syncContactConnection = { contact ->
withApi {
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
if (cStats != null) {
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
}
}
},
syncMemberConnection = { groupInfo, member ->
withApi {
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
if (r != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
}
}
},
findModelChat = { chatId ->
chatModel.getChat(chatId)
},
findModelMember = { memberId ->
chatModel.groupMembers.find { it.id == memberId }
},
setReaction = { cInfo, cItem, add, reaction ->
withApi {
val updatedCI = chatModel.controller.apiChatItemReaction(
rh = chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
itemId = cItem.id,
add = add,
reaction = reaction
)
if (updatedCI != null) {
chatModel.updateChatItem(cInfo, updatedCI)
}
}
},
showItemDetails = { cInfo, cItem ->
withApi {
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
if (ciInfo != null) {
if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
},
deleteMessage = { itemId, mode ->
withApi {
val cInfo = chat.chatInfo
val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId }
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
val groupInfo = toModerate?.first
val groupMember = toModerate?.second
val deletedChatItem: ChatItem?
val toChatItem: ChatItem?
if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) {
val r = chatModel.controller.apiDeleteMemberChatItem(
chatRh,
groupId = groupInfo.groupId,
groupMemberId = groupMember.groupMemberId,
itemId = itemId
)
deletedChatItem = r?.first
toChatItem = r?.second
} else {
val r = chatModel.controller.apiDeleteChatItem(
chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
itemId = itemId,
mode = mode
)
deletedChatItem = r?.deletedChatItem?.chatItem
toChatItem = r?.toChatItem?.chatItem
}
if (toChatItem == null && deletedChatItem != null) {
chatModel.removeChatItem(chatRh, cInfo, deletedChatItem)
} else if (toChatItem != null) {
chatModel.upsertChatItem(chatRh, cInfo, toChatItem)
}
}
ModalManager.end.closeModals()
ModalManager.end.showModal(endButtons = { ShareButton {
clipboard.shareText(itemInfoShareText(chatModel, cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get()))
} }) {
ChatItemInfoView(chatModel, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get())
},
deleteMessages = { itemIds ->
if (itemIds.isNotEmpty()) {
val chatInfo = chat.chatInfo
withBGApi {
val deletedItems: ArrayList<ChatItem> = arrayListOf()
for (itemId in itemIds) {
val di = chatModel.controller.apiDeleteChatItem(
chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal
)?.deletedChatItem?.chatItem
if (di != null) {
deletedItems.add(di)
}
}
for (di in deletedItems) {
chatModel.removeChatItem(chatRh, chatInfo, di)
}
}
}
}
}
},
addMembers = { groupInfo ->
hideKeyboard(view)
withApi {
setGroupMembers(chatRh, groupInfo, chatModel)
},
receiveFile = { fileId, encrypted ->
withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) }
},
cancelFile = { fileId ->
withApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
},
joinGroup = { groupId, onComplete ->
withApi {
chatModel.controller.apiJoinGroup(chatRh, groupId)
onComplete.invoke()
}
},
startCall = out@{ media ->
withBGApi {
val cInfo = chat.chatInfo
if (cInfo is ChatInfo.Direct) {
chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media)
chatModel.showCallView.value = true
chatModel.callCommand.add(WCallCommand.Capabilities(media))
}
}
},
endCall = {
val call = chatModel.activeCall.value
if (call != null) withApi { chatModel.callManager.endCall(call) }
},
acceptCall = { contact ->
hideKeyboard(view)
val invitation = chatModel.callInvitations.remove(contact.id)
if (invitation == null) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended))
} else {
chatModel.callManager.acceptIncomingCall(invitation = invitation)
}
},
acceptFeature = { contact, feature, param ->
withApi {
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
}
},
openDirectChat = { contactId ->
withApi {
openDirectChat(chatRh, contactId, chatModel)
}
},
updateContactStats = { contact ->
withApi {
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
if (r != null) {
val contactStats = r.first
if (contactStats != null)
chatModel.updateContactConnectionStats(chatRh, contact, contactStats)
}
}
},
updateMemberStats = { groupInfo, member ->
withApi {
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
if (r != null) {
val memStats = r.second
if (memStats != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats)
}
}
}
},
syncContactConnection = { contact ->
withApi {
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
if (cStats != null) {
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
}
}
},
syncMemberConnection = { groupInfo, member ->
withApi {
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
if (r != null) {
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
}
}
},
findModelChat = { chatId ->
chatModel.getChat(chatId)
},
findModelMember = { memberId ->
chatModel.groupMembers.find { it.id == memberId }
},
setReaction = { cInfo, cItem, add, reaction ->
withApi {
val updatedCI = chatModel.controller.apiChatItemReaction(
rh = chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
itemId = cItem.id,
add = add,
reaction = reaction
)
if (updatedCI != null) {
chatModel.updateChatItem(cInfo, updatedCI)
}
}
},
showItemDetails = { cInfo, cItem ->
withApi {
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
if (ciInfo != null) {
if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel)
}
ModalManager.end.closeModals()
ModalManager.end.showModal(endButtons = {
ShareButton {
clipboard.shareText(itemInfoShareText(chatModel, cItem, ciInfo, chatModel.controller.appPrefs.developerTools.get()))
}
}) {
ChatItemInfoView(chatModel, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get())
}
}
}
},
addMembers = { groupInfo ->
hideKeyboard(view)
withApi {
setGroupMembers(chatRh, groupInfo, chatModel)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) { close ->
AddGroupMembersView(chatRh, groupInfo, false, chatModel, close)
}
}
},
openGroupLink = { groupInfo ->
hideKeyboard(view)
withApi {
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) {
GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null)
}
}
},
markRead = { range, unreadCountAfter ->
chatModel.markChatItemsRead(chat, range, unreadCountAfter)
ntfManager.cancelNotificationsForChat(chat.id)
withBGApi {
chatModel.controller.apiChatRead(
chatRh,
chat.chatInfo.chatType,
chat.chatInfo.apiId,
range
)
}
},
changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) },
onSearchValueChanged = { value ->
if (searchText.value == value) return@ChatLayout
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
withApi {
apiFindMessages(c, chatModel, value)
searchText.value = value
}
},
onComposed,
developerTools = chatModel.controller.appPrefs.developerTools.get(),
)
}
is ChatInfo.ContactConnection -> {
val close = { chatModel.chatId.value = null }
ModalView(close, showClose = appPlatform.isAndroid, content = {
ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close)
})
LaunchedEffect(chat.id) {
onComposed(chat.id)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) { close ->
AddGroupMembersView(chatRh, groupInfo, false, chatModel, close)
}
chatModel.chatItems.clear()
}
},
openGroupLink = { groupInfo ->
hideKeyboard(view)
withApi {
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
}
is ChatInfo.InvalidJSON -> {
val close = { chatModel.chatId.value = null }
ModalView(close, showClose = appPlatform.isAndroid, content = {
InvalidJSONView(chat.chatInfo.json)
})
LaunchedEffect(chat.id) {
onComposed(chat.id)
ModalManager.end.closeModals()
ModalManager.end.showModalCloseable(true) {
GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null)
}
chatModel.chatItems.clear()
}
},
markRead = { range, unreadCountAfter ->
chatModel.markChatItemsRead(chat, range, unreadCountAfter)
ntfManager.cancelNotificationsForChat(chat.id)
withBGApi {
chatModel.controller.apiChatRead(
chatRh,
chat.chatInfo.chatType,
chat.chatInfo.apiId,
range
)
}
},
changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) },
onSearchValueChanged = { value ->
if (searchText.value == value) return@ChatLayout
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
withApi {
apiFindMessages(c, chatModel, value)
searchText.value = value
}
},
onComposed,
developerTools = chatModel.controller.appPrefs.developerTools.get(),
)
}
else -> {}
}
}
}
@@ -733,7 +762,7 @@ fun ChatInfoToolbar(
}
@Composable
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant) {
fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) {
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
@@ -34,6 +34,7 @@ fun ContextItemView(
fun msgContentView(lines: Int) {
MarkdownText(
contextItem.text, contextItem.formattedText,
toggleSecrets = false,
maxLines = lines,
linkMode = SimplexLinkMode.DESCRIPTION,
modifier = Modifier.fillMaxWidth(),
@@ -13,29 +13,20 @@ import dev.icerock.moko.resources.compose.stringResource
@Composable
fun ScanCodeView(verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, close: () -> Unit) {
Column(
Modifier
.fillMaxSize()
.padding(horizontal = DEFAULT_PADDING)
Modifier.fillMaxSize()
) {
AppBarTitle(stringResource(MR.strings.scan_code), withPadding = false)
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(bottom = DEFAULT_PADDING)
) {
QRCodeScanner { text ->
verifyCode(text) {
if (it) {
close()
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.incorrect_code)
)
}
AppBarTitle(stringResource(MR.strings.scan_code))
QRCodeScanner { text ->
verifyCode(text) {
if (it) {
close()
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.incorrect_code)
)
}
}
}
Text(stringResource(MR.strings.scan_code_from_contacts_app))
Text(stringResource(MR.strings.scan_code_from_contacts_app), Modifier.padding(horizontal = DEFAULT_PADDING))
}
}
@@ -74,9 +74,7 @@ private fun VerifyCodeLayout(
}
}
SectionView {
QRCode(connectionCode, Modifier.aspectRatio(1f))
}
QRCode(connectionCode, padding = PaddingValues(vertical = DEFAULT_PADDING_HALF))
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.weight(2f))
@@ -153,7 +153,7 @@ fun GroupLinkLayout(
}
initialLaunch = false
}
SimpleXLinkQRCode(groupLink, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
SimpleXLinkQRCode(groupLink)
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -289,7 +289,7 @@ fun GroupMemberInfoLayout(
if (member.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
SimpleXLinkQRCode(member.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
SimpleXLinkQRCode(member.contactLink)
val clipboard = LocalClipboardManager.current
ShareAddressButton { clipboard.shareText(simplexChatLink(member.contactLink)) }
if (contactId != null) {
@@ -506,7 +506,7 @@ fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) {
try {
val uri = URI(connReqUri)
withApi {
planAndConnect(chatModel, rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
@@ -112,6 +112,11 @@ private fun GroupPreferencesLayout(
FeatureSection(GroupFeature.Files, allowFiles, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(files = GroupPreference(enable = it)))
}
SectionDividerSpaced(true, maxBottomPadding = false)
val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) }
FeatureSection(GroupFeature.History, enableHistory, groupInfo, preferences, onTTLUpdated) {
applyPrefs(preferences.copy(history = GroupPreference(enable = it)))
}
if (groupInfo.canEdit) {
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ResetSaveButtons(
@@ -15,6 +15,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@@ -119,13 +120,15 @@ private fun GroupWelcomeLayout(
@Composable
private fun TextPreview(text: String, linkMode: SimplexLinkMode, markdown: Boolean = true) {
val uriHandler = LocalUriHandler.current
Column {
SelectionContainer(Modifier.fillMaxWidth()) {
MarkdownText(
text,
formattedText = if (markdown) remember(text) { parseToMarkdown(text) } else null,
toggleSecrets = false,
modifier = Modifier.fillMaxHeight().padding(horizontal = DEFAULT_PADDING),
linkMode = linkMode,
linkMode = linkMode, uriHandler = uriHandler,
style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground, lineHeight = 22.sp)
)
}
@@ -54,6 +54,7 @@ fun FramedItemView(
MarkdownText(
qi.text,
qi.formattedText,
toggleSecrets = true,
maxLines = lines,
overflow = TextOverflow.Ellipsis,
style = TextStyle(fontSize = 15.sp, color = MaterialTheme.colors.onSurface),
@@ -288,7 +289,7 @@ fun CIMarkdownText(
Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) {
val text = if (ci.meta.isLive) ci.content.msgContent?.text ?: ci.text else ci.text
MarkdownText(
text, if (text.isEmpty()) emptyList() else ci.formattedText,
text, if (text.isEmpty()) emptyList() else ci.formattedText, toggleSecrets = true,
meta = ci.meta, chatTTL = chatTTL, linkMode = linkMode,
uriHandler = uriHandler, senderBold = true, onLinkLongClick = onLinkLongClick
)
@@ -60,6 +60,7 @@ fun MarkdownText (
sender: String? = null,
meta: CIMeta? = null,
chatTTL: Int? = null,
toggleSecrets: Boolean,
style: TextStyle = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onSurface, lineHeight = 22.sp),
maxLines: Int = Int.MAX_VALUE,
overflow: TextOverflow = TextOverflow.Clip,
@@ -89,6 +90,7 @@ fun MarkdownText (
) {
var timer: Job? by remember { mutableStateOf(null) }
var typingIdx by rememberSaveable { mutableStateOf(0) }
val showSecrets = remember { mutableStateMapOf<String, Boolean>() }
fun stopTyping() {
timer?.cancel()
timer = null
@@ -127,15 +129,22 @@ fun MarkdownText (
}
Text(annotatedText, style = style, modifier = modifier, maxLines = maxLines, overflow = overflow, inlineContent = inlineContent ?: mapOf())
} else {
var hasLinks = false
var hasAnnotations = false
val annotatedText = buildAnnotatedString {
appendSender(this, sender, senderBold)
for (ft in formattedText) {
for ((i, ft) in formattedText.withIndex()) {
if (ft.format == null) append(ft.text)
else {
else if (toggleSecrets && ft.format is Format.Secret) {
val ftStyle = ft.format.style
hasAnnotations = true
val key = i.toString()
withAnnotation(tag = "SECRET", annotation = key) {
if (showSecrets[key] == true) append(ft.text) else withStyle(ftStyle) { append(ft.text) }
}
} else {
val link = ft.link(linkMode)
if (link != null) {
hasLinks = true
hasAnnotations = true
val ftStyle = ft.format.style
withAnnotation(tag = if (ft.format is Format.SimplexLink) "SIMPLEX_URL" else "URL", annotation = link) {
withStyle(ftStyle) { append(ft.viewText(linkMode)) }
@@ -153,7 +162,7 @@ fun MarkdownText (
withStyle(reserveTimestampStyle) { append("\n" + metaText) }
else */if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) }
}
if (hasLinks && uriHandler != null) {
if (hasAnnotations && uriHandler != null) {
val icon = remember { mutableStateOf(PointerIcon.Default) }
ClickableText(annotatedText, style = style, modifier = modifier.pointerHoverIcon(icon.value), maxLines = maxLines, overflow = overflow,
onLongClick = { offset ->
@@ -177,12 +186,20 @@ fun MarkdownText (
.firstOrNull()?.let { annotation ->
uriHandler.openVerifiedSimplexUri(annotation.item)
}
annotatedText.getStringAnnotations(tag = "SECRET", start = offset, end = offset)
.firstOrNull()?.let { annotation ->
val key = annotation.item
showSecrets[key] = !(showSecrets[key] ?: false)
}
},
onHover = { offset ->
icon.value = annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset)
.firstOrNull()?.let {
PointerIcon.Hand
} ?: annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset)
.firstOrNull()?.let {
PointerIcon.Hand
} ?: annotatedText.getStringAnnotations(tag = "SECRET", start = offset, end = offset)
.firstOrNull()?.let {
PointerIcon.Hand
} ?: PointerIcon.Default
@@ -50,8 +50,8 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
)
Text(stringResource(MR.strings.above_then_preposition_continuation))
}
Text(annotatedStringResource(MR.strings.add_new_contact_to_create_one_time_QR_code), lineHeight = 22.sp)
Text(annotatedStringResource(MR.strings.scan_QR_code_to_connect_to_contact_who_shows_QR_code), lineHeight = 22.sp)
Text(annotatedStringResource(MR.strings.add_contact_button_to_create_link_or_connect_via_link), lineHeight = 22.sp)
Text(annotatedStringResource(MR.strings.create_group_button_to_create_new_group), lineHeight = 22.sp)
}
Column(
@@ -24,17 +24,15 @@ import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
import chat.simplex.common.views.chat.group.deleteGroupDialog
import chat.simplex.common.views.chat.group.leaveGroupDialog
import chat.simplex.common.views.chat.item.InvalidJSONView
import chat.simplex.common.views.chat.item.ItemAction
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
import kotlinx.coroutines.delay
import kotlinx.datetime.Clock
import java.net.URI
@Composable
fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
fun ChatListNavLinkView(chat: Chat, nextChatSelected: State<Boolean>) {
val showMenu = remember { mutableStateOf(false) }
val showMarkRead = remember(chat.chatStats.unreadCount, chat.chatStats.unreadChat) {
chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat
@@ -45,7 +43,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
showMenu.value = false
delay(500L)
}
val selectedChat = remember(chat.id) { derivedStateOf { chat.id == ChatModel.chatId.value } }
val selectedChat = remember(chat.id) { derivedStateOf { chat.id == chatModel.chatId.value } }
val showChatPreviews = chatModel.showChatPreviews.value
val inProgress = remember { mutableStateOf(false) }
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
@@ -75,7 +73,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
},
showMenu,
stopped,
selectedChat
selectedChat,
nextChatSelected,
)
}
is ChatInfo.Group ->
@@ -93,7 +92,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
},
showMenu,
stopped,
selectedChat
selectedChat,
nextChatSelected,
)
is ChatInfo.ContactRequest ->
ChatListNavLinkLayout(
@@ -110,7 +110,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
},
showMenu,
stopped,
selectedChat
selectedChat,
nextChatSelected,
)
is ChatInfo.ContactConnection ->
ChatListNavLinkLayout(
@@ -120,11 +121,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
}
},
click = {
ModalManager.center.closeModals()
ModalManager.end.closeModals()
ModalManager.center.showModalCloseable(true, showClose = appPlatform.isAndroid) { close ->
ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close)
}
chatModel.chatId.value = chat.id
},
dropdownMenuItems = {
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
@@ -133,7 +130,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
},
showMenu,
stopped,
selectedChat
selectedChat,
nextChatSelected,
)
is ChatInfo.InvalidJSON ->
ChatListNavLinkLayout(
@@ -143,13 +141,13 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
}
},
click = {
ModalManager.end.closeModals()
ModalManager.center.showModal(true) { InvalidJSONView(chat.chatInfo.json) }
chatModel.chatId.value = chat.id
},
dropdownMenuItems = null,
showMenu,
stopped,
selectedChat
selectedChat,
nextChatSelected,
)
}
}
@@ -476,10 +474,7 @@ fun ContactConnectionMenuItems(rhId: Long?, chatInfo: ChatInfo.ContactConnection
painterResource(MR.images.ic_delete),
onClick = {
deleteContactConnectionAlert(rhId, chatInfo.contactConnection, chatModel) {
if (chatModel.chatId.value == null) {
ModalManager.center.closeModals()
ModalManager.end.closeModals()
}
chatModel.dismissConnReqView(chatInfo.contactConnection.id)
}
showMenu.value = false
},
@@ -804,7 +799,8 @@ expect fun ChatListNavLinkLayout(
dropdownMenuItems: (@Composable () -> Unit)?,
showMenu: MutableState<Boolean>,
stopped: Boolean,
selectedChat: State<Boolean>
selectedChat: State<Boolean>,
nextChatSelected: State<Boolean>,
)
@Preview/*(
@@ -846,7 +842,8 @@ fun PreviewChatListNavLinkDirect() {
dropdownMenuItems = null,
showMenu = remember { mutableStateOf(false) },
stopped = false,
selectedChat = remember { mutableStateOf(false) }
selectedChat = remember { mutableStateOf(false) },
nextChatSelected = remember { mutableStateOf(false) }
)
}
}
@@ -890,7 +887,8 @@ fun PreviewChatListNavLinkGroup() {
dropdownMenuItems = null,
showMenu = remember { mutableStateOf(false) },
stopped = false,
selectedChat = remember { mutableStateOf(false) }
selectedChat = remember { mutableStateOf(false) },
nextChatSelected = remember { mutableStateOf(false) }
)
}
}
@@ -911,7 +909,8 @@ fun PreviewChatListNavLinkContactRequest() {
dropdownMenuItems = null,
showMenu = remember { mutableStateOf(false) },
stopped = false,
selectedChat = remember { mutableStateOf(false) }
selectedChat = remember { mutableStateOf(false) },
nextChatSelected = remember { mutableStateOf(false) }
)
}
}
@@ -10,11 +10,15 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.platform.*
import androidx.compose.ui.text.TextRange
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.*
import chat.simplex.common.SettingsViewState
import chat.simplex.common.model.*
@@ -29,6 +33,7 @@ import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import java.net.URI
@Composable
@@ -60,10 +65,10 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
}
}
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
var searchInList by rememberSaveable { mutableStateOf("") }
val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) }
val scope = rememberCoroutineScope()
val (userPickerState, scaffoldState ) = settingsState
Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } } },
Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(searchText, scaffoldState.drawerState, userPickerState, stopped)} },
scaffoldState = scaffoldState,
drawerContent = {
tryOrShowError("Settings", error = { ErrorSettingsView() }) {
@@ -73,7 +78,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f),
drawerGesturesEnabled = appPlatform.isAndroid,
floatingActionButton = {
if (searchInList.isEmpty() && !chatModel.desktopNoUserNoRemote) {
if (searchText.value.text.isEmpty() && !chatModel.desktopNoUserNoRemote && chatModel.chatRunning.value == true) {
FloatingActionButton(
onClick = {
if (!stopped) {
@@ -96,24 +101,22 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
}
) {
Box(Modifier.padding(it).padding(end = endPadding)) {
Column(
Box(
modifier = Modifier
.fillMaxSize()
) {
if (chatModel.chats.isNotEmpty()) {
ChatList(chatModel, search = searchInList)
} else if (!chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
Box(Modifier.fillMaxSize()) {
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
OnboardingButtons(showNewChatSheet)
}
Text(stringResource(MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary)
ChatList(chatModel, searchText = searchText)
if (chatModel.chats.isEmpty() && !chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
Text(stringResource(
if (chatModel.chatRunning.value == null) MR.strings.loading_chats else MR.strings.you_have_no_chats), Modifier.align(Alignment.Center), color = MaterialTheme.colors.secondary)
if (!stopped && !newChatSheetState.collectAsState().value.isVisible() && chatModel.chatRunning.value == true && searchText.value.text.isEmpty()) {
OnboardingButtons(showNewChatSheet)
}
}
}
}
}
if (searchInList.isEmpty()) {
if (searchText.value.text.isEmpty()) {
DesktopActiveCallOverlayLayout(newChatSheetState)
// TODO disable this button and sheet for the duration of the switch
tryOrShowError("NewChatSheet", error = {}) {
@@ -168,20 +171,8 @@ private fun ConnectButton(text: String, onClick: () -> Unit) {
}
@Composable
private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, userPickerState: MutableStateFlow<AnimatedViewState>, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
var showSearch by rememberSaveable { mutableStateOf(false) }
val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
if (showSearch) {
BackHandler(onBack = hideSearchOnBack)
}
private fun ChatListToolbar(searchInList: State<TextFieldValue>, drawerState: DrawerState, userPickerState: MutableStateFlow<AnimatedViewState>, stopped: Boolean) {
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
if (chatModel.chats.size > 0) {
barButtons.add {
IconButton({ showSearch = true }) {
Icon(painterResource(MR.images.ic_search_500), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary)
}
}
}
if (stopped) {
barButtons.add {
IconButton(onClick = {
@@ -201,9 +192,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user
val scope = rememberCoroutineScope()
DefaultTopAppBar(
navigationButton = {
if (showSearch) {
NavigationButtonBack(hideSearchOnBack)
} else if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) {
if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) {
NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } }
} else {
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
@@ -227,13 +216,18 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user
fontWeight = FontWeight.SemiBold,
)
if (chatModel.chats.size > 0) {
ToggleFilterButton()
val enabled = remember { derivedStateOf { searchInList.value.text.isEmpty() } }
if (enabled.value) {
ToggleFilterEnabledButton()
} else {
ToggleFilterDisabledButton()
}
}
}
},
onTitleClick = null,
showSearch = showSearch,
onSearchValueChanged = onSearchValueChanged,
showSearch = false,
onSearchValueChanged = {},
buttons = barButtons
)
Divider(Modifier.padding(top = AppBarHeight))
@@ -246,7 +240,8 @@ fun UserProfileButton(image: String?, allRead: Boolean, onButtonClicked: () -> U
Box {
ProfileImage(
image = image,
size = 37.dp
size = 37.dp,
color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)
)
if (!allRead) {
unreadBadge()
@@ -281,7 +276,7 @@ private fun BoxScope.unreadBadge(text: String? = "") {
}
@Composable
private fun ToggleFilterButton() {
private fun ToggleFilterEnabledButton() {
val pref = remember { ChatController.appPrefs.showUnreadAndFavorites }
IconButton(onClick = { pref.set(!pref.get()) }) {
Icon(
@@ -290,7 +285,7 @@ private fun ToggleFilterButton() {
tint = if (pref.state.value) MaterialTheme.colors.background else MaterialTheme.colors.primary,
modifier = Modifier
.padding(3.dp)
.background(color = if (pref.state.value) MaterialTheme.colors.primary else MaterialTheme.colors.background, shape = RoundedCornerShape(50))
.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)
@@ -298,6 +293,22 @@ private fun ToggleFilterButton() {
}
}
@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))
.padding(3.dp)
.size(16.dp)
)
}
}
@Composable
expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>)
@@ -307,11 +318,115 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) {
chatModel.appOpenUrl.value = rhId to uri
} else {
withApi {
planAndConnect(chatModel, rhId, uri, incognito = null, close = null)
planAndConnect(rhId, uri, incognito = null, close = null)
}
}
}
@Composable
private fun ChatListSearchBar(listState: LazyListState, searchText: MutableState<TextFieldValue>, searchShowingSimplexLink: MutableState<Boolean>, searchChatFilteredBySimplexLink: MutableState<String?>) {
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)
SearchTextField(
Modifier.weight(1f).onFocusChanged { focused = it.hasFocus }.focusRequester(focusRequester),
placeholder = stringResource(MR.strings.search_or_paste_simplex_link),
alwaysVisible = true,
searchText = searchText,
enabled = !remember { searchShowingSimplexLink }.value,
trailingContent = null,
) {
searchText.value = searchText.value.copy(it)
}
val hasText = remember { derivedStateOf { searchText.value.text.isNotEmpty() } }
if (hasText.value) {
val hideSearchOnBack: () -> Unit = { searchText.value = TextFieldValue() }
BackHandler(onBack = hideSearchOnBack)
KeyChangeEffect(chatModel.currentRemoteHost.value) {
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 focusManager = LocalFocusManager.current
val keyboardState = getKeyboardState()
LaunchedEffect(keyboardState.value) {
if (keyboardState.value == KeyboardState.Closed && focused) {
focusManager.clearFocus()
}
}
val view = LocalMultiplatformView()
LaunchedEffect(Unit) {
snapshotFlow { searchText.value.text }
.distinctUntilChanged()
.collect {
val link = strHasSingleSimplexLink(it.trim())
if (link != null) {
// if SimpleX link is pasted, show connection dialogue
hideKeyboard(view)
if (link.format is Format.SimplexLink) {
val linkText = link.simplexLinkText(link.format.linkType, link.format.smpHosts)
searchText.value = searchText.value.copy(linkText, selection = TextRange.Zero)
}
searchShowingSimplexLink.value = true
searchChatFilteredBySimplexLink.value = null
connect(link.text, searchChatFilteredBySimplexLink) { searchText.value = TextFieldValue() }
} else if (!searchShowingSimplexLink.value || it.isEmpty()) {
if (it.isNotEmpty()) {
// if some other text is pasted, enter search mode
focusRequester.requestFocus()
} else if (listState.layoutInfo.totalItemsCount > 0) {
listState.scrollToItem(0)
}
searchShowingSimplexLink.value = false
searchChatFilteredBySimplexLink.value = null
}
}
}
}
}
private fun connect(link: String, searchChatFilteredBySimplexLink: MutableState<String?>, cleanup: (() -> Unit)?) {
withBGApi {
planAndConnect(
chatModel.remoteHostId(),
URI.create(link),
incognito = null,
filterKnownContact = { searchChatFilteredBySimplexLink.value = it.id },
filterKnownGroup = { searchChatFilteredBySimplexLink.value = it.id },
close = null,
cleanup = cleanup,
)
}
}
@Composable
private fun ErrorSettingsView() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
@@ -322,7 +437,7 @@ private fun ErrorSettingsView() {
private var lazyListState = 0 to 0
@Composable
private fun ChatList(chatModel: ChatModel, search: String) {
private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldValue>) {
val listState = rememberLazyListState(lazyListState.first, lazyListState.second)
DisposableEffect(Unit) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
@@ -332,44 +447,77 @@ private fun ChatList(chatModel: ChatModel, search: String) {
// In some not always reproducible situations this code produce IndexOutOfBoundsException on Compose's side
// which is related to [derivedStateOf]. Using safe alternative instead
// val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search, allChats.toList()) } }
val chats = filteredChats(showUnreadAndFavorites, search, allChats.toList())
val searchShowingSimplexLink = remember { mutableStateOf(false) }
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.toList())
LazyColumn(
modifier = Modifier.fillMaxWidth(),
Modifier.fillMaxWidth(),
listState
) {
items(chats) { chat ->
ChatListNavLinkView(chat, chatModel)
stickyHeader {
Column(
Modifier
.offset {
val y = if (searchText.value.text.isEmpty()) {
if (listState.firstVisibleItemIndex == 0) -listState.firstVisibleItemScrollOffset else -1000
} else {
0
}
IntOffset(0, y)
}
.background(MaterialTheme.colors.background)
) {
ChatListSearchBar(listState, searchText, searchShowingSimplexLink, searchChatFilteredBySimplexLink)
Divider()
}
}
itemsIndexed(chats) { index, chat ->
val nextChatSelected = remember(chat.id, chats) { derivedStateOf {
chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value
} }
ChatListNavLinkView(chat, nextChatSelected)
}
}
if (chats.isEmpty() && !chatModel.chats.isEmpty()) {
if (chats.isEmpty() && chatModel.chats.isNotEmpty()) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(generalGetString(MR.strings.no_filtered_chats), color = MaterialTheme.colors.secondary)
}
}
}
private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String, chats: List<Chat>): List<Chat> {
val s = searchText.trim().lowercase()
return if (s.isEmpty() && !showUnreadAndFavorites)
chats
else {
chats.filter { chat ->
when (val cInfo = chat.chatInfo) {
is ChatInfo.Direct -> if (s.isEmpty()) {
filtered(chat)
} else {
(viewNameContains(cInfo, s) ||
cInfo.contact.profile.displayName.lowercase().contains(s) ||
cInfo.contact.fullName.lowercase().contains(s))
private fun filteredChats(
showUnreadAndFavorites: Boolean,
searchShowingSimplexLink: State<Boolean>,
searchChatFilteredBySimplexLink: State<String?>,
searchText: String,
chats: List<Chat>
): List<Chat> {
val linkChatId = searchChatFilteredBySimplexLink.value
return if (linkChatId != null) {
chats.filter { it.id == linkChatId }
} else {
val s = if (searchShowingSimplexLink.value) "" else searchText.trim().lowercase()
if (s.isEmpty() && !showUnreadAndFavorites)
chats
else {
chats.filter { chat ->
when (val cInfo = chat.chatInfo) {
is ChatInfo.Direct -> if (s.isEmpty()) {
chat.id == chatModel.chatId.value || filtered(chat)
} else {
(viewNameContains(cInfo, s) ||
cInfo.contact.profile.displayName.lowercase().contains(s) ||
cInfo.contact.fullName.lowercase().contains(s))
}
is ChatInfo.Group -> if (s.isEmpty()) {
chat.id == chatModel.chatId.value || filtered(chat) || cInfo.groupInfo.membership.memberStatus == GroupMemberStatus.MemInvited
} else {
viewNameContains(cInfo, s)
}
is ChatInfo.ContactRequest -> s.isEmpty() || viewNameContains(cInfo, s)
is ChatInfo.ContactConnection -> (s.isNotEmpty() && cInfo.contactConnection.localAlias.lowercase().contains(s)) || (s.isEmpty() && chat.id == chatModel.chatId.value)
is ChatInfo.InvalidJSON -> chat.id == chatModel.chatId.value
}
is ChatInfo.Group -> if (s.isEmpty()) {
(filtered(chat) || cInfo.groupInfo.membership.memberStatus == GroupMemberStatus.MemInvited)
} else {
viewNameContains(cInfo, s)
}
is ChatInfo.ContactRequest -> s.isEmpty() || viewNameContains(cInfo, s)
is ChatInfo.ContactConnection -> s.isNotEmpty() && cInfo.contactConnection.localAlias.lowercase().contains(s)
is ChatInfo.InvalidJSON -> false
}
}
}
@@ -173,6 +173,7 @@ fun ChatPreviewView(
cInfo is ChatInfo.Group && !ci.chatDir.sent -> ci.memberDisplayName
else -> null
},
toggleSecrets = false,
linkMode = linkMode,
senderBold = true,
maxLines = 2,
@@ -116,7 +116,10 @@ fun UserPicker(
}
}
LaunchedEffect(Unit) {
controller.reloadRemoteHosts()
// Controller.ctrl can be null when self-destructing activates
if (controller.ctrl != null && controller.ctrl != -1L) {
controller.reloadRemoteHosts()
}
}
val UsersView: @Composable ColumnScope.() -> Unit = {
users.forEach { u ->
@@ -4,7 +4,6 @@ import SectionBottomSpacer
import SectionDividerSpaced
import SectionTextFooter
import SectionItemView
import SectionSpacer
import SectionView
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
@@ -426,6 +425,7 @@ private fun authStopChat(m: ChatModel) {
}
is LAResult.Error -> {
m.chatRunning.value = true
laFailedAlert()
}
is LAResult.Failed -> {
m.chatRunning.value = true
@@ -459,6 +459,24 @@ suspend fun deleteChatAsync(m: ChatModel) {
m.controller.apiDeleteStorage()
DatabaseUtils.ksDatabasePassword.remove()
m.controller.appPrefs.storeDBPassphrase.set(true)
deleteChatDatabaseFiles()
}
fun deleteChatDatabaseFiles() {
val chat = File(dataDir, chatDatabaseFileName)
val chatBak = File(dataDir, "$chatDatabaseFileName.bak")
val agent = File(dataDir, agentDatabaseFileName)
val agentBak = File(dataDir, "$agentDatabaseFileName.bak")
chat.delete()
chatBak.delete()
agent.delete()
agentBak.delete()
filesDir.deleteRecursively()
remoteHostsDir.deleteRecursively()
tmpDir.deleteRecursively()
tmpDir.mkdir()
DatabaseUtils.ksDatabasePassword.remove()
controller.appPrefs.storeDBPassphrase.set(true)
}
private fun exportArchive(
@@ -62,7 +62,28 @@ class AlertManager {
fun showAlertDialogButtonsColumn(
title: String,
text: AnnotatedString? = null,
text: String? = null,
onDismissRequest: (() -> Unit)? = null,
hostDevice: Pair<Long?, String>? = null,
buttons: @Composable () -> Unit,
) {
showAlert {
AlertDialog(
onDismissRequest = { onDismissRequest?.invoke(); hideAlert() },
title = alertTitle(title),
buttons = {
AlertContent(text, hostDevice, extraPadding = true) {
buttons()
}
},
shape = RoundedCornerShape(corner = CornerSize(25.dp))
)
}
}
fun showAlertDialogButtonsColumn(
title: String,
text: AnnotatedString,
onDismissRequest: (() -> Unit)? = null,
hostDevice: Pair<Long?, String>? = null,
buttons: @Composable () -> Unit,
@@ -17,7 +17,7 @@ object DatabaseUtils {
val ksAppPassword = KeyStoreItem(APP_PASSWORD_ALIAS, appPreferences.encryptedAppPassphrase, appPreferences.initializationVectorAppPassphrase)
val ksSelfDestructPassword = KeyStoreItem(SELF_DESTRUCT_PASSWORD_ALIAS, appPreferences.encryptedSelfDestructPassphrase, appPreferences.initializationVectorSelfDestructPassphrase)
class KeyStoreItem(private val alias: String, val passphrase: SharedPreference<String?>, val initVector: SharedPreference<String?>) {
class KeyStoreItem(val alias: String, val passphrase: SharedPreference<String?>, val initVector: SharedPreference<String?>) {
fun get(): String? {
return cryptor.decryptData(
passphrase.get()?.toByteArrayFromBase64ForPassphrase() ?: return null,
@@ -75,11 +75,11 @@ object DatabaseUtils {
sealed class DBMigrationResult {
@Serializable @SerialName("ok") object OK: DBMigrationResult()
@Serializable @SerialName("invalidConfirmation") object InvalidConfirmation: DBMigrationResult()
@Serializable @SerialName("errorNotADatabase") class ErrorNotADatabase(val dbFile: String): DBMigrationResult()
@Serializable @SerialName("errorMigration") class ErrorMigration(val dbFile: String, val migrationError: MigrationError): DBMigrationResult()
@Serializable @SerialName("errorSQL") class ErrorSQL(val dbFile: String, val migrationSQLError: String): DBMigrationResult()
@Serializable @SerialName("errorNotADatabase") data class ErrorNotADatabase(val dbFile: String): DBMigrationResult()
@Serializable @SerialName("errorMigration") data class ErrorMigration(val dbFile: String, val migrationError: MigrationError): DBMigrationResult()
@Serializable @SerialName("errorSQL") data class ErrorSQL(val dbFile: String, val migrationSQLError: String): DBMigrationResult()
@Serializable @SerialName("errorKeychain") object ErrorKeychain: DBMigrationResult()
@Serializable @SerialName("unknown") class Unknown(val json: String): DBMigrationResult()
@Serializable @SerialName("unknown") data class Unknown(val json: String): DBMigrationResult()
}
@@ -0,0 +1,27 @@
package chat.simplex.common.views.helpers
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import chat.simplex.common.ui.theme.DEFAULT_PADDING
@Composable
fun DefaultProgressView(description: String?) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
CircularProgressIndicator(
Modifier
.padding(bottom = DEFAULT_PADDING)
.size(30.dp),
color = MaterialTheme.colors.secondary,
strokeWidth = 2.5.dp
)
if (description != null) {
Text(description)
}
}
}
}
@@ -36,7 +36,7 @@ fun DefaultTopAppBar(
SearchTextField(Modifier.fillMaxWidth(), alwaysVisible = false, onValueChange = onSearchValueChanged)
}
},
backgroundColor = if (isInDarkTheme()) ToolbarDark else ToolbarLight,
backgroundColor = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f),
navigationIcon = navigationButton,
buttons = if (!showSearch) buttons else emptyList(),
centered = !showSearch,
@@ -38,6 +38,12 @@ enum class ModalPlacement {
START, CENTER, END, FULLSCREEN
}
class ModalData {
private val state = mutableMapOf<String, MutableState<Any>>()
fun <T> stateGetOrPut (key: String, default: () -> T): MutableState<T> =
state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState<T>
}
class ModalManager(private val placement: ModalPlacement? = null) {
private val modalViews = arrayListOf<Pair<Boolean, (@Composable (close: () -> Unit) -> Unit)>>()
private val modalCount = mutableStateOf(0)
@@ -45,15 +51,17 @@ class ModalManager(private val placement: ModalPlacement? = null) {
private var oldViewChanging = AtomicBoolean(false)
private var passcodeView: MutableState<(@Composable (close: () -> Unit) -> Unit)?> = mutableStateOf(null)
fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) {
fun showModal(settings: Boolean = false, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) {
val data = ModalData()
showCustomModal { close ->
ModalView(close, showClose = showClose, endButtons = endButtons, content = content)
ModalView(close, showClose = showClose, endButtons = endButtons, content = { data.content() })
}
}
fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, content: @Composable (close: () -> Unit) -> Unit) {
fun showModalCloseable(settings: Boolean = false, showClose: Boolean = true, content: @Composable ModalData.(close: () -> Unit) -> Unit) {
val data = ModalData()
showCustomModal { close ->
ModalView(close, showClose = showClose, content = { content(close) })
ModalView(close, showClose = showClose, content = { data.content(close) })
}
}
@@ -34,6 +34,8 @@ fun SearchTextField(
alwaysVisible: Boolean,
searchText: MutableState<TextFieldValue> = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) },
placeholder: String = stringResource(MR.strings.search_verb),
enabled: Boolean = true,
trailingContent: @Composable (() -> Unit)? = null,
onValueChange: (String) -> Unit
) {
val focusRequester = remember { FocusRequester() }
@@ -54,12 +56,12 @@ fun SearchTextField(
}
}
val enabled = true
val colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Unspecified,
textColor = MaterialTheme.colors.onBackground,
focusedIndicatorColor = Color.Unspecified,
unfocusedIndicatorColor = Color.Unspecified,
disabledIndicatorColor = Color.Unspecified,
)
val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize)
val interactionSource = remember { MutableInteractionSource() }
@@ -77,6 +79,7 @@ fun SearchTextField(
searchText.value = it
onValueChange(it.text)
},
enabled = rememberUpdatedState(enabled).value,
cursorBrush = SolidColor(colors.cursorColor(false).value),
visualTransformation = VisualTransformation.None,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
@@ -105,7 +108,7 @@ fun SearchTextField(
}) {
Icon(painterResource(MR.images.ic_close), stringResource(MR.strings.icon_descr_close_button), tint = MaterialTheme.colors.primary,)
}
}} else null,
}} else trailingContent,
singleLine = true,
enabled = enabled,
interactionSource = interactionSource,
@@ -60,6 +60,9 @@ fun annotatedStringResource(id: StringResource, vararg args: Any?): AnnotatedStr
}
}
@Composable
expect fun SetupClipboardListener()
// maximum image file size to be auto-accepted
const val MAX_IMAGE_SIZE: Long = 261_120 // 255KB
const val MAX_IMAGE_SIZE_AUTO_RCV: Long = MAX_IMAGE_SIZE * 2
@@ -1,32 +1,45 @@
package chat.simplex.common.views.localauth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.views.database.deleteChatAsync
import chat.simplex.common.views.database.stopChatAsync
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.helpers.DatabaseUtils.ksSelfDestructPassword
import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword
import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.Profile
import chat.simplex.common.platform.*
import chat.simplex.common.views.database.*
import chat.simplex.res.MR
import kotlinx.coroutines.delay
@Composable
fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) {
val passcode = rememberSaveable { mutableStateOf("") }
PasscodeView(passcode, authRequest.title ?: stringResource(MR.strings.la_enter_app_passcode), authRequest.reason, stringResource(MR.strings.submit_passcode),
val allowToReact = rememberSaveable { mutableStateOf(true) }
if (!allowToReact.value) {
BackHandler {
// do nothing until submit action finishes to prevent concurrent removing of storage
}
}
PasscodeView(passcode, authRequest.title ?: stringResource(MR.strings.la_enter_app_passcode), authRequest.reason, stringResource(MR.strings.submit_passcode), buttonsEnabled = allowToReact,
submit = {
val sdPassword = ksSelfDestructPassword.get()
if (sdPassword == passcode.value && authRequest.selfDestruct) {
allowToReact.value = false
deleteStorageAndRestart(m, sdPassword) { r ->
authRequest.completed(r)
}
} else {
val r: LAResult = if (passcode.value == authRequest.password) LAResult.Success else LAResult.Error(generalGetString(MR.strings.incorrect_passcode))
val r: LAResult = if (passcode.value == authRequest.password) {
if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) {
initChatControllerAndRunMigrations(true)
}
LAResult.Success
} else {
LAResult.Error(generalGetString(MR.strings.incorrect_passcode))
}
authRequest.completed(r)
}
},
@@ -38,8 +51,28 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) {
private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (LAResult) -> Unit) {
withBGApi {
try {
stopChatAsync(m)
deleteChatAsync(m)
/** Waiting until [initChatController] finishes */
while (m.ctrlInitInProgress.value) {
delay(50)
}
if (m.chatRunning.value == true) {
stopChatAsync(m)
}
val ctrl = m.controller.ctrl
if (ctrl != null && ctrl != -1L) {
/**
* The following sequence can bring a user here:
* the user opened the app, entered app passcode, went to background, returned back, entered self-destruct code.
* In this case database should be closed to prevent possible situation when OS can deny database removal command
* */
chatCloseStore(ctrl)
}
deleteChatDatabaseFiles()
// Clear sensitive data on screen just in case ModalManager will fail to prevent hiding its modals while database encrypts itself
m.chatId.value = null
m.chatItems.clear()
m.chats.clear()
m.users.clear()
ksAppPassword.set(password)
ksSelfDestructPassword.remove()
ntfManager.cancelAllNotifications()
@@ -67,13 +100,15 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (
m.currentUser.value = createdUser
m.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete)
if (createdUser != null) {
controller.chatModel.chatRunning.value = false
m.controller.startChat(createdUser)
}
ModalManager.fullscreen.closeModals()
ModalManager.closeAllModalsEverywhere()
AlertManager.shared.hideAllAlerts()
AlertManager.privacySensitive.hideAllAlerts()
completed(LAResult.Success)
} catch (e: Exception) {
Log.e(TAG, "Unable to delete storage: ${e.stackTraceToString()}")
completed(LAResult.Error(generalGetString(MR.strings.incorrect_passcode)))
}
}
@@ -12,6 +12,7 @@ import dev.icerock.moko.resources.compose.painterResource
import androidx.compose.ui.unit.dp
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.DEFAULT_PADDING
import chat.simplex.common.views.chat.group.ProgressIndicator
import chat.simplex.common.views.helpers.SimpleButton
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
@@ -23,6 +24,7 @@ fun PasscodeView(
reason: String? = null,
submitLabel: String,
submitEnabled: ((String) -> Boolean)? = null,
buttonsEnabled: State<Boolean> = remember { mutableStateOf(true) },
submit: () -> Unit,
cancel: () -> Unit,
) {
@@ -74,9 +76,9 @@ fun PasscodeView(
}
PasscodeEntry(passcode, true)
Row(Modifier.heightIn(min = 70.dp), verticalAlignment = Alignment.CenterVertically) {
SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), click = cancel)
SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), disabled = !buttonsEnabled.value, click = cancel)
Spacer(Modifier.size(20.dp))
SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4 || !buttonsEnabled.value, click = submit)
}
}
}
@@ -117,8 +119,8 @@ fun PasscodeView(
Modifier.padding(start = 30.dp).height(s * 3),
verticalArrangement = Arrangement.SpaceEvenly
) {
SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), click = cancel)
SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4, click = submit)
SimpleButton(generalGetString(MR.strings.cancel_verb), icon = painterResource(MR.images.ic_close), disabled = !buttonsEnabled.value, click = cancel)
SimpleButton(submitLabel, icon = painterResource(MR.images.ic_done_filled), disabled = submitEnabled?.invoke(passcode.value) == false || passcode.value.length < 4 || !buttonsEnabled.value, click = submit)
}
}
}
@@ -130,6 +132,9 @@ fun PasscodeView(
} else {
HorizontalLayout()
}
if (!buttonsEnabled.value) {
ProgressIndicator()
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
// Disallow to steal a focus by clicking on buttons or using Tab
@@ -5,6 +5,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import chat.simplex.common.platform.BackHandler
import chat.simplex.common.views.helpers.DatabaseUtils
import chat.simplex.common.views.helpers.DatabaseUtils.ksAppPassword
import chat.simplex.common.views.helpers.DatabaseUtils.ksSelfDestructPassword
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.res.MR
@@ -48,7 +49,9 @@ fun SetAppPasscodeView(
}
}
} else {
SetPasswordView(title, generalGetString(MR.strings.save_verb)) {
SetPasswordView(title, generalGetString(MR.strings.save_verb),
// Do not allow to set app passcode == selfDestruct passcode
submitEnabled = { pwd -> pwd != (if (passcodeKeychain.alias == ksSelfDestructPassword.alias) ksAppPassword else ksSelfDestructPassword).get() }) {
enteredPassword = passcode.value
passcode.value = ""
confirming = true
@@ -4,14 +4,16 @@ import androidx.compose.foundation.*
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import chat.simplex.common.platform.chatModel
import dev.icerock.moko.resources.compose.stringResource
import chat.simplex.common.views.helpers.AppBarTitle
import chat.simplex.common.views.helpers.KeyChangeEffect
import chat.simplex.common.views.onboarding.ReadableText
import chat.simplex.common.views.onboarding.ReadableTextWithLink
import chat.simplex.res.MR
@Composable
fun AddContactLearnMore() {
fun AddContactLearnMore(close: () -> Unit) {
Column(
Modifier.verticalScroll(rememberScrollState()),
) {
@@ -20,4 +22,7 @@ fun AddContactLearnMore() {
ReadableText(MR.strings.if_you_cant_meet_in_person)
ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/readme.html#connect-to-friends")
}
KeyChangeEffect(chatModel.chatId.value) {
close()
}
}
@@ -1,186 +0,0 @@
package chat.simplex.common.views.newchat
import SectionBottomSpacer
import SectionTextFooter
import SectionView
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import dev.icerock.moko.resources.compose.painterResource
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.platform.shareText
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
@Composable
fun AddContactView(
chatModel: ChatModel,
rh: RemoteHostInfo?,
connReqInvitation: String,
contactConnection: MutableState<PendingContactConnection?>
) {
val clipboard = LocalClipboardManager.current
AddContactLayout(
rh = rh,
chatModel = chatModel,
incognitoPref = chatModel.controller.appPrefs.incognito,
connReq = connReqInvitation,
contactConnection = contactConnection,
learnMore = {
ModalManager.center.showModal {
Column(
Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween
) {
AddContactLearnMore()
}
}
}
)
}
@Composable
fun AddContactLayout(
chatModel: ChatModel,
rh: RemoteHostInfo?,
incognitoPref: SharedPreference<Boolean>,
connReq: String,
contactConnection: MutableState<PendingContactConnection?>,
learnMore: () -> Unit
) {
val incognito = remember { mutableStateOf(incognitoPref.get()) }
LaunchedEffect(incognito.value) {
withApi {
val contactConnVal = contactConnection.value
if (contactConnVal != null) {
chatModel.controller.apiSetConnectionIncognito(rh?.remoteHostId, contactConnVal.pccConnId, incognito.value)?.let {
contactConnection.value = it
chatModel.updateContactConnection(rh?.remoteHostId, it)
}
}
}
}
Column(
Modifier
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.SpaceBetween,
) {
AppBarTitle(stringResource(MR.strings.add_contact), hostDevice(rh?.remoteHostId))
SectionView(stringResource(MR.strings.one_time_link_short).uppercase()) {
if (connReq.isNotEmpty()) {
SimpleXLinkQRCode(
connReq, Modifier
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
.aspectRatio(1f)
)
} else {
CircularProgressIndicator(
Modifier
.size(36.dp)
.padding(4.dp)
.align(Alignment.CenterHorizontally),
color = MaterialTheme.colors.secondary,
strokeWidth = 3.dp
)
}
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
ShareLinkButton(connReq)
OneTimeLinkLearnMoreButton(learnMore)
}
SectionTextFooter(sharedProfileInfo(chatModel, incognito.value))
SectionBottomSpacer()
}
}
@Composable
fun ShareLinkButton(connReqInvitation: String) {
val clipboard = LocalClipboardManager.current
SettingsActionItem(
painterResource(MR.images.ic_share),
stringResource(MR.strings.share_invitation_link),
click = { clipboard.shareText(simplexChatLink(connReqInvitation)) },
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
@Composable
fun OneTimeLinkLearnMoreButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_info),
stringResource(MR.strings.learn_more),
onClick,
)
}
@Composable
fun IncognitoToggle(
incognitoPref: SharedPreference<Boolean>,
incognito: MutableState<Boolean>,
onClickInfo: () -> Unit
) {
SettingsActionItemWithContent(
icon = if (incognito.value) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy),
text = null,
click = onClickInfo,
iconColor = if (incognito.value) Indigo else MaterialTheme.colors.secondary,
extraPadding = false
) {
SharedPreferenceToggleWithIcon(
stringResource(MR.strings.incognito),
painterResource(MR.images.ic_info),
stopped = false,
onClickInfo = onClickInfo,
preference = incognitoPref,
preferenceState = incognito
)
}
}
fun sharedProfileInfo(
chatModel: ChatModel,
incognito: Boolean
): String {
val name = chatModel.currentUser.value?.displayName ?: ""
return if (incognito) {
generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared)
} else {
String.format(generalGetString(MR.strings.connect__your_profile_will_be_shared), name)
}
}
@Preview/*(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)*/
@Composable
fun PreviewAddContactView() {
SimpleXTheme {
AddContactLayout(
rh = null,
chatModel = ChatModel,
incognitoPref = SharedPreference({ false }, {}),
connReq = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
contactConnection = mutableStateOf(PendingContactConnection.getSampleData()),
learnMore = {},
)
}
}
@@ -139,7 +139,8 @@ fun AddGroupLayout(
createGroup(incognito.value, GroupProfile(
displayName = displayName.value.trim(),
fullName = "",
image = profileImage.value
image = profileImage.value,
groupPreferences = GroupPreferences(history = GroupPreference(GroupFeatureEnabled.ON))
))
},
textColor = MaterialTheme.colors.primary,
@@ -1,57 +1,51 @@
package chat.simplex.common.views.newchat
import SectionBottomSpacer
import SectionItemView
import SectionTextFooter
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chatlist.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.IncognitoView
import chat.simplex.res.MR
import java.net.URI
@Composable
expect fun ScanToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit)
enum class ConnectionLinkType {
INVITATION, CONTACT, GROUP
}
suspend fun planAndConnect(
chatModel: ChatModel,
rhId: Long?,
uri: URI,
incognito: Boolean?,
close: (() -> Unit)?
close: (() -> Unit)?,
cleanup: (() -> Unit)? = null,
filterKnownContact: ((Contact) -> Unit)? = null,
filterKnownGroup: ((GroupInfo) -> Unit)? = null,
) {
val connectionPlan = chatModel.controller.apiConnectPlan(rhId, uri.toString())
if (connectionPlan != null) {
val link = strHasSingleSimplexLink(uri.toString().trim())
val linkText = if (link?.format is Format.SimplexLink)
"<br><br><u>${link.simplexLinkText(link.format.linkType, link.format.smpHosts)}</u>"
else
""
when (connectionPlan) {
is ConnectionPlan.InvitationLink -> when (connectionPlan.invitationLinkPlan) {
InvitationLinkPlan.Ok -> {
Log.d(TAG, "planAndConnect, .InvitationLink, .Ok, incognito=$incognito")
if (incognito != null) {
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
} else {
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_via_invitation_link),
text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
connectDestructive = false
text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText,
connectDestructive = false,
cleanup = cleanup,
)
}
}
@@ -60,9 +54,11 @@ suspend fun planAndConnect(
if (incognito != null) {
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link),
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } },
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
onDismiss = cleanup,
onDismissRequest = cleanup,
destructive = true,
hostDevice = hostDevice(rhId),
)
@@ -70,8 +66,9 @@ suspend fun planAndConnect(
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link)),
connectDestructive = true
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
connectDestructive = true,
cleanup = cleanup,
)
}
}
@@ -79,42 +76,54 @@ suspend fun planAndConnect(
Log.d(TAG, "planAndConnect, .InvitationLink, .Connecting, incognito=$incognito")
val contact = connectionPlan.invitationLinkPlan.contact_
if (contact != null) {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName),
hostDevice = hostDevice(rhId),
)
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
cleanup?.invoke()
}
} else {
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_already_connecting),
generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link),
generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link) + linkText,
hostDevice = hostDevice(rhId),
)
cleanup?.invoke()
}
}
is InvitationLinkPlan.Known -> {
Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito")
val contact = connectionPlan.invitationLinkPlan.contact
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName),
hostDevice = hostDevice(rhId),
)
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
cleanup?.invoke()
}
}
}
is ConnectionPlan.ContactAddress -> when (connectionPlan.contactAddressPlan) {
ContactAddressPlan.Ok -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .Ok, incognito=$incognito")
if (incognito != null) {
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
} else {
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_via_contact_link),
text = AnnotatedString(generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link)),
connectDestructive = false
text = generalGetString(MR.strings.profile_will_be_sent_to_contact_sending_link) + linkText,
connectDestructive = false,
cleanup,
)
}
}
@@ -123,18 +132,21 @@ suspend fun planAndConnect(
if (incognito != null) {
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address),
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } },
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
destructive = true,
onDismiss = cleanup,
onDismissRequest = cleanup,
hostDevice = hostDevice(rhId),
)
} else {
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
text = AnnotatedString(generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address)),
connectDestructive = true
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
connectDestructive = true,
cleanup = cleanup,
)
}
}
@@ -143,9 +155,11 @@ suspend fun planAndConnect(
if (incognito != null) {
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address),
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } },
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
onDismiss = cleanup,
onDismissRequest = cleanup,
destructive = true,
hostDevice = hostDevice(rhId),
)
@@ -153,30 +167,41 @@ suspend fun planAndConnect(
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address)),
connectDestructive = true
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
connectDestructive = true,
cleanup = cleanup,
)
}
}
is ContactAddressPlan.ConnectingProhibit -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito")
val contact = connectionPlan.contactAddressPlan.contact
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName),
hostDevice = hostDevice(rhId),
)
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
cleanup?.invoke()
}
}
is ContactAddressPlan.Known -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito")
val contact = connectionPlan.contactAddressPlan.contact
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName),
hostDevice = hostDevice(rhId),
)
if (filterKnownContact != null) {
filterKnownContact(contact)
} else {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
cleanup?.invoke()
}
}
is ContactAddressPlan.ContactViaAddress -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .ContactViaAddress, incognito=$incognito")
@@ -187,6 +212,7 @@ suspend fun planAndConnect(
} else {
askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close, openChat = false)
}
cleanup?.invoke()
}
}
is ConnectionPlan.GroupLink -> when (connectionPlan.groupLinkPlan) {
@@ -195,33 +221,42 @@ suspend fun planAndConnect(
if (incognito != null) {
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_via_group_link),
text = generalGetString(MR.strings.you_will_join_group),
text = generalGetString(MR.strings.you_will_join_group) + linkText,
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } },
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
onDismiss = cleanup,
onDismissRequest = cleanup,
hostDevice = hostDevice(rhId),
)
} else {
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_via_group_link),
text = AnnotatedString(generalGetString(MR.strings.you_will_join_group)),
connectDestructive = false
text = generalGetString(MR.strings.you_will_join_group) + linkText,
connectDestructive = false,
cleanup = cleanup,
)
}
}
is GroupLinkPlan.OwnLink -> {
Log.d(TAG, "planAndConnect, .GroupLink, .OwnLink, incognito=$incognito")
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
ownGroupLinkConfirmConnect(chatModel, rhId, uri, incognito, connectionPlan, groupInfo, close)
if (filterKnownGroup != null) {
filterKnownGroup(groupInfo)
} else {
ownGroupLinkConfirmConnect(chatModel, rhId, uri, linkText, incognito, connectionPlan, groupInfo, close, cleanup)
}
}
GroupLinkPlan.ConnectingConfirmReconnect -> {
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito")
if (incognito != null) {
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close) } },
onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } },
onDismiss = cleanup,
onDismissRequest = cleanup,
destructive = true,
hostDevice = hostDevice(rhId),
)
@@ -229,8 +264,9 @@ suspend fun planAndConnect(
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan, close,
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
text = AnnotatedString(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link)),
connectDestructive = true
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
connectDestructive = true,
cleanup = cleanup,
)
}
}
@@ -240,37 +276,44 @@ suspend fun planAndConnect(
if (groupInfo != null) {
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_group_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName)
String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName) + linkText
)
} else {
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_already_joining_the_group),
generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText,
hostDevice = hostDevice(rhId),
)
}
cleanup?.invoke()
}
is GroupLinkPlan.Known -> {
Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
openKnownGroup(chatModel, rhId, close, groupInfo)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_group_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName),
hostDevice = hostDevice(rhId),
)
if (filterKnownGroup != null) {
filterKnownGroup(groupInfo)
} else {
openKnownGroup(chatModel, rhId, close, groupInfo)
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_group_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName) + linkText,
hostDevice = hostDevice(rhId),
)
cleanup?.invoke()
}
}
}
}
} else {
Log.d(TAG, "planAndConnect, plan error")
if (incognito != null) {
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close)
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan = null, close, cleanup)
} else {
askCurrentOrIncognitoProfileAlert(
chatModel, rhId, uri, connectionPlan = null, close,
title = generalGetString(MR.strings.connect_plan_connect_via_link),
connectDestructive = false
connectDestructive = false,
cleanup = cleanup,
)
}
}
@@ -282,7 +325,8 @@ suspend fun connectViaUri(
uri: URI,
incognito: Boolean,
connectionPlan: ConnectionPlan?,
close: (() -> Unit)?
close: (() -> Unit)?,
cleanup: (() -> Unit)?,
) {
val pcc = chatModel.controller.apiConnect(rhId, incognito, uri.toString())
val connLinkType = if (connectionPlan != null) planToConnectionLinkType(connectionPlan) else ConnectionLinkType.INVITATION
@@ -300,6 +344,7 @@ suspend fun connectViaUri(
hostDevice = hostDevice(rhId),
)
}
cleanup?.invoke()
}
fun planToConnectionLinkType(connectionPlan: ConnectionPlan): ConnectionLinkType {
@@ -317,8 +362,9 @@ fun askCurrentOrIncognitoProfileAlert(
connectionPlan: ConnectionPlan?,
close: (() -> Unit)?,
title: String,
text: AnnotatedString? = null,
text: String? = null,
connectDestructive: Boolean,
cleanup: (() -> Unit)?,
) {
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
title = title,
@@ -329,7 +375,7 @@ fun askCurrentOrIncognitoProfileAlert(
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
}
}) {
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
@@ -337,18 +383,20 @@ fun askCurrentOrIncognitoProfileAlert(
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup)
}
}) {
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
}
SectionItemView({
AlertManager.privacySensitive.hideAlert()
cleanup?.invoke()
}) {
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
},
onDismissRequest = cleanup,
hostDevice = hostDevice(rhId),
)
}
@@ -367,20 +415,23 @@ fun ownGroupLinkConfirmConnect(
chatModel: ChatModel,
rhId: Long?,
uri: URI,
linkText: String,
incognito: Boolean?,
connectionPlan: ConnectionPlan?,
groupInfo: GroupInfo,
close: (() -> Unit)?,
cleanup: (() -> Unit)?,
) {
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.connect_plan_join_your_group),
text = AnnotatedString(String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName)),
text = String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName) + linkText,
buttons = {
Column {
// Open group
SectionItemView({
AlertManager.privacySensitive.hideAlert()
openKnownGroup(chatModel, rhId, close, groupInfo)
cleanup?.invoke()
}) {
Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -389,7 +440,7 @@ fun ownGroupLinkConfirmConnect(
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
}
}) {
Text(
@@ -402,7 +453,7 @@ fun ownGroupLinkConfirmConnect(
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
}
}) {
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
@@ -411,7 +462,7 @@ fun ownGroupLinkConfirmConnect(
SectionItemView({
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close)
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup)
}
}) {
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
@@ -420,11 +471,13 @@ fun ownGroupLinkConfirmConnect(
// Cancel
SectionItemView({
AlertManager.privacySensitive.hideAlert()
cleanup?.invoke()
}) {
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
}
},
onDismissRequest = cleanup,
hostDevice = hostDevice(rhId),
)
}
@@ -438,77 +491,3 @@ fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, grou
}
}
}
@Composable
fun ConnectContactLayout(
chatModel: ChatModel,
rh: RemoteHostInfo?,
incognitoPref: SharedPreference<Boolean>,
close: () -> Unit
) {
val incognito = remember { mutableStateOf(incognitoPref.get()) }
@Composable
fun QRCodeScanner(close: () -> Unit) {
QRCodeScanner { connReqUri ->
try {
val uri = URI(connReqUri)
withApi {
planAndConnect(chatModel, rh?.remoteHostId, uri, incognito = incognito.value, close)
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.invalid_QR_code),
text = generalGetString(MR.strings.this_QR_code_is_not_a_link)
)
}
}
}
Column(
Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween
) {
AppBarTitle(stringResource(MR.strings.scan_QR_code), hostDevice(rh?.remoteHostId), withPadding = false)
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(bottom = 12.dp)
) { QRCodeScanner(close) }
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
SectionTextFooter(
buildAnnotatedString {
append(sharedProfileInfo(chatModel, incognito.value))
append("\n\n")
append(annotatedStringResource(MR.strings.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link))
}
)
SectionBottomSpacer()
}
}
fun URI.getQueryParameter(param: String): String? {
if (!query.contains("$param=")) return null
return query.substringAfter("$param=").substringBefore("&")
}
@Preview/*(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)*/
@Composable
fun PreviewConnectContactLayout() {
SimpleXTheme {
ConnectContactLayout(
chatModel = ChatModel,
rh = null,
incognitoPref = SharedPreference({ false }, {}),
close = {},
)
}
}
@@ -1,12 +0,0 @@
package chat.simplex.common.views.newchat
import androidx.compose.runtime.*
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.RemoteHostInfo
enum class ConnectViaLinkTab {
SCAN, PASTE
}
@Composable
expect fun ConnectViaLinkView(m: ChatModel, rh: RemoteHostInfo?, close: () -> Unit)
@@ -23,7 +23,7 @@ import chat.simplex.common.views.chatlist.deleteContactConnectionAlert
import chat.simplex.common.views.helpers.*
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.PendingContactConnection
import chat.simplex.common.platform.shareText
import chat.simplex.common.platform.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
@@ -37,16 +37,19 @@ fun ContactConnectionInfoView(
close: () -> Unit
) {
LaunchedEffect(connReqInvitation) {
chatModel.connReqInv.value = connReqInvitation
if (connReqInvitation != null) {
chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false)
}
}
/** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv].
* Otherwise, it will be called here AFTER [AddContactView] is launched and will clear the value too soon.
/** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation].
* Otherwise, it will be called here AFTER [AddContactLearnMore] is launched and will clear the value too soon.
* It will be dropped automatically when connection established or when user goes away from this screen.
* It applies only to Android because on Desktop center space will not be overlapped by [AddContactLearnMore]
**/
DisposableEffect(Unit) {
onDispose {
if (!ModalManager.center.hasModalsOpen()) {
chatModel.connReqInv.value = null
if (!ModalManager.center.hasModalsOpen() || appPlatform.isDesktop) {
chatModel.showingInvitation.value = null
}
}
}
@@ -61,14 +64,14 @@ fun ContactConnectionInfoView(
onLocalAliasChanged = { setContactAlias(rhId, contactConnection, it, chatModel) },
share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) },
learnMore = {
ModalManager.center.showModal {
ModalManager.end.showModalCloseable { close ->
Column(
Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween
) {
AddContactLearnMore()
AddContactLearnMore(close)
}
}
}
@@ -135,11 +138,7 @@ private fun ContactConnectionInfoLayout(
SectionView {
if (!connReq.isNullOrEmpty() && contactConnection.initiated) {
SimpleXLinkQRCode(
connReq, Modifier
.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF)
.aspectRatio(1f)
)
SimpleXLinkQRCode(connReq)
incognitoEnabled()
ShareLinkButton(connReq)
OneTimeLinkLearnMoreButton(learnMore)
@@ -158,6 +157,30 @@ private fun ContactConnectionInfoLayout(
}
}
@Composable
fun ShareLinkButton(connReqInvitation: String) {
val clipboard = LocalClipboardManager.current
SettingsActionItem(
painterResource(MR.images.ic_share),
stringResource(MR.strings.share_invitation_link),
click = {
chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy(connChatUsed = true)
clipboard.shareText(simplexChatLink(connReqInvitation))
},
iconColor = MaterialTheme.colors.primary,
textColor = MaterialTheme.colors.primary,
)
}
@Composable
fun OneTimeLinkLearnMoreButton(onClick: () -> Unit) {
SettingsActionItem(
painterResource(MR.images.ic_info),
stringResource(MR.strings.learn_more),
onClick,
)
}
@Composable
fun DeleteButton(onClick: () -> Unit) {
SettingsActionItem(
@@ -1,118 +0,0 @@
package chat.simplex.common.views.newchat
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.UserAddressView
import chat.simplex.res.MR
enum class CreateLinkTab {
ONE_TIME, LONG_TERM
}
@Composable
fun CreateLinkView(m: ChatModel, rh: RemoteHostInfo?, initialSelection: CreateLinkTab) {
val selection = remember { mutableStateOf(initialSelection) }
val connReqInvitation = rememberSaveable { m.connReqInv }
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) }
val creatingConnReq = rememberSaveable { mutableStateOf(false) }
LaunchedEffect(selection.value) {
if (
selection.value == CreateLinkTab.ONE_TIME
&& connReqInvitation.value.isNullOrEmpty()
&& contactConnection.value == null
&& !creatingConnReq.value
) {
createInvitation(m, rh?.remoteHostId, creatingConnReq, connReqInvitation, contactConnection)
}
}
/** When [AddContactView] is open, we don't need to drop [chatModel.connReqInv].
* Otherwise, it will be called here AFTER [AddContactView] is launched and will clear the value too soon.
* It will be dropped automatically when connection established or when user goes away from this screen.
**/
DisposableEffect(Unit) {
onDispose {
if (!ModalManager.center.hasModalsOpen()) {
m.connReqInv.value = null
}
}
}
val tabTitles = CreateLinkTab.values().map {
when {
it == CreateLinkTab.ONE_TIME && connReqInvitation.value.isNullOrEmpty() && contactConnection.value == null ->
stringResource(MR.strings.create_one_time_link)
it == CreateLinkTab.ONE_TIME ->
stringResource(MR.strings.one_time_link)
it == CreateLinkTab.LONG_TERM ->
stringResource(MR.strings.your_simplex_contact_address)
else -> ""
}
}
Column(
Modifier
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) {
Column(Modifier.weight(1f)) {
when (selection.value) {
CreateLinkTab.ONE_TIME -> {
AddContactView(m, rh,connReqInvitation.value ?: "", contactConnection)
}
CreateLinkTab.LONG_TERM -> {
UserAddressView(m, viaCreateLinkView = true, close = {})
}
}
}
TabRow(
selectedTabIndex = selection.value.ordinal,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
Tab(
selected = selection.value.ordinal == index,
onClick = {
selection.value = CreateLinkTab.values()[index]
},
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (CreateLinkTab.ONE_TIME.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_all_inclusive),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}
}
}
private fun createInvitation(
m: ChatModel,
rhId: Long?,
creatingConnReq: MutableState<Boolean>,
connReqInvitation: MutableState<String?>,
contactConnection: MutableState<PendingContactConnection?>
) {
creatingConnReq.value = true
withApi {
val r = m.controller.apiAddContact(rhId, incognito = m.controller.appPrefs.incognito.get())
if (r != null) {
m.updateContactConnection(rhId, r.second)
connReqInvitation.value = r.first
contactConnection.value = r.second
} else {
creatingConnReq.value = false
}
}
}
@@ -42,12 +42,7 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow<AnimatedView
addContact = {
closeNewChatSheet(false)
ModalManager.center.closeModals()
ModalManager.center.showModal { CreateLinkView(chatModel, chatModel.currentRemoteHost.value, CreateLinkTab.ONE_TIME) }
},
connectViaLink = {
closeNewChatSheet(false)
ModalManager.center.closeModals()
ModalManager.center.showModalCloseable { close -> ConnectViaLinkView(chatModel, chatModel.currentRemoteHost.value, close) }
ModalManager.center.showModalCloseable { close -> NewChatView(chatModel.currentRemoteHost.value, NewChatOption.INVITE, close = close) }
},
createGroup = {
closeNewChatSheet(false)
@@ -59,18 +54,16 @@ fun NewChatSheet(chatModel: ChatModel, newChatSheetState: StateFlow<AnimatedView
}
private val titles = listOf(
MR.strings.share_one_time_link,
if (appPlatform.isAndroid) MR.strings.connect_via_link_or_qr else MR.strings.connect_via_link,
MR.strings.create_group
MR.strings.add_contact_tab,
MR.strings.create_group_button
)
private val icons = listOf(MR.images.ic_add_link, MR.images.ic_qr_code, MR.images.ic_group)
private val icons = listOf(MR.images.ic_add_link, MR.images.ic_group)
@Composable
private fun NewChatSheetLayout(
newChatSheetState: StateFlow<AnimatedViewState>,
stopped: Boolean,
addContact: () -> Unit,
connectViaLink: () -> Unit,
createGroup: () -> Unit,
closeNewChatSheet: (animated: Boolean) -> Unit,
) {
@@ -109,7 +102,7 @@ private fun NewChatSheetLayout(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.End
) {
val actions = remember { listOf(addContact, connectViaLink, createGroup) }
val actions = remember { listOf(addContact, createGroup) }
val backgroundColor = if (isInDarkTheme())
blendARGB(MaterialTheme.colors.primary, Color.Black, 0.7F)
else
@@ -271,7 +264,6 @@ private fun PreviewNewChatSheet() {
MutableStateFlow(AnimatedViewState.VISIBLE),
stopped = false,
addContact = {},
connectViaLink = {},
createGroup = {},
closeNewChatSheet = {},
)
@@ -0,0 +1,436 @@
package chat.simplex.common.views.newchat
import SectionBottomSpacer
import SectionItemView
import SectionTextFooter
import SectionView
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.text.BasicTextField
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.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.VisualTransformation
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.sp
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import kotlinx.coroutines.launch
import java.net.URI
enum class NewChatOption {
INVITE, CONNECT
}
@Composable
fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRCodeScanner: Boolean = false, close: () -> Unit) {
val selection = remember { stateGetOrPut("selection") { selection } }
val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } }
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) }
val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } }
val creatingConnReq = rememberSaveable { mutableStateOf(false) }
val pastedLink = rememberSaveable { mutableStateOf("") }
LaunchedEffect(selection.value) {
if (
selection.value == NewChatOption.INVITE
&& connReqInvitation.isEmpty()
&& contactConnection.value == null
&& !creatingConnReq.value
) {
createInvitation(rh?.remoteHostId, creatingConnReq, connReqInvitation, contactConnection)
}
}
DisposableEffect(Unit) {
onDispose {
/** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation].
* Otherwise, it will be called here AFTER [AddContactLearnMore] is launched and will clear the value too soon.
* It will be dropped automatically when connection established or when user goes away from this screen.
* It applies only to Android because on Desktop center space will not be overlapped by [AddContactLearnMore]
**/
if (chatModel.showingInvitation.value != null && (!ModalManager.center.hasModalsOpen() || appPlatform.isDesktop)) {
val conn = contactConnection.value
if (chatModel.showingInvitation.value?.connChatUsed == false && conn != null) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.keep_unused_invitation_question),
text = generalGetString(MR.strings.you_can_view_invitation_link_again),
confirmText = generalGetString(MR.strings.delete_verb),
dismissText = generalGetString(MR.strings.keep_invitation_link),
destructive = true,
onConfirm = {
withBGApi {
val chatInfo = ChatInfo.ContactConnection(conn)
controller.deleteChat(Chat(remoteHostId = rh?.remoteHostId, chatInfo = chatInfo, chatItems = listOf()))
if (chatModel.chatId.value == chatInfo.id) {
chatModel.chatId.value = null
ModalManager.end.closeModals()
}
}
}
)
}
chatModel.showingInvitation.value = null
}
}
}
val tabTitles = NewChatOption.values().map {
when(it) {
NewChatOption.INVITE ->
stringResource(MR.strings.add_contact_tab)
NewChatOption.CONNECT ->
stringResource(MR.strings.connect_via_link)
}
}
Column(
Modifier.fillMaxSize(),
) {
Box(contentAlignment = Alignment.Center) {
val bottomPadding = DEFAULT_PADDING
AppBarTitle(stringResource(MR.strings.new_chat), hostDevice(rh?.remoteHostId), bottomPadding = bottomPadding)
Column(Modifier.align(Alignment.CenterEnd).padding(bottom = bottomPadding, end = DEFAULT_PADDING)) {
AddContactLearnMoreButton()
}
}
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(
initialPage = selection.value.ordinal,
initialPageOffsetFraction = 0f
) { NewChatOption.values().size }
KeyChangeEffect(pagerState.currentPage) {
selection.value = NewChatOption.values()[pagerState.currentPage]
}
TabRow(
selectedTabIndex = pagerState.currentPage,
backgroundColor = Color.Transparent,
contentColor = MaterialTheme.colors.primary,
) {
tabTitles.forEachIndexed { index, it ->
LeadingIconTab(
selected = pagerState.currentPage == index,
onClick = {
scope.launch {
pagerState.animateScrollToPage(index)
}
},
text = { Text(it, fontSize = 13.sp) },
icon = {
Icon(
if (NewChatOption.INVITE.ordinal == index) painterResource(MR.images.ic_repeat_one) else painterResource(MR.images.ic_qr_code),
it
)
},
selectedContentColor = MaterialTheme.colors.primary,
unselectedContentColor = MaterialTheme.colors.secondary,
)
}
}
HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top) { index ->
Column(
Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState()),
verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top) {
Spacer(Modifier.height(DEFAULT_PADDING))
when (index) {
NewChatOption.INVITE.ordinal -> {
PrepareAndInviteView(rh?.remoteHostId, contactConnection, connReqInvitation, creatingConnReq)
}
NewChatOption.CONNECT.ordinal -> {
ConnectView(rh?.remoteHostId, showQRCodeScanner, pastedLink, close)
}
}
SectionBottomSpacer()
}
}
}
}
@Composable
private fun PrepareAndInviteView(rhId: Long?, contactConnection: MutableState<PendingContactConnection?>, connReqInvitation: String, creatingConnReq: MutableState<Boolean>) {
if (connReqInvitation.isNotEmpty()) {
InviteView(
rhId,
connReqInvitation = connReqInvitation,
contactConnection = contactConnection,
)
} else if (creatingConnReq.value) {
CreatingLinkProgressView()
} else {
RetryButton { createInvitation(rhId, creatingConnReq, connReqInvitation, contactConnection) }
}
}
@Composable
private fun CreatingLinkProgressView() {
DefaultProgressView(stringResource(MR.strings.creating_link))
}
@Composable
private fun RetryButton(onClick: () -> Unit) {
Column(
Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
IconButton(onClick, Modifier.size(30.dp)) {
Icon(painterResource(MR.images.ic_refresh), null)
}
Spacer(Modifier.height(DEFAULT_PADDING))
Text(stringResource(MR.strings.retry_verb))
}
}
@Composable
private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState<PendingContactConnection?>) {
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase()) {
LinkTextView(connReqInvitation, true)
}
Spacer(Modifier.height(10.dp))
SectionView(stringResource(MR.strings.or_show_this_qr_code).uppercase()) {
SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() })
}
Spacer(Modifier.height(10.dp))
val incognito = remember { mutableStateOf(controller.appPrefs.incognito.get()) }
IncognitoToggle(controller.appPrefs.incognito, incognito) {
if (appPlatform.isDesktop) ModalManager.end.closeModals()
ModalManager.end.showModal { IncognitoView() }
}
KeyChangeEffect(incognito.value) {
withBGApi {
val contactConn = contactConnection.value ?: return@withBGApi
val conn = controller.apiSetConnectionIncognito(rhId, contactConn.pccConnId, incognito.value) ?: return@withBGApi
contactConnection.value = conn
chatModel.updateContactConnection(rhId, conn)
}
chatModel.markShowingInvitationUsed()
}
SectionTextFooter(sharedProfileInfo(chatModel, incognito.value))
}
@Composable
private fun AddContactLearnMoreButton() {
IconButton(
{
if (appPlatform.isDesktop) ModalManager.end.closeModals()
ModalManager.end.showModalCloseable { close ->
Column(
Modifier
.fillMaxHeight()
.padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween
) {
AddContactLearnMore(close)
}
}
}
) {
Icon(
painterResource(MR.images.ic_info),
stringResource(MR.strings.learn_more),
)
}
}
@Composable
private fun ConnectView(rhId: Long?, showQRCodeScanner: MutableState<Boolean>, pastedLink: MutableState<String>, close: () -> Unit) {
SectionView(stringResource(MR.strings.paste_the_link_you_received).uppercase()) {
PasteLinkView(rhId, pastedLink, showQRCodeScanner, close)
}
if (appPlatform.isAndroid) {
Spacer(Modifier.height(10.dp))
SectionView(stringResource(MR.strings.or_scan_qr_code).uppercase()) {
QRCodeScanner(showQRCodeScanner) { text ->
withBGApi {
val res = verify(rhId, text, close)
if (!res) {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.invalid_qr_code),
text = generalGetString(MR.strings.code_you_scanned_is_not_simplex_link_qr_code)
)
}
}
}
}
}
}
@Composable
private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRCodeScanner: MutableState<Boolean>, close: () -> Unit) {
if (pastedLink.value.isEmpty()) {
val clipboard = LocalClipboardManager.current
SectionItemView({
val str = clipboard.getText()?.text ?: return@SectionItemView
val link = strHasSingleSimplexLink(str.trim())
if (link != null) {
pastedLink.value = link.text
showQRCodeScanner.value = false
withBGApi { connect(rhId, link.text, close) { pastedLink.value = "" } }
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.invalid_contact_link),
text = generalGetString(MR.strings.the_text_you_pasted_is_not_a_link)
)
}
}) {
Text(stringResource(MR.strings.tap_to_paste_link))
}
} else {
LinkTextView(pastedLink.value, false)
}
}
@Composable
private fun LinkTextView(link: String, share: Boolean) {
val clipboard = LocalClipboardManager.current
Row(Modifier.fillMaxWidth().heightIn(min = 46.dp).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) {
Box(Modifier.weight(1f).clickable {
chatModel.markShowingInvitationUsed()
clipboard.shareText(link)
}) {
BasicTextField(
value = link,
onValueChange = { },
enabled = false,
textStyle = TextStyle(fontSize = 16.sp, color = MaterialTheme.colors.onBackground),
singleLine = true,
decorationBox = @Composable { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox(
value = link,
innerTextField = innerTextField,
contentPadding = PaddingValues(),
label = null,
visualTransformation = VisualTransformation.None,
leadingIcon = null,
trailingIcon = null,
singleLine = true,
enabled = false,
isError = false,
interactionSource = remember { MutableInteractionSource() },
)
})
}
// Element Text() can add ellipsis (...) in random place of the string, sometimes even after half of width of a screen.
// So using BasicTextField + manual ...
Text("…", fontSize = 16.sp)
if (share) {
Spacer(Modifier.width(DEFAULT_PADDING))
IconButton({
chatModel.markShowingInvitationUsed()
clipboard.shareText(link)
}, Modifier.size(20.dp)) {
Icon(painterResource(MR.images.ic_share_filled), null, tint = MaterialTheme.colors.primary)
}
}
}
}
private suspend fun verify(rhId: Long?, text: String?, close: () -> Unit): Boolean {
if (text != null && strIsSimplexLink(text)) {
connect(rhId, text, close)
return true
}
return false
}
private suspend fun connect(rhId: Long?, link: String, close: () -> Unit, cleanup: (() -> Unit)? = null) {
planAndConnect(
rhId,
URI.create(link),
close = close,
cleanup = cleanup,
incognito = null
)
}
private fun createInvitation(
rhId: Long?,
creatingConnReq: MutableState<Boolean>,
connReqInvitation: String,
contactConnection: MutableState<PendingContactConnection?>
) {
if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return
creatingConnReq.value = true
withApi {
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
if (r != null) {
chatModel.updateContactConnection(rhId, r.second)
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false)
contactConnection.value = r.second
} else {
creatingConnReq.value = false
if (alert != null) {
alert()
}
}
}
}
fun strIsSimplexLink(str: String): Boolean {
val parsedMd = parseToMarkdown(str)
return parsedMd != null && parsedMd.size == 1 && parsedMd[0].format is Format.SimplexLink
}
fun strHasSingleSimplexLink(str: String): FormattedText? {
val parsedMd = parseToMarkdown(str) ?: return null
val parsedLinks = parsedMd.filter { it.format?.isSimplexLink ?: false }
if (parsedLinks.size != 1) return null
return parsedLinks[0]
}
@Composable
fun IncognitoToggle(
incognitoPref: SharedPreference<Boolean>,
incognito: MutableState<Boolean>,
onClickInfo: () -> Unit
) {
SettingsActionItemWithContent(
icon = if (incognito.value) painterResource(MR.images.ic_theater_comedy_filled) else painterResource(MR.images.ic_theater_comedy),
text = null,
click = onClickInfo,
iconColor = if (incognito.value) Indigo else MaterialTheme.colors.secondary,
extraPadding = false
) {
SharedPreferenceToggleWithIcon(
stringResource(MR.strings.incognito),
painterResource(MR.images.ic_info),
stopped = false,
onClickInfo = onClickInfo,
preference = incognitoPref,
preferenceState = incognito
)
}
}
fun sharedProfileInfo(
chatModel: ChatModel,
incognito: Boolean
): String {
val name = chatModel.currentUser.value?.displayName ?: ""
return if (incognito) {
generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared)
} else {
String.format(generalGetString(MR.strings.connect__your_profile_will_be_shared), name)
}
}
@@ -1,135 +0,0 @@
package chat.simplex.common.views.newchat
import SectionBottomSpacer
import SectionTextFooter
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.dp
import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.IncognitoView
import chat.simplex.common.views.usersettings.SettingsActionItem
import chat.simplex.res.MR
import java.net.URI
@Composable
fun PasteToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
val connectionLink = remember { mutableStateOf("") }
val clipboard = LocalClipboardManager.current
PasteToConnectLayout(
chatModel = chatModel,
rh = rh,
incognitoPref = chatModel.controller.appPrefs.incognito,
connectionLink = connectionLink,
pasteFromClipboard = {
connectionLink.value = clipboard.getText()?.text ?: return@PasteToConnectLayout
},
close = close
)
}
@Composable
fun PasteToConnectLayout(
chatModel: ChatModel,
rh: RemoteHostInfo?,
incognitoPref: SharedPreference<Boolean>,
connectionLink: MutableState<String>,
pasteFromClipboard: () -> Unit,
close: () -> Unit
) {
val incognito = remember { mutableStateOf(incognitoPref.get()) }
val rhId = rh?.remoteHostId
fun connectViaLink(connReqUri: String) {
try {
val uri = URI(connReqUri)
withApi {
planAndConnect(chatModel, rhId, uri, incognito = incognito.value, close)
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.invalid_connection_link),
text = generalGetString(MR.strings.this_string_is_not_a_connection_link)
)
}
}
Column(
Modifier.verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING),
verticalArrangement = Arrangement.SpaceBetween,
) {
AppBarTitle(stringResource(MR.strings.connect_via_link), hostDevice(rhId), withPadding = false)
Box(Modifier.padding(top = DEFAULT_PADDING, bottom = 6.dp)) {
TextEditor(
connectionLink,
Modifier.height(180.dp),
contentPadding = PaddingValues(),
placeholder = stringResource(MR.strings.paste_the_link_you_received_to_connect_with_your_contact)
)
}
if (connectionLink.value == "") {
SettingsActionItem(
painterResource(MR.images.ic_content_paste),
stringResource(MR.strings.paste_button),
click = pasteFromClipboard,
)
} else {
SettingsActionItem(
painterResource(MR.images.ic_close),
stringResource(MR.strings.clear_verb),
click = { connectionLink.value = "" },
)
}
SettingsActionItem(
painterResource(MR.images.ic_link),
stringResource(MR.strings.connect_button),
click = { connectViaLink(connectionLink.value) },
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
disabled = connectionLink.value.isEmpty() || connectionLink.value.trim().contains(" ")
)
IncognitoToggle(incognitoPref, incognito) { ModalManager.start.showModal { IncognitoView() } }
SectionTextFooter(
buildAnnotatedString {
append(sharedProfileInfo(chatModel, incognito.value))
append("\n\n")
append(annotatedStringResource(MR.strings.you_can_also_connect_by_clicking_the_link))
}
)
SectionBottomSpacer()
}
}
@Preview/*(
uiMode = Configuration.UI_MODE_NIGHT_YES,
name = "Dark Mode"
)*/
@Composable
fun PreviewPasteToConnectTextbox() {
SimpleXTheme {
PasteToConnectLayout(
chatModel = ChatModel,
rh = null,
incognitoPref = SharedPreference({ false }, {}),
connectionLink = remember { mutableStateOf("") },
pasteFromClipboard = {},
close = {}
)
}
}
@@ -14,7 +14,7 @@ import boofcv.alg.drawing.FiducialImageEngine
import boofcv.alg.fiducial.qrcode.*
import chat.simplex.common.model.CryptoFile
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.SimpleXTheme
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import kotlinx.coroutines.launch
@@ -23,14 +23,18 @@ import kotlinx.coroutines.launch
fun SimpleXLinkQRCode(
connReq: String,
modifier: Modifier = Modifier,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF),
tintColor: Color = Color(0xff062d56),
withLogo: Boolean = true
withLogo: Boolean = true,
onShare: (() -> Unit)? = null,
) {
QRCode(
simplexChatLink(connReq),
modifier,
padding,
tintColor,
withLogo
withLogo,
onShare,
)
}
@@ -46,22 +50,24 @@ fun simplexChatLink(uri: String): String {
fun QRCode(
connReq: String,
modifier: Modifier = Modifier,
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF),
tintColor: Color = Color(0xff062d56),
withLogo: Boolean = true
withLogo: Boolean = true,
onShare: (() -> Unit)? = null,
) {
val scope = rememberCoroutineScope()
BoxWithConstraints(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
val maxWidthInPx = with(LocalDensity.current) { maxWidth.roundToPx() }
val qr = remember(maxWidthInPx, connReq, tintColor, withLogo) {
qrCodeBitmap(connReq, maxWidthInPx).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
.let { if (withLogo) it.addLogo() else it }
}
val qr = remember(connReq, tintColor, withLogo) {
qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
.let { if (withLogo) it.addLogo() else it }
}
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Image(
bitmap = qr,
contentDescription = stringResource(MR.strings.image_descr_qr_code),
Modifier
.widthIn(max = 360.dp)
.padding(padding)
.widthIn(max = 400.dp)
.aspectRatio(1f)
.then(modifier)
.clickable {
scope.launch {
@@ -70,6 +76,7 @@ fun QRCode(
val file = saveTempImageUncompressed(image, true)
if (file != null) {
shareFile("", CryptoFile.plain(file.absolutePath))
onShare?.invoke()
}
}
}
@@ -81,7 +88,9 @@ fun qrCodeBitmap(content: String, size: Int = 1024): ImageBitmap {
val qrCode = QrCodeEncoder().addAutomatic(content).setError(QrCode.ErrorLevel.L).fixate()
/** See [QrCodeGeneratorImage.initialize] and [FiducialImageEngine.configure] for size calculation */
val numModules = QrCode.totalModules(qrCode.version)
val borderModule = 1
// Hide border on light themes to make it fit to the same place as camera in QRCodeScanner.
// On dark themes better to show the border
val borderModule = if (CurrentColors.value.colors.isLight) 0 else 1
// val calculatedFinalWidth = (pixelsPerModule * numModules) + 2 * (borderModule * pixelsPerModule)
// size = (x * numModules) + 2 * (borderModule * x)
// size / x = numModules + 2 * borderModule
@@ -1,6 +1,14 @@
package chat.simplex.common.views.newchat
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.*
import androidx.compose.ui.unit.dp
import chat.simplex.common.ui.theme.DEFAULT_PADDING
import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF
@Composable
expect fun QRCodeScanner(onBarcode: (String) -> Unit)
expect fun QRCodeScanner(
showQRCodeScanner: MutableState<Boolean> = remember { mutableStateOf(true) },
padding: PaddingValues = PaddingValues(horizontal = DEFAULT_PADDING * 2f, vertical = DEFAULT_PADDING_HALF),
onBarcode: (String) -> Unit
)
@@ -84,7 +84,7 @@ private fun CreateSimpleXAddressLayout(
Spacer(Modifier.weight(1f))
if (userAddress != null) {
SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
SimpleXLinkQRCode(userAddress.connReqContact)
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
Spacer(Modifier.weight(1f))
ShareViaEmailButton { sendEmail(userAddress) }
@@ -78,17 +78,6 @@ fun ReadableText(text: String, textAlign: TextAlign = TextAlign.Start, padding:
Text(text, modifier = Modifier.padding(padding), textAlign = textAlign, lineHeight = 22.sp)
}
@Composable
fun ReadableMarkdownText(text: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) {
MarkdownText(
text,
formattedText = remember(text) { parseToMarkdown(text) },
modifier = Modifier.padding(padding),
style = TextStyle(textAlign = textAlign, lineHeight = 22.sp, fontSize = 16.sp),
linkMode = ChatController.appPrefs.simplexLinkMode.get(),
)
}
@Preview/*(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
@@ -339,16 +339,9 @@ private fun DevicesView(deviceName: String, remoteCtrls: SnapshotStateList<Remot
@Composable
private fun ScanDesktopAddressView(sessionAddress: MutableState<String>) {
SectionView(stringResource(MR.strings.scan_qr_code_from_desktop).uppercase()) {
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(DEFAULT_PADDING)
) {
QRCodeScanner { text ->
sessionAddress.value = text
processDesktopQRCode(sessionAddress, text)
}
QRCodeScanner { text ->
sessionAddress.value = text
processDesktopQRCode(sessionAddress, text)
}
}
}
@@ -187,11 +187,7 @@ private fun ConnectMobileViewLayout(
SectionView {
if (invitation != null && sessionCode == null && port != null) {
Box {
QRCode(
invitation, Modifier
.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF)
.aspectRatio(1f)
)
QRCode(invitation)
if (staleQrCode) {
Box(Modifier.matchParentSize().background(MaterialTheme.colors.background.copy(alpha = 0.9f)), contentAlignment = Alignment.Center) {
SimpleButtonDecorated(stringResource(MR.strings.refresh_qr_code), painterResource(MR.images.ic_refresh), click = refreshQrCode)
@@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp
import chat.simplex.common.ui.theme.DEFAULT_PADDING
import chat.simplex.common.views.helpers.AppBarTitle
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.common.views.onboarding.ReadableTextWithLink
import chat.simplex.res.MR
@Composable
@@ -31,6 +32,7 @@ fun IncognitoLayout() {
Text(generalGetString(MR.strings.incognito_info_protects))
Text(generalGetString(MR.strings.incognito_info_allows))
Text(generalGetString(MR.strings.incognito_info_share))
ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode")
SectionBottomSpacer()
}
}
@@ -429,6 +429,7 @@ fun SimplexLockView(
ModalManager.fullscreen.showCustomModal { close ->
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
SetAppPasscodeView(
reason = generalGetString(MR.strings.la_app_passcode),
submit = {
passcodeAlert(generalGetString(MR.strings.passcode_changed))
}, cancel = {
@@ -453,6 +454,7 @@ fun SimplexLockView(
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
SetAppPasscodeView(
passcodeKeychain = ksSelfDestructPassword,
reason = generalGetString(MR.strings.self_destruct),
submit = {
selfDestructPasscodeAlert(generalGetString(MR.strings.self_destruct_passcode_changed))
}, cancel = {
@@ -553,7 +555,7 @@ fun SimplexLockView(
fontSize = 16.sp,
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
)
ProfileNameField(selfDestructDisplayName, "", ::isValidDisplayName)
ProfileNameField(selfDestructDisplayName, "", { isValidDisplayName(it.trim()) })
LaunchedEffect(selfDestructDisplayName.value) {
val new = selfDestructDisplayName.value
if (isValidDisplayName(new) && selfDestructDisplayNamePref.get() != new) {
@@ -160,7 +160,7 @@ private fun CustomServer(
if (valid.value) {
SectionDividerSpaced()
SectionView(stringResource(MR.strings.smp_servers_add_to_another_device).uppercase()) {
QRCode(serverAddress.value, Modifier.aspectRatio(1f).padding(horizontal = DEFAULT_PADDING))
QRCode(serverAddress.value)
}
}
}
@@ -20,25 +20,17 @@ fun ScanProtocolServerLayout(rhId: Long?, onNext: (ServerCfg) -> Unit) {
Column(
Modifier
.fillMaxSize()
.padding(horizontal = DEFAULT_PADDING)
) {
AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr), withPadding = false)
Box(
Modifier
.fillMaxWidth()
.aspectRatio(ratio = 1F)
.padding(bottom = 12.dp)
) {
QRCodeScanner { text ->
val res = parseServerAddress(text)
if (res != null) {
onNext(ServerCfg(remoteHostId = rhId, text, false, null, true))
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.smp_servers_invalid_address),
text = generalGetString(MR.strings.smp_servers_check_address)
)
}
AppBarTitle(stringResource(MR.strings.smp_servers_scan_qr))
QRCodeScanner { text ->
val res = parseServerAddress(text)
if (res != null) {
onNext(ServerCfg(remoteHostId = rhId, text, false, null, true))
} else {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.smp_servers_invalid_address),
text = generalGetString(MR.strings.smp_servers_check_address)
)
}
}
}
@@ -207,7 +207,7 @@ private fun UserAddressLayout(
val autoAcceptState = remember { mutableStateOf(AutoAcceptState(userAddress)) }
val autoAcceptStateSaved = remember { mutableStateOf(autoAcceptState.value) }
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
SimpleXLinkQRCode(userAddress.connReqContact, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
SimpleXLinkQRCode(userAddress.connReqContact)
ShareAddressButton { share(simplexChatLink(userAddress.connReqContact)) }
ShareViaEmailButton { sendEmail(userAddress) }
ShareWithContactsButton(shareViaProfile, setProfileAddress)
@@ -147,8 +147,6 @@
<string name="rcv_conn_event_switch_queue_phase_completed">تم تغيير العنوان من أجلك</string>
<string name="cant_delete_user_profile">لا يمكن حذف ملف تعريف المستخدم!</string>
<string name="icon_descr_video_asked_to_receive">طلب لاستلام الفيديو</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b> إضافة جهة اتصال جديدة </b>: لإنشاء رمز الاستجابة السريعة الخاص بك لمرة واحدة لجهة اتصالك.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b> امسح رمز الاستجابة السريعة </b>: للاتصال بجهة الاتصال التي تعرض لك رمز الاستجابة السريعة.]]></string>
<string name="callstatus_in_progress">مكالمتك تحت الإجراء</string>
<string name="change_database_passphrase_question">تغيير عبارة مرور قاعدة البيانات؟</string>
<string name="cannot_access_keychain">لا يمكن الوصول إلى Keystore لحفظ كلمة مرور قاعدة البيانات</string>
@@ -173,8 +171,8 @@
<string name="copy_verb">نسخ</string>
<string name="connect_via_link_verb">اتصل</string>
<string name="server_connected">متصل</string>
<string name="connect_via_group_link">تواصل عبر رابط جماعي؟</string>
<string name="connect_via_invitation_link">تواصل عبر رابط دعوة؟</string>
<string name="connect_via_group_link">انضمام إلى المجموعة؟</string>
<string name="connect_via_invitation_link">اتصل عبر رابط لمرة واحدة؟</string>
<string name="switch_receiving_address_question">تغيير عنوان الاستلام؟</string>
<string name="copied">نٌسخت إلى الحافظة</string>
<string name="clear_verb">مسح</string>
@@ -198,7 +196,6 @@
<string name="smp_server_test_compare_file">قارن الملف</string>
<string name="icon_descr_server_status_error">خطأ</string>
<string name="create_group">إنشاء مجموعة سرية</string>
<string name="create_one_time_link">إنشاء رابط دعوة لمرة واحدة</string>
<string name="error_aborting_address_change">خطأ في إحباط تغيير العنوان</string>
<string name="auth_enable_simplex_lock">تفعيل قفل SimpleX</string>
<string name="auth_confirm_credential">تأكد من بيانات الاعتماد الخاصة بك</string>
@@ -276,7 +273,7 @@
<string name="chat_preferences">تفضيلات الدردشة</string>
<string name="enter_correct_passphrase">أدخل عبارة المرور الصحيحة.</string>
<string name="rcv_group_event_member_connected">متصل</string>
<string name="connect_via_contact_link">تواصل عبر رابط الاتصال؟</string>
<string name="connect_via_contact_link">اتصل عبر عنوان التواصل؟</string>
<string name="error_deleting_link_for_group">خطأ في حذف رابط المجموعة</string>
<string name="notifications_mode_periodic_desc">التحقق من الرسائل الجديدة كل 10 دقائق لمدة تصل إلى دقيقة واحدة</string>
<string name="server_connecting">جار الاتصال</string>
@@ -431,7 +428,7 @@
<string name="files_and_media_prohibited">الملفات والوسائط ممنوعة!</string>
<string name="icon_descr_file">الملف</string>
<string name="snd_group_event_group_profile_updated">حُدّث ملف تعريف المجموعة</string>
<string name="group_display_name_field">اسم عرض المجموعة:</string>
<string name="group_display_name_field">أدخل اسم المجموعة:</string>
<string name="group_members_can_send_voice">يمكن لأعضاء المجموعة إرسال رسائل صوتية.</string>
<string name="files_are_prohibited_in_group">الملفات والوسائط ممنوعة في هذه المجموعة.</string>
<string name="v4_6_group_welcome_message">رسالة ترحيب المجموعة</string>
@@ -613,9 +610,9 @@
<string name="smp_server_test_download_file">نزّل الملف</string>
<string name="auth_disable_simplex_lock">تعطيل قفل SimpleX</string>
<string name="edit_verb">تحرير</string>
<string name="display_name__field">اسم العرض:</string>
<string name="display_name__field">اسم الملف الشخصي:</string>
<string name="icon_descr_email">البريد الإلكتروني</string>
<string name="display_name">اسم العرض</string>
<string name="display_name">أدخل أسمك:</string>
<string name="integrity_msg_duplicate">كرر الرسالة</string>
<string name="share_text_disappears_at">يختفي في: %s</string>
<string name="disappearing_prohibited_in_this_chat">الرسائل المختفية ممنوعة في هذه الدردشة.</string>
@@ -720,7 +717,7 @@
<string name="message_delivery_error_title">خطأ في تسليم الرسالة</string>
<string name="network_and_servers">الشبكة والخوادم</string>
<string name="moderate_verb">إشراف</string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 للجوال: انقر فوق <b>فتح في تطبيق الجوال</b> ، ثم انقر فوق <b>اتصال</b> في التطبيق.]]></string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 للجوال: انقر فوق <b>فتح في تطبيق الجوال</b>، ثم انقر فوق <b>اتصال</b> في التطبيق.]]></string>
<string name="share_text_moderated_at">تحت الإشراف في: %s</string>
<string name="message_reactions_prohibited_in_this_chat">ردود الفعل الرسائل ممنوعة في هذه الدردشة.</string>
<string name="moderated_item_description">مُشرف بواسطة %s</string>
@@ -1095,7 +1092,7 @@
<string name="theme">السمة</string>
<string name="to_start_a_new_chat_help_header">لبدء محادثة جديدة</string>
<string name="to_verify_compare">للتحقق من التشفير بين الطريفين مع جهة اتصالك، قارن (أو امسح) الرمز الموجود على أجهزتك.</string>
<string name="group_is_decentralized">المجموعة لامركزية بالكامل - فهي مرئية فقط للأعضاء.</string>
<string name="group_is_decentralized">لامركزية بالكامل – مرئية للأعضاء فقط.</string>
<string name="theme_system">النظام</string>
<string name="error_smp_test_failed_at_step">فشل الاختبار في الخطوة %s.</string>
<string name="v4_5_italian_interface_descr">بفضل المستخدمين - المساهمة عبر Weblate!</string>
@@ -1168,7 +1165,7 @@
<string name="sender_you_pronoun">أنت</string>
<string name="description_you_shared_one_time_link">لقد شاركت رابط لمرة واحدة</string>
<string name="profile_will_be_sent_to_contact_sending_link">سيتم إرسال ملف التعريفك إلى جهة الاتصال التي تلقيت منها هذا الارتباط.</string>
<string name="you_will_join_group">ستنضم إلى مجموعة يشير إليها هذا الرابط وتتصل بأعضائها.</string>
<string name="you_will_join_group">سوف تتصل بجميع أعضاء المجموعة.</string>
<string name="your_chat_profiles">ملفات تعريف الدردشة الخاصة بك</string>
<string name="your_simplex_contact_address">عنوان SimpleX الخاص بك</string>
<string name="your_SMP_servers">خوادم SMP الخاصة بك</string>
@@ -1324,7 +1321,7 @@
<string name="observer_cant_send_message_title">لا يمكنك إرسال رسائل!</string>
<string name="you_need_to_allow_to_send_voice">تحتاج إلى السماح لجهة الاتصال الخاصة بك بإرسال رسائل صوتية لتتمكن من إرسالها.</string>
<string name="contact_sent_large_file">أرسلت جهة اتصالك ملفًا أكبر من الحجم الأقصى المعتمد حاليًا (%1$s).</string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[يمكنك <font color=#0088ff>الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات</font>.]]></string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[يمكنك <font color="#0088ff">الاتصال بمطوري SimpleX Chat لطرح أي أسئلة وتلقي التحديثات</font>.]]></string>
<string name="smp_servers_your_server">خادمك</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">يُخزن ملف تعريفك على جهازك ومشاركته فقط مع جهات اتصالك. لا تستطيع خوادم SimpleX رؤية ملف تعريفك.</string>
<string name="icon_descr_video_off">الفيديو مقفل</string>
@@ -1405,8 +1402,129 @@
<string name="v5_4_better_groups">مجموعات أفضل</string>
<string name="rcv_group_and_other_events">و%d أحداث أخرى</string>
<string name="connect_plan_already_joining_the_group">جارٍ انضمام بالفعل إلى المجموعة!</string>
<string name="block_member_confirmation">حجب</string>
<string name="block_member_confirmation">حظر</string>
<string name="bad_desktop_address">عنوان سطح المكتب غير صالح</string>
<string name="block_member_desc">سيتم إخفاء كافة الرسائل الجديدة من %s!</string>
<string name="blocked_item_description">محجوب</string>
<string name="blocked_item_description">محظور</string>
<string name="v5_4_block_group_members">حظر أعضاء المجموعة</string>
<string name="rcv_direct_event_contact_deleted">جهة الاتصال المحذوفة</string>
<string name="v5_4_incognito_groups_descr">أنشِئ مجموعة باستخدام ملف تعريف عشوائي.</string>
<string name="create_group_button">أنشِئ مجموعة</string>
<string name="create_another_profile_button">أنشِئ ملف تعريف</string>
<string name="connected_desktop">سطح المكتب متصل</string>
<string name="multicast_connect_automatically">اتصل تلقائيًا</string>
<string name="desktop_address">عنوان سطح المكتب</string>
<string name="marked_deleted_items_description">وضّع علامة \"محذوفة\" على %d من الرسائل</string>
<string name="discover_on_network">اكتشف عبر الشبكة المحلية</string>
<string name="connect_plan_connect_via_link">اتصل عبر الرابط؟</string>
<string name="connect_plan_connect_to_yourself">اتصل بنفسك؟</string>
<string name="desktop_device">سطح المكتب</string>
<string name="connected_to_desktop">متصل بسطح المكتب</string>
<string name="connecting_to_desktop">جار الاتصال بسطح المكتب</string>
<string name="desktop_devices">أجهزة سطح المكتب</string>
<string name="correct_name_to">الاسم الصحيح لـ%s؟</string>
<string name="delete_messages__question">حذف %d رسالة؟</string>
<string name="connect_with_contact_name_question">اتصل مع %1$s؟</string>
<string name="blocked_items_description">%d رسالة محظورة</string>
<string name="block_member_button">حظر العضو</string>
<string name="connected_mobile">الجوّال متصل</string>
<string name="delete_and_notify_contact">حذف وإشعار جهة الاتصال</string>
<string name="desktop_connection_terminated">انتهى الاتصال</string>
<string name="connect_to_desktop">اتصل بسطح المكتب</string>
<string name="disconnect_remote_host">قطع الاتصال</string>
<string name="block_member_question">حظر العضو؟</string>
<string name="rcv_group_events_count">%d أحداث مجموعة</string>
<string name="group_member_role_author">الكاتب</string>
<string name="connected_to_mobile">متصل بالجوّال</string>
<string name="devices">الأجهزة</string>
<string name="multicast_discoverable_via_local_network">مُكتشف عبر الشبكة المحلية</string>
<string name="disconnect_desktop_question">قطع اتصال سطح المكتب؟</string>
<string name="desktop_app_version_is_incompatible">إصدار تطبيق سطح المكتب %s غير متوافق مع هذا التطبيق.</string>
<string name="expand_verb">توسيع</string>
<string name="connect_plan_repeat_connection_request">هل تريد تكرار طلب الاتصال؟</string>
<string name="encryption_renegotiation_error">خطأ في إعادة التفاوض بشأن التشفير</string>
<string name="connect_plan_you_are_already_connecting_to_vName">أنت متصل بالفعل بـ%1$s.</string>
<string name="error_alert_title">خطأ</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">لقد انضممت بالفعل إلى المجموعة عبر هذا الرابط.</string>
<string name="group_members_2">%s و%s</string>
<string name="new_mobile_device">جوّال جديد</string>
<string name="only_one_device_can_work_at_the_same_time">يمكن لجهاز واحد فقط العمل في نفس الوقت</string>
<string name="connect_plan_join_your_group">انضمام إلى مجموعتك؟</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">لقد انضممت بالفعل إلى المجموعة %1$s.</string>
<string name="v5_4_link_mobile_desktop">ربط تطبيقات الجوّال وسطح المكتب! 🔗</string>
<string name="connect_plan_this_is_your_own_one_time_link">هذا هو الرابط الخاص بك لمرة واحدة!</string>
<string name="connect_plan_group_already_exists">المجموعة موجودة بالفعل!</string>
<string name="v5_4_link_mobile_desktop_descr">عبر بروتوكول آمن مقاوم للكم.</string>
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[افتح <i>الاستخدام من سطح المكتب</i> في تطبيق الجوّال وامسح رمز QR ضوئيًا.]]></string>
<string name="v5_4_block_group_members_descr">لإخفاء الرسائل غير المرغوب فيها.</string>
<string name="desktop_incompatible_version">نُسخة غير متوافقة</string>
<string name="new_desktop"><![CDATA[<i>(جديد)</i>]]></string>
<string name="unlink_desktop_question">فك ربط سطح المكتب؟</string>
<string name="linked_desktop_options">خيارات سطح المكتب المرتبطة</string>
<string name="video_decoding_exception_desc">لا يمكن فك تشفير الفيديو. من فضلك، جرب مقطع فيديو مختلفًا أو اتصل بالمطورين.</string>
<string name="rcv_group_event_1_member_connected">%s متصل</string>
<string name="linked_desktops">أسطح المكتب المرتبطة</string>
<string name="v5_4_incognito_groups">مجموعات التخفي</string>
<string name="group_members_n">%s و%s و%d عضو</string>
<string name="this_device">هذا الجهاز</string>
<string name="moderated_items_description">%d من الرسائل يُشرف عليها بواسطة %s</string>
<string name="unblock_member_button">إلغاء حظر العضو</string>
<string name="remote_host_was_disconnected_toast"><![CDATA[جوّال <b>%s</b> قُطع اتصاله]]></string>
<string name="waiting_for_desktop">في انتظار سطح المكتب…</string>
<string name="v5_4_better_groups_descr">انضمام أسرع ورسائل أكثر موثوقية.</string>
<string name="linked_mobiles">الجوّالات المرتبطة</string>
<string name="contact_tap_to_connect">انقر للاتصال</string>
<string name="this_device_name">اسم هذا الجهاز</string>
<string name="connect_plan_you_are_already_in_group_vName">أنت بالفعل في المجموعة %1$s.</string>
<string name="connect_plan_this_is_your_own_simplex_address">هذا هو عنوان SimpleX الخاص بك!</string>
<string name="loading_remote_file_title">جارِ تحميل الملف</string>
<string name="found_desktop">وجدت سطح المكتب</string>
<string name="alert_text_encryption_renegotiation_failed">فشلت إعادة التفاوض على التشفير.</string>
<string name="not_compatible">غير متوافق!</string>
<string name="link_a_mobile">ربط الجوّال</string>
<string name="remove_member_button">إزالة العضو</string>
<string name="unblock_member_question">إلغاء حظر العضو؟</string>
<string name="settings_section_title_use_from_desktop">استخدم من سطح المكتب</string>
<string name="session_code">رمز الجلسة</string>
<string name="connect_plan_repeat_join_request">هل تريد تكرار طلب الانضمام؟</string>
<string name="button_remove_member_question">إزالة العضو؟</string>
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">أنت متصل بالفعل عبر هذا الرابط لمرة واحدة!</string>
<string name="connect_plan_open_group">افتح المجموعة</string>
<string name="this_device_version"><![CDATA[<i>(هذا الجهاز v%s)</i>]]></string>
<string name="unlink_desktop">فك الربط</string>
<string name="unblock_member_desc">سيتم عرض الرسائل من %s!</string>
<string name="this_device_name_shared_with_mobile">ستتم مشاركة اسم الجهاز مع عميل الجوّال متصل.</string>
<string name="error_sending_message_contact_invitation">حدث خطأ أثناء إرسال الدعوة</string>
<string name="verify_code_on_mobile">التحقق من الرمز على الجوّال</string>
<string name="enter_this_device_name">أدخل اسم الجهاز هذا…</string>
<string name="error">خطأ</string>
<string name="non_content_uri_alert_text">لقد شاركت مسار ملف غير صالح. أبلغ عن المشكلة لمطوري التطبيق.</string>
<string name="invalid_name">اسم غير صالح!</string>
<string name="paste_desktop_address">لصق عنوان سطح المكتب</string>
<string name="connect_plan_this_is_your_link_for_group_vName">هذا هو الرابط الخاص بك للمجموعة %1$s!</string>
<string name="verify_code_with_desktop">التحقق من الرمز مع سطح المكتب</string>
<string name="scan_qr_code_from_desktop">مسح رمز QR من سطح المكتب</string>
<string name="unblock_member_confirmation">إلغاء الحظر</string>
<string name="v5_4_more_things_descr">- إشعار اختياريًا جهات الاتصال المحذوفة.
\n- أسماء الملفات الشخصية بمسافات.
\n- و اكثر!</string>
<string name="non_content_uri_alert_title">مسار الملف غير صالح</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">لقد طلبت بالفعل الاتصال عبر هذا العنوان!</string>
<string name="terminal_always_visible">إظهار وحدة التحكم في نافذة جديدة</string>
<string name="scan_from_mobile">المسح من الجوّال</string>
<string name="verify_connections">التحقق من الاتصالات</string>
<string name="loading_remote_file_desc">من فضلك، انتظر حتى يتم تحميل الملف من الجوّال المرتبط</string>
<string name="verify_connection">التحقق من الاتصال</string>
<string name="refresh_qr_code">أعِد التحميل</string>
<string name="random_port">عشوائي</string>
<string name="waiting_for_mobile_to_connect">في انتظار اتصال الجوال:</string>
<string name="open_port_in_firewall_desc">للسماح لتطبيق الجوال بالاتصال بسطح المكتب، افتح هذا المنفذ في جدار الحماية لديك، إذا فعلته</string>
<string name="create_chat_profile">أنشئ ملف تعريف الدردشة</string>
<string name="app_was_crashed">عرض التحطم</string>
<string name="open_port_in_firewall_title">فتح منفذ في جدار الحماية</string>
<string name="disconnect_remote_hosts">اقطع اتصال الجوالات</string>
<string name="no_connected_mobile">لا يوجد جوال متصل</string>
<string name="error_showing_content">خطأ في إظهار المحتوى</string>
<string name="error_showing_message">خطأ في إظهار الرسالة</string>
<string name="callstatus_ended">انتهت المكالمة %1$s</string>
</resources>
@@ -293,9 +293,11 @@
<string name="tap_to_start_new_chat">Tap to start a new chat</string>
<string name="chat_with_developers">Chat with the developers</string>
<string name="you_have_no_chats">You have no chats</string>
<string name="loading_chats">Loading chats…</string>
<string name="no_filtered_chats">No filtered chats</string>
<string name="contact_tap_to_connect">Tap to Connect</string>
<string name="connect_with_contact_name_question">Connect with %1$s?</string>
<string name="search_or_paste_simplex_link">Search or paste SimpleX link</string>
<!-- ChatView.kt -->
<string name="no_selected_chat">No selected chat</string>
@@ -427,6 +429,11 @@
<string name="connect_via_link_or_qr_from_clipboard_or_in_person">(scan or paste from clipboard)</string>
<string name="only_stored_on_members_devices">(only stored by group members)</string>
<!-- QRCodeScanner -->
<string name="enable_camera_access">Enable camera access</string>
<string name="tap_to_scan">Tap to scan</string>
<string name="camera_not_available">Camera not available</string>
<!-- GetImageView -->
<string name="toast_permission_denied">Permission Denied!</string>
<string name="use_camera_button">Camera</string>
@@ -442,8 +449,8 @@
<string name="to_start_a_new_chat_help_header">To start a new chat</string>
<string name="chat_help_tap_button">Tap button</string>
<string name="above_then_preposition_continuation">above, then:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Add new contact</b>: to create your one-time QR Code for your contact.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Scan QR code</b>: to connect to your contact who shows QR code to you.]]></string>
<string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Add contact</b>: to create a new invitation link, or connect via a link you received.]]></string>
<string name="create_group_button_to_create_new_group"><![CDATA[<b>Create group</b>: to create a new group.]]></string>
<string name="to_connect_via_link_title">To connect via link</string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">If you received SimpleX Chat invitation link, you can open it in your browser:</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 desktop: scan displayed QR code from the app, via <b>Scan QR code</b>.]]></string>
@@ -546,11 +553,26 @@
<string name="this_string_is_not_a_connection_link">This string is not a connection link!</string>
<string name="you_can_also_connect_by_clicking_the_link"><![CDATA[You can also connect by clicking the link. If it opens in the browser, click <b>Open in mobile app</b> button.]]></string>
<!-- CreateLinkView.kt -->
<string name="create_one_time_link">Create one-time invitation link</string>
<!-- NewChatView.kt -->
<string name="new_chat">New chat</string>
<string name="add_contact_tab">Add contact</string>
<string name="one_time_link">One-time invitation link</string>
<string name="one_time_link_short">1-time link</string>
<string name="simplex_address">SimpleX address</string>
<string name="or_show_this_qr_code">Or show this code</string>
<string name="or_scan_qr_code">Or scan QR code</string>
<string name="keep_unused_invitation_question">Keep unused invitation?</string>
<string name="you_can_view_invitation_link_again">You can view invitation link again in connection details.</string>
<string name="keep_invitation_link">Keep</string>
<string name="creating_link">Creating link…</string>
<string name="retry_verb">Retry</string>
<string name="share_this_1_time_link">Share this 1-time invite link</string>
<string name="paste_the_link_you_received">Paste the link you received</string>
<string name="the_text_you_pasted_is_not_a_link">The text you pasted is not a SimpleX link.</string>
<string name="tap_to_paste_link">Tap to paste link</string>
<string name="invalid_qr_code">Invalid QR code</string>
<string name="code_you_scanned_is_not_simplex_link_qr_code">The code you scanned is not a SimpleX link QR code.</string>
<!-- ScanCodeView.kt -->
<string name="scan_code">Scan code</string>
@@ -799,9 +821,6 @@
<string name="you_can_change_it_later">Random passphrase is stored in settings as plaintext.\nYou can change it later.</string>
<string name="use_random_passphrase">Use random passphrase</string>
<!-- MakeConnection -->
<string name="paste_the_link_you_received">Paste received link</string>
<!-- Call -->
<string name="incoming_video_call">Incoming video call</string>
<string name="incoming_audio_call">Incoming audio call</string>
@@ -897,6 +916,7 @@
<string name="authentication_cancelled">Authentication cancelled</string>
<string name="la_mode_system">System</string>
<string name="la_mode_passcode">Passcode</string>
<string name="la_app_passcode">App passcode</string>
<string name="la_mode_off">Off</string>
<string name="passcode_set">Passcode set!</string>
<string name="passcode_changed">Passcode changed!</string>
@@ -1439,6 +1459,7 @@
<string name="message_reactions">Message reactions</string>
<string name="voice_messages">Voice messages</string>
<string name="files_and_media">Files and media</string>
<string name="recent_history">Visible history</string>
<string name="audio_video_calls">Audio/video calls</string>
<string name="available_in_v51">\nAvailable in v5.1</string>
<string name="feature_enabled">enabled</string>
@@ -1451,8 +1472,8 @@
<string name="allow_your_contacts_to_send_disappearing_messages">Allow your contacts to send disappearing messages.</string>
<string name="allow_disappearing_messages_only_if">Allow disappearing messages only if your contact allows them.</string>
<string name="prohibit_sending_disappearing_messages">Prohibit sending disappearing messages.</string>
<string name="allow_your_contacts_irreversibly_delete">Allow your contacts to irreversibly delete sent messages.</string>
<string name="allow_irreversible_message_deletion_only_if">Allow irreversible message deletion only if your contact allows it to you.</string>
<string name="allow_your_contacts_irreversibly_delete">Allow your contacts to irreversibly delete sent messages. (24 hours)</string>
<string name="allow_irreversible_message_deletion_only_if">Allow irreversible message deletion only if your contact allows it to you. (24 hours)</string>
<string name="contacts_can_mark_messages_for_deletion">Contacts can mark messages for deletion; you will be able to view them.</string>
<string name="allow_your_contacts_to_send_voice_messages">Allow your contacts to send voice messages.</string>
<string name="allow_voice_messages_only_if">Allow voice messages only if your contact allows them.</string>
@@ -1467,9 +1488,9 @@
<string name="only_you_can_send_disappearing">Only you can send disappearing messages.</string>
<string name="only_your_contact_can_send_disappearing">Only your contact can send disappearing messages.</string>
<string name="disappearing_prohibited_in_this_chat">Disappearing messages are prohibited in this chat.</string>
<string name="both_you_and_your_contacts_can_delete">Both you and your contact can irreversibly delete sent messages.</string>
<string name="only_you_can_delete_messages">Only you can irreversibly delete messages (your contact can mark them for deletion).</string>
<string name="only_your_contact_can_delete">Only your contact can irreversibly delete messages (you can mark them for deletion).</string>
<string name="both_you_and_your_contacts_can_delete">Both you and your contact can irreversibly delete sent messages. (24 hours)</string>
<string name="only_you_can_delete_messages">Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)</string>
<string name="only_your_contact_can_delete">Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)</string>
<string name="message_deletion_prohibited">Irreversible message deletion is prohibited in this chat.</string>
<string name="both_you_and_your_contact_can_send_voice">Both you and your contact can send voice messages.</string>
<string name="only_you_can_send_voice">Only you can send voice messages.</string>
@@ -1487,7 +1508,7 @@
<string name="prohibit_sending_disappearing">Prohibit sending disappearing messages.</string>
<string name="allow_direct_messages">Allow sending direct messages to members.</string>
<string name="prohibit_direct_messages">Prohibit sending direct messages to members.</string>
<string name="allow_to_delete_messages">Allow to irreversibly delete sent messages.</string>
<string name="allow_to_delete_messages">Allow to irreversibly delete sent messages. (24 hours)</string>
<string name="prohibit_message_deletion">Prohibit irreversible message deletion.</string>
<string name="allow_to_send_voice">Allow to send voice messages.</string>
<string name="prohibit_sending_voice">Prohibit sending voice messages.</string>
@@ -1495,11 +1516,13 @@
<string name="prohibit_message_reactions_group">Prohibit messages reactions.</string>
<string name="allow_to_send_files">Allow to send files and media.</string>
<string name="prohibit_sending_files">Prohibit sending files and media.</string>
<string name="enable_sending_recent_history">Send up to 100 last messages to new members.</string>
<string name="disable_sending_recent_history">Do not send history to new members.</string>
<string name="group_members_can_send_disappearing">Group members can send disappearing messages.</string>
<string name="disappearing_messages_are_prohibited">Disappearing messages are prohibited in this group.</string>
<string name="group_members_can_send_dms">Group members can send direct messages.</string>
<string name="direct_messages_are_prohibited_in_chat">Direct messages between members are prohibited in this group.</string>
<string name="group_members_can_delete">Group members can irreversibly delete sent messages.</string>
<string name="group_members_can_delete">Group members can irreversibly delete sent messages. (24 hours)</string>
<string name="message_deletion_prohibited_in_chat">Irreversible message deletion is prohibited in this group.</string>
<string name="group_members_can_send_voice">Group members can send voice messages.</string>
<string name="voice_messages_are_prohibited">Voice messages are prohibited in this group.</string>
@@ -1507,6 +1530,8 @@
<string name="message_reactions_are_prohibited">Message reactions are prohibited in this group.</string>
<string name="group_members_can_send_files">Group members can send files and media.</string>
<string name="files_are_prohibited_in_group">Files and media are prohibited in this group.</string>
<string name="recent_history_is_sent_to_new_members">Up to 100 last messages are sent to new members.</string>
<string name="recent_history_is_not_sent_to_new_members">History is not sent to new members.</string>
<string name="delete_after">Delete after</string>
<string name="ttl_sec">%d sec</string>
<string name="ttl_s">%ds</string>
@@ -1708,20 +1733,20 @@
<!-- Connection plan -->
<string name="connect_plan_connect_to_yourself">Connect to yourself?</string>
<string name="connect_plan_this_is_your_own_one_time_link">This is your own one-time link!</string>
<string name="connect_plan_you_are_already_connecting_to_vName">You are already connecting to %1$s.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[You are already connecting to <b>%1$s</b>.]]></string>
<string name="connect_plan_already_connecting">Already connecting!</string>
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">You are already connecting via this one-time link!</string>
<string name="connect_plan_this_is_your_own_simplex_address">This is your own SimpleX address!</string>
<string name="connect_plan_repeat_connection_request">Repeat connection request?</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">You have already requested connection via this address!</string>
<string name="connect_plan_join_your_group">Join your group?</string>
<string name="connect_plan_this_is_your_link_for_group_vName">This is your link for group %1$s!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[This is your link for group <b>%1$s</b>!]]></string>
<string name="connect_plan_open_group">Open group</string>
<string name="connect_plan_repeat_join_request">Repeat join request?</string>
<string name="connect_plan_group_already_exists">Group already exists!</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">You are already joining the group %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[You are already joining the group <b>%1$s</b>.]]></string>
<string name="connect_plan_already_joining_the_group">Already joining the group!</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link.</string>
<string name="connect_plan_you_are_already_in_group_vName">You are already in group %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[You are already in group <b>%1$s</b>.]]></string>
<string name="connect_plan_connect_via_link">Connect via link?</string>
</resources>
@@ -119,8 +119,6 @@
<string name="back">Назад</string>
<string name="cancel_verb">Отказ</string>
<string name="icon_descr_cancel_live_message">Спри живото съобщение</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Добави нов контакт</b>: за да създадете своя еднократен QR код за вашия контакт.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Сканирай QR код</b>: за да се свържете с вашия контакт, който ви показва QR код.]]></string>
<string name="use_camera_button">Камера</string>
<string name="if_you_cant_meet_in_person">Ако не можете да се срещнете лично, покажете QR код във видеоразговора или споделете линка.</string>
<string name="icon_descr_cancel_link_preview">спри визуализацията на линка</string>
@@ -377,9 +375,9 @@
<string name="delete_after">Изтрий след</string>
<string name="ttl_d">%dд</string>
<string name="v5_1_custom_themes">Персонализирани теми</string>
<string name="connect_via_contact_link">Свързване чрез линк на контакта\?</string>
<string name="connect_via_group_link">Свързване чрез групов линк\?</string>
<string name="connect_via_invitation_link">Свързване чрез линк за покана\?</string>
<string name="connect_via_contact_link">Свързване чрез адрес на контакт?</string>
<string name="connect_via_group_link">Влез в групата?</string>
<string name="connect_via_invitation_link">Свързване чрез еднократен линк за връзка?</string>
<string name="decryption_error">Грешка при декриптиране</string>
<string name="deleted_description">изтрит</string>
<string name="contact_already_exists">Контактът вече съществува</string>
@@ -404,7 +402,6 @@
<string name="delete_contact_menu_action">Изтрий</string>
<string name="delete_group_menu_action">Изтрий</string>
<string name="connect_via_link">Свърване чрез линк</string>
<string name="create_one_time_link">Създай линк за еднократна покана</string>
<string name="database_passphrase_and_export">Парола за базата данни и експортиране</string>
<string name="contribute">Допринеси</string>
<string name="continue_to_next_step">Продължи</string>
@@ -439,7 +436,7 @@
<string name="custom_time_unit_days">дни</string>
<string name="choose_file_title">Избери файл</string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Ако сте получили линк за покана за SimpleX Chat, можете да го отворите във вашия браузър:</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 настолен компютър: сканирайте показания QR код от приложението чрез <b>Сканирай QR код</b>.]]></string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 настолно устройство: сканирай показания QR код от приложението чрез <b>Сканирай QR код</b>.]]></string>
<string name="delete_pending_connection__question">Изтрий предстоящата връзка\?</string>
<string name="icon_descr_email">Електронна поща</string>
<string name="share_invitation_link">Сподели еднократен линк</string>
@@ -449,7 +446,7 @@
<string name="smp_servers_enter_manually">Въведи сървъра ръчно</string>
<string name="smp_servers_delete_server">Изтрий сървър</string>
<string name="dont_create_address">Не създавай адрес</string>
<string name="display_name__field">Показвано име:</string>
<string name="display_name__field">Профилно име:</string>
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Само потребителските устройства съхраняват потребителски профили, контакти, групи и съобщения, изпратени с <b>двуслойно криптиране от край до край</b>.]]></string>
<string name="receipts_contacts_title_disable">Деактивирай потвърждениeто\?</string>
<string name="receipts_contacts_enable_keep_overrides">Активиране (запазване на промените)</string>
@@ -478,7 +475,7 @@
<string name="delivery_receipts_are_disabled">Потвърждениeто за доставка е деактивирано!</string>
<string name="scan_qr_to_connect_to_contact">За да се свърже, вашият контакт може да сканира QR код или да използва линка в приложението.</string>
<string name="direct_messages">Лични съобщения</string>
<string name="display_name">Показвано Име</string>
<string name="display_name">Въведи твоето име:</string>
<string name="display_name_cannot_contain_whitespace">Показваното име не може да съдържа интервал.</string>
<string name="sending_delivery_receipts_will_be_enabled">Изпращането на потвърждениe за доставка ще бъде активирано за всички контакти.</string>
<string name="receipts_contacts_enable_for_all">Активиране за всички</string>
@@ -524,7 +521,7 @@
<string name="member_role_will_be_changed_with_invitation">Ролята ще бъде променена на \"%s\". Членът ще получи нова покана.</string>
<string name="conn_level_desc_direct">директна</string>
<string name="renegotiate_encryption">Предоговори криптирането</string>
<string name="group_display_name_field">Групово показвано име:</string>
<string name="group_display_name_field">Въведи име на групата:</string>
<string name="delete_profile">Изтрий профил</string>
<string name="dont_show_again">Не показвай отново</string>
<string name="ttl_h">%dч</string>
@@ -787,14 +784,14 @@
<string name="custom_time_unit_seconds">секунди</string>
<string name="custom_time_unit_weeks">седмици</string>
<string name="v5_1_japanese_portuguese_interface">Японски и португалски потребителски интерфейс</string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 мобилно: докоснете <b>Отваряне в мобилно приложение</b>, след което докоснете <b>Свързване</b> в приложението.]]></string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 мобилно устройство: докоснете <b>Отваряне в мобилно приложение</b>, след което докоснете <b>Свързване</b> в приложението.]]></string>
<string name="reject_contact_button">Отхвърляне</string>
<string name="mark_unread">Маркирай като непрочетено</string>
<string name="mark_read">Маркирай като прочетено</string>
<string name="mute_chat">Без звук</string>
<string name="image_descr_qr_code">QR код</string>
<string name="icon_descr_more_button">Повече</string>
<string name="read_more_in_user_guide_with_link"><![CDATA[Прочетете повече в <font color=#0088ff>Ръководство за потребителя</font>.]]></string>
<string name="read_more_in_user_guide_with_link"><![CDATA[Прочетете повече в <font color="#0088ff">Ръководство за потребителя</font>.]]></string>
<string name="mark_code_verified">Маркирай като проверено</string>
<string name="is_not_verified">%s не е потвърдено</string>
<string name="is_verified">%s е потвърдено</string>
@@ -814,7 +811,7 @@
<string name="network_use_onion_hosts_no_desc">Няма се използват Onion хостове.</string>
<string name="email_invite_subject">Нека да поговорим в SimpleX Chat</string>
<string name="password_to_show">Парола за показване</string>
<string name="read_more_in_github_with_link"><![CDATA[Прочетете повече в нашето <font color=#0088ff>GitHub хранилище</font>.]]></string>
<string name="read_more_in_github_with_link"><![CDATA[Прочетете повече в нашето <font color="#0088ff">GitHub хранилище</font>.]]></string>
<string name="onboarding_notifications_mode_off">Когато приложението работи</string>
<string name="onboarding_notifications_mode_periodic">Периодично</string>
<string name="paste_the_link_you_received">Постави получения линк</string>
@@ -947,7 +944,7 @@
<string name="v4_6_reduced_battery_usage_descr">Очаквайте скоро още подобрения!</string>
<string name="v5_0_polish_interface">Полски интерфейс</string>
<string name="v5_1_message_reactions">Реакции на съобщения</string>
<string name="you_will_join_group">Ще се присъедините към групата, към която този линк препраща, и ще се свържете с нейните членове.</string>
<string name="you_will_join_group">Ще се свържете с всички членове на групата.</string>
<string name="thousand_abbreviation">х</string>
<string name="opening_database">Отваряне на база данни…</string>
<string name="live">НА ЖИВО</string>
@@ -1191,7 +1188,7 @@
<string name="delete_chat_profile_action_cannot_be_undone_warning">Това действие не може да бъде отменено - вашият профил, контакти, съобщения и файлове ще бъдат безвъзвратно загубени.</string>
<string name="enable_automatic_deletion_message">Това действие не може да бъде отменено - съобщенията, изпратени и получени по-рано от избраното, ще бъдат изтрити. Може да отнеме няколко минути.</string>
<string name="database_backup_can_be_restored">Опитът за промяна на паролата на базата данни не беше завършен.</string>
<string name="group_is_decentralized">Групата е напълно децентрализирана – видима е само за членовете.</string>
<string name="group_is_decentralized">Напълно децентрализирана – видима е само за членовете.</string>
<string name="v4_5_transport_isolation">Транспортна изолация</string>
<string name="v5_0_polish_interface_descr">Благодарение на потребителите – допринесете през Weblate!</string>
<string name="alert_text_msg_bad_hash">Хешът на предишното съобщение е различен.</string>
@@ -1328,7 +1325,7 @@
<string name="auth_you_will_be_required_to_authenticate_when_you_start_or_resume">Ще трябва да се идентифицирате, когато стартирате или възобновите приложението след 30 секунди във фонов режим.</string>
<string name="you_are_observer">вие сте наблюдател</string>
<string name="gallery_video_button">Видео</string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Можете да <font color=#0088ff>се свържете с разработчиците на SimpleX Chat, за да задавате въпроси и да получавате актуализации</font>;.]]></string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Можете да <font color="#0088ff">се свържете с разработчиците на SimpleX Chat, за да задавате въпроси и да получавате актуализации</font>;.]]></string>
<string name="contact_wants_to_connect_with_you">иска да се свърже с вас!</string>
<string name="you_can_also_connect_by_clicking_the_link"><![CDATA[Можете също да се свържете, като натиснете върху линка. Ако се отвори в браузъра, натиснете върху бутона <b>Отваряне в мобилно приложение</b>.]]></string>
<string name="xftp_servers">XFTP сървъри</string>
@@ -1401,4 +1398,130 @@
<string name="compose_send_direct_message_to_connect">Изпрати лично съобщение за свързване</string>
<string name="member_contact_send_direct_message">изпрати лично съобщение</string>
<string name="rcv_group_event_member_created_contact">свързан директно</string>
<string name="expand_verb">Разшири</string>
<string name="v5_4_block_group_members">Блокиране на членове на групата</string>
<string name="connect_plan_repeat_connection_request">Изпрати отново заявката за свързване?</string>
<string name="encryption_renegotiation_error">Грешка при повторно договаряне на криптиране</string>
<string name="rcv_direct_event_contact_deleted">изтрит контакт</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Вече се свързвате с %1$s.</string>
<string name="error_alert_title">Грешка</string>
<string name="v5_4_incognito_groups_descr">Създай група с автоматично генериран профилл.</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Вие вече се присъединявате към групата чрез този линк.</string>
<string name="create_group_button">Създай група</string>
<string name="refresh_qr_code">Опресняване</string>
<string name="create_another_profile_button">Създай профил</string>
<string name="connected_desktop">Свързано настолно устройство</string>
<string name="group_members_2">%s и %s</string>
<string name="new_mobile_device">Ново мобилно устройство</string>
<string name="multicast_connect_automatically">Автоматично свъзрване</string>
<string name="desktop_address">Адрес на настолно устройство</string>
<string name="only_one_device_can_work_at_the_same_time">Само едно устройство може да работи едновременно</string>
<string name="connect_plan_join_your_group">Влез в твоята група?</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Вече се присъединявате към групата %1$s.</string>
<string name="v5_4_link_mobile_desktop">Свържете мобилни и настолни приложения! 🔗</string>
<string name="connect_plan_this_is_your_own_one_time_link">Това е вашят еднократен линк за връзка!</string>
<string name="marked_deleted_items_description">%d съобщения са маркирани като изтрити</string>
<string name="connect_plan_group_already_exists">Групата вече съществува!</string>
<string name="v5_4_link_mobile_desktop_descr">Чрез сигурен квантово устойчив протокол.</string>
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[Отвори <i>Използвай от настолно устройство</i> в мобилното приложение и сканирай QR кода.]]></string>
<string name="connect_plan_already_connecting">В процес на свързване!</string>
<string name="v5_4_block_group_members_descr">Скриване на нежелани съобщения.</string>
<string name="desktop_incompatible_version">Несъвместима версия</string>
<string name="new_desktop"><![CDATA[<i>(ново)</i>]]></string>
<string name="unlink_desktop_question">Забрави настолно устройство?</string>
<string name="v5_4_better_groups">По-добри групи</string>
<string name="linked_desktop_options">Настройки на запомнени настолни устройства</string>
<string name="video_decoding_exception_desc">Видеото не може да бъде декодирано. Моля, опитайте с друг видеоклип или се свържете с разработчиците.</string>
<string name="rcv_group_event_1_member_connected">%s свързан</string>
<string name="random_port">Случаен</string>
<string name="linked_desktops">Запомнени настолни устройство</string>
<string name="discover_on_network">Открий през локалната мрежа</string>
<string name="rcv_group_and_other_events">и %d други събития</string>
<string name="connect_plan_connect_via_link">Свърване чрез линк?</string>
<string name="v5_4_incognito_groups">Инкогнито групи</string>
<string name="connect_plan_already_joining_the_group">Вече се присъединихте към групата!</string>
<string name="group_members_n">%s, %s и %d членове</string>
<string name="this_device">Това устройство</string>
<string name="moderated_items_description">%d съобщения, модерирани от %s</string>
<string name="unblock_member_button">Отблокирай член</string>
<string name="remote_host_was_disconnected_toast"><![CDATA[Връзкарта с <b>%s</b> мобилно устройство бе прекъсната]]></string>
<string name="waiting_for_desktop">Изчакване на настолно устройство…</string>
<string name="v5_4_better_groups_descr">По-бързо присъединяване и по-надеждни съобщения.</string>
<string name="connect_plan_connect_to_yourself">Свърване със себе си?</string>
<string name="linked_mobiles">Запомнени мобилни устройства</string>
<string name="desktop_device">Настолно устройство</string>
<string name="connected_to_desktop">Свързан с настолно устройство</string>
<string name="contact_tap_to_connect">Докосни за свързване</string>
<string name="this_device_name">Името на това устройство</string>
<string name="connect_plan_you_are_already_in_group_vName">Вече сте в група %1$s.</string>
<string name="connect_plan_this_is_your_own_simplex_address">Това е вашият личен SimpleX адрес!</string>
<string name="loading_remote_file_title">Зареждане на файла</string>
<string name="connecting_to_desktop">Свързване с настолно устройство</string>
<string name="found_desktop">Намерено настолно устройство</string>
<string name="alert_text_encryption_renegotiation_failed">Неуспешно повторно договаряне на криптирането.</string>
<string name="desktop_devices">Настолни устройства</string>
<string name="correct_name_to">Поправи име на %s?</string>
<string name="not_compatible">Несъвместим!</string>
<string name="waiting_for_mobile_to_connect">Изчаква се мобилното устройство да се свърже:</string>
<string name="delete_messages__question">Изтрий %d съобщения?</string>
<string name="link_a_mobile">Свържи мобилно устройство</string>
<string name="connect_with_contact_name_question">Свързване с %1$s?</string>
<string name="remove_member_button">Премахни член</string>
<string name="block_member_confirmation">Блокирай</string>
<string name="unblock_member_question">Отблокирай член?</string>
<string name="blocked_items_description">%d блокирани съобщения</string>
<string name="open_port_in_firewall_desc">За да позволите на мобилно приложение да се свърже с настолното, отворете този порт във вашата защитна стена, ако тя е активирана</string>
<string name="block_member_button">Блокирай член</string>
<string name="settings_section_title_use_from_desktop">Използвай от настолно устройство</string>
<string name="connected_mobile">Свързано мобилно устройство</string>
<string name="session_code">Код на сесията</string>
<string name="connect_plan_repeat_join_request">Изпрати отново заявката за присъединяване?</string>
<string name="create_chat_profile">Създай чат профил</string>
<string name="button_remove_member_question">Премахни член?</string>
<string name="app_was_crashed">Покажи грешките</string>
<string name="delete_and_notify_contact">Изтрий и уведоми контакт</string>
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">Вече се свързвате чрез този еднократен линк за връзка!</string>
<string name="connect_plan_open_group">Отвори група</string>
<string name="desktop_connection_terminated">Връзката е прекратена</string>
<string name="this_device_version"><![CDATA[<i>(това устройство v%s)</i>]]></string>
<string name="unlink_desktop">Забрави</string>
<string name="unblock_member_desc">Съобщенията от %s ще бъдат показани!</string>
<string name="this_device_name_shared_with_mobile">Името на устройството ще бъде споделено със свързания мобилен клиент.</string>
<string name="error_sending_message_contact_invitation">Грешка при изпращане на покана</string>
<string name="verify_code_on_mobile">Потвръди кода на мобилното устройство</string>
<string name="open_port_in_firewall_title">Отвори порт в защитната стена</string>
<string name="enter_this_device_name">Въведи името на това устройство…</string>
<string name="error">Грешка</string>
<string name="connect_to_desktop">Свързване с настолно устройство</string>
<string name="non_content_uri_alert_text">Споделихте невалиден файлов път. Докладвайте проблема на разработчиците на приложението.</string>
<string name="disconnect_remote_hosts">Прекъсни връзката с мобилните устройства?</string>
<string name="disconnect_remote_host">Прекъсни връзката</string>
<string name="block_member_question">Блокирай члена?</string>
<string name="rcv_group_events_count">%d групови събития</string>
<string name="invalid_name">Невалидно име!</string>
<string name="group_member_role_author">автор</string>
<string name="connected_to_mobile">Свързан с мобилно устройство</string>
<string name="bad_desktop_address">Грешен адрес на настолното устройство</string>
<string name="paste_desktop_address">Постави адрес на настолно устройство</string>
<string name="connect_plan_this_is_your_link_for_group_vName">Това е вашят линк за група %1$s!</string>
<string name="verify_code_with_desktop">Потвръди кода с настолното устройство</string>
<string name="scan_qr_code_from_desktop">Сканирай QR код от настолното устройство</string>
<string name="unblock_member_confirmation">Отблокирай</string>
<string name="devices">Устройства</string>
<string name="multicast_discoverable_via_local_network">Откриваем през локалната мрежа</string>
<string name="v5_4_more_things_descr">- по желание уведомете изтритите контакти.
\n- имена на профили с интервали.
\n- и още!</string>
<string name="non_content_uri_alert_title">Невалиден файлов път</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">Вече сте заявили връзка през този адрес!</string>
<string name="terminal_always_visible">Покажи конзолата в нов прозорец</string>
<string name="scan_from_mobile">Сканиране от мобилно устройство</string>
<string name="verify_connections">Потвръди връзките</string>
<string name="disconnect_desktop_question">Прекъсни връзката с настолното устройство?</string>
<string name="loading_remote_file_desc">Моля, изчакайте, докато файлът се зареди от свързаното мобилно устройство</string>
<string name="block_member_desc">Всички нови съобщения от %s ще бъдат скрити!</string>
<string name="desktop_app_version_is_incompatible">Версията на настолното приложение %s не е съвместима с това приложение.</string>
<string name="verify_connection">Потвръди връзките</string>
<string name="no_connected_mobile">Няма свързано мобилно устройство</string>
<string name="blocked_item_description">блокиран</string>
</resources>
@@ -11,7 +11,7 @@
<string name="smp_servers_add">Přidat server…</string>
<string name="network_enable_socks_info">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.</string>
<string name="accept_feature">Přijmout</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Povolte svým kontaktům odesílat mizící zprávy.</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Povolit svým kontaktům odesílat mizící zprávy.</string>
<string name="about_simplex_chat">O SimpleX Chat</string>
<string name="smp_servers_add_to_another_device">Přidat do jiného zařízení</string>
<string name="allow_verb">Povolit</string>
@@ -31,11 +31,11 @@
<string name="accept_contact_incognito_button">Přijmout inkognito</string>
<string name="accept_connection_request__question">Přijmout žádost o připojení\?</string>
<string name="all_group_members_will_remain_connected">Všichni členové skupiny zůstanou připojeni.</string>
<string name="allow_irreversible_message_deletion_only_if">Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí.</string>
<string name="allow_irreversible_message_deletion_only_if">Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt povolí.</string>
<string name="allow_direct_messages">Přímé zprávy členům povoleny.</string>
<string name="allow_to_delete_messages">Nevratné mazání odeslaných zpráv povoleno.</string>
<string name="allow_to_delete_messages">Povolit nevratné mazání odeslaných zpráv.</string>
<string name="clear_chat_warning">Všechny zprávy budou smazány – tuto akci nelze vrátit zpět! Zprávy budou smazány POUZE pro vás.</string>
<string name="allow_your_contacts_irreversibly_delete">Umožněte svým kontaktům nevratně odstranit odeslané zprávy.</string>
<string name="allow_your_contacts_irreversibly_delete">Povolit svým kontaktům nevratně odstranit odeslané zprávy.</string>
<string name="allow_your_contacts_to_send_voice_messages">Povolte svým kontaktům odesílání hlasových zpráv.</string>
<string name="button_create_group_link">Vytvořit odkaz</string>
<string name="delete_link_question">Smazat odkaz\?</string>
@@ -45,7 +45,7 @@
<string name="info_row_connection">Připoj</string>
<string name="conn_level_desc_indirect">nepřímé (%1$s)</string>
<string name="conn_stats_section_title_servers">SERVERY</string>
<string name="receiving_via">Příjímáno přez</string>
<string name="receiving_via">Příjímáno přes</string>
<string name="create_secret_group_title">Vytvoření tajné skupiny</string>
<string name="group_display_name_field">Zobrazený název skupiny:</string>
<string name="group_full_name_field">Úplný název skupiny:</string>
@@ -65,7 +65,7 @@
<string name="chat_preferences_always">vždy</string>
<string name="set_group_preferences">Nastavení skupinových předvoleb</string>
<string name="your_preferences">Vaše preference</string>
<string name="timed_messages">Mizící zprávy</string>
<string name="timed_messages">Mizející zprávy</string>
<string name="feature_enabled_for_contact">povoleno pro kontakt</string>
<string name="feature_received_prohibited">přijaté, zakázané</string>
<string name="both_you_and_your_contact_can_send_disappearing">Vy i váš kontakt můžete posílat mizící zprávy.</string>
@@ -87,7 +87,7 @@
<string name="thousand_abbreviation">k</string>
<string name="connect_via_contact_link">Připojit se odkazem\?</string>
<string name="connect_via_invitation_link">Připojit se pozvánkou\?</string>
<string name="connect_via_group_link">Připojit se odkazem skupiny\?</string>
<string name="connect_via_group_link">Připojit se do skupiny?</string>
<string name="profile_will_be_sent_to_contact_sending_link">Váš profil bude odeslán kontaktu, od kterého jste obdrželi tento odkaz.</string>
<string name="server_connected">připojeno</string>
<string name="server_error">chyba</string>
@@ -191,8 +191,6 @@
<string name="to_start_a_new_chat_help_header">Pro zahájení nové konverzace</string>
<string name="chat_help_tap_button">Klepněte na tlačítko</string>
<string name="above_then_preposition_continuation">potom:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Přidejte nový kontakt</b>: vytvořte jednorázý QR kód pro váš kontakt.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Naskenujte QR kód</b>: připojíte se ke kontaktu, který vám QR kód ukázal.]]></string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 počítač: naskenujte QR kód z aplikace přez <b>Skenovat QR kód</b>.]]></string>
<string name="clear_chat_question">Vyčistit chat\?</string>
<string name="clear_verb">Vyčistit</string>
@@ -209,7 +207,6 @@
<string name="you_will_be_connected_when_group_host_device_is_online">Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později!</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Budete připojeni, jakmile bude vaše žádost o připojení přijata, vyčkejte prosím nebo se podívejte později!</string>
<string name="connection_request_sent">Požadavek na připojení byl odeslán!</string>
<string name="create_one_time_link">Vytvořit jednorázovou pozvánku</string>
<string name="one_time_link">Jednorázová pozvánka</string>
<string name="security_code">Bezpečnostní kód</string>
<string name="is_verified">%s je ověřeno</string>
@@ -242,7 +239,7 @@
<string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">Platforma pro zasílání zpráv a aplikace chránící vaše soukromí a bezpečnost.</string>
<string name="create_profile">Vytvořit profil</string>
<string name="profile_is_only_shared_with_your_contacts">Profil je sdílen pouze s vašimi kontakty.</string>
<string name="display_name_cannot_contain_whitespace">Zobrazované jméno nesmí obsahovat mezery.</string>
<string name="display_name_cannot_contain_whitespace">Zobrazované jméno nesmí obsahovat bílé znaky.</string>
<string name="bold_text">tučně</string>
<string name="callstatus_in_progress">probíhající hovor</string>
<string name="decentralized">Decentralizovaná</string>
@@ -268,7 +265,7 @@
<string name="settings_section_title_settings">NASTAVENÍ</string>
<string name="settings_section_title_help">NÁPOVĚDA</string>
<string name="settings_section_title_device">ZAŘÍZENÍ</string>
<string name="settings_section_title_chats">CHATY</string>
<string name="settings_section_title_chats">KONVERZACE</string>
<string name="settings_experimental_features">Experimentální funkce</string>
<string name="settings_section_title_socks">SOCKS PROXY</string>
<string name="settings_section_title_icon">IKONA APLIKACE</string>
@@ -278,29 +275,29 @@
<string name="export_database">Export databáze</string>
<string name="import_database">Import databáze</string>
<string name="delete_database">Smazat databázi</string>
<string name="error_exporting_chat_database">Chyba exportu chat databáze</string>
<string name="error_exporting_chat_database">Chyba při exportu databáze chatu</string>
<string name="import_database_confirmation">Import</string>
<string name="restart_the_app_to_use_imported_chat_database">Restartujte aplikaci, abyste mohli používat importovanou chat databázi.</string>
<string name="restart_the_app_to_use_imported_chat_database">Restartujte aplikaci, abyste mohli používat importovanou databázi chatu.</string>
<string name="delete_chat_profile_question">Smazat chat profil\?</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">Tuto akci nelze vzít zpět! Váš profil, kontakty, zprávy a soubory budou nenávratně ztraceny.</string>
<string name="restart_the_app_to_create_a_new_chat_profile">Restartujte aplikaci a vytvořte nový chat profil.</string>
<string name="you_must_use_the_most_recent_version_of_database">Nejnovější verzi chat databáze musíte používat POUZE v jednom zařízení, jinak se může stát, že přestanete přijímat zprávy od některých kontaktů.</string>
<string name="you_must_use_the_most_recent_version_of_database">Nejnovější verzi databáze chatu musíte používat POUZE v jednom zařízení, jinak se může stát, že přestanete přijímat zprávy od některých kontaktů.</string>
<string name="stop_chat_to_enable_database_actions">Zastavte chat a povolte akce s databází.</string>
<string name="files_and_media_section">Soubory a média</string>
<string name="delete_files_and_media_question">Smazat soubory a média\?</string>
<string name="delete_messages">Odstranit zprávy</string>
<string name="remove_passphrase_from_keychain">Odstranit frázi z úložiště klíčů\?</string>
<string name="remove_passphrase_from_keychain">Odstranit přístupovou frázi z úložiště klíčů?</string>
<string name="notifications_will_be_hidden">Oznámení budou doručována pouze do doby, než se aplikace zastaví!</string>
<string name="remove_passphrase">Odstranit</string>
<string name="update_database">Aktualizovat</string>
<string name="current_passphrase">Aktuální fráze…</string>
<string name="current_passphrase">Aktuální přístupová fráze…</string>
<string name="update_database_passphrase">Aktualizovat přístupovou frázi</string>
<string name="enter_correct_current_passphrase">Zadejte prosím správnou aktuální přístupovou frázi.</string>
<string name="database_is_not_encrypted">Chat databáze není šifrována - nastavte přístupovou frázi pro její ochranu.</string>
<string name="database_is_not_encrypted">Databáze chatu není šifrována - nastavte přístupovou frázi pro její ochranu.</string>
<string name="keychain_is_storing_securely">K bezpečnému uložení přístupové fráze slouží úložiště klíčů Android - umožňuje fungování služby oznámení.</string>
<string name="impossible_to_recover_passphrase"><![CDATA[<b>Upozornění</b>: pokud přístupovou frázi ztratíte, NEBUDE možné ji obnovit ani změnit.]]></string>
<string name="database_will_be_encrypted_and_passphrase_stored">Databáze bude šifrována a přístupová fráze bude uložena v úložišti klíčů.</string>
<string name="store_passphrase_securely">Heslo uložte bezpečně, v případě jeho ztráty jej NEBUDE možné změnit.</string>
<string name="database_will_be_encrypted_and_passphrase_stored">Databáze bude zašifrována a přístupová fráze bude uložena v úložišti klíčů.</string>
<string name="store_passphrase_securely">Přístupovou frázi uložte bezpečně, v případě její ztráty ji NEBUDE možné změnit.</string>
<string name="file_with_path">Soubor: %s</string>
<string name="database_passphrase_is_required">Pro otevření chatu je vyžadována přístupová fráze.</string>
<string name="unknown_error">Neznámá chyba</string>
@@ -309,7 +306,7 @@
<string name="restore_database_alert_desc">Po obnovení zálohy databáze zadejte předchozí frázi. Tuto akci nelze vrátit zpět.</string>
<string name="chat_is_stopped_indication">Chat je zastaven</string>
<string name="chat_archive_header">Chat se archivuje</string>
<string name="delete_chat_archive_question">Smazat chat archiv\?</string>
<string name="delete_chat_archive_question">Smazat archiv chatu?</string>
<string name="join_group_question">Připojit se ke skupině\?</string>
<string name="join_group_button">Připojte se na</string>
<string name="leave_group_button">Opustit</string>
@@ -354,12 +351,12 @@
<string name="next_generation_of_private_messaging">Příští generace soukromé komunikace</string>
<string name="people_can_connect_only_via_links_you_share">Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte.</string>
<string name="integrity_msg_bad_hash">špatný kontrolní součet zprávy</string>
<string name="chat_database_imported">Chat databáze importována</string>
<string name="chat_database_imported">Databáze chatu importována</string>
<string name="new_passphrase">Nová přístupová fráze…</string>
<string name="save_passphrase_and_open_chat">Uložte frázi a otevřete chat</string>
<string name="chat_archive_section">CHAT ARCHIV</string>
<string name="save_passphrase_and_open_chat">Uložte přístupovou frázi a otevřete chat</string>
<string name="chat_archive_section">ARCHIV CHATU</string>
<string name="no_contacts_selected">Nebyl vybrán žádný kontakt</string>
<string name="invite_prohibited_description">Snažíte se pozvat kontakt se kterým jste sdíleli inkognito profil, do skupiny ve které používáte svůj hlavní profil</string>
<string name="invite_prohibited_description">Snažíte se pozvat kontakt, se kterým jste sdíleli inkognito profil, do skupiny, ve které používáte svůj hlavní profil</string>
<string name="info_row_group">Skupina</string>
<string name="network_options_revert">Vrátit</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Aktualizací nastavení se klient znovu připojí ke všem serverům.</string>
@@ -381,8 +378,8 @@
<string name="to_verify_compare">Chcete-li ověřit koncové šifrování u svého kontaktu, porovnejte (nebo naskenujte) kód na svých zařízeních.</string>
<string name="your_settings">Vaše nastavení</string>
<string name="your_simplex_contact_address">Vaše SimpleX adresa</string>
<string name="database_passphrase_and_export">Heslo databáze a export</string>
<string name="your_chat_profiles">Chat profily</string>
<string name="database_passphrase_and_export">Přístupová fráze k databázi a export</string>
<string name="your_chat_profiles">Vaše chatovací profily</string>
<string name="chat_with_the_founder">Zaslat otázky a nápady</string>
<string name="smp_servers_test_server">Test serveru</string>
<string name="enter_one_ICE_server_per_line">Servery ICE (jeden na řádek)</string>
@@ -422,9 +419,9 @@
<string name="image_saved">Obrázek uložen do galerie</string>
<string name="icon_descr_file">Soubor</string>
<string name="large_file">Velký soubor!</string>
<string name="file_will_be_received_when_contact_is_online">Soubor bude přijat, až bude váš kontakt online, vyčkejte prosím, nebo se podívejte později!</string>
<string name="file_will_be_received_when_contact_is_online">Soubor bude přijat, až bude váš kontakt online, vyčkejte prosím nebo se podívejte později!</string>
<string name="file_saved">Soubor uložen</string>
<string name="file_not_found">Soubor nenalezen</string>
<string name="file_not_found">Soubor nebyl nalezen</string>
<string name="voice_message_with_duration">Hlasová zpráva (%1$s)</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Kontakt a všechny zprávy budou smazány - nelze to vzít zpět!</string>
<string name="button_delete_contact">Smazat kontakt</string>
@@ -461,7 +458,7 @@
<string name="protect_app_screen">Skrývat aplikaci</string>
<string name="send_link_previews">Odesílat náhledy odkazů</string>
<string name="full_backup">Zálohovat data aplikace</string>
<string name="confirm_new_passphrase">Potvrdit frázi…</string>
<string name="confirm_new_passphrase">Potvrdit novou přístupovou frázi…</string>
<string name="error_with_info">Chyba: %s</string>
<string name="leave_group_question">Opustit skupinu\?</string>
<string name="icon_descr_group_inactive">Skupina je neaktivní</string>
@@ -469,8 +466,8 @@
<string name="clear_contacts_selection_button">Vyčistit</string>
<string name="switch_verb">Přepnout</string>
<string name="member_role_will_be_changed_with_notification">Role bude změněna na \"%s\". Všichni ve skupině budou informováni.</string>
<string name="error_removing_member">Chyba odebírání člena</string>
<string name="error_saving_group_profile">Chyba ukládání profilu skupiny</string>
<string name="error_removing_member">Chyba při odebrání člena</string>
<string name="error_saving_group_profile">Chyba při ukládání profilu skupiny</string>
<string name="network_option_seconds_label">vteřiny</string>
<string name="incognito_info_allows">Umožňuje mít v jednom chat profilu mnoho anonymních spojení bez sdílení údajů mezi nimi.</string>
<string name="incognito_info_share">Pokud s někým sdílíte inkognito profil, bude použit pro skupiny, do kterých vás pozve.</string>
@@ -487,7 +484,7 @@
<string name="v4_3_irreversible_message_deletion">Nevratné mazání zpráv</string>
<string name="v4_3_improved_server_configuration">Vylepšena konfigurace serveru</string>
<string name="v4_3_improved_privacy_and_security">Vylepšena ochrana soukromí a zabezpečení</string>
<string name="v4_3_improved_privacy_and_security_desc">Skryta obrazovka aplikace v posledních aplikacích.</string>
<string name="v4_3_improved_privacy_and_security_desc">Skrytí obrazovky aplikace v posledních aplikacích.</string>
<string name="v4_4_disappearing_messages_desc">Odeslané zprávy se po uplynutí nastavené doby odstraní.</string>
<string name="v4_4_live_messages">Živé zprávy</string>
<string name="v4_4_live_messages_desc">Příjemci uvidí aktualizaci během psaní.</string>
@@ -496,17 +493,17 @@
<string name="v4_4_french_interface_descr">Díky uživatelům - překládejte prostřednictvím Weblate!</string>
<string name="v4_5_multiple_chat_profiles">Více chat profilů</string>
<string name="v4_5_multiple_chat_profiles_descr">Různá jména, avataři a izolace přenosu.</string>
<string name="v4_5_message_draft">Návrh zpráv</string>
<string name="v4_5_message_draft">Návrh zprávy</string>
<string name="v4_5_message_draft_descr">Zachování posledního návrhu zprávy s přílohami.</string>
<string name="v4_5_transport_isolation">Izolace přenosu</string>
<string name="v4_5_transport_isolation_descr">Podle chat profilu (výchozí) nebo podle připojení (BETA).</string>
<string name="v4_5_transport_isolation_descr">Podle profilu chatu (výchozí) nebo podle připojení (BETA).</string>
<string name="you_will_join_group">Připojíte se ke skupině, na kterou odkazuje tento odkaz, a spojíte se s jejími členy.</string>
<string name="connect_via_link_verb">Připojení</string>
<string name="connected_to_server_to_receive_messages_from_contact">Jste připojeni k serveru, který se používá k přijímání zpráv od tohoto kontaktu.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Pokoušíte se připojit k serveru používaném pro příjem zpráv od tohoto kontaktu (chyba: %1$s).</string>
<string name="marked_deleted_description">označit jako smazáno</string>
<string name="marked_deleted_description">označeno jako smazáno</string>
<string name="sending_files_not_yet_supported">Odesílání souborů zatím není podporováno</string>
<string name="receiving_files_not_yet_supported">Přijímání souborů zatím není podporováno</string>
<string name="receiving_files_not_yet_supported">přijímání souborů zatím není podporováno</string>
<string name="sender_you_pronoun">ty</string>
<string name="unknown_message_format">neznámý formát zprávy</string>
<string name="invalid_message_format">neplatný formát zprávy</string>
@@ -519,8 +516,8 @@
<string name="error_joining_group">Chyba připojování ke skupině</string>
<string name="cannot_receive_file">Nelze přijmout soubor</string>
<string name="sender_cancelled_file_transfer">Odesílatel zrušil přenos souboru.</string>
<string name="error_receiving_file">Chyba příjmu souboru</string>
<string name="error_creating_address">Chyba vytváření adresy</string>
<string name="error_receiving_file">Chyba při příjmu souboru</string>
<string name="error_creating_address">Chyba při vytváření adresy</string>
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Zkontrolujte, zda jste použili správný odkaz, nebo požádejte kontakt, aby vám poslal jiný.</string>
<string name="connection_error_auth_desc">Pokud váš kontakt neodstranil připojení nebo tento odkaz již nebyl použit, může se jednat o chybu – nahlaste ji.
\nChcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu na připojení a zkontrolujte, zda máte stabilní připojení k síti.</string>
@@ -619,7 +616,7 @@
<string name="markdown_help">Nápověda k markdown</string>
<string name="smp_servers_save">Uložit servery</string>
<string name="markdown_in_messages">Markdown ve zprávách</string>
<string name="smp_servers_test_servers">Testovat servery</string>
<string name="smp_servers_test_servers">Testovací servery</string>
<string name="smp_servers_preset_server">Přednastavený server</string>
<string name="smp_servers_your_server">Váš server</string>
<string name="smp_servers_your_server_address">Adresa vašeho serveru</string>
@@ -651,12 +648,12 @@
<string name="core_version">Verze jádra: v%s</string>
<string name="delete_address__question">Smazat adresu\?</string>
<string name="all_your_contacts_will_remain_connected">Všechny vaše kontakty zůstanou připojeny.</string>
<string name="display_name__field">Zobrazované jméno:</string>
<string name="display_name__field">Profilové jméno:</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Váš profil je uložen v zařízení a je sdílen pouze s vašimi kontakty. SimpleX servery váš profil vidět nemohou.</string>
<string name="save_preferences_question">Uložit předvolby\?</string>
<string name="save_and_notify_contact">Uložit a upozornit kontakt</string>
<string name="save_and_notify_contacts">Uložit a upozornit kontakty</string>
<string name="you_control_your_chat">Kontrolujete chat!</string>
<string name="you_control_your_chat">Kontrolujete konverzaci!</string>
<string name="we_do_not_store_contacts_or_messages_on_servers">Na serverech neukládáme žádné vaše kontakty ani zprávy (po doručení).</string>
<string name="your_profile_is_stored_on_your_device">Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení.</string>
<string name="display_name">Zobrazované jméno</string>
@@ -721,13 +718,13 @@
<string name="settings_section_title_you">VY</string>
<string name="settings_section_title_support">PODPOŘIT SIMPLEX CHAT</string>
<string name="settings_developer_tools">Nástroje pro vývojáře</string>
<string name="settings_section_title_incognito">inkognito mód</string>
<string name="your_chat_database">Chat databáze</string>
<string name="settings_section_title_incognito">Inkognito mód</string>
<string name="your_chat_database">Vaše chatovací databáze</string>
<string name="run_chat_section">SPUSTIT CHAT</string>
<string name="chat_is_running">Chat je spuštěn</string>
<string name="chat_is_stopped">Chat je zastaven</string>
<string name="chat_database_section">CHAT DATABÁZE</string>
<string name="database_passphrase">přístupová fráze databáze</string>
<string name="chat_database_section">DATABÁZE CHATU</string>
<string name="database_passphrase">přístupová fráze k databázi</string>
<string name="new_database_archive">Archiv nové databáze</string>
<string name="old_database_archive">Archiv staré databáze</string>
<string name="error_starting_chat">Chyba spouštění chatu</string>
@@ -737,12 +734,12 @@
<string name="set_password_to_export">Nastavte přístupovou frázi pro export</string>
<string name="set_password_to_export_desc">Databáze je šifrována pomocí náhodné přístupové fráze. Před exportem ji změňte.</string>
<string name="error_stopping_chat">Chyba zastavování chatu</string>
<string name="import_database_question">Importovat chat databázi\?</string>
<string name="import_database_question">Importovat databázi chatu?</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Vaše aktuální chat databáze bude SMAZÁNA a NAHRAZENA importovanou databází.
\nTuto akci nelze vzít zpět - vaše profily, kontakty, zprávy a soubory budou nenávratně ztraceny.</string>
<string name="error_deleting_database">Chyba mazání chat databáze</string>
<string name="error_importing_database">Chyba importu chat databáze</string>
<string name="chat_database_deleted">Chat databáze odstraněna</string>
<string name="error_deleting_database">Chyba při mazání databáze chatu</string>
<string name="error_importing_database">Chyba při importu databáze chatu</string>
<string name="chat_database_deleted">Databáze chatu odstraněna</string>
<string name="delete_files_and_media_for_all_users">Odstranit soubory všech chat profilů</string>
<string name="delete_files_and_media_all">Odstranit všechny soubory</string>
<string name="delete_files_and_media_desc">Tuto akci nelze vrátit zpět - všechny přijaté a odeslané soubory a média budou smazány. Obrázky s nízkým rozlišením zůstanou zachovány.</string>
@@ -756,25 +753,25 @@
<string name="enable_automatic_deletion_question">Povolit automatické mazání zpráv\?</string>
<string name="enable_automatic_deletion_message">Tuto akci nelze vzít zpět - zprávy odeslané a přijaté dříve, než bylo zvoleno, budou smazány. Může to trvat několik minut.</string>
<string name="error_changing_message_deletion">Chyba změny nastavení</string>
<string name="save_passphrase_in_keychain">Uložit frázi do úložiště klíčů</string>
<string name="database_encrypted">Databáze šifrována!</string>
<string name="error_encrypting_database">Chyba šifrování databáze</string>
<string name="save_passphrase_in_keychain">Uložit přístupovou frázi do úložiště klíčů</string>
<string name="database_encrypted">Databáze zašifrována!</string>
<string name="error_encrypting_database">Chyba při šifrování databáze</string>
<string name="encrypt_database">Šifrovat</string>
<string name="encrypted_with_random_passphrase">Databáze je šifrována pomocí náhodné přístupové fráze, musíte ji změnit.</string>
<string name="keychain_allows_to_receive_ntfs">K bezpečnému uložení přístupové fráze se použije úložiště klíčů Android, po restartování aplikace nebo změně přístupové fráze - umožní přijímání oznámení.</string>
<string name="you_have_to_enter_passphrase_every_time">Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení.</string>
<string name="encrypt_database_question">Šifrovat databázi\?</string>
<string name="change_database_passphrase_question">Změnit přístupovou frázi databáze\?</string>
<string name="database_will_be_encrypted">Databáze bude šifrována.</string>
<string name="database_encryption_will_be_updated">Přístupová fráze databáze bude aktualizována a uložena do úložiště klíčů.</string>
<string name="database_passphrase_will_be_updated">Přístupová fráze databáze bude aktualizována.</string>
<string name="encrypt_database_question">Zašifrovat databázi?</string>
<string name="change_database_passphrase_question">Změnit přístupovou frázi k databázi?</string>
<string name="database_will_be_encrypted">Databáze bude zašifrována.</string>
<string name="database_encryption_will_be_updated">Přístupová fráze k databázi bude aktualizována a uložena do úložiště klíčů.</string>
<string name="database_passphrase_will_be_updated">Přístupová fráze k databázi bude aktualizována.</string>
<string name="store_passphrase_securely_without_recover">Uložte prosím bezpečně přístupovou frázi, pokud ji ztratíte, NEBUDE možné přistupovat k chatu.</string>
<string name="wrong_passphrase">Špatná přístupová fráze k databázi</string>
<string name="encrypted_database">Šifrovaná databáze</string>
<string name="encrypted_database">Zašifrovaná databáze</string>
<string name="database_error">Chyba databáze</string>
<string name="keychain_error">Chyba klíčenky</string>
<string name="passphrase_is_different">Přístupová fráze databáze se liší od té uložené v klíčence.</string>
<string name="cannot_access_keychain">Nelze získat přístup ke klíčence pro uložení přístupová fráze k databázi.</string>
<string name="passphrase_is_different">Přístupová fráze k databázi se liší od té uložené v klíčence.</string>
<string name="cannot_access_keychain">Nelze získat přístup ke klíčence pro uložení přístupové fráze k databázi.</string>
<string name="unknown_database_error_with_info">Neznámá chyba databáze: %s</string>
<string name="wrong_passphrase_title">Špatná přístupová fráze!</string>
<string name="enter_correct_passphrase">Zadejte správnou přístupovou frázi.</string>
@@ -782,8 +779,8 @@
<string name="database_backup_can_be_restored">Pokus o změnu přístupové fráze databáze nebyl dokončen.</string>
<string name="restore_database_alert_title">Obnovit zálohu databáze\?</string>
<string name="restore_database_alert_confirm">Obnovit</string>
<string name="database_restore_error">Chyba obnovení databáze</string>
<string name="restore_passphrase_not_found_desc">Heslo nebylo v klíčence nalezeno, zadejte jej prosím ručně. K této situaci mohlo dojít, pokud jste obnovili data aplikace pomocí zálohovacího nástroje. Pokud tomu tak není, obraťte se na vývojáře.</string>
<string name="database_restore_error">Chyba při obnovování databáze</string>
<string name="restore_passphrase_not_found_desc">Přístupová fráze nebyla v klíčence nalezena, zadejte jej prosím ručně. K této situaci mohlo dojít, pokud jste obnovili data aplikace pomocí zálohovacího nástroje. Pokud tomu tak není, obraťte se na vývojáře.</string>
<string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Chat můžete spustit v Nastavení / Databáze nebo restartováním aplikace.</string>
<string name="save_archive">Uložit archiv</string>
<string name="delete_archive">Smazat archiv</string>
@@ -793,7 +790,7 @@
<string name="join_group_incognito_button">Připojit se inkognito</string>
<string name="joining_group">Připojit ke skupině</string>
<string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Připojili jste se k této skupině. Připojení k pozvání člena skupiny.</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Přestanete dostávat zprávy z této skupiny. Chat historie bude zachována.</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Přestanete dostávat zprávy z této skupiny. Historie chatu bude zachována.</string>
<string name="alert_title_group_invitation_expired">Platnost pozvánky vypršela!</string>
<string name="alert_message_group_invitation_expired">Pozvánka do skupiny již není platná, byla odstraněna odesílatelem.</string>
<string name="alert_message_no_group">Tato skupina již neexistuje.</string>
@@ -828,8 +825,8 @@
<string name="group_member_status_invited">pozván</string>
<string name="group_member_status_introduced">představen (zaveden)</string>
<string name="group_member_status_intro_invitation">připojení (pozvánka na představení)</string>
<string name="group_member_status_accepted">připojen (přijat)</string>
<string name="group_member_status_announced">připojen (oznámen)</string>
<string name="group_member_status_accepted">připojení (přijato)</string>
<string name="group_member_status_announced">připojení (oznámeno)</string>
<string name="group_member_status_connected">připojen</string>
<string name="group_member_status_complete">komplet</string>
<string name="group_member_status_creator">tvůrce</string>
@@ -854,8 +851,8 @@
<string name="create_group_link">Vytvořit odkaz skupiny</string>
<string name="delete_link">Smazat odkaz</string>
<string name="you_can_share_group_link_anybody_will_be_able_to_connect">Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte.</string>
<string name="error_creating_link_for_group">Chyba vytváření odkazu skupiny</string>
<string name="error_deleting_link_for_group">Chyba odstraňování odkazu skupiny</string>
<string name="error_creating_link_for_group">Chyba při vytváření odkazu skupiny</string>
<string name="error_deleting_link_for_group">Chyba při odstraňování odkazu skupiny</string>
<string name="only_group_owners_can_change_prefs">Předvolby skupiny mohou měnit pouze vlastníci skupiny.</string>
<string name="section_title_for_console">PRO KONSOLE</string>
<string name="info_row_local_name">Místní název</string>
@@ -867,9 +864,9 @@
<string name="change_role">Změnit roli</string>
<string name="change_verb">Změnit</string>
<string name="member_role_will_be_changed_with_invitation">Role bude změněna na \"%s\". Člen obdrží novou pozvánku.</string>
<string name="error_changing_role">Chyba změny role</string>
<string name="error_changing_role">Chyba při změně role</string>
<string name="conn_level_desc_direct">přímo</string>
<string name="sending_via">Odesíláno přez</string>
<string name="sending_via">Odesíláno přes</string>
<string name="network_status">Stav sítě</string>
<string name="switch_receiving_address">Přepínač přijímací adresy</string>
<string name="group_is_decentralized">Skupina je plně decentralizovaná - viditelná pouze pro členy.</string>
@@ -881,7 +878,7 @@
<string name="network_option_ping_count">Počet PING</string>
<string name="network_option_enable_tcp_keep_alive">Povolit TCP keep-alive</string>
<string name="update_network_settings_confirmation">Aktualizovat</string>
<string name="users_delete_question">Smazat chat profil\?</string>
<string name="users_delete_question">Smazat profil chatu?</string>
<string name="users_delete_profile_for">Smazat chat profil pro</string>
<string name="users_delete_with_connections">Profil a připojení k serveru</string>
<string name="users_delete_data_only">Pouze místní data profilu</string>
@@ -1183,7 +1180,7 @@
<string name="learn_more">Zjistit více</string>
<string name="share_with_contacts">Sdílet s kontakty</string>
<string name="group_welcome_preview">Náhled</string>
<string name="opening_database">Otvírání databáze…</string>
<string name="opening_database">Otevírání databáze…</string>
<string name="error_setting_address">Chyba nastavení adresy</string>
<string name="scan_qr_to_connect_to_contact">Pro připojení může váš kontakt naskenovat QR kód, nebo použít odkaz v aplikaci.</string>
<string name="you_can_accept_or_reject_connection">Když někdo požádá o připojení, můžete žádost přijmout nebo odmítnout.</string>
@@ -1373,17 +1370,17 @@
<string name="privacy_show_last_messages">Zobrazit poslední zprávy</string>
<string name="send_receipts_disabled_alert_msg">Tato skupina má více než %1$d členů, doručenky nejsou odeslány.</string>
<string name="in_developing_desc">Tato funkce zatím není podporována. Vyzkoušejte další vydání.</string>
<string name="database_will_be_encrypted_and_passphrase_stored_in_settings">Databáze bude zašifrována a heslo bude uloženo v klíčence.</string>
<string name="database_will_be_encrypted_and_passphrase_stored_in_settings">Databáze bude zašifrována a přístupová fráze bude uloženo v klíčence.</string>
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Všimněte si prosím</b>: relé zpráv a souborů jsou připojeny prostřednictvím proxy SOCKS. Volání a odesílání náhledů odkazů používá přímé připojení.]]></string>
<string name="encrypt_local_files">Šifrovat místní soubory</string>
<string name="you_can_change_it_later">Náhodná přístupová fráze je uložena v nastavení jako prostý text.
\nMůžete ji později změnit.</string>
<string name="database_encryption_will_be_updated_in_settings">Heslo pro šifrování databáze bude aktualizováno a uloženo v klíčence.</string>
<string name="database_encryption_will_be_updated_in_settings">Přístupová fráze k databáze bude aktualizována a uložena v klíčence.</string>
<string name="remove_passphrase_from_settings">Odebrat přístupovou frázi z nastavení\?</string>
<string name="use_random_passphrase">Použít náhodnou přístupovou frázi</string>
<string name="save_passphrase_in_settings">Uložit přístupovou frázi v nastavení</string>
<string name="setup_database_passphrase">Nastavení přístupové fráze databáze</string>
<string name="set_database_passphrase">Nastavit přístupovou frázi databáze</string>
<string name="setup_database_passphrase">Nastavení přístupové fráze k databázi</string>
<string name="set_database_passphrase">Nastavit přístupovou frázi k databázi</string>
<string name="open_database_folder">Otevřít složku databáze</string>
<string name="passphrase_will_be_saved_in_settings">Přístupová fráze bude uložena v nastavení jako prostý text až ji změníte nebo po restartu aplikace.</string>
<string name="settings_is_storing_in_clear_text">Přístupová fráze je uložena v nastavení jako prostý text.</string>
@@ -1404,4 +1401,38 @@
\n- doručenky (až 20 členů).
\n- rychlejší a stabilnější.</string>
<string name="member_contact_send_direct_message">odeslat přímou zprávu</string>
<string name="rcv_direct_event_contact_deleted">smazaný kontakt</string>
<string name="error_alert_title">Chyba</string>
<string name="create_another_profile_button">Vytvořit profil</string>
<string name="connected_desktop">Připojený počítač</string>
<string name="group_members_2">%s a %s</string>
<string name="multicast_connect_automatically">Připojit se automaticky</string>
<string name="desktop_address">Adresa počítače</string>
<string name="connect_plan_join_your_group">Připojit se do skupiny?</string>
<string name="connect_plan_group_already_exists">Skupina již existuje!</string>
<string name="rcv_group_event_1_member_connected">%s připojený</string>
<string name="this_device">Toto zařízení</string>
<string name="desktop_device">Počítač</string>
<string name="this_device_name">Jméno tohoto zařízení</string>
<string name="found_desktop">Počítač nalezen</string>
<string name="delete_messages__question">Smazat %d zpráv?</string>
<string name="remove_member_button">Odebrat člena</string>
<string name="connected_mobile">Připojený telefon</string>
<string name="button_remove_member_question">Odebrat člena?</string>
<string name="connect_plan_open_group">Otevřít skupinu</string>
<string name="desktop_connection_terminated">Spojení ukončeno</string>
<string name="error">Chyba</string>
<string name="connect_to_desktop">Připojit se k počítači</string>
<string name="disconnect_remote_host">Odpojit</string>
<string name="invalid_name">Neplatné jméno!</string>
<string name="group_member_role_author">Autor</string>
<string name="connected_to_mobile">Připojený k telefonu</string>
<string name="bad_desktop_address">Špatná adresa počítače</string>
<string name="devices">Zařízení</string>
<string name="disconnect_desktop_question">Odpojit počítač?</string>
<string name="create_group_button">Vytvořit skupinu</string>
<string name="session_code">Kód relace</string>
<string name="paste_desktop_address">Vložit adres počítače</string>
<string name="blocked_item_description">Blokovaný</string>
<string name="v5_4_better_groups">Lepší skupiny</string>
</resources>
@@ -262,16 +262,14 @@
<string name="gallery_video_button">Video</string>
<!-- help - ChatHelpView.kt -->
<string name="thank_you_for_installing_simplex">Danke, dass Sie SimpleX Chat installiert haben!</string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Sie können sich <font color=#0088ff>mit SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und Updates zu erhalten</font>.]]></string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Sie können sich <font color="#0088ff">mit SimpleX-Chat-Entwicklern verbinden, um Fragen zu stellen und Updates zu erhalten</font>.]]></string>
<string name="to_start_a_new_chat_help_header">Um einen neuen Chat zu starten</string>
<string name="chat_help_tap_button">Schaltfläche antippen</string>
<string name="above_then_preposition_continuation">Danach die gewünschte Aktion auswählen:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Neuen Kontakt hinzufügen</b>: Um Ihren Einmal-QR-Code für Ihren Kontakt zu erstellen.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>QR-Code scannen</b>: Um sich mit Ihrem Kontakt zu verbinden, der Ihnen seinen QR-Code zeigt.]]></string>
<string name="to_connect_via_link_title">Über Link verbinden</string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Wenn Sie einen SimpleX-Chat-Einladungslink erhalten haben, können Sie ihn in Ihrem Browser öffnen:</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 Desktop: Angezeigten QR-Code aus der App scannen, über <b>QR-Code scannen</b>.]]></string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 Handy: Tippen Sie in der App auf „<b>In mobiler App öffnen</b>“ und dann auf „<b>Verbinden</b>“.]]></string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 Mobiltelefon: Tippen Sie in der App auf „<b>In mobiler App öffnen</b>“ und dann auf „<b>Verbinden</b>“.]]></string>
<!-- Contact Request Alert Dialogue - ChatListNavLinkView.kt -->
<string name="accept_connection_request__question">Die Verbindungsanfrage akzeptieren?</string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Wenn Sie ablehnen, wird der Absender NICHT benachrichtigt.</string>
@@ -341,7 +339,6 @@
<string name="this_string_is_not_a_connection_link">Diese Zeichenfolge entspricht keinem gültigen Verbindungslink!</string>
<string name="you_can_also_connect_by_clicking_the_link"><![CDATA[Sie können sich auch verbinden, indem Sie auf den Link klicken. Wenn er im Browser geöffnet wird, klicken Sie auf die Schaltfläche „<b>In mobiler App öffnen</b>“.]]></string>
<!-- CreateLinkView.kt -->
<string name="create_one_time_link">Einmal-Einladungslink erstellen</string>
<string name="one_time_link">Einmal-Einladungslink</string>
<!-- settings - SettingsView.kt -->
<string name="your_settings">Ihre Einstellungen</string>
@@ -1488,14 +1485,14 @@
<string name="expand_verb">Erweitern</string>
<string name="connect_plan_repeat_connection_request">Verbindungsanfrage wiederholen?</string>
<string name="rcv_direct_event_contact_deleted">Gelöschter Kontakt</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Sie sind bereits mit %1$s verbunden.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Sie sind bereits mit <b>%1$s</b> verbunden.]]></string>
<string name="error_alert_title">Fehler</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Sie sind über diesen Link bereits Mitglied der Gruppe.</string>
<string name="create_group_button">Gruppe erstellen</string>
<string name="create_another_profile_button">Profil erstellen</string>
<string name="group_members_2">%s und %s</string>
<string name="connect_plan_join_your_group">Ihrer Gruppe beitreten?</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Sie sind bereits Mitglied in der Gruppe %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Sie sind bereits Mitglied in der Gruppe <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">Das ist Ihr eigener Einmal-Link!</string>
<string name="marked_deleted_items_description">%d Nachrichten als gelöscht markiert</string>
<string name="connect_plan_group_already_exists">Gruppe besteht bereits!</string>
@@ -1510,7 +1507,7 @@
<string name="unblock_member_button">Mitglied freigeben</string>
<string name="connect_plan_connect_to_yourself">Mit Ihnen selbst verbinden?</string>
<string name="contact_tap_to_connect">Zum Verbinden antippen</string>
<string name="connect_plan_you_are_already_in_group_vName">Sie sind bereits Mitglied in der Gruppe %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Sie sind bereits Mitglied in der Gruppe <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">Das ist Ihre eigene SimpleX-Adresse!</string>
<string name="correct_name_to">Richtiger Name für %s?</string>
<string name="delete_messages__question">%d Nachrichten löschen?</string>
@@ -1531,7 +1528,7 @@
<string name="block_member_question">Mitglied blockieren?</string>
<string name="rcv_group_events_count">%d Gruppenereignisse</string>
<string name="invalid_name">Ungültiger Name!</string>
<string name="connect_plan_this_is_your_link_for_group_vName">Das ist Ihr Link für die Gruppe %1$s!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Das ist Ihr Link für die Gruppe <b>%1$s</b>!]]></string>
<string name="unblock_member_confirmation">Freigeben</string>
<string name="non_content_uri_alert_title">Ungültiger Datei-Pfad</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">Sie haben über diese Adresse bereits eine Verbindung beantragt!</string>
@@ -1603,4 +1600,14 @@
<string name="found_desktop">Gefundener Desktop</string>
<string name="not_compatible">Nicht kompatibel!</string>
<string name="multicast_discoverable_via_local_network">Über das lokale Netzwerk auffindbar</string>
<string name="refresh_qr_code">Aktualisieren</string>
<string name="create_chat_profile">Chat-Profil erstellen</string>
<string name="disconnect_remote_hosts">Mobiltelefone trennen</string>
<string name="no_connected_mobile">Kein Mobiltelefon verbunden</string>
<string name="random_port">Zufällig</string>
<string name="open_port_in_firewall_desc">Um einer Mobiltelefon-App die Verbindung zum Desktop zu ermöglichen, öffnen Sie diesen Port in Ihrer Firewall, falls Sie diese aktiviert haben</string>
<string name="open_port_in_firewall_title">Port in Firewall öffnen</string>
<string name="app_was_crashed">Ansicht abgestürzt</string>
<string name="error_showing_content">Fehler beim Anzeigen des Inhalts</string>
<string name="error_showing_message">Fehler beim Anzeigen der Nachricht</string>
</resources>
@@ -56,4 +56,168 @@
<string name="always_use_relay">Να χρησιμοποιείται πάντα αναμεταδότη</string>
<string name="search_verb">Αναζήτηση</string>
<string name="la_mode_off">Ανενεργό</string>
<string name="connect__your_profile_will_be_shared">"Το προφίλ σας %1$s θα μοιραστεί"</string>
<string name="your_simplex_contact_address">Η SimpleX διεύθυνση σας</string>
<string name="full_backup">Αντίγραφο δεδομένων εφαρμογής</string>
<string name="send_disappearing_message_5_minutes">5 λεπτά</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">Θα συνδεθείτε όταν η συσκευή της επαφής σας είναι συνδεμένει, παρακαλώ περιμένετε ή ελέγξτε αργότερα!</string>
<string name="your_ice_servers">Ο ICE διακομιστής σας</string>
<string name="app_version_title">Έκδοση εφαρμογής</string>
<string name="you_sent_group_invitation">Στείλατε πρόσκληση ομάδας</string>
<string name="send_disappearing_message_1_minute">1 λεπτό</string>
<string name="smp_servers_your_server">Ο διακομιστής σας</string>
<string name="address_section_title">Διεύθυνση</string>
<string name="abort_switch_receiving_address_confirm">Ακύρωση</string>
<string name="back">Πίσω</string>
<string name="send_disappearing_message_30_seconds">30 δευτερόλεπτα</string>
<string name="you_will_join_group">Θα συνδεθείτε με όλα τα μέλη της ομάδας.</string>
<string name="contact_wants_to_connect_via_call">%1$s θέλει να συνδεθεί μαζί σου μέσω</string>
<string name="allow_message_reactions">Επιτρέπονται αντιδράσεις μηνύματος.</string>
<string name="your_XFTP_servers">Ο διακομιστής XFTP σας</string>
<string name="smp_servers_your_server_address">Η διεύθυνση του διακομιστή σας</string>
<string name="your_profile_is_stored_on_your_device">Το προφίλ, επαφές και παραδομένα μηνύματα σας είναι αποθηκευμένα στην συσκευή σας.</string>
<string name="your_SMP_servers">Ο διακομιστής SMP σας</string>
<string name="allow_to_send_files">Επιτρέπεται να σταλούν αρχεία και μέσα.</string>
<string name="incognito_random_profile">Το τυχαίο προφίλ σας</string>
<string name="group_info_section_title_num_members">%1$s ΜΕΛΗ</string>
<string name="turn_off_battery_optimization_button">Επιτρέπεται</string>
<string name="your_preferences">Οι προτιμήσεις σας</string>
<string name="group_member_role_author">Συντάκτης</string>
<string name="your_ICE_servers">Ο ΙCE διακομιστής σας</string>
<string name="v5_0_app_passcode">Κωδικός εφαρμογής</string>
<string name="connect_via_member_address_alert_desc">Αίτημα σύνδεσης θα σταλεί σε αυτό το μέλος της ομάδας.</string>
<string name="settings_section_title_icon">ΟΙΚΟΝΑ ΕΦΑΡΜΟΓΗΣ</string>
<string name="settings_section_title_app">Εφαρμογή</string>
<string name="your_settings">Οι ρυθμίσεις σας</string>
<string name="app_version_name">Έκδοση εφαρμογής: v%s</string>
<string name="callstatus_error">σφάλμα κλήσης</string>
<string name="feature_cancelled_item">"ακυρώθηκε %s"</string>
<string name="icon_descr_cancel_link_preview">ακύρωση πρόβλεψη συνδέσμου</string>
<string name="change_database_passphrase_question">Αλλαγή κωδικού πρόσβασης βάση δεδομένων?</string>
<string name="la_change_app_passcode">Αλλαγή κωδικού πρόσβασης</string>
<string name="rcv_group_event_changed_member_role">Αλλαγή ρόλου του %s σε %s</string>
<string name="color_background">Φόντο</string>
<string name="database_initialization_error_title">Δεν είναι δυνατή η προετοιμασία της βάσης δεδομένων</string>
<string name="connect__a_new_random_profile_will_be_shared">Ένα νέο τυχαίο προφίλ θα μοιραστεί.</string>
<string name="alert_title_cant_invite_contacts">Δεν είναι δυνατή η πρόσκληση επαφών!</string>
<string name="chat_archive_header">Αρχείο συνομιλίας</string>
<string name="switch_receiving_address">Αλλαγή διεύθυνσης λήψης</string>
<string name="auth_unavailable">Πιστοποίηση μη διαθέσιμη</string>
<string name="change_verb">Αλλαγή</string>
<string name="available_in_v51">"
\nΔιαθέσιμο στην έκδοση 5.1"</string>
<string name="icon_descr_call_ended">Τέλος κλήσης</string>
<string name="settings_section_title_calls">ΚΛΗΣΕΙΣ</string>
<string name="auto_accept_contact">Αυτόματη αποδοχή</string>
<string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d αποτυχία κρυπτογράφησης μηνύματος</string>
<string name="snd_conn_event_switch_queue_phase_changing_for_member">αλλαγή διεύθυνσης για %s…</string>
<string name="change_member_role_question">Αλλαγή ρόλου ομάδας;</string>
<string name="chat_archive_section">ΑΡΧΕΙΟ ΣΥΝΟΜΙΛΙΑΣ</string>
<string name="invite_prohibited">Δεν είναι δυνατή η πρόσκληση επαφής!</string>
<string name="v4_2_auto_accept_contact_requests">Αυτόματη αποδοχή αιτήματος επαφής</string>
<string name="callstatus_calling">Κλήση…</string>
<string name="icon_descr_cancel_file_preview">Ακύρωση πρόβλεψη αρχείου</string>
<string name="block_member_confirmation">Φραγή</string>
<string name="icon_descr_cancel_image_preview">Ακύρωση πρόβλεψη εικόνας</string>
<string name="snd_conn_event_switch_queue_phase_changing">αλλαγή διεύθυνσης…</string>
<string name="v5_2_more_things">Μερικά παραπάνω πράγματα</string>
<string name="authentication_cancelled">Πιστοποίηση ακυρώθηκε</string>
<string name="change_self_destruct_passcode">Αλλαγή κωδικού αυτοκαταστροφής</string>
<string name="change_role">Αλλαγή ρολου</string>
<string name="icon_descr_cancel_live_message">Ακύρωση ζωντανών μυνημάτων</string>
<string name="all_app_data_will_be_cleared">Όλα τα δεδομένα της εφαρμογής διαγράφηκαν.</string>
<string name="appearance_settings">Εμφάνιση</string>
<string name="callstatus_ended">Τέλος κλήσης %1$s</string>
<string name="cancel_verb">Ακύρωση</string>
<string name="switch_receiving_address_question">Αλλαγή διεύθυνσης λήψης;</string>
<string name="rcv_conn_event_switch_queue_phase_changing">αλλαγή διεύθυνσης…</string>
<string name="change_self_destruct_mode">Αλλαγή λειτουργίας αυτοκαταστροφής</string>
<string name="use_camera_button">Κάμερα</string>
<string name="callstatus_in_progress">κλήση σε εξέλιξη</string>
<string name="auto_accept_images">Αυτόματη αποδοχή εικόνων</string>
<string name="rcv_group_event_changed_your_role">Αλλαγή του ρόλου σας σε %s</string>
<string name="icon_descr_call_progress">Κλήση σε εξέλιξη</string>
<string name="la_auth_failed">Πιστοποίηση απέτυχε</string>
<string name="connection_local_display_name">"σύνδεση %1$d"</string>
<string name="create_simplex_address">Δημιουργία διεύθυνση SimpleX</string>
<string name="status_contact_has_e2e_encryption">επαφή έχει κρυπτογράφηση από άκρο σε άκρο</string>
<string name="v5_4_incognito_groups_descr">Δημιουργία μια ομάδας χρησιμοποιώντας ένα τυχαίο προφίλ.</string>
<string name="create_group_button">Δημιουργία ομάδας</string>
<string name="create_another_profile_button">Δημιουργία προφίλ</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Η επαφή και όλα τα μηνύματα θα διαγραφούν - αυτό δεν μπορεί να αναιρεθεί!</string>
<string name="create_profile">Δημιουργία προφίλ</string>
<string name="contacts_can_mark_messages_for_deletion">Οι επαφές μπορούν να επισημάνουν μηνύματα προς διαγραφή; θα μπορείτε να τα δείτε.</string>
<string name="connect_via_invitation_link">Σύνδεση μέσω μιας εφάπαξ σύνδεσης;</string>
<string name="button_create_group_link">Δημιουργία σύνδεσμου</string>
<string name="connect_via_link_or_qr">Σύνδεση μέσω σύνδεσμο/κωδικό γρήγορης ανταπόκρισης</string>
<string name="connection_error_auth">Σφάλμα σύνδεσης (πιστοποίηση)</string>
<string name="group_member_status_connecting">συνδέεται</string>
<string name="group_connection_pending">συνδέεται…</string>
<string name="notification_preview_mode_contact">Όνομα επαφής</string>
<string name="create_address">Δημιουργία διεύθυνσης</string>
<string name="copy_verb">Αντιγραφή</string>
<string name="continue_to_next_step">Συνέχεια</string>
<string name="connect_plan_connect_via_link">Σύνδεση μέσω σύνδεσμο;</string>
<string name="contact_already_exists">Επαφή υπάρχει ήδη</string>
<string name="connect_plan_connect_to_yourself">Σύνδεση στον εαυτό σας;</string>
<string name="create_group">Δημιουργία μυστικής ομάδας</string>
<string name="connected_to_desktop">Συνδεδεμένη σε επιφάνεια εργασίας</string>
<string name="group_member_status_creator">δημιουργός</string>
<string name="connection_request_sent">Αίτημα σύνδεσης στάλθηκε!</string>
<string name="connecting_to_desktop">Συνδεμένο σε επιφάνεια εργασίας</string>
<string name="network_session_mode_entity">Σύνδεση</string>
<string name="correct_name_to">Διόρθωση ονόματος σε %s;</string>
<string name="connection_timeout">Λήξη χρονικού ορίου σύνδεσης</string>
<string name="group_member_status_accepted">σύνδεση (αποδεκτή)</string>
<string name="connect_with_contact_name_question">Σύνδεση με %1$s?</string>
<string name="create_your_profile">Δημιουργία του προφίλ σου</string>
<string name="display_name_connecting">συνδέεται…</string>
<string name="create_profile_button">Δημιουργία</string>
<string name="contact_preferences">Προτιμήσεις επαφής</string>
<string name="connected_mobile">Συνδεδεμένο κινητό</string>
<string name="info_row_connection">Σύνδεση</string>
<string name="create_chat_profile">Δημιουργία προφίλ συνομιλίας</string>
<string name="callstate_connecting">συνδέεται…</string>
<string name="desktop_connection_terminated">Σύνδεση τερματίστηκε</string>
<string name="server_connecting">Συνδε</string>
<string name="connect_via_link_incognito">Σύνδεση ανώνυμης περιήγησης</string>
<string name="display_name_connection_established">σύνδεση επετεύχθη</string>
<string name="status_contact_has_no_e2e_encryption">επαφή δεν έχει κρυπτογράφηση από άκρο σε άκρο</string>
<string name="chat_preferences_contact_allows">Επαφή επιτρέπει</string>
<string name="group_member_status_announced">σύνδεση (ανακοινώθηκε)</string>
<string name="icon_descr_server_status_connected">Συνδεδεμένος</string>
<string name="contact_connection_pending">συνδέεται…</string>
<string name="create_group_link">Δημιουργία σύνδεσμο ομάδας</string>
<string name="connect_to_desktop">Σύνδεση σε επιφάνεια εργασίας</string>
<string name="archive_created_on_ts">Δημιουργήθηκε στις %1$s</string>
<string name="connected_to_mobile">Συνδεδεμένο στο κινητό</string>
<string name="connect_via_link">Σύνδεση μέσω σύνδεσμο</string>
<string name="receipts_section_contacts">Επαφές</string>
<string name="smp_server_test_create_file">Δημιουργία αρχείου</string>
<string name="create_secret_group_title">Δημιουργία μυστικής ομάδας</string>
<string name="connection_error">Σφάλμα σύνδεσης</string>
<string name="alert_title_contact_connection_pending">Η επαφή δεν είναι συνδράμει αυτή τη στιγμή!</string>
<string name="rcv_group_event_member_created_contact">Συνδεδεμένο απευθείας</string>
<string name="your_privacy">Η ιδιωτικότητά σας</string>
<string name="contact_sent_large_file">Η επαφή σας έστειλε ένα αρχείο το οποίο είναι μεγαλύτερο από το παρόν υποστηριζόμενο μέγεθος (%1$s).</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Το προφίλ της συνομιλίας σας θα σταλεί στην επαφή σας</string>
<string name="connect_use_new_incognito_profile">Χρήση νέου ανώνυμου προφίλ</string>
<string name="connect_plan_already_connecting">Ήδη συνδέεται</string>
<string name="you_rejected_group_invitation">Απορρίψατε την πρόσκληση της ομάδας</string>
<string name="connect_via_contact_link">Σύνδεση μέσω διεύθυνση επαφής</string>
<string name="connect_use_current_profile">Χρήση του τρέχων προφίλ</string>
<string name="connect_via_link_verb">Σύνδεση</string>
<string name="your_current_profile">Το τρέχον προφίλ σας</string>
<string name="snd_group_event_member_deleted">αφαιρέσατε %1$s</string>
<string name="connect_via_group_link">"Συμμετοχή ομάδας;"</string>
<string name="your_contacts_will_remain_connected">Οι επαφες σας θα παραμένουν συνδεδεμένες.</string>
<string name="trying_to_connect_to_server_to_receive_messages">Προσπάθεια σύνδεσης με τον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν την επαφή.</string>
<string name="profile_will_be_sent_to_contact_sending_link">Το προφίλ σου θα σταλεί στην επαφή από την οποία έλαβες αυτόν τον σύνδεσμο.</string>
<string name="server_error">σφάλμα</string>
<string name="opening_database">Άνοιγμα βάση δεδομένων…</string>
<string name="app_was_crashed">Η προβολή συνετρίβη</string>
<string name="server_connected">συνδεδεμένο</string>
<string name="non_content_uri_alert_text">Κοινοποιήσατε μια μη έγκυρη διαδρομή αρχείου. Αναφέρετε το πρόβλημα στους προγραμματιστές της εφαρμογής.</string>
<string name="non_content_uri_alert_title">Μη έγκυρη διαδρομή αρχείου</string>
<string name="connected_to_server_to_receive_messages_from_contact">Είστε συνδεδεμένοι στον διακομιστή που χρησιμοποιείται για τη λήψη μηνυμάτων από αυτήν την επαφή.</string>
</resources>
@@ -3,8 +3,8 @@
<string name="auth_unavailable">Autenticación no disponible</string>
<string name="accept_contact_button">Aceptar</string>
<string name="network_settings">Configuración avanzada de red</string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Mejor para la batería</b>. Recibirás notificaciones sólo cuando la aplicación se esté ejecutando, SIN servicio en segundo plano.]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Bueno para la batería</b>. El servicio en segundo plano comprueba si hay mensajes cada 10 minutos. Puedes perder llamadas o mensajes urgentes.]]></string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Óptimo para la batería</b>. Recibirás notificaciones sólo cuando la aplicación esté abierta (SIN servicio en segundo plano).]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Bueno para la batería</b>. El servicio en segundo plano comprueba si hay mensajes cada 10 minutos. Podrías no recibir a tiempo llamadas o mensajes urgentes.]]></string>
<string name="accept_call_on_lock_screen">Aceptar</string>
<string name="full_backup">Copia de seguridad de los datos de la aplicación</string>
<string name="chat_item_ttl_day">un dia</string>
@@ -27,7 +27,6 @@
<string name="notifications_mode_service">Siempre activo</string>
<string name="allow_verb">Permitir</string>
<string name="above_then_preposition_continuation">y después:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Añadir nuevo contacto</b>: para crear tu código QR de un solo uso para tu contacto.]]></string>
<string name="accept_connection_request__question">¿Aceptar solicitud de conexión\?</string>
<string name="accept_contact_incognito_button">Aceptar incógnito</string>
<string name="clear_chat_warning">Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse!</string>
@@ -76,12 +75,10 @@
<string name="icon_descr_asked_to_receive">Solicita recibir la imagen</string>
<string name="impossible_to_recover_passphrase"><![CDATA[<b>Atención</b>: NO podrás recuperar o cambiar la contraseña si la pierdes.]]></string>
<string name="both_you_and_your_contact_can_send_voice">Tanto tú como tu contacto podéis enviar mensajes de voz.</string>
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>¡Consume más batería!</b> El servicio en segundo plano se ejecuta siempre y las notificaciones se mostrarán en cuanto haya mensajes disponibles.]]></string>
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>¡Consume más batería!</b> El servicio en segundo plano se ejecuta continuamente y las notificaciones se mostrarán de inmediato.]]></string>
<string name="both_you_and_your_contacts_can_delete">Tanto tú como tu contacto podéis eliminar de forma irreversible los mensajes enviados.</string>
<string name="both_you_and_your_contact_can_send_disappearing">Tanto tú como tu contacto podéis enviar mensajes temporales.</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Escanear código QR</b>: para conectar con tu contacto mediante su código QR.]]></string>
<string name="create_profile_button">Crear</string>
<string name="create_one_time_link">Crea enlace de invitación de un uso</string>
<string name="create_group">Crea grupo secreto</string>
<string name="database_passphrase_will_be_updated">La contraseña de cifrado de la base de datos será actualizada.</string>
<string name="info_row_database_id">ID base de datos</string>
@@ -162,7 +159,7 @@
<string name="icon_descr_server_status_connected">Conectado</string>
<string name="copied">Copiado en portapapeles</string>
<string name="share_one_time_link">Crea enlace de invitación de un uso.</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 PC: escanéa el código QR desde la aplicación mediante <b>Escanear código QR </b>]]></string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 Ordenador: escanéa el código QR desde la aplicación mediante <b>Escanear código QR </b>]]></string>
<string name="delete_contact_menu_action">Eliminar</string>
<string name="delete_group_menu_action">Eliminar</string>
<string name="alert_title_contact_connection_pending">¡El contacto aun no se ha conectado!</string>
@@ -184,13 +181,13 @@
<string name="notification_preview_somebody">Contacto oculto:</string>
<string name="delete_verb">Eliminar</string>
<string name="edit_verb">Editar</string>
<string name="delete_member_message__question">¿Eliminar el mensaje de miembro\?</string>
<string name="delete_member_message__question">¿Eliminar el mensaje del miembro?</string>
<string name="icon_descr_edited">editado</string>
<string name="image_decoding_exception_title">Error de decodificación</string>
<string name="create_address">Crear dirección</string>
<string name="delete_address">Eliminar dirección</string>
<string name="delete_address__question">¿Eliminar la dirección\?</string>
<string name="display_name__field">Nombre mostrado:</string>
<string name="display_name__field">Nombre del perfil:</string>
<string name="callstate_connecting">conectando…</string>
<string name="decentralized">Descentralizada</string>
<string name="database_will_be_encrypted">La base de datos será cifrada.</string>
@@ -413,7 +410,7 @@
<string name="section_title_for_console">PARA CONSOLA</string>
<string name="error_changing_role">Error al cambiar rol</string>
<string name="conn_stats_section_title_servers">SERVIDORES</string>
<string name="group_display_name_field">Introduce un nombre para el grupo:</string>
<string name="group_display_name_field">Nombre del grupo:</string>
<string name="group_preferences">Preferencias de grupo</string>
<string name="group_members_can_send_dms">Los miembros del grupo pueden enviar mensajes directos.</string>
<string name="group_members_can_delete">Los miembros del grupo pueden eliminar mensajes de forma irreversible.</string>
@@ -452,7 +449,7 @@
<string name="network_settings_title">Configuración de red</string>
<string name="network_use_onion_hosts_no_desc_in_alert">No se usarán hosts .onion</string>
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Sólo los dispositivos cliente almacenan perfiles de usuario, contactos, grupos y mensajes enviados con <b>cifrado de extremo a extremo de 2 capas</b> .]]></string>
<string name="onboarding_notifications_mode_subtitle">Puede cambiarse más tarde en Configuración.</string>
<string name="onboarding_notifications_mode_subtitle">Puedes cambiar estos ajustes más tarde en Configuración.</string>
<string name="onboarding_notifications_mode_service">Instantánea</string>
<string name="join_group_button">Únete</string>
<string name="join_group_incognito_button">Únete en modo incógnito</string>
@@ -499,7 +496,7 @@
<string name="invalid_message_format">formato de mensaje no válido</string>
<string name="live">EN VIVO</string>
<string name="moderated_description">eliminado por el moderador</string>
<string name="display_name_invited_to_connect">invitado a conectarse</string>
<string name="display_name_invited_to_connect">invitación a conectarse</string>
<string name="service_notifications_disabled">¡Las notificaciones instantáneas están desactivadas!</string>
<string name="notification_preview_new_message">mensaje nuevo</string>
<string name="notification_new_contact_request">Nueva solicitud de contacto</string>
@@ -553,7 +550,7 @@
<string name="group_member_status_removed">expulsado</string>
<string name="group_member_status_invited">ha sido invitado</string>
<string name="no_contacts_to_add">Sin contactos que añadir</string>
<string name="new_member_role">Nuevo rol de miembro</string>
<string name="new_member_role">Nuevo rol del miembro</string>
<string name="initial_member_role">Rol inicial</string>
<string name="info_row_local_name">Nombre local</string>
<string name="network_status">Estado de la red</string>
@@ -713,7 +710,7 @@
<string name="save_and_notify_group_members">Guardar y notificar a los miembros del grupo</string>
<string name="connection_error_auth_desc">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.</string>
<string name="periodic_notifications_desc">La aplicación recoge nuevos mensajes periódicamente por lo que usa un pequeño porcentaje de la batería al día. La aplicación no hace uso de notificaciones automáticas: los datos de tu dispositivo no se envían a los servidores.</string>
<string name="periodic_notifications_desc">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.</string>
<string name="la_notice_title_simplex_lock">Bloqueo SimpleX</string>
<string name="auth_unlock">Desbloquear</string>
<string name="this_text_is_available_in_settings">Este texto está disponible en Configuración</string>
@@ -722,7 +719,7 @@
<string name="using_simplex_chat_servers">Usando servidores SimpleX Chat.</string>
<string name="network_session_mode_transport_isolation">Aislamiento de transporte</string>
<string name="strikethrough_text">tachado</string>
<string name="use_chat">Usar Chat</string>
<string name="use_chat">Abrir SimpleX</string>
<string name="settings_section_title_socks">PROXY SOCKS</string>
<string name="settings_section_title_themes">TEMAS</string>
<string name="stop_chat_confirmation">Detener</string>
@@ -764,8 +761,8 @@
<string name="add_contact_or_create_group">Inciar chat nuevo</string>
<string name="stop_chat_to_export_import_or_delete_chat_database">Para exportar, importar o eliminar la base de datos debes detener Chat. Durante la parada no podrás recibir o enviar mensajes.</string>
<string name="thank_you_for_installing_simplex">Gracias por instalar SimpleX Chat!</string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">Para proteger la 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.</string>
<string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Para proteger tu información, activa Bloqueo SimpleX.
<string name="to_protect_privacy_simplex_has_ids_for_queues">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.</string>
<string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_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.</string>
<string name="updating_settings_will_reconnect_client_to_all_servers">Al actualizar la configuración, el cliente se reconectará a todos los servidores.</string>
<string name="use_simplex_chat_servers__question">¿Usar servidores SimpleX Chat\?</string>
@@ -838,7 +835,7 @@
<string name="chat_help_tap_button">Pulsa el botón</string>
<string name="to_start_a_new_chat_help_header">Para iniciar un chat nuevo</string>
<string name="switch_receiving_address">Cambiar servidor de recepción</string>
<string name="group_is_decentralized">Completamente descentralizado: sólo visible para los miembros.</string>
<string name="group_is_decentralized">Completamente descentralizado: sólo visible a los miembros.</string>
<string name="to_connect_via_link_title">Para conectarte mediante enlace</string>
<string name="smp_servers_test_failed">¡Error en prueba del servidor!</string>
<string name="smp_servers_test_some_failed">Algunos servidores no superaron la prueba:</string>
@@ -883,7 +880,7 @@
<string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Tu contacto debe estar en línea para que se complete la conexión.
\nPuedes cancelar esta conexión y eliminar el contacto (e intentarlo más tarde con un enlace nuevo).</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">La base de datos actual será ELIMINADA y SUSTITUIDA por la importada.
\nEsta acción no se puede deshacer. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente.</string>
\nEsta acción no podrá deshacerse. Tu perfil, contactos, mensajes y archivos actuales se perderán irreversiblemente.</string>
<string name="incognito_random_profile">Tu perfil aleatorio</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Te conectarás cuando tu solicitud se acepte, por favor espera o compruébalo más tarde.</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">Te conectarás cuando el dispositivo de tu contacto esté en línea, por favor espera o compruébalo más tarde.</string>
@@ -936,12 +933,12 @@
<string name="chat_preferences_you_allow">Permites</string>
<string name="app_name">SimpleX</string>
<string name="profile_will_be_sent_to_contact_sending_link">Tu perfil se enviará al contacto del que has recibido este enlace.</string>
<string name="you_will_join_group">Te unirás al grupo al que hace referencia este enlace y te conectarás con sus miembros.</string>
<string name="you_will_join_group">Te conectarás con todos los miembros del grupo.</string>
<string name="sender_you_pronoun">tú</string>
<string name="connected_to_server_to_receive_messages_from_contact">Estás conectado al servidor usado para recibir mensajes de este contacto.</string>
<string name="description_via_contact_address_link">mediante enlace de dirección de contacto</string>
<string name="description_via_group_link">mediante enlace de grupo</string>
<string name="description_you_shared_one_time_link">has compartido enlace de un solo uso</string>
<string name="description_you_shared_one_time_link">enlace de un solo uso</string>
<string name="description_you_shared_one_time_link_incognito">has compartido enlace de un solo uso en módo incógnito</string>
<string name="you_have_no_chats">No tienes chats</string>
<string name="contact_sent_large_file">El contacto ha enviado un archivo mayor al máximo admitido (%1$s ).</string>
@@ -1002,7 +999,7 @@
<string name="you_can_hide_or_mute_user_profile">Puedes ocultar o silenciar un perfil. Mantenlo pulsado para abrir el menú.</string>
<string name="you_will_still_receive_calls_and_ntfs">Seguirás recibiendo llamadas y notificaciones de los perfiles silenciados cuando estén activos.</string>
<string name="v4_6_group_moderation_descr">Ahora los administradores pueden:
\n- borrar mensajes de los miembros.
\n- eliminar mensajes de los miembros.
\n- desactivar el rol miembro (a rol \"observador\")</string>
<string name="to_reveal_profile_enter_password">Para hacer visible tu perfil oculto, introduce la contraseña completa en el campo de búsqueda del menú Mis perfiles.</string>
<string name="database_upgrade">Actualización de la base de datos</string>
@@ -1125,7 +1122,7 @@
<string name="learn_more">Más información</string>
<string name="if_you_cant_meet_in_person">Si no puedes reunirte en persona, **muestra el código QR por videollamada**, o comparte el enlace.</string>
<string name="scan_qr_to_connect_to_contact">Para conectarse, tu contacto puede escanear el código QR o usar el enlace en la aplicación.</string>
<string name="create_simplex_address">Crear tu dirección SimpleX</string>
<string name="create_simplex_address">Crear dirección SimpleX</string>
<string name="auto_accept_contact">Auto aceptar</string>
<string name="group_welcome_preview">Vista previa</string>
<string name="opening_database">Abriendo base de datos…</string>
@@ -1328,7 +1325,7 @@
<string name="v5_2_message_delivery_receipts">¡Confirmación de entrega de mensajes!</string>
<string name="v5_2_more_things_descr">- entrega de mensajes más estable.
\n- grupos un poco mejores.
\n- ¡y más!</string>
\n- ¡...y más!</string>
<string name="sending_delivery_receipts_will_be_enabled">El envío de confirmaciones de entrega se activará para todos los contactos.</string>
<string name="v5_2_message_delivery_receipts_descr">¡El doble check que nos faltaba! ✅</string>
<string name="you_can_enable_delivery_receipts_later">Puedes activar más tarde en Configuración</string>
@@ -1391,14 +1388,14 @@
<string name="error_creating_member_contact">Error al establecer contacto con el miembro</string>
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Atención</b>: los servidores de retransmisión están conectados mediante SOCKS proxy. Las llamadas y las previsualizaciones de enlaces usan conexión directa.]]></string>
<string name="encrypt_local_files">Cifra archivos locales</string>
<string name="v5_3_new_desktop_app">Nueva aplicación para PC!</string>
<string name="v5_3_new_desktop_app">Nueva aplicación para ordenador!</string>
<string name="v5_3_new_interface_languages">6 idiomas nuevos para el interfaz</string>
<string name="v5_3_encrypt_local_files_descr">Cifrado de los nuevos archivos locales (excepto vídeos).</string>
<string name="compose_send_direct_message_to_connect">Enviar mensaje directo para conectar</string>
<string name="v5_3_discover_join_groups">Descubre y únete a grupos</string>
<string name="v5_3_simpler_incognito_mode">Modo incógnito simplificado</string>
<string name="v5_3_new_interface_languages_descr">Árabe, Búlgaro, Finlandés, Hebreo, Tailandés y Ucraniano - gracias a los usuarios y Weblate.</string>
<string name="v5_3_new_desktop_app_descr">Crea perfil nuevo en la aplicación para PC. 💻</string>
<string name="v5_3_new_desktop_app_descr">Crea perfil nuevo en la aplicación para ordenador. 💻</string>
<string name="error_sending_message_contact_invitation">Error al enviar invitación</string>
<string name="v5_3_simpler_incognito_mode_descr">Activa incógnito al conectar.</string>
<string name="v5_3_discover_join_groups_descr">- conexión al servicio de directorio (BETA)!
@@ -1407,62 +1404,130 @@
<string name="member_contact_send_direct_message">Enviar mensaje directo</string>
<string name="rcv_group_event_member_created_contact">conectado directamente</string>
<string name="expand_verb">Expandir</string>
<string name="encryption_renegotiation_error">Error en renegociación de cifrado</string>
<string name="encryption_renegotiation_error">Error de renegociación de cifrado</string>
<string name="rcv_direct_event_contact_deleted">contacto eliminado</string>
<string name="error_alert_title">Error</string>
<string name="create_group_button">Crear grupo</string>
<string name="create_another_profile_button">Crear perfil</string>
<string name="connected_desktop">Escritorio conectado</string>
<string name="connected_desktop">Ordenador conectado</string>
<string name="new_mobile_device">Nuevo dispositivo móvil</string>
<string name="desktop_address">Dirección desktop</string>
<string name="desktop_address">Dirección ordenador</string>
<string name="only_one_device_can_work_at_the_same_time">Sólo un dispositivo puede funcionar al mismo tiempo</string>
<string name="connect_plan_join_your_group">¿Unirse a tu grupo?</string>
<string name="marked_deleted_items_description">%d mensajes marcados como borrados</string>
<string name="marked_deleted_items_description">%d mensaje(s) marcado(s) eliminado(s)</string>
<string name="connect_plan_group_already_exists">¡El grupo ya existe!</string>
<string name="connect_plan_already_connecting">¡Ya está en proceso de conexión!</string>
<string name="connect_plan_already_connecting">¡Ya en proceso de conexión!</string>
<string name="desktop_incompatible_version">Versión incompatible</string>
<string name="new_desktop"><![CDATA[<i>(nuevo)</i>]]></string>
<string name="linked_desktop_options">Opciones escritorio enlazado</string>
<string name="linked_desktops">Desktop enlazados</string>
<string name="discover_on_network">Descubrir en red</string>
<string name="rcv_group_and_other_events">, y hay %d eventos más</string>
<string name="linked_desktop_options">Opciones ordenador enlazado</string>
<string name="linked_desktops">Ordenadores enlazados</string>
<string name="discover_on_network">Descubrir en red local</string>
<string name="rcv_group_and_other_events">y %d evento(s) más</string>
<string name="connect_plan_connect_via_link">¿Conectar vía enlace?</string>
<string name="connect_plan_already_joining_the_group">¡Ya está en proceso de unirse al grupo!</string>
<string name="moderated_items_description">%d mensajes moderados por %s</string>
<string name="connect_plan_already_joining_the_group">¡En proceso de unirte al grupo!</string>
<string name="moderated_items_description">%d mensaje(s) moderado(s) por %s</string>
<string name="connect_plan_connect_to_yourself">¿Conectarte a tí mismo?</string>
<string name="linked_mobiles">Móviles enlazados</string>
<string name="desktop_device">Desktop</string>
<string name="connected_to_desktop">Conectado al escritorio</string>
<string name="desktop_device">Ordenador</string>
<string name="connected_to_desktop">Conectado con ordenador</string>
<string name="loading_remote_file_title">Cargando archivo</string>
<string name="connecting_to_desktop">Conectando a desktop</string>
<string name="alert_text_encryption_renegotiation_failed">La renegociación de cifrado ha fallado.</string>
<string name="desktop_devices">Dispositivo desktop</string>
<string name="connecting_to_desktop">Conectando con ordenador</string>
<string name="alert_text_encryption_renegotiation_failed">Renegociación de cifrado fallida.</string>
<string name="desktop_devices">Ordenadores</string>
<string name="correct_name_to">¿Corregir el nombre a %s?</string>
<string name="delete_messages__question">Elimina %d mensajes?</string>
<string name="link_a_mobile">Enlazar móvil</string>
<string name="connect_with_contact_name_question">¿Conectar con %1$s?</string>
<string name="block_member_confirmation">Bloquear</string>
<string name="blocked_items_description">%d mensajes bloqueados</string>
<string name="blocked_items_description">%d mensaje(s) bloqueado(s)</string>
<string name="block_member_button">Bloquear miembro</string>
<string name="connected_mobile">Móvil conectado</string>
<string name="delete_and_notify_contact">Eliminar y notificar contacto</string>
<string name="connect_plan_open_group">Grupo abierto</string>
<string name="desktop_connection_terminated">Conexión terminada</string>
<string name="desktop_connection_terminated">Conexión finalizada</string>
<string name="this_device_version"><![CDATA[<i>(este dispositivo v%s)</i>]]></string>
<string name="unblock_member_desc">¡Los mensajes de %s serán mostrados!</string>
<string name="enter_this_device_name">Introduce el nombre de este dispositivo…</string>
<string name="enter_this_device_name">Nombre de este dispositivo…</string>
<string name="error">Error</string>
<string name="connect_to_desktop">Conectar a desktop</string>
<string name="connect_to_desktop">Conectar con ordenador</string>
<string name="disconnect_remote_host">Desconectar</string>
<string name="block_member_question">Bloquear miembro?</string>
<string name="rcv_group_events_count">%d eventos de grupo</string>
<string name="block_member_question">¿Bloquear miembro?</string>
<string name="rcv_group_events_count">%d evento(s) de grupo</string>
<string name="invalid_name">¡Nombre no válido!</string>
<string name="connected_to_mobile">Conectado a móvil</string>
<string name="bad_desktop_address">Dirección de escritorio incorrecta</string>
<string name="bad_desktop_address">Dirección ordenador incorrecta</string>
<string name="devices">Dispositivo</string>
<string name="non_content_uri_alert_title">Ruta archivo no valida.</string>
<string name="disconnect_desktop_question">¿Desconectar desktop?</string>
<string name="block_member_desc">Los mensajes nuevos de %s estarán ocultos!</string>
<string name="desktop_app_version_is_incompatible">La versión de aplicación del desktop %s no es compatible con esta aplicación.</string>
<string name="disconnect_desktop_question">¿Desconectar ordenador?</string>
<string name="block_member_desc">¡Los mensajes nuevos de %s estarán ocultos!</string>
<string name="desktop_app_version_is_incompatible">La versión de aplicación del ordenador %s no es compatible con esta aplicación.</string>
<string name="blocked_item_description">bloqueado</string>
<string name="v5_4_block_group_members">Bloquear miembros del grupo</string>
<string name="connect_plan_repeat_connection_request">¿Repetir solicitud de conexión?</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Ya estás conectando con %1$s.</string>
<string name="v5_4_incognito_groups_descr">Crear grupo usando perfil aleatorio.</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Ya estás uniéndote al grupo mediante este enlace.</string>
<string name="refresh_qr_code">Recargar</string>
<string name="group_members_2">%s y %s</string>
<string name="multicast_connect_automatically">Conectar automáticamente</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Ya estás uniéndote al grupo %1$s.</string>
<string name="v5_4_link_mobile_desktop">¡Enlazar aplicación móvil con ordenador!</string>
<string name="connect_plan_this_is_your_own_one_time_link">¡Este es tu propio enlace de un solo uso!</string>
<string name="v5_4_link_mobile_desktop_descr">Mediante protocolo seguro de resistencia cuántica.</string>
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[Abre <i>Usar desde ordenador</i> en la aplicación móvil y escanea el código QR.]]></string>
<string name="v5_4_block_group_members_descr">Para ocultar mensajes no deseados.</string>
<string name="unlink_desktop_question">¿Desenlazar ordenador?</string>
<string name="v5_4_better_groups">Grupos mejorados</string>
<string name="video_decoding_exception_desc">El vídeo no puede ser decodificado. Por favor, prueba con otro vídeo o contacta con los desarrolladores.</string>
<string name="rcv_group_event_1_member_connected">%s conectado</string>
<string name="random_port">Aleatorio</string>
<string name="v5_4_incognito_groups">Grupos incógnito</string>
<string name="group_members_n">%s, %s y %d miembros</string>
<string name="this_device">Este dispositivo</string>
<string name="unblock_member_button">Desbloquear miembro</string>
<string name="remote_host_was_disconnected_toast"><![CDATA[Móvil <b>%s</b> desconectado]]></string>
<string name="waiting_for_desktop">Esperando ordenador…</string>
<string name="v5_4_better_groups_descr">Mensajería más segura y conexión más rápida.</string>
<string name="contact_tap_to_connect">Pulsa para conectar</string>
<string name="this_device_name">Nombre del dispositivo</string>
<string name="connect_plan_you_are_already_in_group_vName">Ya estás en el grupo %1$s.</string>
<string name="connect_plan_this_is_your_own_simplex_address">¡Esta es tu propia dirección SimpleX!</string>
<string name="found_desktop">Ordenador encontrado</string>
<string name="not_compatible">¡No compatible!</string>
<string name="waiting_for_mobile_to_connect">Esperando conexión móvil:</string>
<string name="remove_member_button">Eliminar miembro</string>
<string name="unblock_member_question">¿Desbloquear miembro?</string>
<string name="open_port_in_firewall_desc">Para permitir que la aplicación móvil se conecte al ordenador, abre este puerto en el firewall si está habilitado</string>
<string name="settings_section_title_use_from_desktop">Usar desde ordenador</string>
<string name="session_code">Código de sesión</string>
<string name="connect_plan_repeat_join_request">¿Repetir solicitud de admisión?</string>
<string name="create_chat_profile">Crear perfil de chat</string>
<string name="button_remove_member_question">¿Eliminar miembro?</string>
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">¡Ya estás conectando mediante este enlace de un solo uso!</string>
<string name="unlink_desktop">Desenlazar</string>
<string name="this_device_name_shared_with_mobile">El nombre del dispositivo será compartido con el cliente móvil conectado.</string>
<string name="verify_code_on_mobile">Verificar código en móvil</string>
<string name="open_port_in_firewall_title">Abrir puerto en firewall</string>
<string name="non_content_uri_alert_text">Has compartido una ruta no válida. Informa a los desarrolladores del problema.</string>
<string name="disconnect_remote_hosts">Desconectar móviles</string>
<string name="group_member_role_author">autor</string>
<string name="paste_desktop_address">Pegar dirección de ordenador</string>
<string name="connect_plan_this_is_your_link_for_group_vName">¡Este es tu enlace para el grupo %1$s!</string>
<string name="verify_code_with_desktop">Verificar código con ordenador</string>
<string name="scan_qr_code_from_desktop">Escanear código QR desde ordenador</string>
<string name="unblock_member_confirmation">Desbloquear</string>
<string name="multicast_discoverable_via_local_network">Detectable mediante red local</string>
<string name="v5_4_more_things_descr">- notificar opcionalmente a los contactos eliminados.
\n- nombres de perfil con espacios.
\n- ¡...y más!</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">¡Ya has solicitado la conexión mediante esta dirección!</string>
<string name="terminal_always_visible">Mostrar consola en ventana nueva</string>
<string name="scan_from_mobile">Escáner desde móvil</string>
<string name="verify_connections">Verificar conexiones</string>
<string name="loading_remote_file_desc">Por favor, espere mientras el archivo se carga desde el móvil enlazado</string>
<string name="verify_connection">Verificar conexión</string>
<string name="no_connected_mobile">Ningún móvil conectado</string>
<string name="app_was_crashed">Error aplicación</string>
<string name="error_showing_content">error mostrando contenido</string>
<string name="error_showing_message">error mostrando mensaje</string>
</resources>
@@ -188,7 +188,6 @@
<string name="allow_your_contacts_to_send_disappearing_messages">Salli kontaktiesi lähettää katoavia viestejä.</string>
<string name="timed_messages">Katoavat viestit</string>
<string name="icon_descr_context">Kontekstikuvake</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Skannaa QR-koodi</b>: muodostaaksesi yhteyden kontaktiisi, joka näyttää QR-koodin sinulle.]]></string>
<string name="icon_descr_cancel_live_message">Peruuta live-viesti</string>
<string name="configure_ICE_servers">Määritä ICE-palvelimet</string>
<string name="add_address_to_your_profile">Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi.</string>
@@ -231,7 +230,6 @@
<string name="clear_verb">Tyhjennä</string>
<string name="clear_chat_button">Tyhjennä keskustelu</string>
<string name="clear_chat_question">Tyhjennä keskustelu\?</string>
<string name="create_one_time_link">Luo kertaluonteinen kutsulinkki</string>
<string name="app_version_name">Sovellusversio: v%s</string>
<string name="callstatus_calling">soittaa…</string>
<string name="delete_chat_profile">Poista keskusteluprofiili</string>
@@ -271,7 +269,6 @@
<string name="icon_descr_server_status_disconnected">Katkaistu</string>
<string name="back">Takaisin</string>
<string name="connect_via_link_or_qr">Yhdistä linkillä / QR-koodilla</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Lisää uusi kontakti</b>: luo kertakäyttöinen QR-koodi kontaktille.]]></string>
<string name="clear_chat_menu_action">Tyhjennä</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 työpöytä: skannaa esitetty QR-koodi sovelluksesta käyttämällä <b>Skannaa QR-koodi</b>.]]></string>
<string name="delete_pending_connection__question">Poistetaanko odottava yhteys\?</string>
@@ -206,7 +206,6 @@
<string name="gallery_video_button">Vidéo</string>
<string name="to_start_a_new_chat_help_header">Pour démarrer une nouvelle discussion</string>
<string name="chat_help_tap_button">Appuyez sur le bouton</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Scanner un code QR</b> : pour vous connecter à votre contact qui vous montre un code QR.]]></string>
<string name="to_connect_via_link_title">Pour se connecter via un lien</string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Si vous avez reçu un lien d\'invitation SimpleX Chat, vous pouvez l\'ouvrir dans votre navigateur :</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 bureau : scanner le code QR affiché depuis l\'app, via <b>Scanner le code QR</b>.]]></string>
@@ -246,7 +245,6 @@
<string name="paste_button">Coller</string>
<string name="this_string_is_not_a_connection_link">Cette chaîne n\'est pas un lien de connexion !</string>
<string name="you_can_also_connect_by_clicking_the_link"><![CDATA[Vous pouvez aussi vous connecter en cliquant sur le lien. Si il s\'ouvre dans le navigateur, cliquez sur <b>Ouvrir dans l\'app mobile</b>.]]></string>
<string name="create_one_time_link">Créer un lien d\'invitation unique</string>
<string name="text_field_set_contact_placeholder">Définir le nom du contact…</string>
<string name="icon_descr_server_status_disconnected">Déconnecté</string>
<string name="icon_descr_server_status_error">Erreur</string>
@@ -296,7 +294,6 @@
<string name="thank_you_for_installing_simplex">Merci d\'avoir installé SimpleX Chat !</string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Vous pouvez <font color="#0088ff">vous connecter aux développeurs de SimpleX Chat pour leur poser des questions et recevoir des réponses :</font>.]]></string>
<string name="above_then_preposition_continuation">ci-dessus, puis :</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Ajouter un nouveau contact</b> : afin de créer un code QR à usage unique pour votre contact.]]></string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Si vous choisissez de la rejeter, l\'expéditeur·rice NE sera PAS notifié·e.</string>
<string name="accept_contact_button">Accepter</string>
<string name="mute_chat">Muet</string>
@@ -901,8 +898,8 @@
<string name="invalid_data">données invalides</string>
<string name="invalid_chat">chat invalide</string>
<string name="icon_descr_cancel_live_message">Annuler le message dynamique</string>
<string name="feature_offered_item">offert %s</string>
<string name="feature_offered_item_with_param">offert %s: %2s</string>
<string name="feature_offered_item">propose %s</string>
<string name="feature_offered_item_with_param">propose %s : %2s</string>
<string name="feature_cancelled_item">annulé %s</string>
<string name="app_version_title">Version de l\'app</string>
<string name="core_simplexmq_version">simplexmq : v%s (%2s)</string>
@@ -1407,14 +1404,14 @@
<string name="expand_verb">Développer</string>
<string name="connect_plan_repeat_connection_request">Répéter la demande de connexion ?</string>
<string name="rcv_direct_event_contact_deleted">contact supprimé</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Vous êtes déjà connecté(e) à %1$s.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Vous êtes déjà connecté(e) à <b>%1$s</b>.]]></string>
<string name="error_alert_title">Erreur</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Vous êtes déjà en train de rejoindre le groupe via ce lien.</string>
<string name="create_group_button">Créer un groupe</string>
<string name="create_another_profile_button">Créer le profil</string>
<string name="group_members_2">%s et %s</string>
<string name="connect_plan_join_your_group">Rejoindre votre groupe ?</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Vous êtes déjà en train de rejoindre le groupe %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Vous êtes déjà en train de rejoindre le groupe <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">Voici votre propre lien unique !</string>
<string name="marked_deleted_items_description">%d messages marqués comme supprimés</string>
<string name="connect_plan_group_already_exists">Ce groupe existe déjà !</string>
@@ -1429,7 +1426,7 @@
<string name="unblock_member_button">Débloquer ce membre</string>
<string name="connect_plan_connect_to_yourself">Se connecter à soi-même ?</string>
<string name="contact_tap_to_connect">Tapez pour vous connecter</string>
<string name="connect_plan_you_are_already_in_group_vName">Vous êtes déjà dans le groupe %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Vous êtes déjà dans le groupe <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">Voici votre propre adresse SimpleX !</string>
<string name="correct_name_to">Corriger le nom pour %s ?</string>
<string name="delete_messages__question">Supprimer %d messages ?</string>
@@ -1450,7 +1447,7 @@
<string name="block_member_question">Bloquer ce membre ?</string>
<string name="rcv_group_events_count">%d événements de groupe</string>
<string name="invalid_name">Nom invalide !</string>
<string name="connect_plan_this_is_your_link_for_group_vName">Voici votre lien pour le groupe %1$s !</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Voici votre lien pour le groupe <b>%1$s</b> !]]></string>
<string name="unblock_member_confirmation">Débloquer</string>
<string name="non_content_uri_alert_title">Chemin du fichier invalide</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">Vous avez déjà demandé une connexion via cette adresse !</string>
@@ -1522,4 +1519,14 @@
<string name="found_desktop">Bureau trouvé</string>
<string name="not_compatible">Non compatible !</string>
<string name="multicast_discoverable_via_local_network">Accessible via le réseau local</string>
<string name="refresh_qr_code">Rafraîchir</string>
<string name="create_chat_profile">Créer un profil de chat</string>
<string name="no_connected_mobile">Pas de mobile connecté</string>
<string name="disconnect_remote_hosts">Déconnecter les mobiles</string>
<string name="random_port">Aléatoire</string>
<string name="open_port_in_firewall_desc">Afin de permettre à une application mobile de se connecter à votre ordinateur, ouvrez ce port dans votre pare-feu, si vous l\'avez activé</string>
<string name="app_was_crashed">Crash d\'aperçu</string>
<string name="open_port_in_firewall_title">Ouvrir le port de votre pare-feu</string>
<string name="error_showing_content">erreur d\'affichage de contenu</string>
<string name="error_showing_message">erreur d\'affichage de message</string>
</resources>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M512-491.5V-663q0-11.925-8.288-20.213-8.287-8.287-20.212-8.287-12.5 0-20.75 8.287-8.25 8.288-8.25 20.213v183q0 5.832 2 10.675 2 4.842 5.543 9.325l143.572 148.538Q614-301.5 627-302q13-.5 22-9.5 8.5-8.5 8.5-21t-8.458-21.458L512-491.5ZM480.064-85q-80.971 0-153.129-31.263-72.159-31.263-125.797-85Q147.5-255 116.25-327.023 85-399.046 85-479.936q0-80.971 31.263-153.129 31.263-72.159 85-125.797Q255-812.5 327.023-843.75 399.046-875 479.936-875q80.971 0 153.129 31.263 72.159 31.263 125.797 85Q812.5-705 843.75-632.977 875-560.954 875-480.064q0 80.971-31.263 153.129-31.263 72.159-85 125.797Q705-147.5 632.977-116.25 560.954-85 480.064-85ZM480-480Zm-.25 337.5q138.75 0 238.25-99.25t99.5-238q0-138.75-99.48-238.25-99.481-99.5-238.02-99.5-139 0-238.25 99.48Q142.5-618.539 142.5-480q0 139 99.25 238.25t238 99.25Z"/></svg>

After

Width:  |  Height:  |  Size: 909 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M512-491.5V-663q0-11.925-8.288-20.213-8.287-8.287-20.212-8.287-12.5 0-20.75 8.287-8.25 8.288-8.25 20.213v183q0 5.832 2 10.675 2 4.842 5.543 9.325l143.572 148.538Q614-301.5 627-302q13-.5 22-9.5 8.5-8.5 8.5-21t-8.458-21.458L512-491.5ZM480.064-85q-80.971 0-153.129-31.263-72.159-31.263-125.797-85Q147.5-255 116.25-327.023 85-399.046 85-479.936q0-80.971 31.263-153.129 31.263-72.159 85-125.797Q255-812.5 327.023-843.75 399.046-875 479.936-875q80.971 0 153.129 31.263 72.159 31.263 125.797 85Q812.5-705 843.75-632.977 875-560.954 875-480.064q0 80.971-31.263 153.129-31.263 72.159-85 125.797Q705-147.5 632.977-116.25 560.954-85 480.064-85Z"/></svg>

After

Width:  |  Height:  |  Size: 739 B

@@ -41,7 +41,7 @@
<string name="description_via_contact_address_link_incognito">incognito via link indirizzo del contatto</string>
<string name="description_via_one_time_link">via link una tantum</string>
<string name="description_via_one_time_link_incognito">incognito via link una tantum</string>
<string name="simplex_link_contact">Indirizzo del contatto SimpleX</string>
<string name="simplex_link_contact">Indirizzo di contatto SimpleX</string>
<string name="simplex_link_invitation">Invito SimpleX una tantum</string>
<string name="simplex_link_group">Link gruppo SimpleX</string>
<string name="simplex_link_mode_full">Link completo</string>
@@ -292,8 +292,6 @@
<string name="delete_group_menu_action">Elimina</string>
<string name="direct_messages_are_prohibited_in_chat">I messaggi diretti tra i membri sono vietati in questo gruppo.</string>
<string name="display_name">Inserisci il tuo nome:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Aggiungi un contatto</b>: per creare il tuo codice QR una tantum per il tuo contatto.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Scansiona codice QR</b>: per connetterti al contatto che ti mostra il codice QR.]]></string>
<string name="choose_file">File</string>
<string name="clear_chat_button">Svuota chat</string>
<string name="clear_chat_question">Svuotare la chat\?</string>
@@ -319,7 +317,6 @@
<string name="clear_verification">Annulla la verifica</string>
<string name="connect_button">Connetti</string>
<string name="connect_via_link">Connetti via link</string>
<string name="create_one_time_link">Crea link di invito una tantum</string>
<string name="database_passphrase_and_export">Password del database ed esportazione</string>
<string name="smp_servers_enter_manually">Inserisci il server manualmente</string>
<string name="how_to_use_simplex_chat">Come si usa</string>
@@ -508,7 +505,7 @@
<string name="keychain_allows_to_receive_ntfs">Android Keystore verrà usato per memorizzare in modo sicuro la password dopo il riavvio dell\'app o la modifica della password; consentirà di ricevere le notifiche.</string>
<string name="impossible_to_recover_passphrase"><![CDATA[<b>Nota bene</b>: NON potrai recuperare o cambiare la password se la perdi.]]></string>
<string name="change_database_passphrase_question">Cambiare password del database\?</string>
<string name="confirm_new_passphrase">Conferma password nuova…</string>
<string name="confirm_new_passphrase">Conferma nuova password…</string>
<string name="current_passphrase">Password attuale…</string>
<string name="database_encrypted">Database crittografato!</string>
<string name="database_passphrase_will_be_updated">La password di crittografia del database verrà aggiornata.</string>
@@ -623,7 +620,7 @@
<string name="save_servers_button">Salva</string>
<string name="saved_ICE_servers_will_be_removed">I server WebRTC ICE salvati verranno rimossi.</string>
<string name="share_link">Condividi link</string>
<string name="star_on_github">Stella su GitHub</string>
<string name="star_on_github">Dai una stella su GitHub</string>
<string name="update_onion_hosts_settings_question">Aggiornare l\'impostazione degli host .onion\?</string>
<string name="network_disable_socks">Usare una connessione internet diretta\?</string>
<string name="network_use_onion_hosts">Usa gli host .onion</string>
@@ -775,7 +772,7 @@
<string name="group_invitation_tap_to_join">Tocca per entrare</string>
<string name="group_invitation_tap_to_join_incognito">Toccare per entrare in incognito</string>
<string name="alert_message_no_group">Questo gruppo non esiste più.</string>
<string name="rcv_group_event_updated_group_profile">profilo del gruppo aggiornato</string>
<string name="rcv_group_event_updated_group_profile">ha aggiornato il profilo del gruppo</string>
<string name="you_are_invited_to_group">Sei stato/a invitato/a al gruppo</string>
<string name="snd_conn_event_switch_queue_phase_completed">hai cambiato indirizzo</string>
<string name="snd_conn_event_switch_queue_phase_completed_for_member">hai cambiato l\'indirizzo per %s</string>
@@ -1368,9 +1365,9 @@
<string name="system_restricted_background_desc">SimpleX non può funzionare in secondo piano. Riceverai le notifiche solo quando l\'app è aperta.</string>
<string name="system_restricted_background_warn"><![CDATA[Per attivare le notifiche, scegli <b>Utilizzo batteria dell\'app</b> / <b>Senza restrizioni</b> nelle impostazioni.]]></string>
<string name="system_restricted_background_in_call_warn"><![CDATA[Per effettuare chiamate in secondo piano, scegli <b>Utilizzo batteria dell\'app</b> / <b>Senza restrizioni</b> nelle impostazioni.]]></string>
<string name="rcv_group_event_2_members_connected">%s e %s sono connessi/e</string>
<string name="rcv_group_event_n_members_connected">%s, %s e altri %d membri sono connessi</string>
<string name="rcv_group_event_3_members_connected">%s, %s e %s sono connessi/e</string>
<string name="rcv_group_event_2_members_connected">%s e %s si sono connessi/e</string>
<string name="rcv_group_event_n_members_connected">%s, %s e altri %d membri si sono connessi</string>
<string name="rcv_group_event_3_members_connected">%s, %s e %s si sono connessi/e</string>
<string name="privacy_message_draft">Bozza</string>
<string name="privacy_show_last_messages">Mostra gli ultimi messaggi</string>
<string name="database_will_be_encrypted_and_passphrase_stored_in_settings">Il database verrà crittografato e la password conservata nelle impostazioni.</string>
@@ -1380,7 +1377,7 @@
<string name="remove_passphrase_from_settings">Rimuovere la password dalle impostazioni\?</string>
<string name="use_random_passphrase">Usa password casuale</string>
<string name="save_passphrase_in_settings">Salva password nelle impostazioni</string>
<string name="setup_database_passphrase">Configura password del database</string>
<string name="setup_database_passphrase">Configura la password del database</string>
<string name="set_database_passphrase">Imposta password del database</string>
<string name="open_database_folder">Apri cartella del database</string>
<string name="passphrase_will_be_saved_in_settings">La password verrà conservata nelle impostazioni come testo normale dopo averla cambiata o il riavvio dell\'app.</string>
@@ -1407,14 +1404,14 @@
<string name="expand_verb">Espandi</string>
<string name="connect_plan_repeat_connection_request">Ripetere la richiesta di connessione?</string>
<string name="rcv_direct_event_contact_deleted">contatto eliminato</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Ti stai già connettendo a %1$s.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Ti stai già connettendo a <b>%1$s</b>.]]></string>
<string name="error_alert_title">Errore</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Stai già entrando nel gruppo tramite questo link.</string>
<string name="create_group_button">Crea gruppo</string>
<string name="create_another_profile_button">Crea profilo</string>
<string name="group_members_2">%s e %s</string>
<string name="connect_plan_join_your_group">Entrare nel tuo gruppo?</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Stai già entrando nel gruppo %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Stai già entrando nel gruppo <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">Questo è il tuo link una tantum!</string>
<string name="marked_deleted_items_description">%d messaggi contrassegnati eliminati</string>
<string name="connect_plan_group_already_exists">Il gruppo esiste già!</string>
@@ -1429,7 +1426,7 @@
<string name="unblock_member_button">Sblocca membro</string>
<string name="connect_plan_connect_to_yourself">Connettersi a te stesso?</string>
<string name="contact_tap_to_connect">Tocca per connettere</string>
<string name="connect_plan_you_are_already_in_group_vName">Sei già nel gruppo %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Sei già nel gruppo <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">Questo è il tuo indirizzo SimpleX!</string>
<string name="correct_name_to">Correggere il nome a %s?</string>
<string name="delete_messages__question">Eliminare %d messaggi?</string>
@@ -1450,7 +1447,7 @@
<string name="block_member_question">Bloccare il membro?</string>
<string name="rcv_group_events_count">%d eventi del gruppo</string>
<string name="invalid_name">Nome non valido!</string>
<string name="connect_plan_this_is_your_link_for_group_vName">Questo è il tuo link per il gruppo %1$s!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Questo è il tuo link per il gruppo <b>%1$s</b>!]]></string>
<string name="unblock_member_confirmation">Sblocca</string>
<string name="non_content_uri_alert_title">Percorso file non valido</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">Hai già richiesto la connessione tramite questo indirizzo!</string>
@@ -1469,7 +1466,7 @@
<string name="unlink_desktop_question">Scollegare il desktop?</string>
<string name="linked_desktop_options">Opzioni del desktop collegato</string>
<string name="linked_desktops">Desktop collegati</string>
<string name="discover_on_network">Trova nella rete</string>
<string name="discover_on_network">Individua via rete locale</string>
<string name="this_device">Questo dispositivo</string>
<string name="linked_mobiles">Cellulari collegati</string>
<string name="desktop_device">Desktop</string>
@@ -1517,4 +1514,19 @@
\n- e molto altro!</string>
<string name="waiting_for_mobile_to_connect">In attesa che il cellulare si connette:</string>
<string name="group_member_role_author">autore</string>
<string name="multicast_connect_automatically">Connetti automaticamente</string>
<string name="waiting_for_desktop">In attesa del desktop…</string>
<string name="found_desktop">Desktop trovato</string>
<string name="not_compatible">Non compatibile!</string>
<string name="multicast_discoverable_via_local_network">individuabile via rete locale</string>
<string name="refresh_qr_code">Ricarica</string>
<string name="create_chat_profile">Crea profilo di chat</string>
<string name="no_connected_mobile">Nessun cellulare connesso</string>
<string name="disconnect_remote_hosts">Disconnetti cellulari</string>
<string name="random_port">Casuale</string>
<string name="open_port_in_firewall_desc">Per consentire a un\'app mobile di connettersi al desktop, apri questa porta nel tuo firewall, se è attivo</string>
<string name="app_was_crashed">Vedi crash</string>
<string name="open_port_in_firewall_title">Apri porta nel firewall</string>
<string name="error_showing_content">errore di visualizzazione del contenuto</string>
<string name="error_showing_message">errore di visualizzazione del messaggio</string>
</resources>
@@ -93,7 +93,6 @@
<string name="v4_2_auto_accept_contact_requests">אשר אוטומטית בקשות ליצירת קשר.</string>
<string name="authentication_cancelled">אימות בוטל</string>
<string name="auth_unavailable">אימות לא זמין</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>הוסיפו איש קשר חדש</b>: ליצירת קוד QR חד־פעמי עבור איש הקשר שלכם.]]></string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>הטוב ביותר לסוללה</b>. התראות יוצגו רק כאשר האפליקציה מופעלת (ללא שירות רקע).]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>טוב לסוללה</b>. שירות הרקע ייבדוק הודעות כל 10 דקות. שיחות או הודעות דחופות עלולות להתפספס.]]></string>
<string name="both_you_and_your_contacts_can_delete">גם אתם וגם איש הקשר יכולים למחוק באופן בלתי הפיך הודעות שנשלחו.</string>
@@ -103,7 +102,6 @@
<string name="cancel_verb">ביטול</string>
<string name="icon_descr_cancel_live_message">בטל הודעה חיה</string>
<string name="use_camera_button">מצלמה</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>סירקו קוד QR</b>: כדי להתחבר לאיש קשר המציג לכם קוד QR.]]></string>
<string name="icon_descr_cancel_link_preview">בטל תצוגה מקדימה של קישורים</string>
<string name="callstatus_error">שגיאת שיחה</string>
<string name="callstatus_in_progress">שיחה מתמשכת</string>
@@ -219,7 +217,6 @@
<string name="copied">הועתק ללוח</string>
<string name="share_one_time_link">צור קישור הזמנה חד־פעמי</string>
<string name="create_group">צור קבוצה סודית</string>
<string name="create_one_time_link">צור קישור הזמנה חד־פעמי</string>
<string name="contribute">תרומה</string>
<string name="core_version">גרסת ליבה: v%s</string>
<string name="create_address">צור כתובת</string>
@@ -73,12 +73,10 @@
<string name="auth_unavailable">認証不可能</string>
<string name="auto_accept_images">画像を自動的に受信</string>
<string name="notifications_mode_service_desc">バックグラウンド機能が常にオンで、メッセージが到着次第に通知が出ます。</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>新しい連絡先を追加</b>:使い捨てのQRコードを発行]]></string>
<string name="turning_off_service_and_periodic">電池省エネをオンに、バックグラウンド機能と定期的な受信依頼をオフにします。設定メニューにて変更できます。</string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>電池消費が最少</b>:アプリがアクティブ時のみに通知が出ます(バックグラウンドサービス無し)。]]></string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><![CDATA[<b>設定メニューにてオフにできます。</b> アプリがアクティブ時に通知が出ます。]]></string>
<string name="both_you_and_your_contacts_can_delete">あなたと連絡相手が送信済みメッセージを永久削除できます。</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>QRコードを読み込み</b>:連絡相手のQRコードをスキャンすると繋がります。]]></string>
<string name="chat_archive_section">チャットのアーカイブ</string>
<string name="delete_chat_archive_question">チャットのアーカイブを削除しますか?</string>
<string name="join_group_incognito_button">シークレットモードで参加</string>
@@ -524,7 +522,6 @@
<string name="mute_chat">ミュート</string>
<string name="delete_pending_connection__question">接続待ちの繋がりを削除しますか?</string>
<string name="connect_button">接続</string>
<string name="create_one_time_link">使い捨てリンクを発行する</string>
<string name="one_time_link">使い捨ての招待リンク</string>
<string name="database_passphrase_and_export">データベース暗証フレーズとエキスポート</string>
<string name="how_to_use_simplex_chat">使い方</string>
@@ -27,7 +27,6 @@
<string name="icon_descr_context">컨텍스트 아이콘</string>
<string name="icon_descr_server_status_connected">연결됨</string>
<string name="back">뒤로</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>새 대화 상대 추가</b> : 대화를 위한 일회용 QR 코드 만들기]]></string>
<string name="cancel_verb">취소</string>
<string name="icon_descr_cancel_live_message">라이브 메시지 취소</string>
<string name="choose_file">파일 선택</string>
@@ -171,7 +170,6 @@
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>배터리에 가장 좋음</b>. 앱이 실행 중일 때만 알림을 받게 됩니다 (백그라운드에서 실행되지 않음).]]></string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><![CDATA[<b>설정을 통해 비활성화할 수 있습니다.</b> – 앱이 실행되는 동안 알림이 표시됩니다.]]></string>
<string name="both_you_and_your_contact_can_send_disappearing">당신과 대화 상대 모두 사라지는 메시지를 보낼 수 있습니다.</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>QR 코드 스캔</b>: QR 코드를 보여주는 사람과 대화할 수 있습니다.]]></string>
<string name="cannot_access_keychain">데이터베이스 암호를 저장하고 있는 Keystore에 접근할 수 없습니다.</string>
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>배터리 더욱 사용</b>! 백그라운드 서비스가 항상 실행됩니다. - 메시지를 수신되는 즉시 알림이 표시됩니다.]]></string>
<string name="callstatus_ended">통화 종료됨 %1$s</string>
@@ -202,7 +200,6 @@
<string name="status_contact_has_e2e_encryption">대화 상대와 종단간 암호화됨</string>
<string name="alert_title_contact_connection_pending">대화 상대와 아직 연결되지 않았습니다!</string>
<string name="archive_created_on_ts">%1$s에 생성 완료</string>
<string name="create_one_time_link">일회용 초대 링크 생성</string>
<string name="create_secret_group_title">비밀 그룹 생성</string>
<string name="accept_contact_incognito_button">익명 수락</string>
<string name="chat_item_ttl_month">1개월</string>
@@ -117,7 +117,6 @@
<string name="image_descr_qr_code">QR kodas</string>
<string name="icon_descr_help">pagalba</string>
<string name="icon_descr_email">El. paštas</string>
<string name="create_one_time_link">Sukurti vienkartinio pakvietimo nuorodą</string>
<string name="scan_code">Skenuoti kodą</string>
<string name="database_passphrase_and_export">Duomenų bazės slaptafrazė ir eksportavimas</string>
<string name="smp_servers_delete_server">Ištrinti serverį</string>
@@ -387,8 +386,6 @@
<string name="error_smp_test_certificate">Gali būti, kad liudijimo kontrolinis kodas serverio adrese yra neteisingas</string>
<string name="smp_server_test_delete_file">Ištrinti failą</string>
<string name="image_decoding_exception_title">Dekodavimo klaida</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Pridėti naują adresatą</b>: norėdami sukurti adresatui vienkartinį QR kodą.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Skenuoti QR kodą</b>: norėdami prisijungti prie adresato, kuris jums rodo QR kodą.]]></string>
<string name="clear_chat_button">Išvalyti pokalbį</string>
<string name="unmute_chat">Įjungti pranešimus</string>
<string name="invalid_QR_code">Neteisingas QR kodas</string>
@@ -66,7 +66,6 @@
<string name="allow_voice_messages_question">Spraak berichten toestaan\?</string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Goed voor de batterij</b>. Achtergrondservice controleert berichten elke 10 minuten. Mogelijk mist u oproepen of dringende berichten.]]></string>
<string name="integrity_msg_bad_hash">Onjuiste bericht hash</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Scan QR-code</b>: om verbinding te maken met uw contact die u de QR-code laat zien.]]></string>
<string name="integrity_msg_bad_id">Onjuiste bericht-ID</string>
<string name="call_already_ended">Oproep al beëindigd!</string>
<string name="chat_item_ttl_month">1 maand</string>
@@ -94,7 +93,6 @@
<string name="network_session_mode_user_description"><![CDATA[Er wordt een aparte TCP-verbinding (en SOCKS-referentie) gebruikt <b> voor elk chat profiel dat je in de app hebt </b>.]]></string>
<string name="audio_call_no_encryption">audio oproep (niet e2e versleuteld)</string>
<string name="notifications_mode_service_desc">Achtergrondservice is altijd actief, meldingen worden weergegeven zodra de berichten beschikbaar zijn.</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Nieuw contact toevoegen</b>: om een eenmalige QR-code voor uw contact te maken.]]></string>
<string name="icon_descr_call_ended">Oproep beëindigd</string>
<string name="turning_off_service_and_periodic">Batterijoptimalisatie is actief, waardoor achtergrondservice en periodieke verzoeken om nieuwe berichten worden uitgeschakeld. Je kunt ze weer inschakelen via instellingen.</string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Het beste voor de batterij</b>. U ontvangt alleen meldingen wanneer de app wordt uitgevoerd (GEEN achtergrondservice).]]></string>
@@ -203,13 +201,12 @@
<string name="clear_verification">Verwijderd verificatie</string>
<string name="connect_button">Verbind</string>
<string name="connect_via_link">Maak verbinding via link</string>
<string name="create_one_time_link">Maak een eenmalige uitnodiging link</string>
<string name="colored_text">gekleurd</string>
<string name="callstatus_connecting">Oproep verbinden…</string>
<string name="create_profile_button">Maak</string>
<string name="create_profile">Maak een profiel aan</string>
<string name="delete_address">Adres verwijderen</string>
<string name="always_use_relay">Verbinden via relais</string>
<string name="always_use_relay">Altijd relay gebruiken</string>
<string name="status_contact_has_e2e_encryption">contact heeft e2e-codering</string>
<string name="status_contact_has_no_e2e_encryption">contact heeft geen e2e versleuteling</string>
<string name="set_password_to_export_desc">De database is versleuteld met een willekeurige wachtwoord. Wijzig dit voordat u exporteert.</string>
@@ -741,7 +738,7 @@
<string name="relay_server_if_necessary">Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien.</string>
<string name="your_ice_servers">Uw ICE servers</string>
<string name="alert_title_skipped_messages">Overgeslagen berichten</string>
<string name="call_connection_via_relay">via relais</string>
<string name="call_connection_via_relay">via relay</string>
<string name="icon_descr_video_off">Video uit</string>
<string name="protect_app_screen">App scherm verbergen</string>
<string name="your_privacy">Uw privacy</string>
@@ -1383,7 +1380,7 @@
<string name="open_database_folder">Database map openen</string>
<string name="passphrase_will_be_saved_in_settings">Het wachtwoord wordt als platte tekst in de instellingen opgeslagen nadat u deze hebt gewijzigd of de app opnieuw hebt opgestart.</string>
<string name="settings_is_storing_in_clear_text">Het wachtwoord wordt als leesbare tekst in de instellingen opgeslagen.</string>
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Let op</b>: bericht en bestands relais zijn verbonden via SOCKS-proxy. Voor oproepen en het verzenden van link voorbeelden wordt gebruik gemaakt van een directe verbinding.]]></string>
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Let op</b>: bericht en bestands relays zijn verbonden via SOCKS-proxy. Voor oproepen en het verzenden van link voorbeelden wordt gebruik gemaakt van een directe verbinding.]]></string>
<string name="encrypt_local_files">Versleutel lokale bestanden</string>
<string name="v5_3_encrypt_local_files">Versleutel opgeslagen bestanden en media</string>
<string name="v5_3_new_desktop_app">Nieuwe desktop app!</string>
@@ -1402,7 +1399,7 @@
<string name="compose_send_direct_message_to_connect">Stuur een direct bericht om verbinding te maken</string>
<string name="member_contact_send_direct_message">stuur een direct bericht</string>
<string name="rcv_group_event_member_created_contact">direct verbonden</string>
<string name="expand_verb">Uitbreiden</string>
<string name="expand_verb">Uitklappen</string>
<string name="connect_plan_repeat_connection_request">Verbindingsverzoek herhalen?</string>
<string name="rcv_direct_event_contact_deleted">verwijderd contact</string>
<string name="error_alert_title">Fout</string>
@@ -1431,7 +1428,7 @@
<string name="block_member_button">Lid blokkeren</string>
<string name="connect_plan_repeat_join_request">Deelnameverzoek herhalen?</string>
<string name="button_remove_member_question">Lid verwijderen?</string>
<string name="delete_and_notify_contact">Contact verwijderen en op de hoogte stellen</string>
<string name="delete_and_notify_contact">Verwijderen en contact op de hoogte stellen</string>
<string name="connect_plan_open_group">Open groep</string>
<string name="unblock_member_desc">Berichten van %s worden getoond!</string>
<string name="error_sending_message_contact_invitation">Fout bij verzenden van uitnodiging</string>
@@ -1442,17 +1439,17 @@
<string name="terminal_always_visible">Console in nieuw venster weergeven</string>
<string name="block_member_desc">Alle nieuwe berichten van %s worden verborgen!</string>
<string name="blocked_item_description">geblokkeerd</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Je bent al verbonden met %1$s.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Je bent al verbonden met <b>%1$s</b>.]]></string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Je wordt al lid van de groep via deze link.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Je bent al lid van de groep %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Je bent al lid van de groep <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">Dit is uw eigen eenmalige link!</string>
<string name="unblock_member_button">Lid deblokkeren</string>
<string name="connect_plan_you_are_already_in_group_vName">Je zit al in groep %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Je zit al in groep <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">Dit is uw eigen SimpleX adres!</string>
<string name="unblock_member_question">Lid deblokkeren?</string>
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">Je maakt al verbinding via deze eenmalige link!</string>
<string name="non_content_uri_alert_text">Je hebt een ongeldig bestandslocatie gedeeld. Rapporteer het probleem aan de app-ontwikkelaars.</string>
<string name="connect_plan_this_is_your_link_for_group_vName">Dit is jouw link voor groep %1$s!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Dit is jouw link voor groep <b>%1$s</b>!]]></string>
<string name="unblock_member_confirmation">Deblokkeren</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">U heeft al een verbinding aangevraagd via dit adres!</string>
<string name="encryption_renegotiation_error">Fout bij heronderhandeling van codering</string>
@@ -1520,4 +1517,14 @@
<string name="found_desktop">Desktop gevonden</string>
<string name="not_compatible">Niet compatibel!</string>
<string name="multicast_discoverable_via_local_network">Vindbaar via lokaal netwerk</string>
<string name="refresh_qr_code">Vernieuwen</string>
<string name="create_chat_profile">Chatprofiel aanmaken</string>
<string name="disconnect_remote_hosts">Mobiele telefoons loskoppelen</string>
<string name="no_connected_mobile">Geen verbonden mobiel</string>
<string name="random_port">willekeurig</string>
<string name="open_port_in_firewall_desc">Om een mobiele app verbinding te laten maken met de desktop, opent u deze poort in uw firewall, als u deze hebt ingeschakeld</string>
<string name="app_was_crashed">Weergave gecrasht</string>
<string name="open_port_in_firewall_title">Open poort in firewall</string>
<string name="error_showing_content">Fout bij het tonen van inhoud</string>
<string name="error_showing_message">fout bij weergeven bericht</string>
</resources>
@@ -229,7 +229,6 @@
<string name="accept_contact_button">Akceptuj</string>
<string name="accept_contact_incognito_button">Akceptuj incognito</string>
<string name="clear_chat_warning">Wszystkie wiadomości zostaną usunięte - nie można tego cofnąć! Wiadomości zostaną usunięte TYLKO dla Ciebie.</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Zeskanuj kod QR</b>: aby połączyć się z kontaktem, który pokaże Ci kod QR.]]></string>
<string name="icon_descr_cancel_link_preview">anuluj podgląd linku</string>
<string name="clear_verb">Wyczyść</string>
<string name="clear_chat_menu_action">Wyczyść</string>
@@ -917,7 +916,6 @@
<string name="auth_unavailable">Uwierzytelnianie niedostępne</string>
<string name="attach">Dołącz</string>
<string name="back">Wstecz</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Dodaj nowy kontakt</b>: aby stworzyć swój jednorazowy kod QR dla kontaktu.]]></string>
<string name="turning_off_service_and_periodic">Optymalizacja baterii jest aktywna, wyłącza usługi w tle i okresowe żądania nowych wiadomości. Możesz je ponownie włączyć za pośrednictwem ustawień.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><![CDATA[<b>Można je wyłączyć poprzez ustawienia</b> - powiadomienia nadal będą pokazywane podczas działania aplikacji.]]></string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Najlepsze dla baterii</b>. Będziesz otrzymywać powiadomienia tylko wtedy, gdy aplikacja jest uruchomiona (NIE w tle).]]></string>
@@ -933,7 +931,6 @@
<string name="connection_error_auth">Błąd połączenia (UWIERZYTELNIANIE)</string>
<string name="connect_via_link_or_qr">Połącz się przez link / kod QR</string>
<string name="archive_created_on_ts">Utworzony na %1$s</string>
<string name="create_one_time_link">Utwórz jednorazowy link do zaproszenia</string>
<string name="create_group">Utwórz tajną grupę</string>
<string name="create_secret_group_title">Utwórz tajną grupę</string>
<string name="set_password_to_export_desc">Baza danych jest zaszyfrowana przy użyciu losowego hasła. Proszę zmienić je przed eksportem.</string>
@@ -1427,12 +1424,12 @@
<string name="blocked_item_description">zablokowany</string>
<string name="expand_verb">Rozszerz</string>
<string name="connect_plan_repeat_connection_request">Powtórzyć prośbę połączenia?</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Już jesteś połączony z %1$s.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Już jesteś połączony z <b>%1$s</b>.]]></string>
<string name="error_alert_title">Błąd</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Już dołączasz do grupy przez ten link.</string>
<string name="group_members_2">%s i %s</string>
<string name="connect_plan_join_your_group">Dołączyć do twojej grupy?</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Już dołączasz do grupy %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Już dołączasz do grupy <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">To jest twój jednorazowy link!</string>
<string name="connect_plan_group_already_exists">Grupa już istnieje!</string>
<string name="video_decoding_exception_desc">Wideo nie może zostać zdekodowane, spróbuj inne wideo lub skontaktuj się z deweloperami.</string>
@@ -1440,7 +1437,7 @@
<string name="group_members_n">%s, %s i %d członków</string>
<string name="unblock_member_button">Odblokuj członka</string>
<string name="contact_tap_to_connect">Dotknij aby połączyć</string>
<string name="connect_plan_you_are_already_in_group_vName">Już jesteś w grupie %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Już jesteś w grupie <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">To jest twój własny adres SimpleX!</string>
<string name="remove_member_button">Usuń członka</string>
<string name="unblock_member_question">Odblokować członka?</string>
@@ -1452,7 +1449,7 @@
<string name="error_sending_message_contact_invitation">Błąd wysyłania zaproszenia</string>
<string name="non_content_uri_alert_text">Udostępniłeś nieprawidłową ścieżkę pliku. Zgłoś problem do deweloperów aplikacji.</string>
<string name="invalid_name">Nieprawidłowa nazwa!</string>
<string name="connect_plan_this_is_your_link_for_group_vName">To jest twój link zaproszenia do grupy %1$s!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[To jest twój link zaproszenia do grupy <b>%1$s</b>!]]></string>
<string name="unblock_member_confirmation">Odblokuj</string>
<string name="non_content_uri_alert_title">Nieprawidłowa ścieżka pliku</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">Już prosiłeś o połączenie na ten adres!</string>
@@ -24,8 +24,6 @@
<string name="icon_descr_cancel_live_message">Cancelar mensagem ao vivo</string>
<string name="back">Voltar</string>
<string name="choose_file">Arquivo</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Adicionar novo contato</b>: para criar seu QR code de uso único para seu contato.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Escanear código QR</b>: para se conectar ao seu contato que mostra o código QR para você.]]></string>
<string name="accept_contact_button">Aceitar</string>
<string name="clear_chat_question">Limpar chat\?</string>
<string name="clear_verb">Limpar</string>
@@ -374,7 +372,6 @@
<string name="auth_device_authentication_is_disabled_turning_off">A autenticação do dispositivo está desativada. Desativando o bloqueio SimpleX.</string>
<string name="for_everybody">Para todos</string>
<string name="notification_preview_mode_hidden">Oculto</string>
<string name="create_one_time_link">Gerar um link de convite de uso único.</string>
<string name="how_to_use_your_servers">Como usar seus servidores</string>
<string name="import_database_confirmation">Importar</string>
<string name="import_database_question">Importar banco de dados de chat\?</string>
@@ -211,7 +211,6 @@
<string name="info_row_group">Grupo</string>
<string name="icon_descr_audio_on">Áudio ligado</string>
<string name="authentication_cancelled">Autenticação cancelada</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b> Adicionar novo contato</b>: para criar o seu código QR de utilização única para o seu contato.]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b> Bom para a bateria </b>. O serviço em segundo plano verifica se há mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]></string>
<string name="turning_off_service_and_periodic">A otimização da bateria está ativa, desativando o serviço em segundo plano e os pedidos periódicos de novas mensagens. Você pode reativá-los através das definições.</string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Melhor para a bateria</b>. Apenas receberá notificações enquanto a app estiver em execução (SEM serviço em segundo plano)]]></string>
@@ -225,7 +224,6 @@
<string name="calls_prohibited_with_this_contact">Chamadas de áudio/vídeo são proibidas.</string>
<string name="notifications_mode_service_desc">O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis.</string>
<string name="la_authenticate">Autenticar</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Leia o código QR </b>: para se conectar ao seu contato que lhe mostra o código QR.]]></string>
<string name="callstatus_ended">chamada finalizada %1$s</string>
<string name="callstatus_calling">a chamar…</string>
<string name="callstatus_error">erro de chamada</string>
@@ -366,7 +364,6 @@
<string name="connection_error">Erro de conexão</string>
<string name="connection_local_display_name">conexão %1$d</string>
<string name="contact_already_exists">O contato já existe</string>
<string name="create_one_time_link">Criar convite de ligação de utilização única</string>
<string name="one_time_link">Convite de ligação de utilização única</string>
<string name="save_servers_button">Salvar</string>
<string name="settings_section_title_incognito">Modo anónimo</string>
@@ -267,12 +267,10 @@
<string name="to_start_a_new_chat_help_header">Чтобы начать новый чат</string>
<string name="chat_help_tap_button">Нажмите кнопку</string>
<string name="above_then_preposition_continuation">сверху, затем:</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Добавить новый контакт</b>: чтобы создать одноразовый QR код/ссылку для Вашего контакта.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>Сканировать QR код</b>: чтобы соединиться с контактом, который показывает Вам QR код.]]></string>
<string name="to_connect_via_link_title">Чтобы соединиться через ссылку</string>
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Если Вы получили ссылку с приглашением из SimpleX Chat, Вы можете открыть ее в браузере:</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 на компьютере: сосканируйте показанный QR код из приложения через <b>Сканировать QR код</b>.]]></string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 на мобильном: намжите кнопку <b>Open in mobile app</b> на веб странице, затем нажмите <b>Соединиться</b> в приложении.]]></string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 на мобильном: нажмите кнопку <b>Open in mobile app</b> на веб странице, затем нажмите <b>Соединиться</b> в приложении.]]></string>
<!-- Contact Request Alert Dialogue - CharListNavLinkView.kt -->
<string name="accept_connection_request__question">Принять запрос на соединение?</string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Отправителю НЕ будет послано уведомление, если Вы отклоните запрос на соединение.</string>
@@ -339,7 +337,6 @@
<string name="connect_button">Соединиться</string>
<string name="paste_button">Вставить</string>
<!-- CreateLinkView.kt -->
<string name="create_one_time_link">Создать одноразовую ссылку</string>
<string name="one_time_link">Одноразовая ссылка</string>
<!-- settings - SettingsView.kt -->
<string name="your_settings">Настройки</string>
@@ -1566,9 +1563,9 @@
<string name="block_member_desc">Все новые сообщения от %s будут скрыты!</string>
<string name="desktop_app_version_is_incompatible">Версия настольного приложения %s несовместима с этим приложением.</string>
<string name="blocked_item_description">заблокировано</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Вы уже соединяетесь с %1$s.</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Вы уже соединяетесь с <b>%1$s</b>.]]></string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Вы уже вступаете в группу по этой ссылке.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Вы уже вступаете в группу %1$s.</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Вы уже вступаете в группу <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">Это ваша собственная одноразовая ссылка!</string>
<string name="v5_4_link_mobile_desktop_descr">Через безопасный квантово-устойчивый протокол.</string>
<string name="v5_4_block_group_members_descr">Чтобы скрыть нежелательные сообщения.</string>
@@ -1580,7 +1577,7 @@
<string name="unblock_member_button">Разблокировать члена группы</string>
<string name="contact_tap_to_connect">Нажмите чтобы соединиться</string>
<string name="this_device_name">Имя этого устройства</string>
<string name="connect_plan_you_are_already_in_group_vName">Вы уже состоите в группе %1$s.</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Вы уже состоите в группе <b>%1$s</b>.]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">Это ваш собственный адрес SimpleX!</string>
<string name="unblock_member_question">Разблокировать члена группы?</string>
<string name="settings_section_title_use_from_desktop">Использовать с компьютера</string>
@@ -1590,7 +1587,7 @@
<string name="this_device_name_shared_with_mobile">Имя устройства будет доступно подключенному мобильному клиенту.</string>
<string name="verify_code_on_mobile">Сверьте код на мобильном</string>
<string name="non_content_uri_alert_text">Указан неверный путь к файлу. Сообщите о проблеме разработчикам приложения.</string>
<string name="connect_plan_this_is_your_link_for_group_vName">Это ваша ссылка на группу %1$s!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Это ваша ссылка на группу <b>%1$s</b>!]]></string>
<string name="verify_code_with_desktop">Сверьте код с компьютером</string>
<string name="scan_qr_code_from_desktop">Сканировать QR код с компьютера</string>
<string name="unblock_member_confirmation">Разблокировать</string>
@@ -1605,4 +1602,11 @@
<string name="not_compatible">Несовместимая версия!</string>
<string name="group_member_role_author">автор</string>
<string name="multicast_discoverable_via_local_network">Найти через локальную сеть</string>
<string name="refresh_qr_code">Обновить</string>
<string name="random_port">Случайный</string>
<string name="open_port_in_firewall_desc">Чтобы разрешить мобильному приложению подключаться к компьютеру, откройте этот порт в брандмауэре, если он включен</string>
<string name="create_chat_profile">Создать профиль чата</string>
<string name="open_port_in_firewall_title">Открыть порт в брандмауэре</string>
<string name="disconnect_remote_hosts">Отключить мобильные</string>
<string name="no_connected_mobile">Нет подключённых мобильных</string>
</resources>
@@ -112,8 +112,6 @@
<string name="both_you_and_your_contact_can_add_message_reactions">ทั้งคุณและผู้ติดต่อของคุณสามารถเพิ่มปฏิกิริยาต่อข้อความได้</string>
<string name="both_you_and_your_contacts_can_delete">ทั้งคุณและผู้ติดต่อของคุณสามารถลบข้อความที่ส่งแล้วอย่างถาวรได้</string>
<string name="use_camera_button">กล้อง</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b> เพิ่มผู้ติดต่อใหม่ </b>: เพื่อสร้างรหัส QR แบบใช้ครั้งเดียวสําหรับผู้ติดต่อของคุณ]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>สแกนรหัส QR</b>: เพื่อเชื่อมต่อกับผู้ติดต่อที่แสดงรหัส QR ให้คุณ]]></string>
<string name="learn_more_about_address">เกี่ยวกับที่อยู่ SimpleX</string>
<string name="bold_text">ตัวหนา</string>
<string name="callstatus_calling">กำลังโทร…</string>
@@ -205,7 +203,6 @@
<string name="icon_descr_close_button">ปุ่มปิด</string>
<string name="connect_button">เชื่อมต่อ</string>
<string name="connect_via_link">เชื่อมต่อผ่านลิงก์</string>
<string name="create_one_time_link">สร้างลิงก์เชิญแบบใช้ครั้งเดียว</string>
<string name="clear_verification">ล้างการยืนยัน</string>
<string name="chat_console">คอนโซลแชท</string>
<string name="smp_servers_check_address">ตรวจสอบที่อยู่เซิร์ฟเวอร์แล้วลองอีกครั้ง</string>
@@ -3,7 +3,6 @@
<string name="notifications">Bildirimler</string>
<string name="connect_via_link_or_qr">Bağlantı ya da karekod ile bağlan</string>
<string name="create_group">Gizli grup oluştur</string>
<string name="create_one_time_link">Tek seferlik davet bağlantısı oluştur</string>
<string name="your_simplex_contact_address">SimpleX addresin</string>
<string name="callstatus_missed">cevapsız çağrı</string>
<string name="incoming_video_call">Gelen görüntülü arama</string>
@@ -48,7 +47,7 @@
<string name="app_version_name">Uygulama sürümü: v%s</string>
<string name="icon_descr_audio_call">sesli arama</string>
<string name="audio_call_no_encryption">sesli arama (uçtan uca şifreli değil)</string>
<string name="answer_call">Aramaya cevap ver</string>
<string name="answer_call">Aramayı cevapla</string>
<string name="full_backup">Uygulama veri yedekleme</string>
<string name="all_app_data_will_be_cleared">Tüm uygulama verileri silinir.</string>
<string name="settings_section_title_app">UYGULAMA</string>
@@ -242,7 +241,7 @@
<string name="icon_descr_call_rejected">Geri çevrilmiş çağrı</string>
<string name="icon_descr_call_ended">Görüşme bitti.</string>
<string name="alert_text_decryption_error_too_many_skipped">%1$d mesaj atlanıldı.</string>
<string name="chat_database_deleted">Konuşma veri tabanı silindi</string>
<string name="chat_database_deleted">Sohbet veri tabanı silindi</string>
<string name="error_with_info">Hata: %s</string>
<string name="unknown_error">Bilinmeyen hata</string>
<string name="rcv_group_event_member_connected">bağlanıldı</string>
@@ -257,10 +256,10 @@
<string name="chat_preferences_yes">evet</string>
<string name="accept_feature">Onayla</string>
<string name="allow_disappearing_messages_only_if">Konuştuğun kişiler, kendiliğinden yok olan mesajlara izin veriyorsa sen de ver.</string>
<string name="allow_your_contacts_irreversibly_delete">Konuştuğun kişilerin gönderilen mesajları kalıcı olarak silmesine izin ver.</string>
<string name="allow_your_contacts_irreversibly_delete">Kişilerinin gönderilen mesajları kalıcı olarak silmesine izin ver.</string>
<string name="prohibit_sending_disappearing_messages">Kendiliğinden yok olan measj gönderimini engelle.</string>
<string name="allow_irreversible_message_deletion_only_if">Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver.</string>
<string name="allow_message_reactions_only_if">Konuştuğun kişi, mesaj tepkilerine izin veriyorsa sen de ver.</string>
<string name="allow_message_reactions_only_if">Yalnızca kişin mesaj tepkilerine izin veriyorsa sen de ver.</string>
<string name="only_you_can_send_disappearing">Sadece sen kendiliğinden yok olan mesaj gönderebilirsin.</string>
<string name="only_your_contact_can_send_disappearing">Sadece konuştuğun kişi kendiliğinden yok olan mesaj gönderebilir.</string>
<string name="both_you_and_your_contact_can_send_disappearing">Konuştuğun kişi de sen de kendiliğinden yok olan mesaj gönderebilirsiniz.</string>
@@ -269,17 +268,17 @@
<string name="v4_4_disappearing_messages_desc">Gönderilen mesajlar, önceden belirlenmiş bir süre sonra silecektir.</string>
<string name="v5_1_self_destruct_passcode_descr">Kullanıldığında bütün veriler silinir.</string>
<string name="timed_messages">Kendiliğinden yok olan mesajlar</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Konuştuğun kişilerin sana, kendiğinden yok olan mesajlar göndermesine izin ver.</string>
<string name="allow_your_contacts_to_send_disappearing_messages">Kişilerinin sana, kendiğinden yok olan mesajlar göndermesine izin ver.</string>
<string name="disappearing_messages_are_prohibited">Bu grupta kendiliğinden yok olan mesajlara izin verilmiyor.</string>
<string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d mesaj deşifrelenemedi.</string>
<string name="group_info_section_title_num_members">%1$s ÜYE</string>
<string name="integrity_msg_skipped">%1$d atlanılmış mesaj</string>
<string name="allow_calls_only_if">Konuştuğun kişi, aramalara izin veriyorsa sen de ver.</string>
<string name="allow_calls_only_if">Yalnızca irtibat kişiniz izin veriyorsa aramalara izin verin.</string>
<string name="allow_your_contacts_adding_message_reactions">Konuştuğun kişilerin mesajlarına tepki eklemesine izin ver.</string>
<string name="allow_your_contacts_to_call">Konuştuğun kişilerin seni aramasına izin ver.</string>
<string name="all_your_contacts_will_remain_connected">Konuştuğun kişilerin tümü bağlı kalacaktır.</string>
<string name="allow_your_contacts_to_call">Kişilerinin seni aramasına izin ver.</string>
<string name="all_your_contacts_will_remain_connected">Kişilerin tümü bağlı kalacaktır.</string>
<string name="icon_descr_cancel_file_preview">Dosya ön izlemesini iptal et</string>
<string name="chat_database_imported">Konuşma veri tabanı içe aktarıldı</string>
<string name="chat_database_imported">Sohbet veri tabanı içe aktarıldı</string>
<string name="icon_descr_cancel_image_preview">Görsel ön izlemesini iptal et</string>
<string name="clear_chat_question">Konuşmayı temizle\?</string>
<string name="v4_6_chinese_spanish_interface">Çince ve İspanyolca arayüz</string>
@@ -296,7 +295,7 @@
<string name="settings_section_title_calls">ARAMALAR</string>
<string name="settings_section_title_chats">KONUŞMALAR</string>
<string name="settings_section_title_you">SEN</string>
<string name="chat_database_section">KONUŞMA VERİ TABANI</string>
<string name="chat_database_section">SOHBET VERİ TABANI</string>
<string name="remove_passphrase">Kaldır</string>
<string name="wrong_passphrase_title">Yanlış parola!</string>
<string name="confirm_database_upgrades">Veri tabanının yapısal ilerlemelerini onayla</string>
@@ -309,8 +308,8 @@
<string name="member_info_section_title_member">ÜYE</string>
<string name="group_members_can_send_disappearing">Grup üyeleri kendiliğinden yok olan mesajlar gönderebilir.</string>
<string name="prohibit_sending_disappearing">Kendiliğinden yok olan mesaj gönderimini engelle.</string>
<string name="allow_voice_messages_only_if">Konuştuğun kişi, sesli mesajlara izin veriyorsa sen de ver.</string>
<string name="allow_your_contacts_to_send_voice_messages">Konuştuğun kişilerin sesli mesaj göndermesine izin ver.</string>
<string name="allow_voice_messages_only_if">Yalnızca kişiniz sesli mesaj göndermeye izin veriyorsa sen de ver.</string>
<string name="allow_your_contacts_to_send_voice_messages">Kişilerinin sesli mesaj göndermesine izin ver.</string>
<string name="both_you_and_your_contact_can_add_message_reactions">Konuştuğun kişi de sen de mesajlara tepki ekleyebilirsinsiz.</string>
<string name="both_you_and_your_contact_can_send_voice">Konuştuğun kişi de sen de sesli mesaj gönderebilirsiniz.</string>
<string name="v4_4_disappearing_messages">Kendiliğinden yok olan mesajlar</string>
@@ -410,7 +409,7 @@
<string name="decentralized">Özeksiz</string>
<string name="confirm_passcode">Erişim kodunu onayla</string>
<string name="incorrect_passcode">Yanlış erişim kodu</string>
<string name="new_passcode">Yeni Şifre</string>
<string name="new_passcode">Yeni erişim kodu</string>
<string name="submit_passcode">Gönder</string>
<string name="la_mode_passcode">Erişim kodu</string>
<string name="passcode_changed">Erişim kodu değişti!</string>
@@ -736,7 +735,6 @@
<string name="v4_3_irreversible_message_deletion">Geri alınamaz mesaj silme</string>
<string name="v4_5_italian_interface">İtalyanca arayüz</string>
<string name="choose_file_title">Dosya seç</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>QR kodunu tara</b>: size QR kodunu gösteren kişiyle bağlantı kurmak için.]]></string>
<string name="invite_friends">Arkadaşlarınızı davet edin</string>
<string name="bold_text">kalın</string>
<string name="italic_text">İtalik</string>
@@ -790,7 +788,6 @@
<string name="delete_message_cannot_be_undone_warning">Mesajlar silinecek - bu geri alınamaz!</string>
<string name="switch_receiving_address_question">Alıcı adresini değiştir\?</string>
<string name="back">Geri</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>Yeni kişi ekle</b>: Kişiniz için tek seferlik QR Kodunuzu oluşturmak için.]]></string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Bağlantı isteğiniz kabul edildiğinde bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin!</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">Kişinizin cihazı çevrimiçi olduğunda bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin!</string>
<string name="learn_more">Daha fazla bilgi edinin</string>
@@ -877,9 +874,9 @@
<string name="your_contacts_will_remain_connected">Kişileriniz bağlı kalacaktır.</string>
<string name="you_can_create_it_later">Daha sonra oluşturabilirsiniz</string>
<string name="you_can_use_markdown_to_format_messages__prompt">Mesajları biçimlendirmek için markdown kullanabilirsiniz:</string>
<string name="your_chat_database">Mesaj veri tabanınız</string>
<string name="your_chat_database">Sohbet veri tabanınız</string>
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Mevcut sohbet veritabanınız SİLİNECEK ve içe aktarılan veritabanıyla DEĞİŞTİRİLECEKTİR.
\nBu işlem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri alınamaz şekilde kaybolacaktır.</string>
\nBu eylem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri alınamaz şekilde kaybolacaktır.</string>
<string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Bu gruba katıldınız. Davet eden grup üyesine bağlanılıyor.</string>
<string name="you_are_invited_to_group_join_to_connect_with_group_members">Gruba davetlisiniz. Grup üyeleriyle bağlantı kurmak için katılın.</string>
<string name="alert_title_cant_invite_contacts_descr">Bu grup için gizli bir profil kullanıyorsunuz - ana profilinizi paylaşmayı önlemek için kişileri davet etmeye izin verilmiyor</string>
@@ -902,7 +899,7 @@
<string name="contact_sent_large_file">Kişiniz desteklenen maksimum boyuttan (%1$s) daha büyük bir dosya gönderdi.</string>
<string name="video_will_be_received_when_contact_completes_uploading">Kişiniz yüklemeyi tamamladığında video alınacaktır.</string>
<string name="you_can_also_connect_by_clicking_the_link"><![CDATA[Ayrıca bağlantıya tıklayarak da bağlanabilirsiniz. Eğer bağlantı tarayıcda açılırsa, <b>mobil uygulamada aç</b> seçeneğine tıklayın.]]></string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Soru sormak ve güncellemeleri almak için <font color=#0088ff>SimpleX Chat geliştiricilerine bağlanabilirsiniz</font>.]]></string>
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Soru sormak ve güncellemeleri almak için <font color="#0088ff">SimpleX Chat geliştiricilerine bağlanabilirsiniz</font>.]]></string>
<string name="you_can_hide_or_mute_user_profile">Bir kullanıcının profilini gizleyebilir veya sessize alabilirsiniz - menü için basılı tutun.</string>
<string name="you_must_use_the_most_recent_version_of_database">Sohbet veritabanınızın en son sürümünü SADECE bir cihazda kullanmalısınız, aksi takdirde bazı kişilerden daha fazla mesaj alamayabilirsiniz.</string>
<string name="wrong_passphrase">Yanlış veritabanı parolası</string>
@@ -976,7 +973,7 @@
<string name="to_connect_via_link_title">Link ile bağlanmak için</string>
<string name="this_link_is_not_a_valid_connection_link">Bu geçerli bir bağlantı linki değil</string>
<string name="this_QR_code_is_not_a_link">Bu QR kodu bir bağlantı değil!</string>
<string name="read_more_in_user_guide_with_link"><![CDATA[Daha fazla bilgi için <font color=#0088ff>Kullanıcı Kılavuzu</font>.]]></string>
<string name="read_more_in_user_guide_with_link"><![CDATA[Daha fazla bilgi için <font color="#0088ff">Kullanıcı Kılavuzu</font>.]]></string>
<string name="paste_button">Yapıştır</string>
<string name="this_string_is_not_a_connection_link">Bu dize bir bağlantı linki değil!</string>
<string name="rate_the_app">Uygulamaya puan verin</string>
@@ -1004,7 +1001,7 @@
<string name="paste_the_link_you_received">Alınan linki yapıştır</string>
<string name="unknown_database_error_with_info">Bilinmeyen veri tabanı hatası: %s</string>
<string name="open_chat">Sohbeti aç</string>
<string name="restore_database_alert_desc">Veritabanı yedeğini geri yükledikten sonra önceki şifreyi girin. Bu işlem geri alınamaz.</string>
<string name="restore_database_alert_desc">Veritabanı yedeğini geri yükledikten sonra önceki şifreyi girin. Bu eylem geri alınamaz.</string>
<string name="network_option_ping_interval">PING aralığı</string>
<string name="network_option_protocol_timeout_per_kb">KB başına protokol zaman aşımı</string>
<string name="unhide_chat_profile">Sohbet profilini gizlemeyi kaldır</string>
@@ -1020,7 +1017,7 @@
<string name="unfavorite_chat">Favorilerden çıkar</string>
<string name="make_profile_private">Sohbeti gizli yap!</string>
<string name="profile_update_will_be_sent_to_contacts">Profil güncellemesi kişilerinize gönderilecektir.</string>
<string name="read_more_in_github_with_link"><![CDATA[<font color=#0088ff>GitHub repomuzda</font> daha fazlasını okuyun.]]></string>
<string name="read_more_in_github_with_link"><![CDATA[<font color="#0088ff">GitHub repomuzda</font> daha fazlasını okuyun.]]></string>
<string name="alert_text_fragment_please_report_to_developers">Lütfen geliştiricilere bildirin.</string>
<string name="users_delete_with_connections">Profil ve sunucu bağlantıları</string>
<string name="user_unhide">gizlemeyi kaldır</string>
@@ -1069,4 +1066,212 @@
<string name="stop_sharing">Paylaşmayı durdur</string>
<string name="stop_rcv_file__title">Dosya almayı durdur?</string>
<string name="stop_chat_question">Sohbeti durdur?</string>
<string name="expand_verb">Genişlet</string>
<string name="connect_plan_repeat_connection_request">Bağlantı isteğini tekrarla?</string>
<string name="no_details">detay yok</string>
<string name="database_initialization_error_desc">Veri tabanı düzgün çalışmıyor. Daha fazla bilgi için dokunun</string>
<string name="opensource_protocol_and_code_anybody_can_run_servers">Açık kaynaklı protokol ve kod - sunucuları herkes çalıştırabilir.</string>
<string name="rcv_group_event_open_chat">Aç</string>
<string name="settings_notification_preview_mode_title">Önizlemeyi göster</string>
<string name="v5_3_encrypt_local_files">Depolanan dosyaları ve medyayı şifrele</string>
<string name="only_you_can_send_voice">Sadece sen sesli mesaj gönderebilirsin.</string>
<string name="update_network_settings_confirmation">Güncelle</string>
<string name="database_backup_can_be_restored">Veritabanı parolasını değiştirme girişimi tamamlanmadı.</string>
<string name="update_database_passphrase">Veri tabanı parolasını güncelle</string>
<string name="turn_off_system_restriction_button">Uygulama ayarlarını aç</string>
<string name="enable_automatic_deletion_message">Bu eylem geri alınamaz - seçilenden daha önce gönderilen ve alınan mesajlar silinecektir. Birkaç dakika sürebilir.</string>
<string name="create_group_button">Grup oluştur</string>
<string name="custom_time_picker_select">Seç</string>
<string name="only_you_can_make_calls">Sadece sen arama yapabilirsin.</string>
<string name="new_mobile_device">Yeni mobil cihaz</string>
<string name="use_chat">Sohbeti kullan</string>
<string name="multicast_connect_automatically">Otomatik olarak bağlan</string>
<string name="share_verb">Paylaş</string>
<string name="rcv_group_event_n_members_connected">%s, %s ve %d diğer üye bağlandı</string>
<string name="only_your_contact_can_make_calls">Sadece senin kişin arama yapabilir.</string>
<string name="desktop_address">Masaüstü adresi</string>
<string name="smp_servers_test_some_failed">Bazı sunucular testi geçemedi:</string>
<string name="chat_with_the_founder">Sorularınızı ve fikirlerinizi gönderin</string>
<string name="delete_files_and_media_desc">Bu eylem geri alınamaz - tüm alınan ve gönderilen dosyalar ve medyalar silinecek. Düşük çözünürlükteki resimlar kalacaktır.</string>
<string name="encrypt_local_files">Yerel dosyaları şifrele</string>
<string name="ntf_channel_calls">SimpleX Chat aramaları</string>
<string name="v5_3_new_desktop_app">Yeni masaüstü uygulaması!</string>
<string name="v5_3_new_interface_languages">6 yeni arayüz dili</string>
<string name="notification_preview_mode_contact_desc">Sadece kişiyi göster</string>
<string name="connect_plan_group_already_exists">Grup zaten mevcut!</string>
<string name="icon_descr_speaker_on">Hoparlör açık</string>
<string name="only_your_contact_can_send_voice">Sadece senin kişin sesli mesaj gönderebilir.</string>
<string name="in_developing_desc">Bu özellik henüz desteklenmiyor. Bir sonraki sürümü deneyin.</string>
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[Mobile uygulamadan <i>Masaüstünden kullan</i> seçeneğini aç ve karekodu okut.]]></string>
<string name="thank_you_for_installing_simplex">SimpleX Chat\'i yüklediğiniz için teşekkürler!</string>
<string name="skip_inviting_button">Üyeleri davet etmeyi atla</string>
<string name="connect_plan_already_connecting">Zaten bağlanılıyor!</string>
<string name="icon_descr_send_message">Mesaj Gönder</string>
<string name="contact_you_shared_link_with_wont_be_able_to_connect">Bu bağlantıyı paylaştığınız kişi bağlanamayacak!</string>
<string name="desktop_incompatible_version">Uyumsuz sürüm</string>
<string name="new_desktop"><![CDATA[<i>(yeni)</i>]]></string>
<string name="notification_preview_mode_message_desc">Kişiyi ve mesajı göster</string>
<string name="v5_4_better_groups">Daha iyi gruplar</string>
<string name="video_decoding_exception_desc">Videonun kodu çözülemiyor. Lütfen farklı bir video deneyin veya geliştiricilerle iletişime geçin.</string>
<string name="non_fatal_errors_occured_during_import">İçe aktarma sırasında bir takım hatlar oluştu - daha fazla detay için sohbet konsoluna bakabilirsiniz.</string>
<string name="show_developer_options">Geliştirici seçeneklerini göster</string>
<string name="rcv_group_event_1_member_connected">%s bağlandı</string>
<string name="network_disable_socks_info">Onaylarsanız, mesajlaşma sunucuları IP adresinizi ve sağlayıcınızı - hangi sunuculara bağlandığınızı - görebilecektir.</string>
<string name="share_with_contacts">Kişilerle paylaş</string>
<string name="chat_item_ttl_seconds">%s saniye (sn)</string>
<string name="recipient_colon_delivery_status">%s: %s</string>
<string name="system_restricted_background_desc">SimpleX arka planda çalışamaz. Bildirimleri sadece uygulama çalışırken alırsınız.</string>
<string name="connect_plan_connect_via_link">Bağlantı ile bağlan?</string>
<string name="database_encryption_will_be_updated_in_settings">Veri tabanı şifreleme parolası güncellenecek ve ayarlarda depolanacaktır.</string>
<string name="connect_plan_already_joining_the_group">Zaten gruba bağlanılıyor!</string>
<string name="group_members_n">%s, %s ve %d üye</string>
<string name="this_device">Bu cihaz</string>
<string name="share_address_with_contacts_question">Adresi kişilerle paylaş?</string>
<string name="system_restricted_background_in_call_desc">Uygulama arka planda 1 dakika kaldıktan sonra kapatılabilir.</string>
<string name="text_field_set_contact_placeholder">Kişi ismini ayarla…</string>
<string name="send_us_an_email">Bize e-posta gönder</string>
<string name="v4_2_security_assessment_desc">SimpleX Chat\'in güvenliği Trail of Bits tarafından denetlenmiştir.</string>
<string name="simplex_service_notification_title">SimpleX Chat hizmeti</string>
<string name="is_verified">%s onaylı</string>
<string name="password_to_show">Göserilecek parola</string>
<string name="incognito_info_allows">Tek bir sohbet profilinde, aralarında herhangi bir veri paylaşımı olmadan birden fazla anonim bağlantı kurmaya izin verir.</string>
<string name="select_contacts">Kişileri seç</string>
<string name="v4_6_chinese_spanish_interface_descr">Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun!</string>
<string name="v5_3_simpler_incognito_mode">Basitleştirilmiş gizli mod</string>
<string name="update_network_settings_question">Ağ ayarlarını güncelle?</string>
<string name="shutdown_alert_question">Kapat?</string>
<string name="you_invited_a_contact">Bir kişiyi davet ettin</string>
<string name="is_not_verified">%s onaylı değil</string>
<string name="this_device_name">Bu cihazın ismi</string>
<string name="smp_server_test_upload_file">Dosya yükle</string>
<string name="network_use_onion_hosts_required">Gerekli</string>
<string name="ntf_channel_messages">SimpleX Chat mesajlar</string>
<string name="share_link">Bağlantı paylaş</string>
<string name="icon_descr_simplex_team">SimpleX Ekibi</string>
<string name="rcv_group_event_3_members_connected">%s, %s ve %s bağlandı</string>
<string name="network_options_revert">Geri al</string>
<string name="settings_section_title_socks">SOCKS PROXY</string>
<string name="desktop_devices">Masaüstür cihazlar</string>
<string name="smp_servers">SMP sunuclar</string>
<string name="not_compatible">Uyumlu değil!</string>
<string name="v4_4_verify_connection_security">Bağlantı güvenliğini onayla</string>
<string name="connect_with_contact_name_question">%1$s ile bağlan?</string>
<string name="remove_member_button">Üyeyi çıkar</string>
<string name="open_database_folder">Veri tabanı dosyasını aç</string>
<string name="settings_section_title_use_from_desktop">Masaüstünden kullan</string>
<string name="network_proxy_port">port %d</string>
<string name="share_address">Adres paylaş</string>
<string name="connect_plan_repeat_join_request">Bağlantı talebini tekrarla?</string>
<string name="button_remove_member_question">Üyeyi çıkar?</string>
<string name="verify_security_code">Güvenlik kodunu onayla</string>
<string name="show_dev_options">Göster:</string>
<string name="receipts_section_groups">Küçük gruplar (maks 20)</string>
<string name="v5_3_new_interface_languages_descr">Arapça, Bulgarca, Fince, İbranice, Tayca ve Ukraynaca - kullanıcılar ve Weblate sayesinde.</string>
<string name="port_verb">Port</string>
<string name="connection_you_accepted_will_be_cancelled">Kabul ettiğiniz bağlantı iptal edilecek!</string>
<string name="connect_plan_open_group">Grubu aç</string>
<string name="privacy_show_last_messages">Son mesajları göster</string>
<string name="this_device_version"><![CDATA[<i>bu cihaz s%s</i>]]></string>
<string name="core_simplexmq_version">simplexmq: v%s (%2s)</string>
<string name="update_database">Güncelle</string>
<string name="this_device_name_shared_with_mobile">Cihaz ismi bağlı olduğu mobil istemci ile paylaşılacak.</string>
<string name="v5_3_new_desktop_app_descr">Masaüstü uygulamasında yeni bir profil oluştur. 💻</string>
<string name="set_contact_name">Kişi ismini ayarla</string>
<string name="verify_code_on_mobile">Kodu mobilde onayla</string>
<string name="share_image">Medya paylaş…</string>
<string name="passphrase_will_be_saved_in_settings">Parola, siz onu değiştirdikten veya uygulamayı yeniden başlattıktan sonra ayarlarda düz metin olarak depolanacak.</string>
<string name="paste_the_link_you_received_to_connect_with_your_contact">Kişinizle bağlantı kurmak için aldığınız bağlantıyı yapıştırın…</string>
<string name="settings_shutdown">Kapat</string>
<string name="icon_descr_sent_msg_status_sent">gönderildi</string>
<string name="send_live_message">Canlı mesaj gönder</string>
<string name="enter_this_device_name">Bu cihazın adını girin…</string>
<string name="smp_servers_per_user">Mevcut sohbet profilinizin yeni bağlantıları için sunucular</string>
<string name="show_QR_code">QR kodu göster</string>
<string name="network_socks_proxy_settings">SOCKS proxy ayarları</string>
<string name="rcv_group_events_count">%d grup etkinlikleri</string>
<string name="invalid_name">Geçersiz isim!</string>
<string name="la_lock_mode">SimpleX Kilit modu</string>
<string name="share_file">Dosya paylaş…</string>
<string name="periodic_notifications_desc">Uygulama yeni mesajları periyodik olarak alır - günde pilin yüzde birkaçını kullanır. Uygulama anlık bildirimleri kullanmaz - cihazınızdan gelen veriler sunuculara gönderilmez.</string>
<string name="first_platform_without_user_ids">Herhangi bir kullanıcı tanımlayıcısı olmayan ilk platform - tasarım gereği gizli.</string>
<string name="icon_descr_speaker_off">Hoparlör kapalı</string>
<string name="sync_connection_force_desc">Şifreleme çalışıyor ve yeni bir şifreleme anlaşması gerekli değil. Yoksa bağlantı hataları ortaya çıkabilir!</string>
<string name="show_call_on_lock_screen">Göster</string>
<string name="send_verb">Gönder</string>
<string name="paste_desktop_address">Masaüstü adresini yapıştır</string>
<string name="verify_code_with_desktop">Kodu masaüstü ile onayla</string>
<string name="scan_qr_code_from_desktop">Masaüstünden karekodu tara</string>
<string name="image_descr_simplex_logo">SimpleX logo</string>
<string name="devices">Cihazlar</string>
<string name="delete_chat_profile_action_cannot_be_undone_warning">Bu eylem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri döndürülemez şekilde kaybolacaktır.</string>
<string name="v5_4_more_things_descr">- isteğe bağlı olarak silinen kişileri bildirme.
\n- boşluklu profil adları.
\n- ve daha fazlası!</string>
<string name="la_notice_title_simplex_lock">SimpleX Kilit</string>
<string name="connect_via_member_address_alert_desc">Bu grup üyesine bağlantı isteği gönderilecek.</string>
<string name="only_your_contact_can_add_message_reactions">Sadece senin kişin mesaj tepkileri ekleyebilir.</string>
<string name="settings_is_storing_in_clear_text">Parola, ayarlarda düz metin olarak saklanır.</string>
<string name="auth_simplex_lock_turned_on">SimpleX Kilit aktif</string>
<string name="terminal_always_visible">Yeni pencerede konsolu göster</string>
<string name="scan_from_mobile">Mobilden tara</string>
<string name="verify_connections">Bağlantıları onayla</string>
<string name="share_message">Mesaj paylaş…</string>
<string name="alert_text_msg_bad_hash">Önceki mesajın hash\'i farklı.</string>
<string name="lock_not_enabled">SimpleX Kilit aktif değil!</string>
<string name="chat_lock">SimpleX Kilit</string>
<string name="connect_via_member_address_alert_title">Direkt bağlan?</string>
<string name="receipts_section_description">Bu ayarlar mevcut profiliniz içindir</string>
<string name="smp_servers_test_failed">Sunucu testi başarısız!</string>
<string name="verify_connection">Bağlantıyı onayla</string>
<string name="add_contact_or_create_group">Yeni sohbet başlat</string>
<string name="connect__your_profile_will_be_shared">Profiliniz %1$s paylaşılacaktır.</string>
<string name="v5_4_incognito_groups_descr">Rastgele bir profil kullanarak grup oluştur.</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">Gruba zaten bu bağlantı üzerinden katılıyorsunuz.</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Sohbet profiliniz kişinize
\ngönderilecek</string>
<string name="invite_prohibited_description">Gizli bir profil paylaştığınız kişiyi ana profilinizi kullandığınız gruba davet etmeye çalışıyorsunuz</string>
<string name="rcv_conn_event_verification_code_reset">güvenlik kodu değiştirildi</string>
<string name="v4_6_audio_video_calls_descr">Bluetooth desteği ve diğer iyileştirmeler.</string>
<string name="icon_descr_settings">Ayarlar</string>
<string name="settings_section_title_settings">AYARLAR</string>
<string name="compose_send_direct_message_to_connect">Bağlanmak için direkt mesaj gönderin</string>
<string name="security_code">Güvenlik kodu</string>
<string name="v5_4_better_groups_descr">Daha hızlı gruplara katılma ve daha güvenilir mesajlar.</string>
<string name="group_main_profile_sent">Sohbet profiliniz grup üyelerine gönderilecek</string>
<string name="connect_plan_connect_to_yourself">Kendine bağlan?</string>
<string name="notifications_mode_periodic">Periyodik olarak başlar</string>
<string name="connect_plan_this_is_your_own_simplex_address">Bu sizin kendi SimpleX adresiniz!</string>
<string name="v4_3_irreversible_message_deletion_desc">Kişileriniz tam mesaj silme işlemine izin verebilir.</string>
<string name="icon_descr_sent_msg_status_send_failed">gönderme başarısız</string>
<string name="sender_may_have_deleted_the_connection_request">Gönderen kişi bağlantı isteğini silmiş olabilir.</string>
<string name="create_chat_profile">Sohbet profili oluştur</string>
<string name="error_sending_message_contact_invitation">Davetiye gönderirken hata</string>
<string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Sohbeti uygulama Ayarları/Veritabanı üzerinden veya uygulamayı yeniden başlatarak başlatabilirsiniz.</string>
<string name="connect_to_desktop">Masaüstüne bağlan</string>
<string name="button_send_direct_message">Direkt mesaj gönder</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">Bu adres üzerinden zaten bağlantı talebinde bulundunuz!</string>
<string name="member_contact_send_direct_message">direkt mesaj gönder</string>
<string name="sender_cancelled_file_transfer">Gönderen kişi dosya aktarımını iptal etti.</string>
<string name="only_you_can_delete_messages">Mesajları yalnızca siz geri döndürülemez şekilde silebilirsiniz (kişiniz bunları silinmek üzere işaretleyebilir).</string>
<string name="connect_plan_you_are_already_connecting_to_vName">Zaten %1$s\'ye bağlanıyorsunuz.</string>
<string name="whats_new_thanks_to_users_contribute_weblate">Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun!</string>
<string name="lock_mode">Kilit modu</string>
<string name="only_one_device_can_work_at_the_same_time">Aynı anda yalnızca bir cihaz çalışabilir</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">Zaten %1$s grubuna katılıyorsunuz.</string>
<string name="group_invitation_tap_to_join">Katılmak için dokun</string>
<string name="v5_0_polish_interface_descr">Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun!</string>
<string name="v5_4_link_mobile_desktop_descr">Güvenli kuantum dirençli protokol ile.</string>
<string name="star_on_github">Bize Github\'da yıldız verin</string>
<string name="auth_you_will_be_required_to_authenticate_when_you_start_or_resume">Arka planda 30 saniye kaldıktan sonra uygulamayı başlattığınızda veya devam ettirdiğinizde kimlik doğrulaması yapmanız gerekecektir.</string>
<string name="v4_5_italian_interface_descr">Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun!</string>
<string name="waiting_for_desktop">Masaüstü bekleniyor…</string>
<string name="database_is_not_encrypted">Sohbet veri tabanınız şifreli değildir - korumak için parola ayarlayın.</string>
<string name="you_will_still_receive_calls_and_ntfs">Aktif olduklarında sessize alınmış profillerden arama ve bildirim almaya devam edersiniz.</string>
<string name="connect_plan_you_are_already_in_group_vName">Zaten %1$s grubundasınız.</string>
<string name="v4_4_french_interface_descr">Kullanıcılara teşekkürler - Weblate aracılığıyla katkıda bulunun!</string>
<string name="connect_plan_you_are_already_connecting_via_this_one_time_link">Bu tek seferlik bağlantı üzerinden zaten bağlanıyorsunuz!</string>
<string name="non_content_uri_alert_text">Geçersiz bir dosya yolu paylaştınız. Sorunu uygulama geliştiricilerine bildirin.</string>
<string name="disable_notifications_button">Bildirimleri devre dışı bırak</string>
<string name="non_content_uri_alert_title">Geçersiz dosya yolu</string>
<string name="only_your_contact_can_delete">Yalnızca kişiniz mesajları geri alınamaz şekilde silebilir (silinmeleri için işaretleyebilirsiniz).</string>
</resources>
File diff suppressed because it is too large Load Diff
@@ -118,7 +118,6 @@
<string name="network_session_mode_entity_description">每个联系人和群组成员 <b>将使用单独的 TCP 连接(和 SOCKS 凭证)</b>。
\n<b>请注意</b>:如果您有很多连接,您的电池和流量消耗可能会大大增加,并且某些连接可能会失败。</string>
<string name="back">返回</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>添加新联系人</b>:为您的联系人创建一次性二维码。]]></string>
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b> 最长续航 </b>。您只会在应用程序运行时收到通知(无后台服务)。]]></string>
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b> 较长续航 </b>。后台服务每 10 分钟检查一次消息。您可能会错过来电或者紧急信息。]]></string>
<string name="bold_text">加粗</string>
@@ -129,7 +128,6 @@
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b> 使用更多电量 </b>!后台服务始终运行——一旦收到消息,就会显示通知。]]></string>
<string name="impossible_to_recover_passphrase"><![CDATA[<b>请注意</b>:如果您丢失密码,您将无法恢复或者更改密码。]]></string>
<string name="call_already_ended">通话已结束!</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>扫描二维码</b> :与向您展示二维码的联系人联系。]]></string>
<string name="alert_title_cant_invite_contacts">无法邀请联系人!</string>
<string name="invite_prohibited">无法邀请联系人!</string>
<string name="cancel_verb">取消</string>
@@ -338,7 +336,6 @@
<string name="create_secret_group_title">创建私密群组</string>
<string name="v4_5_multiple_chat_profiles_descr">不同的名字、头像和传输隔离。</string>
<string name="v4_4_french_interface">法语界面</string>
<string name="create_one_time_link">创建一次性邀请链接</string>
<string name="how_to_use_simplex_chat">如何使用它</string>
<string name="server_error">错误</string>
<string name="display_name_connecting">连接中……</string>
@@ -1407,14 +1404,14 @@
<string name="expand_verb">展开</string>
<string name="connect_plan_repeat_connection_request">重复连接请求吗?</string>
<string name="rcv_direct_event_contact_deleted">已删除联系人</string>
<string name="connect_plan_you_are_already_connecting_to_vName">你已经在连接到 %1$s。</string>
<string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[你已经在连接到 <b>%1$s</b>。]]></string>
<string name="error_alert_title">错误</string>
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">你已经在通过此链接加入该群。</string>
<string name="create_group_button">建群</string>
<string name="create_another_profile_button">创建个人资料</string>
<string name="group_members_2">%s 和 %s</string>
<string name="connect_plan_join_your_group">加入你的群吗?</string>
<string name="connect_plan_you_are_already_joining_the_group_vName">你已经在加入 %1$s 群。</string>
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[你已经在加入 <b>%1$s</b> 群。]]></string>
<string name="connect_plan_this_is_your_own_one_time_link">这是你自己的一次性链接!</string>
<string name="marked_deleted_items_description">%d 条消息被标记为删除</string>
<string name="connect_plan_group_already_exists">群已存在!</string>
@@ -1428,7 +1425,7 @@
<string name="unblock_member_button">解封成员</string>
<string name="connect_plan_connect_to_yourself">连接到你自己?</string>
<string name="contact_tap_to_connect">轻按连接</string>
<string name="connect_plan_you_are_already_in_group_vName">你已经在%1$s 群内。</string>
<string name="connect_plan_you_are_already_in_group_vName"><![CDATA[你已经在<b>%1$s</b> 群内。]]></string>
<string name="connect_plan_this_is_your_own_simplex_address">这是你自己的 SimpleX 地址!</string>
<string name="correct_name_to">更正名称为 %s?</string>
<string name="delete_messages__question">删除 %d 条消息吗?</string>
@@ -1449,7 +1446,7 @@
<string name="block_member_question">封禁成员吗?</string>
<string name="rcv_group_events_count">%d 个群事件</string>
<string name="invalid_name">无效名称!</string>
<string name="connect_plan_this_is_your_link_for_group_vName">这是给你的 %1$s 群链接!</string>
<string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[这是给你的 <b>%1$s</b> 群链接!]]></string>
<string name="unblock_member_confirmation">解封</string>
<string name="non_content_uri_alert_title">无效的文件路径</string>
<string name="connect_plan_you_have_already_requested_connection_via_this_address">你已经请求通过此地址进行连接!</string>
@@ -1521,4 +1518,15 @@
<string name="found_desktop">找到了桌面</string>
<string name="not_compatible">不兼容!</string>
<string name="multicast_discoverable_via_local_network">可通过本地网络发现</string>
<string name="refresh_qr_code">刷新</string>
<string name="create_chat_profile">创建聊天资料</string>
<string name="no_connected_mobile">没有已连接的移动设备</string>
<string name="disconnect_remote_hosts">断开移动设备连接</string>
<string name="random_port">随机</string>
<string name="open_port_in_firewall_desc">要允许移动应用连接到桌面,请在防火墙中打开此端口,如果你已启用了它</string>
<string name="open_port_in_firewall_title">在防火墙中打开端口</string>
<string name="app_was_crashed">查看崩溃</string>
<string name="moderated_items_description">%d 条消息由 %s 主持</string>
<string name="error_showing_content">显示内容出错</string>
<string name="error_showing_message">显示消息出错</string>
</resources>
@@ -47,8 +47,6 @@
<string name="allow_voice_messages_question">允許使用語音訊息?</string>
<string name="cancel_verb">取消</string>
<string name="icon_descr_cancel_live_message">取消實況訊息</string>
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b>新增新的聯絡人</b>:建立你的一次性二維碼給你的聯絡人。]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b>掃描二維碼</b>:連接到向你出示二維碼的聯絡人。]]></string>
<string name="choose_file">選擇檔案</string>
<string name="use_camera_button">相機</string>
<string name="from_gallery_button">從圖片庫選擇圖片</string>
@@ -424,7 +422,6 @@
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 桌面版:在應用程式內掃描一個已存在的二維碼,透過 <b>掃描二維碼</b>。]]></string>
<string name="icon_descr_settings">設定</string>
<string name="this_QR_code_is_not_a_link">這個二維碼不是一個連結!</string>
<string name="create_one_time_link">建立一次性邀請連結</string>
<string name="network_use_onion_hosts_prefer">當可行的時候</string>
<string name="core_version">核心版本:v%s</string>
<string name="core_simplexmq_version">simplexmq: v%s (%2s)</string>
@@ -2,8 +2,7 @@ package chat.simplex.common.platform
import chat.simplex.common.model.*
import chat.simplex.common.views.call.RcvCallInvitation
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.common.views.helpers.withBGApi
import chat.simplex.common.views.helpers.*
import java.util.*
import chat.simplex.res.MR
@@ -25,10 +24,7 @@ fun initApp() {
override fun cancelAllNotifications() = chat.simplex.common.model.NtfManager.cancelAllNotifications()
}
applyAppLocale()
withBGApi {
initChatController()
runMigrations()
}
initChatControllerAndRunMigrations(false)
// LALAL
//testCrypto()
}
@@ -4,6 +4,8 @@ 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 java.io.File
import java.net.URI
@@ -36,3 +38,5 @@ onExternalDrag(enabled) {
}
actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpenDetector { action() }
actual fun Modifier.desktopPointerHoverIconHand(): Modifier = Modifier.pointerHoverIcon(PointerIcon.Hand)
@@ -1,5 +1,6 @@
package chat.simplex.common.views.chatlist
import SectionDivider
import androidx.compose.foundation.*
import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.layout.*
@@ -33,11 +34,11 @@ actual fun ChatListNavLinkLayout(
dropdownMenuItems: (@Composable () -> Unit)?,
showMenu: MutableState<Boolean>,
stopped: Boolean,
selectedChat: State<Boolean>
selectedChat: State<Boolean>,
nextChatSelected: State<Boolean>,
) {
var modifier = Modifier.fillMaxWidth()
if (!stopped) modifier = modifier
.background(color = if (selectedChat.value) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified)
.combinedClickable(onClick = click, onLongClick = { showMenu.value = true })
.onRightClick { showMenu.value = true }
CompositionLocalProvider(
@@ -52,10 +53,17 @@ actual fun ChatListNavLinkLayout(
) {
chatLinkPreview()
}
if (selectedChat.value) {
Box(Modifier.matchParentSize().background(MaterialTheme.colors.onBackground.copy(0.05f)))
}
if (dropdownMenuItems != null) {
DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems)
}
}
}
Divider()
if (selectedChat.value || nextChatSelected.value) {
Divider()
} else {
SectionDivider()
}
}
@@ -1,14 +1,20 @@
package chat.simplex.common.views.helpers
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Density
import chat.simplex.common.model.CIFile
import chat.simplex.common.model.readCryptoFile
import chat.simplex.common.platform.*
import chat.simplex.common.simplexWindowState
import kotlinx.coroutines.delay
import java.io.ByteArrayInputStream
import java.io.File
import java.io.*
import java.net.URI
import javax.imageio.ImageIO
@@ -17,6 +23,7 @@ import kotlin.io.encoding.ExperimentalEncodingApi
private val bStyle = SpanStyle(fontWeight = FontWeight.Bold)
private val iStyle = SpanStyle(fontStyle = FontStyle.Italic)
private val uStyle = SpanStyle(textDecoration = TextDecoration.Underline)
private fun fontStyle(color: String) =
SpanStyle(color = Color(color.replace("#", "ff").toLongOrNull(16) ?: Color.White.toArgb().toLong()))
@@ -54,6 +61,22 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat
}
break
}
text.substringSafe(innerI, 2) == "u>" -> {
val textStart = innerI + 2
for (insideTagI in textStart until text.length) {
if (text[insideTagI] == '<') {
withStyle(uStyle) { append(text.substring(textStart, insideTagI)) }
skipTil = insideTagI + 4
break
}
}
break
}
text.substringSafe(innerI, 3) == "br>" -> {
val textStart = innerI + 3
append("\n")
skipTil = textStart
}
text.substringSafe(innerI, 4) == "font" -> {
var textStart = innerI + 5
var color = "#000000"
@@ -85,6 +108,18 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat
AnnotatedString(text)
}
@Composable
actual fun SetupClipboardListener() {
val clipboard = LocalClipboardManager.current
chatModel.clipboardHasText.value = clipboard.hasText()
LaunchedEffect(Unit) {
while (true) {
delay(1000)
chatModel.clipboardHasText.value = clipboard.hasText()
}
}
}
actual fun getAppFileUri(fileName: String): URI {
val rh = chatModel.currentRemoteHost.value
return if (rh == null) {
@@ -1,11 +0,0 @@
package chat.simplex.common.views.newchat
import androidx.compose.runtime.*
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.RemoteHostInfo
@Composable
actual fun ConnectViaLinkView(m: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
// TODO this should close if remote host changes in model
PasteToConnectView(m, rh, close)
}
@@ -1,8 +1,13 @@
package chat.simplex.common.views.newchat
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.*
@Composable
actual fun QRCodeScanner(onBarcode: (String) -> Unit) {
actual fun QRCodeScanner(
showQRCodeScanner: MutableState<Boolean>,
padding: PaddingValues,
onBarcode: (String) -> Unit
) {
//LALAL
}
@@ -1,15 +0,0 @@
package chat.simplex.common.views.newchat
import androidx.compose.runtime.Composable
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.RemoteHostInfo
@Composable
actual fun ScanToConnectView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
ConnectContactLayout(
chatModel = chatModel,
rh = rh,
incognitoPref = chatModel.controller.appPrefs.incognito,
close = close
)
}