android: i18n (#529)

* internationalization framework

* rearrange strings

* typo

* minor id & xliff changes

* response to comments

* colour comments and verb suffixes

* add russian language file

* fix interpolation error

* final strings

* russian translations

* update Russian translations, refactor strings to full sentences, add prefixes to content description names

* fix layouts, improve font spacing

* split sentence about User address, font line height

* typo

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* update Russian translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* remove an

* update Russian translations

Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>

* commas

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
IanRDavies
2022-04-16 09:29:29 +01:00
committed by GitHub
co-authored by Evgeny Poberezkin JRoberts
parent 2058e904e6
commit d201c9528a
40 changed files with 896 additions and 313 deletions
@@ -25,9 +25,9 @@ import chat.simplex.app.views.WelcomeView
import chat.simplex.app.views.chat.ChatView
import chat.simplex.app.views.chatlist.ChatListView
import chat.simplex.app.views.chatlist.openChat
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.newchat.*
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.connectViaUri
import chat.simplex.app.views.newchat.withUriAction
import java.util.concurrent.TimeUnit
//import kotlinx.serialization.decodeFromString
@@ -122,10 +122,18 @@ fun connectIfOpenedViaUri(uri: Uri, chatModel: ChatModel) {
chatModel.appOpenUrl.value = uri
} else {
withUriAction(uri) { action ->
val title = when (action) {
"contact" -> generalGetString(R.string.connect_via_contact_link)
"invitation" -> generalGetString(R.string.connect_via_invitation_link)
else -> {
Log.e(TAG, "URI has unexpected action. Alert shown.")
action
}
}
AlertManager.shared.showAlertMsg(
title = "Connect via $action link?",
text = "Your profile will be sent to the contact that you received this link from.",
confirmText = "Connect",
title = title,
text = generalGetString(R.string.profile_will_be_sent_to_contact_sending_link),
confirmText = generalGetString(R.string.connect_via_link_verb),
onConfirm = {
withApi {
Log.d(TAG, "connectIfOpenedViaUri: connecting")
@@ -41,6 +41,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
override fun onCreate() {
super.onCreate()
context = this
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
withApi {
val user = chatController.apiGetActiveUser()
@@ -65,9 +66,10 @@ class SimplexApp: Application(), LifecycleEventObserver {
}
companion object {
lateinit var context: SimplexApp private set
init {
val socketName = "local.socket.address.listen.native.cmd2"
val s = Semaphore(0)
thread(name="stdout/stderr pipe") {
Log.d(TAG, "starting server")
@@ -82,7 +84,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
val inStreamReader = InputStreamReader(inStream)
val input = BufferedReader(inStreamReader)
while(true) {
while (true) {
val line = input.readLine() ?: break
Log.w("$TAG (stdout/stderr)", line)
logbuffer.add(line)
@@ -7,8 +7,10 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.style.TextDecoration
import chat.simplex.app.R
import chat.simplex.app.ui.theme.SecretColor
import chat.simplex.app.ui.theme.SimplexBlue
import chat.simplex.app.views.helpers.generalGetString
import kotlinx.datetime.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
@@ -267,12 +269,12 @@ data class Chat (
@Serializable
sealed class NetworkStatus {
val statusString: String get() = if (this is Connected) "Server connected" else "Connecting server…"
val statusString: String get() = if (this is Connected) generalGetString(R.string.server_connected) else generalGetString(R.string.server_connecting)
val statusExplanation: String get() =
when {
this is Connected -> "You are connected to the server used to receive messages from this contact."
this is Error -> "Trying to connect to the server used to receive messages from this contact (error: $error)."
else -> "Trying to connect to the server used to receive messages from this contact."
when (this) {
is Connected -> generalGetString(R.string.connected_to_server_to_receive_messages_from_contact)
is Error -> String.format(generalGetString(R.string.trying_to_connect_to_server_to_receive_messages_with_error), error)
else -> generalGetString(R.string.trying_to_connect_to_server_to_receive_messages)
}
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
@@ -568,7 +570,7 @@ data class ChatItem (
id: Long = 1,
dir: CIDirection = CIDirection.DirectRcv(),
ts: Instant = Clock.System.now(),
text: String = "this item is deleted",
text: String = "this item is deleted", // sample not localized
status: CIStatus = CIStatus.RcvRead()
) =
ChatItem(
@@ -694,25 +696,25 @@ sealed class CIContent: ItemContent {
@Serializable @SerialName("sndDeleted")
class SndDeleted(val deleteMode: CIDeleteMode): CIContent() {
override val text get() = "deleted"
override val text get() = generalGetString(R.string.deleted_description)
override val msgContent get() = null
}
@Serializable @SerialName("rcvDeleted")
class RcvDeleted(val deleteMode: CIDeleteMode): CIContent() {
override val text get() = "deleted"
override val text get() = generalGetString(R.string.deleted_description)
override val msgContent get() = null
}
@Serializable @SerialName("sndFileInvitation")
class SndFileInvitation(val fileId: Long, val filePath: String): CIContent() {
override val text get() = "sending files is not supported yet"
override val text get() = generalGetString(R.string.sending_files_not_yet_supported)
override val msgContent get() = null
}
@Serializable @SerialName("rcvFileInvitation")
class RcvFileInvitation(val rcvFileTransfer: RcvFileTransfer): CIContent() {
override val text get() = "receiving files is not supported yet"
override val text get() = generalGetString(R.string.receiving_files_not_yet_supported)
override val msgContent get() = null
}
}
@@ -729,7 +731,7 @@ class CIQuote (
override val text: String get() = content.text
fun sender(user: User): String? = when (chatDir) {
is CIDirection.DirectSnd -> "you"
is CIDirection.DirectSnd -> generalGetString(R.string.sender_you_pronoun)
is CIDirection.DirectRcv -> null
is CIDirection.GroupSnd -> user.displayName
is CIDirection.GroupRcv -> chatDir.groupMember.memberProfile.displayName
@@ -782,7 +784,7 @@ object MsgContentSerializer : KSerializer<MsgContent> {
return if (json is JsonObject) {
if ("type" in json) {
val t = json["type"]?.jsonPrimitive?.content ?: ""
val text = json["text"]?.jsonPrimitive?.content ?: "unknown message format"
val text = json["text"]?.jsonPrimitive?.content ?: generalGetString(R.string.unknown_message_format)
when (t) {
"text" -> MsgContent.MCText(text)
"link" -> {
@@ -792,10 +794,10 @@ object MsgContentSerializer : KSerializer<MsgContent> {
else -> MsgContent.MCUnknown(t, text, json)
}
} else {
MsgContent.MCUnknown(text = "invalid message format", json = json)
MsgContent.MCUnknown(text = generalGetString(R.string.invalid_message_format), json = json)
}
} else {
MsgContent.MCUnknown(text = "invalid message format", json = json)
MsgContent.MCUnknown(text = generalGetString(R.string.invalid_message_format), json = json)
}
}
@@ -15,6 +15,7 @@ import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -182,8 +183,8 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
else -> {
Log.e(TAG, "setUserSMPServers bad response: ${r.responseType} ${r.details}")
AlertManager.shared.showAlertMsg(
"Error saving SMP servers",
"Make sure SMP server addresses are in correct format, line separated and are not duplicated."
generalGetString(R.string.error_saving_smp_servers),
generalGetString(R.string.ensure_smp_server_address_are_correct_format_and_unique)
)
false
}
@@ -202,15 +203,17 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
when {
r is CR.SentConfirmation || r is CR.SentInvitation -> return true
r is CR.ContactAlreadyExists -> {
AlertManager.shared.showAlertMsg("Contact already exists",
"You are already connected to ${r.contact.displayName} via this link."
AlertManager.shared.showAlertMsg(
generalGetString(R.string.contact_already_exists),
String.format(generalGetString(R.string.you_are_already_connected_to_vName_via_this_link), r.contact.displayName)
)
return false
}
r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat
&& r.chatError.errorType is ChatErrorType.InvalidConnReq -> {
AlertManager.shared.showAlertMsg("Invalid connection link",
"Please check that you used the correct link or ask your contact to send you another one."
AlertManager.shared.showAlertMsg(
generalGetString(R.string.invalid_connection_link),
generalGetString(R.string.please_check_correct_link_and_maybe_ask_for_a_new_one)
)
return false
}
@@ -229,8 +232,8 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
val e = r.chatError
if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) {
AlertManager.shared.showAlertMsg(
"Can't delete contact!",
"Contact ${e.errorType.contact.displayName} cannot be deleted, it is a member of the group(s) ${e.errorType.groupNames}."
generalGetString(R.string.cannot_delete_contact),
String.format(generalGetString(R.string.contact_cannot_be_deleted_as_they_are_in_groups), e.errorType.contact.displayName, e.errorType.groupNames)
)
}
}
@@ -410,35 +413,22 @@ open class ChatController(private val ctrl: ChatCtrl, private val ntfManager: Nt
Row {
Icon(
Icons.Outlined.Bolt,
contentDescription = "Instant notifications",
contentDescription = generalGetString(R.string.icon_descr_instant_notifications),
)
Text("Private instant notifications!", fontWeight = FontWeight.Bold)
Text(generalGetString(R.string.private_instant_notifications), fontWeight = FontWeight.Bold)
}
},
text = {
Column {
Text(
buildAnnotatedString {
append("To preserve your privacy, instead of push notifications the app has a ")
withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
append("SimpleX background service")
}
append(" it uses a few percent of the battery per day.")
},
annotatedStringResource(R.string.to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery),
Modifier.padding(bottom = 8.dp)
)
Text(
buildAnnotatedString {
withStyle(SpanStyle(fontWeight = FontWeight.Medium)) {
append("It can be disabled via settings")
}
append(" notifications will still be shown while the app is running.")
}
)
Text(annotatedStringResource(R.string.it_can_disabled_via_settings_notifications_still_shown))
}
},
confirmButton = {
Button(onClick = AlertManager.shared::hideAlert) { Text("Ok") }
Button(onClick = AlertManager.shared::hideAlert) { Text(generalGetString(R.string.ok)) }
}
)
}
@@ -716,7 +706,7 @@ sealed class CR {
is Invalid -> str
}
fun noDetails(): String ="${responseType}: no details"
fun noDetails(): String ="${responseType}: " + generalGetString(R.string.no_details)
}
abstract class TerminalItem {
@@ -7,7 +7,7 @@ val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val Gray = Color(0x22222222)
val SimplexBlue = Color(0, 136, 255, 255)
val SimplexBlue = Color(0, 136, 255, 255) // If this value changes also need to update #0088ff in string resource files
val SimplexGreen = Color(98, 196, 103, 255)
val SecretColor = Color(0x40808080)
val LightGray = Color(241, 242, 246, 255)
@@ -5,7 +5,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.Composable
private val DarkColorPalette = darkColors(
primary = SimplexBlue,
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = DarkGray,
// background = Color.Black,
@@ -20,7 +20,7 @@ private val DarkColorPalette = darkColors(
// onError: Color = Color.Black,
)
private val LightColorPalette = lightColors(
primary = SimplexBlue,
primary = SimplexBlue, // If this value changes also need to update #0088ff in string resource files
primaryVariant = SimplexGreen,
secondary = LightGray,
// background = Color.White,
@@ -19,9 +19,7 @@ import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.SendMsgView
import chat.simplex.app.views.helpers.CloseSheetBar
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.newchat.ModalManager
import chat.simplex.app.views.helpers.*
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.launch
@@ -17,6 +17,7 @@ import chat.simplex.app.R
import chat.simplex.app.SimplexService
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Profile
import chat.simplex.app.views.helpers.generalGetString
import chat.simplex.app.views.helpers.withApi
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
@@ -39,22 +40,22 @@ fun WelcomeView(chatModel: ChatModel) {
) {
Image(
painter = painterResource(R.drawable.logo),
contentDescription = "Simplex Logo",
contentDescription = generalGetString(R.string.image_descr_simplex_logo),
modifier = Modifier.padding(vertical = 15.dp)
)
Text(
"You control your chat!",
generalGetString(R.string.you_control_your_chat),
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.onBackground
)
Text(
"The messaging and application platform protecting your privacy and security.",
generalGetString(R.string.the_messaging_and_app_platform_protecting_your_privacy_and_security),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground
)
Spacer(Modifier.height(8.dp))
Text(
"We don't store any of your contacts or messages (once delivered) on the servers.",
generalGetString(R.string.we_do_not_store_contacts_or_messages_on_servers),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground
)
@@ -79,19 +80,19 @@ fun CreateProfilePanel(chatModel: ChatModel) {
modifier=Modifier.fillMaxSize()
) {
Text(
"Create profile",
generalGetString(R.string.create_profile),
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(vertical = 5.dp)
)
Text(
"Your profile is stored on your device and shared only with your contacts.",
generalGetString(R.string.your_profile_is_stored_on_your_decide_and_shared_only_with_your_contacts),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground
)
Spacer(Modifier.height(10.dp))
Text(
"Display Name",
generalGetString(R.string.display_name),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(bottom = 3.dp)
@@ -113,7 +114,7 @@ fun CreateProfilePanel(chatModel: ChatModel) {
),
singleLine = true
)
val errorText = if(!isValidDisplayName(displayName)) "Display name cannot contain whitespace." else ""
val errorText = if(!isValidDisplayName(displayName)) generalGetString(R.string.display_name_cannot_contain_whitespace) else ""
Text(
errorText,
@@ -123,7 +124,7 @@ fun CreateProfilePanel(chatModel: ChatModel) {
Spacer(Modifier.height(3.dp))
Text(
"Full Name (Optional)",
generalGetString(R.string.full_name_optional__prompt),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(bottom = 5.dp)
@@ -157,6 +158,6 @@ fun CreateProfilePanel(chatModel: ChatModel) {
}
},
enabled = (displayName.isNotEmpty() && isValidDisplayName(displayName))
) { Text("Create") }
) { Text(generalGetString(R.string.create_profile_button)) }
}
}
@@ -16,6 +16,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@@ -29,9 +30,9 @@ fun ChatInfoView(chatModel: ChatModel, close: () -> Unit) {
close = close,
deleteContact = {
AlertManager.shared.showAlertMsg(
title = "Delete contact?",
text = "Contact and all messages will be deleted - this cannot be undone!",
confirmText = "Delete",
title = generalGetString(R.string.delete_contact__question),
text = generalGetString(R.string.delete_contact_all_messages_deleted_cannot_undo_warning),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
val cInfo = chat.chatInfo
withApi {
@@ -96,7 +97,8 @@ fun ChatInfoLayout(chat: Chat, close: () -> Unit, deleteContact: () -> Unit) {
Box(Modifier.padding(48.dp)) {
SimpleButton(
"Delete contact", icon = Icons.Outlined.Delete,
generalGetString(R.string.button_delete_contact),
icon = Icons.Outlined.Delete,
color = Color.Red,
click = deleteContact
)
@@ -110,13 +112,13 @@ fun ServerImage(chat: Chat) {
val status = chat.serverInfo.networkStatus
when {
status is Chat.NetworkStatus.Connected ->
Icon(Icons.Filled.Circle, "Connected", tint = MaterialTheme.colors.primaryVariant)
Icon(Icons.Filled.Circle, generalGetString(R.string.icon_descr_server_status_connected), tint = MaterialTheme.colors.primaryVariant)
status is Chat.NetworkStatus.Disconnected ->
Icon(Icons.Filled.Pending, "Disconnected", tint = HighOrLowlight)
Icon(Icons.Filled.Pending, generalGetString(R.string.icon_descr_server_status_disconnected), tint = HighOrLowlight)
status is Chat.NetworkStatus.Error ->
Icon(Icons.Filled.Error, "Error", tint = HighOrLowlight)
Icon(Icons.Filled.Error, generalGetString(R.string.icon_descr_server_status_error), tint = HighOrLowlight)
else ->
Icon(Icons.Outlined.Circle, "Pending", tint = HighOrLowlight)
Icon(Icons.Outlined.Circle, generalGetString(R.string.icon_descr_server_status_pending), tint = HighOrLowlight)
}
}
@@ -22,13 +22,13 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.TAG
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.chat.item.ChatItemView
import chat.simplex.app.views.chatlist.openChat
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.ModalManager
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.*
@@ -169,7 +169,7 @@ fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) {
IconButton(onClick = back) {
Icon(
Icons.Outlined.ArrowBackIos,
"Back",
generalGetString(R.string.back),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
@@ -1,6 +1,6 @@
package chat.simplex.app.views.chat
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.*
import chat.simplex.app.model.*
import chat.simplex.app.views.helpers.ComposeLinkView
@@ -12,10 +12,12 @@ import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.CIDirection
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.*
import chat.simplex.app.views.helpers.generalGetString
import kotlinx.datetime.Clock
@Composable
@@ -50,7 +52,7 @@ fun ContextItemView(
}) {
Icon(
Icons.Outlined.Close,
contentDescription = "Cancel",
contentDescription = generalGetString(R.string.cancel_verb),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
@@ -1,7 +1,6 @@
package chat.simplex.app.views.chat
import android.content.res.Configuration
import android.util.Log
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
@@ -21,13 +20,12 @@ import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.TAG
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.*
import chat.simplex.app.views.helpers.getLinkPreview
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.delay
@Composable
@@ -129,7 +127,7 @@ fun SendMsgView(
val color = if (msg.value.isNotEmpty()) MaterialTheme.colors.primary else Color.Gray
Icon(
if (editing) Icons.Filled.Check else Icons.Outlined.ArrowUpward,
"Send Message",
generalGetString(R.string.icon_descr_send_message),
tint = Color.White,
modifier = Modifier
.size(36.dp)
@@ -12,9 +12,11 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimplexBlue
import chat.simplex.app.views.helpers.generalGetString
import kotlinx.datetime.Clock
@Composable
@@ -25,7 +27,7 @@ fun CIMetaView(chatItem: ChatItem) {
Icon(
Icons.Filled.Edit,
modifier = Modifier.height(12.dp).padding(end = 1.dp),
contentDescription = "Edited",
contentDescription = generalGetString(R.string.icon_descr_edited),
tint = HighOrLowlight,
)
}
@@ -45,16 +47,16 @@ fun CIMetaView(chatItem: ChatItem) {
fun CIStatusView(status: CIStatus) {
when (status) {
is CIStatus.SndSent -> {
Icon(Icons.Filled.Check, "sent", Modifier.height(12.dp), tint = HighOrLowlight)
Icon(Icons.Filled.Check, generalGetString(R.string.icon_descr_sent_msg_status_sent), Modifier.height(12.dp), tint = HighOrLowlight)
}
is CIStatus.SndErrorAuth -> {
Icon(Icons.Filled.Close, "unauthorized send", Modifier.height(12.dp), tint = Color.Red)
Icon(Icons.Filled.Close, generalGetString(R.string.icon_descr_sent_msg_status_unauthorized_send), Modifier.height(12.dp), tint = Color.Red)
}
is CIStatus.SndError -> {
Icon(Icons.Filled.WarningAmber, "send failed", Modifier.height(12.dp), tint = Color.Yellow)
Icon(Icons.Filled.WarningAmber, generalGetString(R.string.icon_descr_sent_msg_status_send_failed), Modifier.height(12.dp), tint = Color.Yellow)
}
is CIStatus.RcvNew -> {
Icon(Icons.Filled.Circle, "unread", Modifier.height(12.dp), tint = SimplexBlue)
Icon(Icons.Filled.Circle, generalGetString(R.string.icon_descr_received_msg_status_unread), Modifier.height(12.dp), tint = SimplexBlue)
}
else -> {}
}
@@ -16,8 +16,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
import kotlinx.datetime.Clock
@@ -55,21 +55,21 @@ fun ChatItemView(
}
if (cItem.isMsgContent) {
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
ItemAction("Reply", Icons.Outlined.Reply, onClick = {
ItemAction(generalGetString(R.string.reply_verb), Icons.Outlined.Reply, onClick = {
editingItem.value = null
quotedItem.value = cItem
showMenu = false
})
ItemAction("Share", Icons.Outlined.Share, onClick = {
ItemAction(generalGetString(R.string.share_verb), Icons.Outlined.Share, onClick = {
shareText(cxt, cItem.content.text)
showMenu = false
})
ItemAction("Copy", Icons.Outlined.ContentCopy, onClick = {
ItemAction(generalGetString(R.string.copy_verb), Icons.Outlined.ContentCopy, onClick = {
copyText(cxt, cItem.content.text)
showMenu = false
})
if (cItem.chatDir.sent && cItem.meta.editable) {
ItemAction("Edit", Icons.Filled.Edit, onClick = {
ItemAction(generalGetString(R.string.edit_verb), Icons.Filled.Edit, onClick = {
quotedItem.value = null
editingItem.value = cItem
msg.value = cItem.content.text
@@ -77,7 +77,7 @@ fun ChatItemView(
})
}
ItemAction(
"Delete",
generalGetString(R.string.delete_verb),
Icons.Outlined.Delete,
onClick = {
showMenu = false
@@ -109,8 +109,8 @@ private fun ItemAction(text: String, icon: ImageVector, onClick: () -> Unit, col
fun deleteMessageAlertDialog(chatItem: ChatItem, deleteMessage: (Long, CIDeleteMode) -> Unit) {
AlertManager.shared.showAlertDialogButtons(
title = "Delete message?",
text = "Message will be deleted - this cannot be undone!",
title = generalGetString(R.string.delete_message__question),
text = generalGetString(R.string.delete_message_cannot_be_undone_warning),
buttons = {
Row(
Modifier
@@ -121,13 +121,13 @@ fun deleteMessageAlertDialog(chatItem: ChatItem, deleteMessage: (Long, CIDeleteM
Button(onClick = {
deleteMessage(chatItem.id, CIDeleteMode.cidmInternal)
AlertManager.shared.hideAlert()
}) { Text("For me only") }
}) { Text(generalGetString(R.string.for_me_only)) }
// if (chatItem.meta.editable) {
// Spacer(Modifier.padding(horizontal = 4.dp))
// Button(onClick = {
// deleteMessage(chatItem.id, CIDeleteMode.cidmBroadcast)
// AlertManager.shared.hideAlert()
// }) { Text("For everyone") }
// }) { Text(generalGetString(R.string.for_everybody)) }
// }
}
}
@@ -1,9 +1,8 @@
package chat.simplex.app.views.chat.item
import android.content.res.Configuration
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.Composable
@@ -11,10 +10,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.tooling.preview.*
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.model.*
import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
@@ -1,4 +1,4 @@
package chat.simplex.app.views.chat
package chat.simplex.app.views.chatlist
import android.content.res.Configuration
import androidx.compose.foundation.clickable
@@ -10,11 +10,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.*
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.annotatedStringResource
import chat.simplex.app.views.helpers.generalGetString
import chat.simplex.app.views.usersettings.simplexTeamUri
val bold = SpanStyle(fontWeight = FontWeight.Bold)
@@ -27,18 +31,13 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
) {
val uriHandler = LocalUriHandler.current
Text("Thank you for installing SimpleX Chat!")
Text(generalGetString(R.string.thank_you_for_installing_simplex), lineHeight = 22.sp)
Text(
buildAnnotatedString {
append("You can ")
withStyle(SpanStyle(color = MaterialTheme.colors.primary)) {
append("connect to SimpleX Chat founder")
}
append(".")
},
annotatedStringResource(R.string.you_can_connect_to_simplex_chat_founder),
modifier = Modifier.clickable(onClick = {
uriHandler.openUri(simplexTeamUri)
})
}),
lineHeight = 22.sp
)
Column(
@@ -47,33 +46,24 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text(
"To start a new chat",
style = MaterialTheme.typography.h2
generalGetString(R.string.to_start_a_new_chat_help_header),
style = MaterialTheme.typography.h2,
lineHeight = 22.sp
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("Tap button")
Text(generalGetString(R.string.chat_help_tap_button))
Icon(
Icons.Outlined.PersonAdd,
"Add Contact",
generalGetString(R.string.add_contact),
modifier = if (addContact != null) Modifier.clickable(onClick = addContact) else Modifier,
)
Text("above, then:")
Text(generalGetString(R.string.above_then_preposition_continuation))
}
Text(
buildAnnotatedString {
withStyle(bold) { append("Add new contact") }
append(": to create your one-time QR Code for your contact.")
}
)
Text(
buildAnnotatedString {
withStyle(bold) { append("Scan QR code") }
append(": to connect to your contact who shows QR code to you.")
}
)
Text(annotatedStringResource(R.string.add_new_contact_to_create_one_time_QR_code), lineHeight = 22.sp)
Text(annotatedStringResource(R.string.scan_QR_code_to_connect_to_contact_who_shows_QR_code), lineHeight = 22.sp)
}
Column(
@@ -81,24 +71,10 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
Text("To connect via link", style = MaterialTheme.typography.h2)
Text("If you received SimpleX Chat invitation link you can open it in your browser:")
Text(
buildAnnotatedString {
append("\uD83D\uDCBB desktop: scan displayed QR code from the app, via ")
withStyle(bold) { append("Scan QR code") }
append(".")
}
)
Text(
buildAnnotatedString {
append("\uD83D\uDCF1 mobile: tap ")
withStyle(bold) { append("Open in mobile app") }
append(", then tap ")
withStyle(bold) { append("Connect") }
append(" in the app.")
}
)
Text(generalGetString(R.string.to_connect_via_link_title), style = MaterialTheme.typography.h2)
Text(generalGetString(R.string.if_you_received_simplex_invitation_link_you_can_open_in_browser), lineHeight = 22.sp)
Text(annotatedStringResource(R.string.desktop_scan_QR_code_from_app_via_scan_QR_code), lineHeight = 22.sp)
Text(annotatedStringResource(R.string.mobile_tap_open_in_mobile_app_then_tap_connect_in_app), lineHeight = 22.sp)
}
}
}
@@ -112,6 +88,6 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) {
@Composable
fun PreviewChatHelpLayout() {
SimpleXTheme {
ChatHelpView({})
ChatHelpView {}
}
}
@@ -11,11 +11,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.helpers.*
import kotlinx.datetime.Clock
@Composable
@@ -42,9 +41,9 @@ suspend fun openChat(chatModel: ChatModel, cInfo: ChatInfo) {
fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
AlertManager.shared.showAlertDialog(
title = "Accept connection request?",
text = "If you choose to reject sender will NOT be notified.",
confirmText = "Accept",
title = generalGetString(R.string.accept_connection_request__question),
text = generalGetString(R.string.if_you_choose_to_reject_the_sender_will_not_be_notified),
confirmText = generalGetString(R.string.accept_contact_button),
onConfirm = {
withApi {
val contact = chatModel.controller.apiAcceptContactRequest(contactRequest.apiId)
@@ -54,7 +53,7 @@ fun contactRequestAlertDialog(contactRequest: ChatInfo.ContactRequest, chatModel
}
}
},
dismissText = "Reject",
dismissText = generalGetString(R.string.reject_contact_button),
onDismiss = {
withApi {
chatModel.controller.apiRejectContactRequest(contactRequest.apiId)
@@ -14,11 +14,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.ToolbarDark
import chat.simplex.app.ui.theme.ToolbarLight
import chat.simplex.app.views.chat.ChatHelpView
import chat.simplex.app.views.newchat.ModalManager
import chat.simplex.app.views.helpers.ModalManager
import chat.simplex.app.views.helpers.generalGetString
import chat.simplex.app.views.newchat.NewChatSheet
import chat.simplex.app.views.usersettings.SettingsView
import kotlinx.coroutines.CoroutineScope
@@ -110,25 +111,28 @@ fun Help(scaffoldCtrl: ScaffoldController, displayName: String?) {
.fillMaxWidth()
.padding(16.dp)
) {
val welcomeMsg = if (displayName != null) {
String.format(generalGetString(R.string.personal_welcome), displayName)
} else generalGetString(R.string.welcome)
Text(
text = if (displayName != null) "Welcome ${displayName}!" else "Welcome!",
text = welcomeMsg,
Modifier.padding(bottom = 24.dp),
style = MaterialTheme.typography.h1,
color = MaterialTheme.colors.onBackground
)
ChatHelpView({ scaffoldCtrl.toggleSheet() })
ChatHelpView { scaffoldCtrl.toggleSheet() }
Row(
Modifier.padding(top = 30.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"This text is available in settings",
generalGetString(R.string.this_text_is_available_in_settings),
color = MaterialTheme.colors.onBackground
)
Icon(
Icons.Outlined.Settings,
"Settings",
generalGetString(R.string.icon_descr_settings),
tint = MaterialTheme.colors.onBackground,
modifier = Modifier.clickable(onClick = { scaffoldCtrl.toggleDrawer() })
)
@@ -150,13 +154,13 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) {
IconButton(onClick = { scaffoldCtrl.toggleDrawer() }) {
Icon(
Icons.Outlined.Menu,
"Settings",
generalGetString(R.string.icon_descr_settings),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
}
Text(
"Your chats",
generalGetString(R.string.your_chats),
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(5.dp)
@@ -164,7 +168,7 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) {
IconButton(onClick = { scaffoldCtrl.toggleSheet() }) {
Icon(
Icons.Outlined.PersonAdd,
"Add Contact",
generalGetString(R.string.add_contact),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
@@ -14,13 +14,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.Chat
import chat.simplex.app.model.getTimestampText
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.item.MarkdownText
import chat.simplex.app.views.helpers.ChatInfoImage
import chat.simplex.app.views.helpers.badgeLayout
import chat.simplex.app.views.helpers.*
@Composable
fun ChatPreviewView(chat: Chat) {
@@ -63,7 +63,7 @@ fun ChatPreviewView(chat: Chat) {
val n = chat.chatStats.unreadCount
if (n > 0) {
Text(
if (n < 1000) "$n" else "${n / 1000}k",
if (n < 1000) "$n" else "${n / 1000}" + generalGetString(R.string.thousand_abbreviation),
color = MaterialTheme.colors.onPrimary,
fontSize = 14.sp,
modifier = Modifier
@@ -8,10 +8,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.Chat
import chat.simplex.app.model.getTimestampText
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.views.helpers.ChatInfoImage
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun ContactRequestView(chat: Chat) {
@@ -31,7 +33,7 @@ fun ContactRequestView(chat: Chat) {
color = MaterialTheme.colors.primary
)
Text(
"wants to connect to you!",
generalGetString(R.string.contact_wants_to_connect_with_you),
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
@@ -4,6 +4,7 @@ import android.util.Log
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import chat.simplex.app.R
import chat.simplex.app.TAG
class AlertManager {
@@ -40,9 +41,9 @@ class AlertManager {
fun showAlertDialog(
title: String,
text: String? = null,
confirmText: String = "Ok",
confirmText: String = generalGetString(R.string.ok),
onConfirm: (() -> Unit)? = null,
dismissText: String = "Cancel",
dismissText: String = generalGetString(R.string.cancel_verb),
onDismiss: (() -> Unit)? = null
) {
val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) }
@@ -69,7 +70,7 @@ class AlertManager {
fun showAlertMsg(
title: String, text: String? = null,
confirmText: String = "Ok", onConfirm: (() -> Unit)? = null
confirmText: String = generalGetString(R.string.ok), onConfirm: (() -> Unit)? = null
) {
val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) }
showAlert {
@@ -17,6 +17,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.Chat
import chat.simplex.app.model.ChatInfo
import chat.simplex.app.ui.theme.SimpleXTheme
@@ -39,7 +40,7 @@ fun ProfileImage(
if (image == null) {
Icon(
icon,
contentDescription = "profile image placeholder",
contentDescription = generalGetString(R.string.icon_descr_profile_image_placeholder),
tint = MaterialTheme.colors.secondary,
modifier = Modifier.fillMaxSize()
)
@@ -47,7 +48,7 @@ fun ProfileImage(
val imageBitmap = base64ToBitmap(image).asImageBitmap()
Image(
imageBitmap,
"profile image",
generalGetString(R.string.image_descr_profile_image),
contentScale = ContentScale.Crop,
modifier = Modifier.size(size).padding(size / 12).clip(CircleShape)
)
@@ -10,6 +10,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.ui.theme.SimpleXTheme
@Composable
@@ -24,7 +25,7 @@ fun CloseSheetBar(close: () -> Unit) {
IconButton(onClick = close) {
Icon(
Icons.Outlined.Close,
"Close button",
generalGetString(R.string.icon_descr_close_button),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
@@ -27,8 +27,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import chat.simplex.app.BuildConfig
import chat.simplex.app.TAG
import chat.simplex.app.*
import chat.simplex.app.R
import chat.simplex.app.views.newchat.ActionButton
import java.io.ByteArrayOutputStream
import java.io.File
@@ -148,7 +148,7 @@ fun GetImageBottomSheet(
else galleryLauncher.launch("image/*")
hideBottomSheet()
} else {
Toast.makeText(context, "Permission Denied!", Toast.LENGTH_SHORT).show()
Toast.makeText(context, generalGetString(R.string.toast_camera_permission_denied), Toast.LENGTH_SHORT).show()
}
}
@@ -166,7 +166,7 @@ fun GetImageBottomSheet(
.padding(horizontal = 8.dp, vertical = 30.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
ActionButton(null, "Use Camera", icon = Icons.Outlined.PhotoCamera) {
ActionButton(null, generalGetString(R.string.use_camera_button), icon = Icons.Outlined.PhotoCamera) {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) -> {
cameraLauncher.launch(null)
@@ -178,7 +178,7 @@ fun GetImageBottomSheet(
}
}
}
ActionButton(null, "From Gallery", icon = Icons.Outlined.Collections) {
ActionButton(null, generalGetString(R.string.from_gallery_button), icon = Icons.Outlined.Collections) {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) -> {
galleryLauncher.launch("image/*")
@@ -17,6 +17,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.LinkPreview
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
@@ -72,7 +73,7 @@ fun ComposeLinkView(linkPreview: LinkPreview, cancelPreview: () -> Unit) {
val imageBitmap = base64ToBitmap(linkPreview.image).asImageBitmap()
Image(
imageBitmap,
"preview image",
generalGetString(R.string.image_descr_link_preview),
modifier = Modifier.width(80.dp).height(60.dp).padding(end = 8.dp)
)
Column(Modifier.fillMaxWidth().weight(1F)) {
@@ -85,7 +86,7 @@ fun ComposeLinkView(linkPreview: LinkPreview, cancelPreview: () -> Unit) {
IconButton(onClick = cancelPreview, modifier = Modifier.padding(0.dp)) {
Icon(
Icons.Outlined.Close,
contentDescription = "Cancel Preview",
contentDescription = generalGetString(R.string.icon_descr_cancel_link_preview),
tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(10.dp)
)
@@ -98,7 +99,7 @@ fun ChatItemLinkView(linkPreview: LinkPreview) {
Column {
Image(
base64ToBitmap(linkPreview.image).asImageBitmap(),
"link image",
generalGetString(R.string.image_descr_link_preview),
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth,
)
@@ -1,4 +1,4 @@
package chat.simplex.app.views.newchat
package chat.simplex.app.views.helpers
import android.util.Log
import androidx.activity.compose.BackHandler
@@ -11,7 +11,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import chat.simplex.app.TAG
import chat.simplex.app.views.helpers.CloseSheetBar
@Composable
fun ModalView(close: () -> Unit, content: @Composable () -> Unit) {
@@ -1,9 +1,23 @@
package chat.simplex.app.views.helpers
import android.content.res.Resources
import android.graphics.Rect
import android.graphics.Typeface
import android.text.Spanned
import android.text.SpannedString
import android.text.style.*
import android.view.ViewTreeObserver
import androidx.annotation.StringRes
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.*
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.style.BaselineShift
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.*
import androidx.core.text.HtmlCompat
import chat.simplex.app.SimplexApp
import kotlinx.coroutines.*
fun withApi(action: suspend CoroutineScope.() -> Unit): Job =
@@ -38,3 +52,146 @@ fun getKeyboardState(): State<KeyboardState> {
return keyboardState
}
// Resource to annotated string from
// https://stackoverflow.com/questions/68549248/android-jetpack-compose-how-to-show-styled-text-from-string-resources
fun generalGetString(id: Int) : String {
return SimplexApp.context.getString(id)
}
@Composable
@ReadOnlyComposable
private fun resources(): Resources {
LocalConfiguration.current
return LocalContext.current.resources
}
fun Spanned.toHtmlWithoutParagraphs(): String {
return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
.substringAfter("<p dir=\"ltr\">").substringBeforeLast("</p>")
}
fun Resources.getText(@StringRes id: Int, vararg args: Any): CharSequence {
val escapedArgs = args.map {
if (it is Spanned) it.toHtmlWithoutParagraphs() else it
}.toTypedArray()
val resource = SpannedString(getText(id))
val htmlResource = resource.toHtmlWithoutParagraphs()
val formattedHtml = String.format(htmlResource, *escapedArgs)
return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
val resources = resources()
val density = LocalDensity.current
return remember(id) {
val text = resources.getText(id)
spannableStringToAnnotatedString(text, density)
}
}
private fun spannableStringToAnnotatedString(
text: CharSequence,
density: Density,
): AnnotatedString {
return if (text is Spanned) {
with(density) {
buildAnnotatedString {
append((text.toString()))
text.getSpans(0, text.length, Any::class.java).forEach {
val start = text.getSpanStart(it)
val end = text.getSpanEnd(it)
when (it) {
is StyleSpan -> when (it.style) {
Typeface.NORMAL -> addStyle(
SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Normal,
),
start,
end
)
Typeface.BOLD -> addStyle(
SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Normal
),
start,
end
)
Typeface.ITALIC -> addStyle(
SpanStyle(
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic
),
start,
end
)
Typeface.BOLD_ITALIC -> addStyle(
SpanStyle(
fontWeight = FontWeight.Bold,
fontStyle = FontStyle.Italic
),
start,
end
)
}
is TypefaceSpan -> addStyle(
SpanStyle(
fontFamily = when (it.family) {
FontFamily.SansSerif.name -> FontFamily.SansSerif
FontFamily.Serif.name -> FontFamily.Serif
FontFamily.Monospace.name -> FontFamily.Monospace
FontFamily.Cursive.name -> FontFamily.Cursive
else -> FontFamily.Default
}
),
start,
end
)
is AbsoluteSizeSpan -> addStyle(
SpanStyle(fontSize = if (it.dip) it.size.dp.toSp() else it.size.toSp()),
start,
end
)
is RelativeSizeSpan -> addStyle(
SpanStyle(fontSize = it.sizeChange.em),
start,
end
)
is StrikethroughSpan -> addStyle(
SpanStyle(textDecoration = TextDecoration.LineThrough),
start,
end
)
is UnderlineSpan -> addStyle(
SpanStyle(textDecoration = TextDecoration.Underline),
start,
end
)
is SuperscriptSpan -> addStyle(
SpanStyle(baselineShift = BaselineShift.Superscript),
start,
end
)
is SubscriptSpan -> addStyle(
SpanStyle(baselineShift = BaselineShift.Subscript),
start,
end
)
is ForegroundColorSpan -> addStyle(
SpanStyle(color = Color(it.foregroundColor)),
start,
end
)
else -> addStyle(SpanStyle(color = Color.White), start, end)
}
}
}
}
} else {
AnnotatedString(text.toString())
}
}
@@ -10,15 +10,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.generalGetString
import chat.simplex.app.views.helpers.shareText
@Composable
@@ -42,11 +43,11 @@ fun AddContactLayout(connReq: String, share: () -> Unit) {
verticalArrangement = Arrangement.SpaceBetween,
) {
Text(
"Add contact",
generalGetString(R.string.add_contact),
style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
)
Text(
"Show QR code to your contact\nto scan from the app",
generalGetString(R.string.show_QR_code_for_your_contact_to_scan_from_the_app__multiline),
style = MaterialTheme.typography.h3,
textAlign = TextAlign.Center,
)
@@ -57,20 +58,14 @@ fun AddContactLayout(connReq: String, share: () -> Unit) {
.padding(vertical = 3.dp)
)
Text(
buildAnnotatedString {
append("If you cannot meet in person, you can ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append("scan QR code in the video call")
}
append(", or you can share the invitation link via any other channel.")
},
generalGetString(R.string.if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.caption.copy(fontSize=if(screenHeight > 600.dp) 20.sp else 16.sp),
lineHeight = 22.sp,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = if(screenHeight > 600.dp) 16.dp else 8.dp)
)
SimpleButton("Share invitation link", icon = Icons.Outlined.Share, click = share)
SimpleButton(generalGetString(R.string.share_invitation_link), icon = Icons.Outlined.Share, click = share)
Spacer(Modifier.height(10.dp))
}
}
@@ -8,15 +8,15 @@ import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.helpers.*
@Composable
fun ConnectContactView(chatModel: ChatModel, close: () -> Unit) {
@@ -31,8 +31,8 @@ fun ConnectContactView(chatModel: ChatModel, close: () -> Unit) {
}
} catch (e: RuntimeException) {
AlertManager.shared.showAlertMsg(
title = "Invalid QR code",
text = "This QR code is not a link!"
title = generalGetString(R.string.invalid_QR_code),
text = generalGetString(R.string.this_QR_code_is_not_a_link)
)
}
close()
@@ -48,8 +48,8 @@ fun withUriAction(uri: Uri, run: suspend (String) -> Unit) {
withApi { run(action) }
} else {
AlertManager.shared.showAlertMsg(
title = "Invalid link!",
text = "This link is not a valid connection link!"
title = generalGetString(R.string.invalid_contact_link),
text = generalGetString(R.string.this_link_is_not_a_valid_connection_link)
)
}
}
@@ -57,12 +57,11 @@ fun withUriAction(uri: Uri, run: suspend (String) -> Unit) {
suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri) {
val r = chatModel.controller.apiConnect(uri.toString())
if (r) {
val whenConnected =
if (action == "contact") "your connection request is accepted"
else "your contact's device is online"
AlertManager.shared.showAlertMsg(
title = "Connection request sent!",
text = "You will be connected when $whenConnected, please wait or check later!"
title = generalGetString(R.string.connection_request_sent),
text =
if (action == "contact") generalGetString(R.string.you_will_be_connected_when_your_connection_request_is_accepted)
else generalGetString(R.string.you_will_be_connected_when_your_contacts_device_is_online)
)
}
}
@@ -75,11 +74,11 @@ fun ConnectContactLayout(qrCodeScanner: @Composable () -> Unit, close: () -> Uni
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Scan QR code",
generalGetString(R.string.scan_QR_code),
style = MaterialTheme.typography.h1.copy(fontWeight = FontWeight.Normal),
)
Text(
"Your chat profile will be sent\nto your contact",
generalGetString(R.string.your_chat_profile_will_be_sent_to_your_contact),
style = MaterialTheme.typography.h3,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 4.dp)
@@ -90,18 +89,8 @@ fun ConnectContactLayout(qrCodeScanner: @Composable () -> Unit, close: () -> Uni
.aspectRatio(ratio = 1F)
) { qrCodeScanner() }
Text(
buildAnnotatedString {
append("If you cannot meet in person, you can ")
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
append("scan QR code in the video call")
}
append(", or you can create the invitation link.")
},
textAlign = TextAlign.Center,
style = MaterialTheme.typography.caption,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 4.dp)
annotatedStringResource(R.string.if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link),
lineHeight = 22.sp
)
}
}
@@ -14,11 +14,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chatlist.ScaffoldController
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.helpers.*
import com.google.accompanist.permissions.rememberPermissionState
@Composable
@@ -57,8 +58,10 @@ fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) {
.weight(1F)
.fillMaxWidth()) {
ActionButton(
"Add contact", "(create QR code\nor link)",
Icons.Outlined.PersonAdd, click = addContact
generalGetString(R.string.add_contact),
generalGetString(R.string.create_QR_code_or_link__bracketed__multiline),
Icons.Outlined.PersonAdd,
click = addContact
)
}
Box(
@@ -66,8 +69,10 @@ fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) {
.weight(1F)
.fillMaxWidth()) {
ActionButton(
"Scan QR code", "(in person or in video call)",
Icons.Outlined.QrCode, click = scanCode
generalGetString(R.string.scan_QR_code),
generalGetString(R.string.in_person_or_in_video_call__bracketed),
Icons.Outlined.QrCode,
click = scanCode
)
}
Box(
@@ -75,8 +80,10 @@ fun NewChatSheetLayout(addContact: () -> Unit, scanCode: () -> Unit) {
.weight(1F)
.fillMaxWidth()) {
ActionButton(
"Create Group", "(coming soon!)",
Icons.Outlined.GroupAdd, disabled = true
generalGetString(R.string.create_group),
generalGetString(R.string.coming_soon__bracketed),
Icons.Outlined.GroupAdd,
disabled = true
)
}
}
@@ -7,7 +7,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.tooling.preview.Preview
import chat.simplex.app.R
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.generalGetString
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
@@ -16,7 +18,7 @@ import com.google.zxing.qrcode.QRCodeWriter
fun QRCode(connReq: String, modifier: Modifier = Modifier) {
Image(
bitmap = qrCodeBitmap(connReq, 1024).asImageBitmap(),
contentDescription = "QR Code",
contentDescription = generalGetString(R.string.image_descr_qr_code),
modifier = modifier
)
}
@@ -3,6 +3,8 @@ package chat.simplex.app.views.usersettings
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -10,9 +12,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chat.ChatHelpView
import chat.simplex.app.views.chatlist.ChatHelpView
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun HelpView(chatModel: ChatModel) {
@@ -24,9 +28,12 @@ fun HelpView(chatModel: ChatModel) {
@Composable
fun HelpLayout(displayName: String) {
Column(horizontalAlignment = Alignment.Start) {
Column(
Modifier.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.Start
){
Text(
"Welcome $displayName!",
String.format(generalGetString(R.string.personal_welcome), displayName),
Modifier.padding(bottom = 24.dp),
style = MaterialTheme.typography.h1,
)
@@ -10,29 +10,38 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.R
import chat.simplex.app.model.Format
import chat.simplex.app.model.FormatColor
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.generalGetString
@Composable
fun MarkdownHelpView() {
Column {
Text(
"How to use markdown",
generalGetString(R.string.how_to_use_markdown),
style = MaterialTheme.typography.h1,
)
Text(
"You can use markdown to format messages:",
generalGetString(R.string.you_can_use_markdown_to_format_messages__prompt),
Modifier.padding(vertical = 16.dp)
)
MdFormat("*bold*", "bold", Format.Bold())
MdFormat("_italic_", "italic", Format.Italic())
MdFormat("~strike~", "strike", Format.StrikeThrough())
MdFormat("`a + b`", "a + b", Format.Snippet())
val bold = generalGetString(R.string.bold)
val italic = generalGetString(R.string.italic)
val strikethrough = generalGetString(R.string.strikethrough)
val equation = generalGetString(R.string.a_plus_b)
val colored = generalGetString(R.string.colored)
val secret = generalGetString(R.string.secret)
MdFormat("*$bold*", bold, Format.Bold())
MdFormat("_${italic}_", italic, Format.Italic())
MdFormat("~$strikethrough~", strikethrough, Format.StrikeThrough())
MdFormat("`$equation`", equation, Format.Snippet())
Row {
MdSyntax("!1 colored!")
MdSyntax("!1 $colored!")
Text(buildAnnotatedString {
withStyle(Format.Colored(FormatColor.red).style) { append("colored") }
withStyle(Format.Colored(FormatColor.red).style) { append(colored) }
append(" (")
appendColor(this, "1", FormatColor.red, ", ")
appendColor(this, "2", FormatColor.green, ", ")
@@ -43,10 +52,10 @@ fun MarkdownHelpView() {
})
}
Row {
MdSyntax("#secret#")
MdSyntax("#$secret#")
SelectionContainer {
Text(buildAnnotatedString {
withStyle(Format.Secret().style) { append("secret") }
withStyle(Format.Secret().style) { append(secret) }
})
}
}
@@ -56,7 +65,7 @@ fun MarkdownHelpView() {
@Composable
fun MdSyntax(markdown: String) {
Text(markdown, Modifier
.width(100.dp)
.width(120.dp)
.padding(bottom = 4.dp))
}
@@ -20,11 +20,11 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.AlertManager
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.helpers.*
@Composable
fun SMPServersView(chatModel: ChatModel) {
@@ -60,9 +60,9 @@ fun SMPServersView(chatModel: ChatModel) {
if (userSMPServers != null) {
if (userSMPServers.isNotEmpty()) {
AlertManager.shared.showAlertMsg(
title = "Use SimpleX Chat servers?",
text = "Saved SMP servers will be removed.",
confirmText = "Confirm",
title = generalGetString(R.string.use_simplex_chat_servers__question),
text = generalGetString(R.string.saved_SMP_servers_will_br_removed),
confirmText = generalGetString(R.string.confirm_verb),
onConfirm = {
saveSMPServers(listOf())
isUserSMPServers = false
@@ -108,14 +108,14 @@ fun SMPServersLayout(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Your SMP servers",
generalGetString(R.string.your_SMP_servers),
Modifier.padding(bottom = 24.dp),
style = MaterialTheme.typography.h1
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text("Configure SMP servers", Modifier.padding(end = 24.dp))
Text(generalGetString(R.string.configure_SMP_servers), Modifier.padding(end = 24.dp))
Switch(
checked = isUserSMPServers,
onCheckedChange = isUserSMPServersOnOff,
@@ -127,9 +127,9 @@ fun SMPServersLayout(
}
if (!isUserSMPServers) {
Text("Using SimpleX Chat servers.")
Text(generalGetString(R.string.using_simplex_chat_servers), lineHeight = 22.sp)
} else {
Text("Enter one SMP server per line:")
Text(generalGetString(R.string.enter_one_SMP_server_per_line))
if (editSMPServers) {
BasicTextField(
value = userSMPServersStr,
@@ -173,14 +173,14 @@ fun SMPServersLayout(
Column(horizontalAlignment = Alignment.Start) {
Row {
Text(
"Cancel",
generalGetString(R.string.cancel_verb),
color = MaterialTheme.colors.primary,
modifier = Modifier
.clickable(onClick = cancelEdit)
)
Spacer(Modifier.padding(horizontal = 8.dp))
Text(
"Save",
generalGetString(R.string.save_servers_button),
color = MaterialTheme.colors.primary,
modifier = Modifier.clickable(onClick = {
val servers = userSMPServersStr.split("\n")
@@ -219,7 +219,7 @@ fun SMPServersLayout(
) {
Column(horizontalAlignment = Alignment.Start) {
Text(
"Edit",
generalGetString(R.string.edit_verb),
color = MaterialTheme.colors.primary,
modifier = Modifier
.clickable(onClick = editOn)
@@ -241,9 +241,9 @@ fun howToButton() {
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable { uriHandler.openUri("https://github.com/simplex-chat/simplexmq#using-smp-server-and-smp-agent") }
) {
Text("How to", color = MaterialTheme.colors.primary)
Text(generalGetString(R.string.how_to), color = MaterialTheme.colors.primary)
Icon(
Icons.Outlined.OpenInNew, "How to", tint = MaterialTheme.colors.primary,
Icons.Outlined.OpenInNew, generalGetString(R.string.how_to), tint = MaterialTheme.colors.primary,
modifier = Modifier.padding(horizontal = 5.dp)
)
}
@@ -12,7 +12,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
@@ -24,8 +23,7 @@ import chat.simplex.app.model.Profile
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.TerminalView
import chat.simplex.app.views.helpers.ProfileImage
import chat.simplex.app.views.newchat.ModalManager
import chat.simplex.app.views.helpers.*
@Composable
fun SettingsView(chatModel: ChatModel) {
@@ -71,7 +69,7 @@ fun SettingsLayout(
.padding(top = 16.dp)
) {
Text(
"Your settings",
generalGetString(R.string.your_settings),
style = MaterialTheme.typography.h1,
modifier = Modifier.padding(start = 8.dp)
)
@@ -93,39 +91,39 @@ fun SettingsLayout(
SettingsSectionView(showModal { UserAddressView(it) }) {
Icon(
Icons.Outlined.QrCode,
contentDescription = "Address",
contentDescription = generalGetString(R.string.icon_descr_address),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text("Your SimpleX contact address")
Text(generalGetString(R.string.your_simplex_contact_address))
}
Spacer(Modifier.height(24.dp))
SettingsSectionView(showModal { HelpView(it) }) {
Icon(
Icons.Outlined.HelpOutline,
contentDescription = "Chat help",
contentDescription = generalGetString(R.string.icon_descr_help),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text("How to use SimpleX Chat")
Text(generalGetString(R.string.how_to_use_simplex_chat))
}
Divider(Modifier.padding(horizontal = 8.dp))
SettingsSectionView(showModal { MarkdownHelpView() }) {
Icon(
Icons.Outlined.TextFormat,
contentDescription = "Markdown help",
contentDescription = generalGetString(R.string.markdown_help),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text("Markdown in messages")
Text(generalGetString(R.string.markdown_in_messages))
}
Divider(Modifier.padding(horizontal = 8.dp))
SettingsSectionView({ uriHandler.openUri(simplexTeamUri) }) {
Icon(
Icons.Outlined.Tag,
contentDescription = "SimpleX Team",
contentDescription = generalGetString(R.string.icon_descr_simplex_team),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
"Chat with the founder",
generalGetString(R.string.chat_with_the_founder),
color = MaterialTheme.colors.primary
)
}
@@ -133,11 +131,11 @@ fun SettingsLayout(
SettingsSectionView({ uriHandler.openUri("mailto:chat@simplex.chat") }) {
Icon(
Icons.Outlined.Email,
contentDescription = "Email",
contentDescription = generalGetString(R.string.icon_descr_email),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
"Send us email",
generalGetString(R.string.send_us_an_email),
color = MaterialTheme.colors.primary
)
}
@@ -146,19 +144,20 @@ fun SettingsLayout(
SettingsSectionView(showModal { SMPServersView(it) }) {
Icon(
Icons.Outlined.Dns,
contentDescription = "SMP servers",
contentDescription = generalGetString(R.string.smp_servers),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text("SMP servers")
Text(generalGetString(R.string.smp_servers))
}
Divider(Modifier.padding(horizontal = 8.dp))
SettingsSectionView() {
Icon(
Icons.Outlined.Bolt,
contentDescription = "Private notifications",
contentDescription = generalGetString(R.string.private_notifications),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text("Private notifications", Modifier
Text(
generalGetString(R.string.private_notifications), Modifier
.padding(end = 24.dp)
.fillMaxWidth()
.weight(1F))
@@ -176,10 +175,10 @@ fun SettingsLayout(
SettingsSectionView(showTerminal) {
Icon(
painter = painterResource(id = R.drawable.ic_outline_terminal),
contentDescription = "Chat console",
contentDescription = generalGetString(R.string.chat_console),
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text("Chat console")
Text(generalGetString(R.string.chat_console))
}
Divider(Modifier.padding(horizontal = 8.dp))
SettingsSectionView({ uriHandler.openUri("https://github.com/simplex-chat/simplex-chat") }) {
@@ -188,14 +187,7 @@ fun SettingsLayout(
contentDescription = "GitHub",
)
Spacer(Modifier.padding(horizontal = 4.dp))
Text(
buildAnnotatedString {
append("Install ")
withStyle(SpanStyle(color = MaterialTheme.colors.primary)) {
append("SimpleX Chat for terminal")
}
}
)
Text(annotatedStringResource(R.string.install_simplex_chat_for_terminal))
}
Divider(Modifier.padding(horizontal = 8.dp))
SettingsSectionView() {
@@ -13,6 +13,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.ui.theme.SimpleButton
import chat.simplex.app.ui.theme.SimpleXTheme
@@ -32,9 +34,9 @@ fun UserAddressView(chatModel: ChatModel) {
share = { userAddress: String -> shareText(cxt, userAddress) },
deleteAddress = {
AlertManager.shared.showAlertMsg(
title = "Delete address?",
text = "All your contacts will remain connected.",
confirmText = "Delete",
title = generalGetString(R.string.delete_address__question),
text = generalGetString(R.string.all_your_contacts_will_remain_connected),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
chatModel.controller.apiDeleteUserAddress()
@@ -58,14 +60,14 @@ fun UserAddressLayout(
verticalArrangement = Arrangement.Top
) {
Text(
"Your chat address",
generalGetString(R.string.your_chat_address),
Modifier.padding(bottom = 16.dp),
style = MaterialTheme.typography.h1,
)
Text(
"You can share your address as a link or as a QR code - anybody will be able to connect to you, " +
"and if you later delete it - you won't lose your contacts.",
generalGetString(R.string.you_can_share_your_address_anybody_will_be_able_to_connect),
Modifier.padding(bottom = 12.dp),
lineHeight = 22.sp
)
Column(
Modifier.fillMaxWidth(),
@@ -73,7 +75,12 @@ fun UserAddressLayout(
verticalArrangement = Arrangement.SpaceEvenly
) {
if (userAddress == null) {
SimpleButton("Create address", icon = Icons.Outlined.QrCode, click = createAddress)
Text(
generalGetString(R.string.if_you_delete_address_you_wont_lose_contacts),
Modifier.padding(bottom = 12.dp),
lineHeight = 22.sp
)
SimpleButton(generalGetString(R.string.create_address), icon = Icons.Outlined.QrCode, click = createAddress)
} else {
QRCode(userAddress, Modifier.weight(1f, fill = false).aspectRatio(1f))
Row(
@@ -82,11 +89,11 @@ fun UserAddressLayout(
modifier = Modifier.padding(vertical = 10.dp)
) {
SimpleButton(
"Share link",
generalGetString(R.string.share_link),
icon = Icons.Outlined.Share,
click = { share(userAddress) })
SimpleButton(
"Delete address",
generalGetString(R.string.delete_address),
icon = Icons.Outlined.Delete,
color = Color.Red,
click = deleteAddress
@@ -19,11 +19,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Profile
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.helpers.*
import chat.simplex.app.views.newchat.ModalView
import com.google.accompanist.insets.ProvideWindowInsets
import com.google.accompanist.insets.navigationBarsWithImePadding
import kotlinx.coroutines.launch
@@ -89,16 +90,16 @@ fun UserProfileLayout(
horizontalAlignment = Alignment.Start
) {
Text(
"Your chat profile",
generalGetString(R.string.your_chat_profile),
Modifier.padding(bottom = 24.dp),
style = MaterialTheme.typography.h1,
color = MaterialTheme.colors.onBackground
)
Text(
"Your profile is stored on your device and shared only with your contacts.\n\n" +
"SimpleX servers cannot see your profile.",
generalGetString(R.string.your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it),
Modifier.padding(bottom = 24.dp),
color = MaterialTheme.colors.onBackground
color = MaterialTheme.colors.onBackground,
lineHeight = 22.sp
)
if (editProfile.value) {
Column(
@@ -124,14 +125,14 @@ fun UserProfileLayout(
ProfileNameTextField(displayName)
ProfileNameTextField(fullName)
Row {
TextButton("Cancel") {
TextButton(generalGetString(R.string.cancel_verb)) {
displayName.value = profile.displayName
fullName.value = profile.fullName
profileImage.value = profile.image
editProfile.value = false
}
Spacer(Modifier.padding(horizontal = 8.dp))
TextButton("Save (and notify contacts)") {
TextButton(generalGetString(R.string.save_and_notify_contacts)) {
saveProfile(displayName.value, fullName.value, profileImage.value)
}
}
@@ -154,9 +155,9 @@ fun UserProfileLayout(
}
}
}
ProfileNameRow("Display name:", profile.displayName)
ProfileNameRow("Full name:", profile.fullName)
TextButton("Edit") { editProfile.value = true }
ProfileNameRow(generalGetString(R.string.display_name__field), profile.displayName)
ProfileNameRow(generalGetString(R.string.full_name__field), profile.fullName)
TextButton(generalGetString(R.string.edit_verb)) { editProfile.value = true }
}
}
if (savedKeyboardState != keyboardState) {
@@ -223,7 +224,7 @@ fun EditImageButton(click: () -> Unit) {
) {
Icon(
Icons.Outlined.PhotoCamera,
contentDescription = "Edit image",
contentDescription = generalGetString(R.string.edit_image),
tint = MaterialTheme.colors.primary,
modifier = Modifier.size(36.dp)
)
@@ -235,7 +236,7 @@ fun DeleteImageButton(click: () -> Unit) {
IconButton(onClick = click) {
Icon(
Icons.Outlined.Close,
contentDescription = "Delete image",
contentDescription = generalGetString(R.string.delete_image),
tint = MaterialTheme.colors.primary,
)
}
@@ -0,0 +1,218 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name"><xliff:g id="appName">SimpleX</xliff:g></string>
<string name="thousand_abbreviation">т</string>
<!-- Connect via Link - MainActivity.kt -->
<string name="connect_via_contact_link">Соединиться через ссылку-контакт?</string>
<string name="connect_via_invitation_link">Соединиться через ссылку-приглашение?</string>
<string name="profile_will_be_sent_to_contact_sending_link">Ваш профиль будет отправлен контакту, от которого вы получили эту ссылку.</string>
<string name="connect_via_link_verb">Соединиться</string>
<!-- Server info - ChatModel.kt -->
<string name="server_connected">Соединение установлено</string>
<string name="server_connecting">Соединение устанавливается…</string>
<string name="connected_to_server_to_receive_messages_from_contact">Установлено соединение с сервером, через который вы получаете сообщения от этого контакта.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Устанавливается соединение с сервером, через который вы получаете сообщения от этого контакта (ошибка: <xliff:g id="errorMsg">%1$s</xliff:g>).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Устанавливается соединение с сервером, через который вы получаете сообщения от этого контакта.</string>
<!-- Item Content - ChatModel.kt -->
<string name="deleted_description">удалено</string>
<string name="sending_files_not_yet_supported">отправка файлов не поддерживается</string>
<string name="receiving_files_not_yet_supported">получение файлов не поддерживается</string>
<string name="sender_you_pronoun">вы</string>
<string name="unknown_message_format">неизвестный формат сообщения</string>
<string name="invalid_message_format">неверный формат сообщения</string>
<!-- SMP Server Information - SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Ошибка при сохранении SMP серверов</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Пожалуйста, проверьте, что адреса SMP серверов имеют правильный формат, каждый адрес на отдельной строке и не повторяется.</string>
<!-- API Error Responses - SimpleXAPI.kt -->
<string name="contact_already_exists">Существующий контакт</string>
<string name="you_are_already_connected_to_vName_via_this_link">Вы уже соединены с <xliff:g id="contactName" example="Alice">%1$s!</xliff:g> через эту ссылку.</string>
<string name="invalid_connection_link">Ошибка в ссылке контакта</string>
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Пожалуйста, проверьте, что вы использовали правильную ссылку, или попросите ваш контакт отправить вам новую.</string>
<string name="cannot_delete_contact">Невозможно удалить контакт!</string>
<string name="contact_cannot_be_deleted_as_they_are_in_groups">Контакт <xliff:g id="contactName" example="Jane Doe">%1$s!</xliff:g> не может быть удален, так как является членом групп(ы) <xliff:g id="groups" example="[team, chess club]">%2$s</xliff:g>.</string>
<string name="icon_descr_instant_notifications">Мгновенные уведомления</string>
<!-- background service notice - SimpleXAPI.kt -->
<string name="private_instant_notifications">Приватные мгновенные уведомления!</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">Чтобы защитить ваши личные данные, вместо уведомлений от сервера приложение запускает <b>фоновый сервис <xliff:g id="appName">SimpleX</xliff:g></b>, который потребляет несколько процентов батареи в день.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>Он может быть выключен через Настройки</b> – вы продолжите получать уведомления о сообщениях пока приложение запущено.</string>
<!-- SimpleX Chat foreground Service -->
<string name="simplex_service_notification_title"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> сервис</string>
<string name="simplex_service_notification_text">Приём сообщений…</string>
<!-- Chat Actions - ChatItemView.kt (and general) -->
<string name="reply_verb">Ответить</string>
<string name="share_verb">Поделиться</string>
<string name="copy_verb">Скопировать</string>
<string name="edit_verb">Редактировать</string>
<string name="delete_verb">Удалить</string>
<string name="delete_message__question">Удалить сообщение?</string>
<string name="delete_message_cannot_be_undone_warning">Сообщение будет удалено – это действие нельзя отменить!</string>
<string name="for_me_only">Только для меня</string>
<string name="for_everybody">Для всех</string>
<!-- CIMetaView.kt -->
<string name="icon_descr_edited">отредактировано</string>
<string name="icon_descr_sent_msg_status_sent">отправлено</string>
<string name="icon_descr_sent_msg_status_unauthorized_send">ошибка авторизации при отправке</string>
<string name="icon_descr_sent_msg_status_send_failed">ошибка при отправке</string>
<string name="icon_descr_received_msg_status_unread">не прочитано</string>
<!-- ChatListView.kt -->
<string name="personal_welcome">Здравствуйте <xliff:g>%1$s</xliff:g>!</string>
<string name="welcome">Здравствуйте!</string>
<string name="this_text_is_available_in_settings">Этот текст можно найти в Настройках</string>
<string name="your_chats">Ваши чаты</string>
<!-- Chat Info Actions - ChatInfoView.kt -->
<string name="delete_contact__question">Удалить контакт?</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Контакт и все сообщения будут удалены - это действие нельзя отменить!</string>
<string name="button_delete_contact">Удалить контакт</string>
<string name="icon_descr_server_status_connected">Соединение с сервером установлено</string>
<string name="icon_descr_server_status_disconnected">Соединение с сервером не установлено</string>
<string name="icon_descr_server_status_error">Ошибка соединения с сервером</string>
<string name="icon_descr_server_status_pending">Ожидается соединение с сервером</string>
<!-- Message Actions - SendMsgView.kt -->
<string name="icon_descr_send_message">Отправить сообщение</string>
<!-- General Actions / Responses -->
<string name="back">Назад</string>
<string name="cancel_verb">Отменить</string>
<string name="confirm_verb">Подтвердить</string>
<string name="ok"></string>
<string name="no_details">нет описания</string>
<string name="add_contact">Добавить контакт</string>
<string name="scan_QR_code">Сканировать QR код</string>
<!-- NewChatSheet -->
<string name="create_QR_code_or_link__bracketed__multiline">(создать QR код или ссылку)</string>
<string name="in_person_or_in_video_call__bracketed">(при встрече или через видео звонок)</string>
<string name="create_group">Создать группу</string>
<string name="coming_soon__bracketed">(скоро!)</string>
<!-- GetImageView -->
<string name="toast_camera_permission_denied">Разрешение не получено!</string>
<string name="use_camera_button">Использовать камеру</string>
<string name="from_gallery_button">Открыть галерею</string>
<!-- help - ChatHelpView.kt -->
<string name="thank_you_for_installing_simplex">Спасибо что установили <xliff:g id="appNameFull">SimpleX Chat</xliff:g>!</string>
<string name="you_can_connect_to_simplex_chat_founder">Вы можете <font color="#0088ff">соединиться с разработчиками</font>, чтобы задать любые вопросы или получать уведомления о новых версиях.</string>
<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"><b>Добавить новый контакт</b>: чтобы создать одноразовый QR код/ссылку для вашего контакта.</string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><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">Если вы получили ссылку с приглашением из <xliff:g id="appName">SimpleX Chat</xliff:g>, вы можете открыть ее в браузере:</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code">💻 на компьютере: сосканируйте показанный QR код из приложения через <b>Сканировать QR код</b>.</string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app">📱 на мобильном: намжите кнопку <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>
<string name="accept_contact_button">Принять</string>
<string name="reject_contact_button">Отклонить</string>
<!-- Contact Request Information - ContactRequestView.kt -->
<string name="contact_wants_to_connect_with_you">хочет соединиться с вами!</string>
<!-- Image Placeholder - ChatInfoImage.kt -->
<string name="icon_descr_profile_image_placeholder">аватар не установлен</string>
<string name="image_descr_profile_image">аватар</string>
<!-- Content Descriptions -->
<string name="icon_descr_close_button">закрыть</string>
<string name="image_descr_link_preview">изображение превью ссылки</string>
<string name="icon_descr_cancel_link_preview">удалить превью ссылки</string>
<string name="icon_descr_settings">Настройки</string>
<string name="image_descr_qr_code">QR код</string>
<string name="icon_descr_address"><xliff:g id="appName">SimpleX</xliff:g> адрес</string>
<string name="icon_descr_help">Помощь</string>
<string name="icon_descr_simplex_team"><xliff:g id="appName">SimpleX</xliff:g> команда</string>
<string name="image_descr_simplex_logo"><xliff:g id="appName">SimpleX</xliff:g> логотип</string>
<string name="icon_descr_email">Email</string>
<!-- Add Contact - AddContactView.kt -->
<string name="invalid_QR_code">Неверный QR код</string>
<string name="this_QR_code_is_not_a_link">Этот QR код не является ссылкой!</string>
<string name="invalid_contact_link">Неверная ссылка!</string>
<string name="this_link_is_not_a_valid_connection_link">Эта ссылка не является ссылкой-приглашением!</string>
<string name="connection_request_sent">Запрос на соединение послан!</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Соединение будет установлено когда ваш запрос будет принят. Пожалуйста, подождите или проверьте позже!</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">Соединение будет установлено когда ваш контакт будет онлайн. Пожалуйста, подождите или проверьте позже!</string>
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">Покажите QR код вашему контакту, чтобы сосканировать его из приложения</string>
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel">Если вы не можете встретиться лично, вы можете <b>показать QR код во время видео звонка</b> или отправить ссылку через любой другой канал связи.</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Ваш профиль будет отправлен\nвашему контакту</string>
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">Если вы не можете встретиться лично, вы можете <b>сосканировать QR код во время видео звонка</b>, или ваш контакт может отправить вам ссылку.</string>
<string name="share_invitation_link">Поделиться ссылкой</string>
<!-- settings - SettingsView.kt -->
<string name="your_settings">Настройки</string>
<string name="your_simplex_contact_address">Ваш <xliff:g id="appName">SimpleX</xliff:g> адрес</string>
<string name="how_to_use_simplex_chat">Как использовать <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
<string name="markdown_help">Форматирование сообщений</string>
<string name="markdown_in_messages">Форматирование сообщений</string>
<string name="chat_with_the_founder">Соединиться с разработчиками</string>
<string name="send_us_an_email">Отправить email</string>
<string name="private_notifications">Приватные уведомления</string>
<string name="chat_console">Консоль</string>
<string name="smp_servers">SMP серверы</string>
<string name="install_simplex_chat_for_terminal"><font color="#0088ff"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> для терминала</font></string>
<string name="use_simplex_chat_servers__question">Использовать серверы предосталенные <xliff:g id="appNameFull">SimpleX Chat</xliff:g>?</string>
<string name="saved_SMP_servers_will_br_removed">Сохраненные SMP серверы будут удалены.</string>
<string name="your_SMP_servers">Ваши SMP серверы</string>
<string name="configure_SMP_servers">Настройка SMP серверов</string>
<string name="using_simplex_chat_servers">Используются серверы предоставленные <xliff:g id="appNameFull">SimpleX Chat</xliff:g>.</string>
<string name="enter_one_SMP_server_per_line">Введите SMP серверы, каждый сервер в отдельной строке:</string>
<string name="how_to">Информация</string>
<string name="save_servers_button">Сохранить</string>
<!-- Address Items - UserAddressView.kt -->
<string name="create_address">Создать адрес</string>
<string name="delete_address__question">Удалить адрес?</string>
<string name="all_your_contacts_will_remain_connected">Все контакты, которые соединились через этот адрес, сохранятся.</string>
<string name="your_chat_address">Ваш <xliff:g id="appName">SimpleX</xliff:g> адрес</string>
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">Вы можете использовать адрес как ссылку или как QR код - через него можно с вами соединиться.</string>
<string name="if_you_delete_address_you_wont_lose_contacts">Вы сможете удалить адрес, сохранив контакты, которые через него соединились.</string>
<string name="share_link">Поделиться\nссылкой</string>
<string name="delete_address">Удалить\nадрес</string>
<!-- User profile details - UserProfileView.kt -->
<string name="display_name__field">Имя профиля:</string>
<string name="full_name__field">"Полное имя:</string>
<string name="your_chat_profile">Ваш профиль</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам.\n\n<xliff:g id="appName">SimpleX</xliff:g> серверы не могут получить доступ к вашему профилю.</string>
<string name="edit_image">Поменять аватар</string>
<string name="delete_image">Удалить аватар</string>
<string name="save_and_notify_contacts">Сохранить (и послать обновление контактам)</string>
<!-- Welcome Prompts - WelcomeView.kt -->
<string name="you_control_your_chat">Вы котролируете ваш чат!</string>
<string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">Платформа для сообщений и приложений, которая защищает вашу личную информацию и безопасность.</string>
<string name="we_do_not_store_contacts_or_messages_on_servers">Мы не храним ваши контакты и сообщения (после доставки) на серверах.</string>
<string name="create_profile">Создать профиль</string>
<string name="your_profile_is_stored_on_your_decide_and_shared_only_with_your_contacts">Ваш профиль хранится на вашем устройстве и отправляется только вашим контактам.</string>
<string name="display_name_cannot_contain_whitespace">Имя профиля не может содержать пробелы.</string>
<string name="display_name">Имя профиля</string>
<string name="full_name_optional__prompt">Полное имя (не обязательно)</string>
<string name="create_profile_button">Создать</string>
<!-- markdown demo - MarkdownHelpView.kt -->
<string name="how_to_use_markdown">Как форматировать</string>
<string name="you_can_use_markdown_to_format_messages__prompt">Вы можете форматировать сообщения:</string>
<string name="bold">жирный</string>
<string name="italic">курсив</string>
<string name="strikethrough">зачеркнуть</string>
<string name="a_plus_b">a + b</string>
<string name="colored">цвет</string>
<string name="secret">секрет</string>
</resources>
@@ -1,7 +1,218 @@
<resources>
<string name="app_name">SimpleX</string>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name"><xliff:g id="appName">SimpleX</xliff:g></string>
<string name="thousand_abbreviation">k</string>
<!-- Connect via Link - MainActivity.kt -->
<string name="connect_via_contact_link">Connect via contact link?</string>
<string name="connect_via_invitation_link">Connect via invitation link?</string>
<string name="profile_will_be_sent_to_contact_sending_link">Your profile will be sent to the contact that you received this link from.</string>
<string name="connect_via_link_verb">Connect</string>
<!-- Server info - ChatModel.kt -->
<string name="server_connected">Server connected</string>
<string name="server_connecting">Connecting server…</string>
<string name="connected_to_server_to_receive_messages_from_contact">You are connected to the server used to receive messages from this contact.</string>
<string name="trying_to_connect_to_server_to_receive_messages_with_error">Trying to connect to the server used to receive messages from this contact (error: <xliff:g id="errorMsg">%1$s</xliff:g>).</string>
<string name="trying_to_connect_to_server_to_receive_messages">Trying to connect to the server used to receive messages from this contact.</string>
<!-- Item Content - ChatModel.kt -->
<string name="deleted_description">deleted</string>
<string name="sending_files_not_yet_supported">sending files is not supported yet</string>
<string name="receiving_files_not_yet_supported">receiving files is not supported yet</string>
<string name="sender_you_pronoun">you</string>
<string name="unknown_message_format">unknown message format</string>
<string name="invalid_message_format">invalid message format</string>
<!-- SMP Server Information - SimpleXAPI.kt -->
<string name="error_saving_smp_servers">Error saving SMP servers</string>
<string name="ensure_smp_server_address_are_correct_format_and_unique">Make sure SMP server addresses are in correct format, line separated and are not duplicated.</string>
<!-- API Error Responses - SimpleXAPI.kt -->
<string name="contact_already_exists">Contact already exists</string>
<string name="you_are_already_connected_to_vName_via_this_link">You are already connected to <xliff:g id="contactName" example="Alice">%1$s!</xliff:g> via this link.</string>
<string name="invalid_connection_link">Invalid connection link</string>
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Please check that you used the correct link or ask your contact to send you another one.</string>
<string name="cannot_delete_contact">Can\'t delete contact!</string>
<string name="contact_cannot_be_deleted_as_they_are_in_groups">Contact <xliff:g id="contactName" example="Jane Doe">%1$s!</xliff:g> cannot be deleted, they are a member of the group(s) <xliff:g id="groups" example="[team, chess club]">%2$s</xliff:g>.</string>
<string name="icon_descr_instant_notifications">Instant notifications</string>
<!-- background service notice - SimpleXAPI.kt -->
<string name="private_instant_notifications">Private instant notifications!</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery">To preserve your privacy, instead of push notifications the app has a <b><xliff:g id="appName">SimpleX</xliff:g> background service</b> it uses a few percent of the battery per day.</string>
<string name="it_can_disabled_via_settings_notifications_still_shown"><b>It can be disabled via settings</b> notifications will still be shown while the app is running.</string>
<!-- SimpleX Chat foreground Service -->
<string name="simplex_service_notification_title">SimpleX Chat service</string>
<string name="simplex_service_notification_text">Waiting for incoming messages</string>
<string name="simplex_service_notification_title"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> service</string>
<string name="simplex_service_notification_text">Receiving messages</string>
<!-- Chat Actions - ChatItemView.kt (and general) -->
<string name="reply_verb">Reply</string>
<string name="share_verb">Share</string>
<string name="copy_verb">Copy</string>
<string name="edit_verb">Edit</string>
<string name="delete_verb">Delete</string>
<string name="delete_message__question">Delete message?</string>
<string name="delete_message_cannot_be_undone_warning">Message will be deleted - this cannot be undone!</string>
<string name="for_me_only">For me only</string>
<string name="for_everybody">For everybody</string>
<!-- CIMetaView.kt -->
<string name="icon_descr_edited">edited</string>
<string name="icon_descr_sent_msg_status_sent">sent</string>
<string name="icon_descr_sent_msg_status_unauthorized_send">unauthorized send</string>
<string name="icon_descr_sent_msg_status_send_failed">send failed</string>
<string name="icon_descr_received_msg_status_unread">unread</string>
<!-- ChatListView.kt -->
<string name="personal_welcome">Welcome <xliff:g>%1$s</xliff:g>!</string>
<string name="welcome">Welcome!</string>
<string name="this_text_is_available_in_settings">This text is available in settings</string>
<string name="your_chats">Your chats</string>
<!-- Chat Info Actions - ChatInfoView.kt -->
<string name="delete_contact__question">Delete contact?</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Contact and all messages will be deleted - this cannot be undone!</string>
<string name="button_delete_contact">Delete contact</string>
<string name="icon_descr_server_status_connected">Connected</string>
<string name="icon_descr_server_status_disconnected">Disconnected</string>
<string name="icon_descr_server_status_error">Error</string>
<string name="icon_descr_server_status_pending">Pending</string>
<!-- Message Actions - SendMsgView.kt -->
<string name="icon_descr_send_message">Send Message</string>
<!-- General Actions / Responses -->
<string name="back">Back</string>
<string name="cancel_verb">Cancel</string>
<string name="confirm_verb">Confirm</string>
<string name="ok">Ok</string>
<string name="no_details">no details</string>
<string name="add_contact">Add contact</string>
<string name="scan_QR_code">Scan QR code</string>
<!-- NewChatSheet -->
<string name="create_QR_code_or_link__bracketed__multiline">(create QR code\nor link)</string>
<string name="in_person_or_in_video_call__bracketed">(in person or in video call)</string>
<string name="create_group">Create Group</string>
<string name="coming_soon__bracketed">(coming soon!)</string>
<!-- GetImageView -->
<string name="toast_camera_permission_denied">Permission Denied!</string>
<string name="use_camera_button">Use Camera</string>
<string name="from_gallery_button">From Gallery</string>
<!-- help - ChatHelpView.kt -->
<string name="thank_you_for_installing_simplex">Thank you for installing <xliff:g id="appNameFull">SimpleX Chat</xliff:g>!</string>
<string name="you_can_connect_to_simplex_chat_founder">You can <font color="#0088ff">connect to <xliff:g id="appNameFull">SimpleX Chat</xliff:g> developers to ask any questions and to receive updates</font>.</string>
<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"><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"><b>Scan QR code</b>: to connect to your contact who shows QR code to you.</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 <xliff:g id="appName">SimpleX Chat</xliff:g> invitation link, you can open it in your browser:</string>
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code">💻 desktop: scan displayed QR code from the app, via <b>Scan QR code</b>.</string>
<string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app">📱 mobile: tap <b>Open in mobile app</b>, then tap <b>Connect</b> in the app.</string>
<!-- Contact Request Alert Dialogue - CharListNavLinkView.kt -->
<string name="accept_connection_request__question">Accept connection request?</string>
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">If you choose to reject sender will NOT be notified.</string>
<string name="accept_contact_button">Accept</string>
<string name="reject_contact_button">Reject</string>
<!-- Contact Request Information - ContactRequestView.kt -->
<string name="contact_wants_to_connect_with_you">wants to connect to you!</string>
<!-- Image Placeholder - ChatInfoImage.kt -->
<string name="icon_descr_profile_image_placeholder">profile image placeholder</string>
<string name="image_descr_profile_image">profile image</string>
<!-- Content Descriptions -->
<string name="icon_descr_close_button">Close button</string>
<string name="image_descr_link_preview">link preview image</string>
<string name="icon_descr_cancel_link_preview">cancel link preview</string>
<string name="icon_descr_settings">Settings</string>
<string name="image_descr_qr_code">QR Code</string>
<string name="icon_descr_address"><xliff:g id="appName">SimpleX</xliff:g> Address</string>
<string name="icon_descr_help">help</string>
<string name="icon_descr_simplex_team"><xliff:g id="appName">SimpleX</xliff:g> Team</string>
<string name="image_descr_simplex_logo"><xliff:g id="appName">SimpleX</xliff:g> Logo</string>
<string name="icon_descr_email">Email</string>
<!-- Add Contact - AddContactView.kt -->
<string name="invalid_QR_code">Invalid QR code</string>
<string name="this_QR_code_is_not_a_link">This QR code is not a link!</string>
<string name="invalid_contact_link">Invalid link!</string>
<string name="this_link_is_not_a_valid_connection_link">This link is not a valid connection link!</string>
<string name="connection_request_sent">Connection request sent!</string>
<string name="you_will_be_connected_when_your_connection_request_is_accepted">You will be connected when your connection request is accepted, please wait or check later!</string>
<string name="you_will_be_connected_when_your_contacts_device_is_online">You will be connected when your contact\'s device is online, please wait or check later!</string>
<string name="show_QR_code_for_your_contact_to_scan_from_the_app__multiline">Show QR code for your contact\nto scan from the app</string>
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel">If you cannot meet in person, you can <b>show QR code in the video call</b>, or you can share the invitation link via any other channel.</string>
<string name="your_chat_profile_will_be_sent_to_your_contact">Your chat profile will be sent\nto your contact</string>
<string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link">If you cannot meet in person, you can <b>scan QR code in the video call</b>, or your contact can share an invitation link.</string>
<string name="share_invitation_link">Share invitation link</string>
<!-- settings - SettingsView.kt -->
<string name="your_settings">Your settings</string>
<string name="your_simplex_contact_address">Your <xliff:g id="appName">SimpleX</xliff:g> contact address</string>
<string name="how_to_use_simplex_chat">How to use <xliff:g id="appNameFull">SimpleX Chat</xliff:g></string>
<string name="markdown_help">Markdown help</string>
<string name="markdown_in_messages">Markdown in messages</string>
<string name="chat_with_the_founder">Connect to the developers</string>
<string name="send_us_an_email">Send us email</string>
<string name="private_notifications">Private notifications</string>
<string name="chat_console">Chat console</string>
<string name="smp_servers">SMP servers</string>
<string name="install_simplex_chat_for_terminal">Install <font color="#0088ff"><xliff:g id="appNameFull">SimpleX Chat</xliff:g> for terminal</font></string>
<string name="use_simplex_chat_servers__question">Use <xliff:g id="appNameFull">SimpleX Chat</xliff:g> servers?</string>
<string name="saved_SMP_servers_will_br_removed">Saved SMP servers will be removed.</string>
<string name="your_SMP_servers">Your SMP servers</string>
<string name="configure_SMP_servers">Configure SMP servers</string>
<string name="using_simplex_chat_servers">Using <xliff:g id="appNameFull">SimpleX Chat</xliff:g> servers.</string>
<string name="enter_one_SMP_server_per_line">Enter one SMP server per line:</string>
<string name="how_to">How to</string>
<string name="save_servers_button">Save</string>
<!-- Address Items - UserAddressView.kt -->
<string name="create_address">Create address</string>
<string name="delete_address__question">Delete address?</string>
<string name="all_your_contacts_will_remain_connected">All your contacts will remain connected.</string>
<string name="your_chat_address">Your chat address</string>
<string name="you_can_share_your_address_anybody_will_be_able_to_connect">You can share your address as a link or as a QR code - anybody will be able to connect to you.</string>
<string name="if_you_delete_address_you_wont_lose_contacts">If you later delete it - you won\'t lose your contacts.</string>
<string name="share_link">Share link</string>
<string name="delete_address">Delete address</string>
<!-- User profile details - UserProfileView.kt -->
<string name="display_name__field">Display name:</string>
<string name="full_name__field">"Full name:</string>
<string name="your_chat_profile">Your chat profile</string>
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Your profile is stored on your device and shared only with your contacts.\n\n<xliff:g id="appName">SimpleX</xliff:g> servers cannot see your profile.</string>
<string name="edit_image">Edit image</string>
<string name="delete_image">Delete image</string>
<string name="save_and_notify_contacts">Save (and notify contacts)</string>
<!-- Welcome Prompts - WelcomeView.kt -->
<string name="you_control_your_chat">You control your chat!</string>
<string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">The messaging and application platform protecting your privacy and security.</string>
<string name="we_do_not_store_contacts_or_messages_on_servers">We don\'t store any of your contacts or messages (once delivered) on the servers.</string>
<string name="create_profile">Create profile</string>
<string name="your_profile_is_stored_on_your_decide_and_shared_only_with_your_contacts">Your profile is stored on your device and shared only with your contacts.</string>
<string name="display_name_cannot_contain_whitespace">Display name cannot contain whitespace.</string>
<string name="display_name">Display Name</string>
<string name="full_name_optional__prompt">Full Name (Optional)</string>
<string name="create_profile_button">Create</string>
<!-- markdown demo - MarkdownHelpView.kt -->
<string name="how_to_use_markdown">How to use markdown</string>
<string name="you_can_use_markdown_to_format_messages__prompt">You can use markdown to format messages:</string>
<string name="bold">bold</string>
<string name="italic">italic</string>
<string name="strikethrough">strike</string>
<string name="a_plus_b">a + b</string>
<string name="colored">colored</string>
<string name="secret">secret</string>
</resources>