android: Automatic message deletion (#1171)
* android: Automatic message deletion * Disable changing TTL when this operation is already happening * corrections * update translations * afterSetCiTTL Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
83c1340830
commit
04719ff8df
@@ -40,6 +40,7 @@ class ChatModel(val controller: ChatController) {
|
||||
val terminalItems = mutableStateListOf<TerminalItem>()
|
||||
val userAddress = mutableStateOf<String?>(null)
|
||||
val userSMPServers = mutableStateOf<(List<String>)?>(null)
|
||||
val chatItemTTL = mutableStateOf<ChatItemTTL>(ChatItemTTL.None)
|
||||
|
||||
// set when app opened from external intent
|
||||
val clearOverlays = mutableStateOf<Boolean>(false)
|
||||
@@ -1554,3 +1555,34 @@ sealed class SndGroupEvent() {
|
||||
is GroupUpdated -> generalGetString(R.string.snd_group_event_group_profile_updated)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class ChatItemTTL: Comparable<ChatItemTTL?> {
|
||||
object Day: ChatItemTTL()
|
||||
object Week: ChatItemTTL()
|
||||
object Month: ChatItemTTL()
|
||||
data class Seconds(val secs: Long): ChatItemTTL()
|
||||
object None: ChatItemTTL()
|
||||
|
||||
override fun compareTo(other: ChatItemTTL?): Int = (seconds ?: Long.MAX_VALUE).compareTo(other?.seconds ?: Long.MAX_VALUE)
|
||||
|
||||
val seconds: Long?
|
||||
get() =
|
||||
when (this) {
|
||||
is None -> null
|
||||
is Day -> 86400L
|
||||
is Week -> 7 * 86400L
|
||||
is Month -> 30 * 86400L
|
||||
is Seconds -> secs
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromSeconds(seconds: Long?): ChatItemTTL =
|
||||
when (seconds) {
|
||||
null -> None
|
||||
86400L -> Day
|
||||
7 * 86400L -> Week
|
||||
30 * 86400L -> Month
|
||||
else -> Seconds(seconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +234,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
apiSetIncognito(chatModel.incognito.value)
|
||||
chatModel.userAddress.value = apiGetUserAddress()
|
||||
chatModel.userSMPServers.value = getUserSMPServers()
|
||||
chatModel.chatItemTTL.value = getChatItemTTL()
|
||||
val chats = apiGetChats()
|
||||
chatModel.updateChats(chats)
|
||||
chatModel.currentUser.value = user
|
||||
@@ -320,7 +321,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
|
||||
suspend fun apiStartChat(): Boolean {
|
||||
val r = sendCmd(CC.StartChat())
|
||||
val r = sendCmd(CC.StartChat(expire = true))
|
||||
when (r) {
|
||||
is CR.ChatStarted -> return true
|
||||
is CR.ChatRunning -> return false
|
||||
@@ -373,7 +374,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
throw Exception("failed to set storage encryption: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
private suspend fun apiGetChats(): List<Chat> {
|
||||
suspend fun apiGetChats(): List<Chat> {
|
||||
val r = sendCmd(CC.ApiGetChats())
|
||||
if (r is CR.ApiChats ) return r.chats
|
||||
throw Error("failed getting the list of chats: ${r.responseType} ${r.details}")
|
||||
@@ -436,6 +437,18 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getChatItemTTL(): ChatItemTTL {
|
||||
val r = sendCmd(CC.APIGetChatItemTTL())
|
||||
if (r is CR.ChatItemTTL) return ChatItemTTL.fromSeconds(r.chatItemTTL)
|
||||
throw Exception("failed to get chat item TTL: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun setChatItemTTL(chatItemTTL: ChatItemTTL) {
|
||||
val r = sendCmd(CC.APISetChatItemTTL(chatItemTTL.seconds))
|
||||
if (r is CR.CmdOk) return
|
||||
throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}")
|
||||
}
|
||||
|
||||
suspend fun apiGetNetworkConfig(): NetCfg? {
|
||||
val r = sendCmd(CC.APIGetNetworkConfig())
|
||||
if (r is CR.NetworkConfig) return r.networkConfig
|
||||
@@ -1313,7 +1326,7 @@ sealed class CC {
|
||||
class Console(val cmd: String): CC()
|
||||
class ShowActiveUser: CC()
|
||||
class CreateActiveUser(val profile: Profile): CC()
|
||||
class StartChat: CC()
|
||||
class StartChat(val expire: Boolean): CC()
|
||||
class ApiStopChat: CC()
|
||||
class SetFilesFolder(val filesFolder: String): CC()
|
||||
class SetIncognito(val incognito: Boolean): CC()
|
||||
@@ -1336,6 +1349,8 @@ sealed class CC {
|
||||
class ApiUpdateGroupProfile(val groupId: Long, val groupProfile: GroupProfile): CC()
|
||||
class GetUserSMPServers: CC()
|
||||
class SetUserSMPServers(val smpServers: List<String>): CC()
|
||||
class APISetChatItemTTL(val seconds: Long?): CC()
|
||||
class APIGetChatItemTTL: CC()
|
||||
class APISetNetworkConfig(val networkConfig: NetCfg): CC()
|
||||
class APIGetNetworkConfig: CC()
|
||||
class APISetChatSettings(val type: ChatType, val id: Long, val chatSettings: ChatSettings): CC()
|
||||
@@ -1369,10 +1384,10 @@ sealed class CC {
|
||||
is Console -> cmd
|
||||
is ShowActiveUser -> "/u"
|
||||
is CreateActiveUser -> "/u ${profile.displayName} ${profile.fullName}"
|
||||
is StartChat -> "/_start"
|
||||
is StartChat -> "/_start subscribe=on expire=${onOff(expire)}"
|
||||
is ApiStopChat -> "/_stop"
|
||||
is SetFilesFolder -> "/_files_folder $filesFolder"
|
||||
is SetIncognito -> "/incognito ${if (incognito) "on" else "off"}"
|
||||
is SetIncognito -> "/incognito ${onOff(incognito)}"
|
||||
is ApiExportArchive -> "/_db export ${json.encodeToString(config)}"
|
||||
is ApiImportArchive -> "/_db import ${json.encodeToString(config)}"
|
||||
is ApiDeleteStorage -> "/_db delete"
|
||||
@@ -1391,6 +1406,8 @@ sealed class CC {
|
||||
is ApiUpdateGroupProfile -> "/_group_profile #$groupId ${json.encodeToString(groupProfile)}"
|
||||
is GetUserSMPServers -> "/smp_servers"
|
||||
is SetUserSMPServers -> "/smp_servers ${smpServersStr(smpServers)}"
|
||||
is APISetChatItemTTL -> "/_ttl ${chatItemTTLStr(seconds)}"
|
||||
is APIGetChatItemTTL -> "/ttl"
|
||||
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
|
||||
is APIGetNetworkConfig -> "/network"
|
||||
is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}"
|
||||
@@ -1447,6 +1464,8 @@ sealed class CC {
|
||||
is ApiUpdateGroupProfile -> "apiUpdateGroupProfile"
|
||||
is GetUserSMPServers -> "getUserSMPServers"
|
||||
is SetUserSMPServers -> "setUserSMPServers"
|
||||
is APISetChatItemTTL -> "apiSetChatItemTTL"
|
||||
is APIGetChatItemTTL -> "apiGetChatItemTTL"
|
||||
is APISetNetworkConfig -> "/apiSetNetworkConfig"
|
||||
is APIGetNetworkConfig -> "/apiGetNetworkConfig"
|
||||
is APISetChatSettings -> "/apiSetChatSettings"
|
||||
@@ -1479,6 +1498,11 @@ sealed class CC {
|
||||
|
||||
class ItemRange(val from: Long, val to: Long)
|
||||
|
||||
fun chatItemTTLStr(seconds: Long?): String {
|
||||
if (seconds == null) return "none"
|
||||
return seconds.toString()
|
||||
}
|
||||
|
||||
val obfuscated: CC
|
||||
get() = when (this) {
|
||||
is ApiStorageEncryption -> ApiStorageEncryption(DBEncryptionConfig(obfuscate(config.currentKey), obfuscate(config.newKey)))
|
||||
@@ -1487,6 +1511,8 @@ sealed class CC {
|
||||
|
||||
private fun obfuscate(s: String): String = if (s.isEmpty()) "" else "***"
|
||||
|
||||
private fun onOff(b: Boolean): String = if (b) "on" else "off"
|
||||
|
||||
companion object {
|
||||
fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
|
||||
|
||||
@@ -1637,6 +1663,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("apiChats") class ApiChats(val chats: List<Chat>): CR()
|
||||
@Serializable @SerialName("apiChat") class ApiChat(val chat: Chat): CR()
|
||||
@Serializable @SerialName("userSMPServers") class UserSMPServers(val smpServers: List<String>): CR()
|
||||
@Serializable @SerialName("chatItemTTL") class ChatItemTTL(val chatItemTTL: Long? = null): CR()
|
||||
@Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR()
|
||||
@Serializable @SerialName("contactInfo") class ContactInfo(val contact: Contact, val connectionStats: ConnectionStats, val customUserProfile: Profile? = null): CR()
|
||||
@Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats?): CR()
|
||||
@@ -1726,6 +1753,7 @@ sealed class CR {
|
||||
is ApiChats -> "apiChats"
|
||||
is ApiChat -> "apiChat"
|
||||
is UserSMPServers -> "userSMPServers"
|
||||
is ChatItemTTL -> "chatItemTTL"
|
||||
is NetworkConfig -> "networkConfig"
|
||||
is ContactInfo -> "contactInfo"
|
||||
is GroupMemberInfo -> "groupMemberInfo"
|
||||
@@ -1813,6 +1841,7 @@ sealed class CR {
|
||||
is ApiChats -> json.encodeToString(chats)
|
||||
is ApiChat -> json.encodeToString(chat)
|
||||
is UserSMPServers -> json.encodeToString(smpServers)
|
||||
is ChatItemTTL -> json.encodeToString(chatItemTTL)
|
||||
is NetworkConfig -> json.encodeToString(networkConfig)
|
||||
is ContactInfo -> "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}"
|
||||
is GroupMemberInfo -> "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats_)}"
|
||||
|
||||
@@ -41,6 +41,7 @@ import kotlinx.datetime.*
|
||||
import java.io.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@Composable
|
||||
fun DatabaseView(
|
||||
@@ -67,6 +68,7 @@ fun DatabaseView(
|
||||
LaunchedEffect(m.chatRunning) {
|
||||
runChat.value = m.chatRunning.value ?: true
|
||||
}
|
||||
val chatItemTTL = remember { mutableStateOf(m.chatItemTTL.value) }
|
||||
Box(
|
||||
Modifier.fillMaxSize(),
|
||||
) {
|
||||
@@ -82,11 +84,21 @@ fun DatabaseView(
|
||||
chatLastStart,
|
||||
chatDbDeleted.value,
|
||||
appFilesCountAndSize,
|
||||
chatItemTTL,
|
||||
startChat = { startChat(m, runChat, chatLastStart, m.chatDbChanged) },
|
||||
stopChatAlert = { stopChatAlert(m, runChat, context) },
|
||||
exportArchive = { exportArchive(context, m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
|
||||
deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
|
||||
deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(context, appFilesCountAndSize) },
|
||||
onChatItemTTLSelected = {
|
||||
val oldValue = chatItemTTL.value
|
||||
chatItemTTL.value = it
|
||||
if (it < oldValue) {
|
||||
setChatItemTTLAlert(m, chatItemTTL, progressIndicator, appFilesCountAndSize, context)
|
||||
} else if (it != oldValue) {
|
||||
setCiTTL(m, chatItemTTL, progressIndicator, appFilesCountAndSize, context)
|
||||
}
|
||||
},
|
||||
showSettingsModal
|
||||
)
|
||||
if (progressIndicator.value) {
|
||||
@@ -119,11 +131,13 @@ fun DatabaseLayout(
|
||||
chatLastStart: MutableState<Instant?>,
|
||||
chatDbDeleted: Boolean,
|
||||
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
|
||||
chatItemTTL: MutableState<ChatItemTTL>,
|
||||
startChat: () -> Unit,
|
||||
stopChatAlert: () -> Unit,
|
||||
exportArchive: () -> Unit,
|
||||
deleteChatAlert: () -> Unit,
|
||||
deleteAppFilesAndMedia: () -> Unit,
|
||||
onChatItemTTLSelected: (ChatItemTTL) -> Unit,
|
||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)
|
||||
) {
|
||||
val stopped = !runChat
|
||||
@@ -204,7 +218,9 @@ fun DatabaseLayout(
|
||||
)
|
||||
SectionSpacer()
|
||||
|
||||
SectionView(stringResource(R.string.files_section)) {
|
||||
SectionView(stringResource(R.string.data_section)) {
|
||||
SectionItemView { TtlOptions(chatItemTTL, rememberUpdatedState(!progressIndicator), onChatItemTTLSelected) }
|
||||
SectionDivider()
|
||||
val deleteFilesDisabled = operationsDisabled || appFilesCountAndSize.value.first == 0
|
||||
SectionItemView(
|
||||
deleteAppFilesAndMedia,
|
||||
@@ -227,6 +243,48 @@ fun DatabaseLayout(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setChatItemTTLAlert(
|
||||
m: ChatModel, selectedChatItemTTL: MutableState<ChatItemTTL>,
|
||||
progressIndicator: MutableState<Boolean>,
|
||||
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
|
||||
context: Context
|
||||
) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(R.string.enable_automatic_deletion_question),
|
||||
text = generalGetString(R.string.enable_automatic_deletion_message),
|
||||
confirmText = generalGetString(R.string.delete_messages),
|
||||
onConfirm = { setCiTTL(m, selectedChatItemTTL, progressIndicator, appFilesCountAndSize, context) },
|
||||
onDismiss = { selectedChatItemTTL.value = m.chatItemTTL.value }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TtlOptions(current: State<ChatItemTTL>, enabled: State<Boolean>, onSelected: (ChatItemTTL) -> Unit) {
|
||||
val values = remember {
|
||||
val all: ArrayList<ChatItemTTL> = arrayListOf(ChatItemTTL.None, ChatItemTTL.Month, ChatItemTTL.Week, ChatItemTTL.Day)
|
||||
if (current.value is ChatItemTTL.Seconds) {
|
||||
all.add(current.value)
|
||||
}
|
||||
all.map {
|
||||
when (it) {
|
||||
is ChatItemTTL.None -> it to generalGetString(R.string.chat_item_ttl_none)
|
||||
is ChatItemTTL.Day -> it to generalGetString(R.string.chat_item_ttl_day)
|
||||
is ChatItemTTL.Week -> it to generalGetString(R.string.chat_item_ttl_week)
|
||||
is ChatItemTTL.Month -> it to generalGetString(R.string.chat_item_ttl_month)
|
||||
is ChatItemTTL.Seconds -> it to String.format(generalGetString(R.string.chat_item_ttl_seconds), it.secs)
|
||||
}
|
||||
}
|
||||
}
|
||||
ExposedDropDownSettingRow(
|
||||
generalGetString(R.string.delete_messages_after),
|
||||
values,
|
||||
current,
|
||||
icon = null,
|
||||
enabled = enabled,
|
||||
onSelected = onSelected
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RunChatSetting(
|
||||
runChat: Boolean,
|
||||
@@ -250,7 +308,7 @@ fun RunChatSetting(
|
||||
)
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
Switch(
|
||||
enabled= !chatDbDeleted,
|
||||
enabled = !chatDbDeleted,
|
||||
checked = runChat,
|
||||
onCheckedChange = { runChatSwitch ->
|
||||
if (runChatSwitch) {
|
||||
@@ -533,6 +591,48 @@ private fun deleteChat(m: ChatModel, progressIndicator: MutableState<Boolean>) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setCiTTL(
|
||||
m: ChatModel,
|
||||
chatItemTTL: MutableState<ChatItemTTL>,
|
||||
progressIndicator: MutableState<Boolean>,
|
||||
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
|
||||
context: Context
|
||||
) {
|
||||
Log.d(TAG, "DatabaseView setChatItemTTL ${chatItemTTL.value.seconds ?: -1}")
|
||||
progressIndicator.value = true
|
||||
withApi {
|
||||
try {
|
||||
m.controller.setChatItemTTL(chatItemTTL.value)
|
||||
// Update model on success
|
||||
m.chatItemTTL.value = chatItemTTL.value
|
||||
afterSetCiTTL(m, progressIndicator, appFilesCountAndSize, context)
|
||||
} catch (e: Exception) {
|
||||
// Rollback to model's value
|
||||
chatItemTTL.value = m.chatItemTTL.value
|
||||
afterSetCiTTL(m, progressIndicator, appFilesCountAndSize, context)
|
||||
AlertManager.shared.showAlertMsg(generalGetString(R.string.error_changing_message_deletion), e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun afterSetCiTTL(
|
||||
m: ChatModel,
|
||||
progressIndicator: MutableState<Boolean>,
|
||||
appFilesCountAndSize: MutableState<Pair<Int, Long>>,
|
||||
context: Context
|
||||
) {
|
||||
progressIndicator.value = false
|
||||
appFilesCountAndSize.value = directoryFileCountAndSize(getAppFilesDirectory(context))
|
||||
withApi {
|
||||
try {
|
||||
val chats = m.controller.apiGetChats()
|
||||
m.updateChats(chats)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "apiGetChats error: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteFilesAndMediaAlert(context: Context, appFilesCountAndSize: MutableState<Pair<Int, Long>>) {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(R.string.delete_files_and_media_question),
|
||||
@@ -575,12 +675,14 @@ fun PreviewDatabaseLayout() {
|
||||
chatLastStart = remember { mutableStateOf(Clock.System.now()) },
|
||||
chatDbDeleted = false,
|
||||
appFilesCountAndSize = remember { mutableStateOf(0 to 0L) },
|
||||
chatItemTTL = remember { mutableStateOf(ChatItemTTL.None) },
|
||||
startChat = {},
|
||||
stopChatAlert = {},
|
||||
exportArchive = {},
|
||||
deleteChatAlert = {},
|
||||
deleteAppFilesAndMedia = {},
|
||||
showSettingsModal = { {} }
|
||||
showSettingsModal = { {} },
|
||||
onChatItemTTLSelected = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,12 +593,22 @@
|
||||
<string name="restart_the_app_to_create_a_new_chat_profile">Starten Sie die App neu, um ein neues Chat-Profil zu erstellen.</string>
|
||||
<string name="you_must_use_the_most_recent_version_of_database">Sie dürfen die neueste Version Ihrer Chat-Datenbank NUR auf einem Gerät verwenden, andernfalls erhalten Sie möglicherweise keine Nachrichten mehr von einigen Ihrer Kontakte.</string>
|
||||
<string name="stop_chat_to_enable_database_actions">Chat beenden, um Datenbankaktionen zu erlauben.</string>
|
||||
<string name="files_section">DATEIEN</string>
|
||||
<string name="data_section">DATA</string>
|
||||
<string name="delete_files_and_media">Dateien \& Medien löschen</string>
|
||||
<string name="delete_files_and_media_question">Dateien und Medien löschen?</string>
|
||||
<string name="delete_files_and_media_desc">Diese Aktion kann nicht rückgängig gemacht werden - alle empfangenen und gesendeten Dateien und Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten.</string>
|
||||
<string name="no_received_app_files">Keine empfangenen oder gesendeten Dateien</string>
|
||||
<string name="total_files_count_and_size">%d Datei(en) mit einem Gesamtspeicherverbrauch von %s</string>
|
||||
<string name="chat_item_ttl_none">no</string>
|
||||
<string name="chat_item_ttl_day">1 day</string>
|
||||
<string name="chat_item_ttl_week">1 week</string>
|
||||
<string name="chat_item_ttl_month">1 month</string>
|
||||
<string name="chat_item_ttl_seconds">%s second(s)</string>
|
||||
<string name="delete_messages_after">Delete messages after</string>
|
||||
<string name="enable_automatic_deletion_question">Enable automatic message deletion?</string>
|
||||
<string name="enable_automatic_deletion_message">This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes.</string>
|
||||
<string name="delete_messages">Delete messages</string>
|
||||
<string name="error_changing_message_deletion">Error changing setting</string>
|
||||
|
||||
<!-- DatabaseEncryptionView.kt -->
|
||||
<string name="save_passphrase_in_keychain">Passwort im Keystore sichern</string>
|
||||
|
||||
@@ -593,12 +593,22 @@
|
||||
<string name="restart_the_app_to_create_a_new_chat_profile">Перезапустите приложение, чтобы создать новый профиль.</string>
|
||||
<string name="you_must_use_the_most_recent_version_of_database">Используйте самую последнюю версию архива чата и ТОЛЬКО на одном устройстве, иначе вы можете перестать получать сообщения от некоторых контактов.</string>
|
||||
<string name="stop_chat_to_enable_database_actions">Остановите чат, чтобы разблокировать операции с архивом чата.</string>
|
||||
<string name="files_section">ФАЙЛЫ</string>
|
||||
<string name="data_section">ДАННЫЕ</string>
|
||||
<string name="delete_files_and_media">Удалить файлы и медиа</string>
|
||||
<string name="delete_files_and_media_question">Удалить файлы и медиа?</string>
|
||||
<string name="delete_files_and_media_desc">Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении.</string>
|
||||
<string name="no_received_app_files">Нет полученных или отправленных файлов</string>
|
||||
<string name="total_files_count_and_size">%d файл(ов) общим размером %s</string>
|
||||
<string name="chat_item_ttl_none">нет</string>
|
||||
<string name="chat_item_ttl_day">1 день</string>
|
||||
<string name="chat_item_ttl_week">1 неделю</string>
|
||||
<string name="chat_item_ttl_month">1 месяц</string>
|
||||
<string name="chat_item_ttl_seconds">%s секунд</string>
|
||||
<string name="delete_messages_after">Удалять сообщения через</string>
|
||||
<string name="enable_automatic_deletion_question">Включить автоматическое удаление сообщений?</string>
|
||||
<string name="enable_automatic_deletion_message">Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут.</string>
|
||||
<string name="delete_messages">Удалить сообщения</string>
|
||||
<string name="error_changing_message_deletion">Ошибка при изменении настройки</string>
|
||||
|
||||
<!-- DatabaseEncryptionView.kt -->
|
||||
<string name="save_passphrase_in_keychain">Сохранить пароль в Keystore</string>
|
||||
|
||||
@@ -593,12 +593,22 @@
|
||||
<string name="restart_the_app_to_create_a_new_chat_profile">Restart the app to create a new chat profile.</string>
|
||||
<string name="you_must_use_the_most_recent_version_of_database">You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts.</string>
|
||||
<string name="stop_chat_to_enable_database_actions">Stop chat to enable database actions.</string>
|
||||
<string name="files_section">FILES</string>
|
||||
<string name="data_section">DATA</string>
|
||||
<string name="delete_files_and_media">Delete files \& media</string>
|
||||
<string name="delete_files_and_media_question">Delete files and media?</string>
|
||||
<string name="delete_files_and_media_desc">This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain.</string>
|
||||
<string name="no_received_app_files">No received or sent files</string>
|
||||
<string name="total_files_count_and_size">%d file(s) with total size of %s</string>
|
||||
<string name="chat_item_ttl_none">no</string>
|
||||
<string name="chat_item_ttl_day">1 day</string>
|
||||
<string name="chat_item_ttl_week">1 week</string>
|
||||
<string name="chat_item_ttl_month">1 month</string>
|
||||
<string name="chat_item_ttl_seconds">%s second(s)</string>
|
||||
<string name="delete_messages_after">Delete messages after</string>
|
||||
<string name="enable_automatic_deletion_question">Enable automatic message deletion?</string>
|
||||
<string name="enable_automatic_deletion_message">This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes.</string>
|
||||
<string name="delete_messages">Delete messages</string>
|
||||
<string name="error_changing_message_deletion">Error changing setting</string>
|
||||
|
||||
<!-- DatabaseEncryptionView.kt -->
|
||||
<string name="save_passphrase_in_keychain">Save passphrase in Keystore</string>
|
||||
|
||||
Reference in New Issue
Block a user