From a294f58cc7e4810bec58c338afa49acd6967303c Mon Sep 17 00:00:00 2001 From: Diogo Date: Fri, 17 Jan 2025 23:26:49 +0000 Subject: [PATCH] android, desktop: support chat item ttl per chat --- .../chat/simplex/common/model/ChatModel.kt | 37 +++++-- .../chat/simplex/common/model/SimpleXAPI.kt | 22 +++- .../simplex/common/views/chat/ChatInfoView.kt | 101 +++++++++++++++++- .../common/views/chat/ChatItemsLoader.kt | 5 +- .../views/chat/group/GroupChatInfoView.kt | 39 ++++++- .../common/views/database/DatabaseView.kt | 39 ++++--- .../commonMain/resources/MR/base/strings.xml | 7 ++ 7 files changed, 215 insertions(+), 35 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 4eb0b350cb..b1f0e93704 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1484,6 +1484,7 @@ data class Contact( val contactGroupMemberId: Long? = null, val contactGrpInvSent: Boolean, val chatTags: List, + val chatItemTTL: Long?, override val chatDeleted: Boolean, val uiThemes: ThemeModeOverrides? = null, ): SomeChat, NamedChat { @@ -1567,7 +1568,8 @@ data class Contact( contactGrpInvSent = false, chatDeleted = false, uiThemes = null, - chatTags = emptyList() + chatTags = emptyList(), + chatItemTTL = null, ) } } @@ -1726,6 +1728,7 @@ data class GroupInfo ( val chatTs: Instant?, val uiThemes: ThemeModeOverrides? = null, val chatTags: List, + val chatItemTTL: Long?, override val localAlias: String, ): SomeChat, NamedChat { override val chatType get() = ChatType.Group @@ -1774,7 +1777,8 @@ data class GroupInfo ( chatTs = Clock.System.now(), uiThemes = null, chatTags = emptyList(), - localAlias = "" + localAlias = "", + chatItemTTL = null ) } } @@ -4186,32 +4190,49 @@ enum class SwitchPhase { @SerialName("completed") Completed } -sealed class ChatItemTTL: Comparable { +sealed class ChatItemTTL: Comparable { object Day: ChatItemTTL() object Week: ChatItemTTL() object Month: ChatItemTTL() + object Year: 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) + override fun compareTo(other: ChatItemTTL): Int = + (seconds.takeIf { it != 0L } ?: Long.MAX_VALUE) + .compareTo(other.seconds.takeIf { it != 0L } ?: Long.MAX_VALUE) - val seconds: Long? + val seconds: Long get() = when (this) { - is None -> null + is None -> 0 is Day -> 86400L is Week -> 7 * 86400L is Month -> 30 * 86400L + is Year -> 365 * 86400L is Seconds -> secs } + val text: String + get() = when(this) { + is None -> generalGetString(MR.strings.chat_item_ttl_none) + is Day -> generalGetString(MR.strings.chat_item_ttl_day) + is Week -> generalGetString(MR.strings.chat_item_ttl_week) + is Month -> generalGetString(MR.strings.chat_item_ttl_month) + is Year -> generalGetString(MR.strings.chat_item_ttl_year) + is Seconds -> String.format(generalGetString(MR.strings.chat_item_ttl_seconds), secs) + } + + val doesNotExpire: Boolean get() = this is None + companion object { - fun fromSeconds(seconds: Long?): ChatItemTTL = + fun fromSeconds(seconds: Long): ChatItemTTL = when (seconds) { - null -> None + 0L -> None 86400L -> Day 7 * 86400L -> Week 30 * 86400L -> Month + 365 * 86400L -> Year else -> Seconds(seconds) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index f891d206a3..887380750e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -1182,7 +1182,13 @@ object ChatController { suspend fun getChatItemTTL(rh: Long?): ChatItemTTL { val userId = currentUserId("getChatItemTTL") val r = sendCmd(rh, CC.APIGetChatItemTTL(userId)) - if (r is CR.ChatItemTTL) return ChatItemTTL.fromSeconds(r.chatItemTTL) + if (r is CR.ChatItemTTL) { + if (r.chatItemTTL != null) { + return ChatItemTTL.fromSeconds(r.chatItemTTL) + } else { + throw Exception("chatItemTTLResponse: invalid ttl") + } + } throw Exception("failed to get chat item TTL: ${r.responseType} ${r.details}") } @@ -1193,6 +1199,13 @@ object ChatController { throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } + suspend fun setChatTTL(rh: Long?, chatType: ChatType, id: Long, chatItemTTL: ChatItemTTL?) { + val userId = currentUserId("setChatTTL") + val r = sendCmd(rh, CC.APISetChatTTL(userId, chatType, id, chatItemTTL?.seconds)) + if (r is CR.CmdOk) return + throw Exception("failed to set chat TTL: ${r.responseType} ${r.details}") + } + suspend fun apiSetNetworkConfig(cfg: NetCfg, showAlertOnError: Boolean = true, ctrl: ChatCtrl? = null): Boolean { val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl) return when (r) { @@ -3383,8 +3396,9 @@ sealed class CC { class ApiGetUsageConditions(): CC() class ApiSetConditionsNotified(val conditionsId: Long): CC() class ApiAcceptConditions(val conditionsId: Long, val operatorIds: List): CC() - class APISetChatItemTTL(val userId: Long, val seconds: Long?): CC() + class APISetChatItemTTL(val userId: Long, val seconds: Long): CC() class APIGetChatItemTTL(val userId: Long): CC() + class APISetChatTTL(val userId: Long, val chatType: ChatType, val id: Long, val seconds: Long?): CC() class APISetNetworkConfig(val networkConfig: NetCfg): CC() class APIGetNetworkConfig: CC() class APISetNetworkInfo(val networkInfo: UserNetworkInfo): CC() @@ -3567,6 +3581,7 @@ sealed class CC { is ApiAcceptConditions -> "/_accept_conditions ${conditionsId} ${operatorIds.joinToString(",")}" is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}" is APIGetChatItemTTL -> "/_ttl $userId" + is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id)} ${chatItemTTLStr(seconds)}" is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}" is APIGetNetworkConfig -> "/network" is APISetNetworkInfo -> "/_network info ${json.encodeToString(networkInfo)}" @@ -3727,6 +3742,7 @@ sealed class CC { is ApiAcceptConditions -> "apiAcceptConditions" is APISetChatItemTTL -> "apiSetChatItemTTL" is APIGetChatItemTTL -> "apiGetChatItemTTL" + is APISetChatTTL -> "apiSetChatTTL" is APISetNetworkConfig -> "apiSetNetworkConfig" is APIGetNetworkConfig -> "apiGetNetworkConfig" is APISetNetworkInfo -> "apiSetNetworkInfo" @@ -3812,7 +3828,7 @@ sealed class CC { data class ItemRange(val from: Long, val to: Long) fun chatItemTTLStr(seconds: Long?): String { - if (seconds == null) return "none" + if (seconds == null) return "default" return seconds.toString() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index 2b3cf773cc..3a9d0947b0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -16,8 +16,10 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.text.* import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.* @@ -36,12 +38,13 @@ import chat.simplex.common.model.* import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.withChats -import chat.simplex.common.model.ChatModel.withReportsChatsIfOpen import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* +import chat.simplex.common.views.chat.group.ProgressIndicator import chat.simplex.common.views.chatlist.updateChatSettings +import chat.simplex.common.views.database.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.delay @@ -74,6 +77,9 @@ fun ChatInfoView( } val chatRh = chat.remoteHostId val sendReceipts = remember(contact.id) { mutableStateOf(SendReceipts.fromBool(contact.chatSettings.sendRcpts, currentUser.sendRcptsContacts)) } + val chatItemTTL = remember(contact.id) { mutableStateOf(if (contact.chatItemTTL != null) ChatItemTTL.fromSeconds(contact.chatItemTTL) else null) } + val progressIndicator = rememberSaveable(contact.id) { mutableStateOf(false) } + ChatInfoLayout( chat, contact, @@ -84,6 +90,13 @@ fun ChatInfoView( updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel) sendReceipts.value = sendRcpts }, + chatItemTTL = chatItemTTL, + setChatItemTTL = { + val previousChatTTL = chatItemTTL.value + chatItemTTL.value = it + + setChatTTLAlert(chatModel, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, progressIndicator) + }, connStats = connStats, contactNetworkStatus.value, customUserProfile, @@ -173,8 +186,13 @@ fun ChatInfoView( } }, close = close, - onSearchClicked = onSearchClicked + onSearchClicked = onSearchClicked, + enabled = remember { derivedStateOf { !progressIndicator.value } } ) + + if (progressIndicator.value) { + ProgressIndicator() + } } } @@ -504,6 +522,8 @@ fun ChatInfoLayout( currentUser: User, sendReceipts: State, setSendReceipts: (SendReceipts) -> Unit, + chatItemTTL: MutableState, + setChatItemTTL: (ChatItemTTL?) -> Unit, connStats: MutableState, contactNetworkStatus: NetworkStatus, customUserProfile: Profile?, @@ -520,7 +540,8 @@ fun ChatInfoLayout( syncContactConnectionForce: () -> Unit, verifyClicked: () -> Unit, close: () -> Unit, - onSearchClicked: () -> Unit + onSearchClicked: () -> Unit, + enabled: State ) { val cStats = connStats.value val scrollState = rememberScrollState() @@ -528,7 +549,7 @@ fun ChatInfoLayout( KeyChangeEffect(chat.id) { scope.launch { scrollState.scrollTo(0) } } - ColumnWithScrollBar { + ColumnWithScrollBar(Modifier.alpha(if (enabled.value) 1f else 0.6f)) { Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center @@ -583,6 +604,13 @@ fun ChatInfoLayout( // } else if (developerTools) { // SynchronizeConnectionButtonForce(syncContactConnectionForce) // } + TtlOptions( + chatItemTTL, + enabled = remember { mutableStateOf(true) }, + onSelected = setChatItemTTL, + default = chatModel.chatItemTTL, + icon = painterResource(MR.images.ic_delete), + ) } WallpaperButton { @@ -1308,6 +1336,66 @@ fun queueInfoText(info: Pair): String { return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo) } +fun setChatTTLAlert( + m: ChatModel, + rhId: Long?, + chatInfo: ChatInfo, + selectedChatTTL: MutableState, + previousChatTTL: ChatItemTTL?, + progressIndicator: MutableState +) { + val disablingExpiration = selectedChatTTL.value?.doesNotExpire == true || (selectedChatTTL.value == null && chatModel.chatItemTTL.value.doesNotExpire) + val changingExpiration = selectedChatTTL.value == null || (previousChatTTL?.doesNotExpire == false) + AlertManager.shared.showAlertDialog( + title = generalGetString( + if (disablingExpiration) { + MR.strings.disable_automatic_deletion_question + } else if (changingExpiration) { + MR.strings.change_automatic_deletion_question + } else MR.strings.enable_automatic_deletion_question), + text = generalGetString(if (disablingExpiration) MR.strings.disable_automatic_deletion_message else MR.strings.change_automatic_chat_deletion_message), + confirmText = generalGetString(if (disablingExpiration) MR.strings.disable_automatic_deletion else MR.strings.delete_messages), + onConfirm = { setChatTTL(m, rhId, chatInfo, selectedChatTTL, progressIndicator, previousChatTTL) }, + onDismiss = { selectedChatTTL.value = previousChatTTL }, + onDismissRequest = { selectedChatTTL.value = previousChatTTL }, + destructive = true, + ) +} + +private fun setChatTTL( + m: ChatModel, + rhId: Long?, + chatInfo: ChatInfo, + chatTTL: MutableState, + progressIndicator: MutableState, + previousChatTTL: ChatItemTTL? + ) { + progressIndicator.value = true + withBGApi { + try { + m.controller.setChatTTL(rhId, chatInfo.chatType, chatInfo.apiId, chatTTL.value) + afterSetChatTTL(m, chatInfo, progressIndicator) + } catch (e: Exception) { + chatTTL.value = previousChatTTL + afterSetChatTTL(m, chatInfo, progressIndicator) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_message_deletion), e.stackTraceToString()) + } + } +} + +private fun afterSetChatTTL(m: ChatModel, chatInfo: ChatInfo, progressIndicator: MutableState) { + withApi { + try { + // this is using current remote host on purpose - if it changes during update, it will load correct chats + apiLoadMessages(m.remoteHostId(), chatInfo.chatType, chatInfo.apiId, contentTag = null, pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT), replaceChat = true) + } catch (e: Exception) { + Log.e(TAG, "apiGetChat error: ${e.message}") + } finally { + progressIndicator.value = false + } + } +} + @Preview @Composable fun PreviewChatInfoLayout() { @@ -1322,6 +1410,8 @@ fun PreviewChatInfoLayout() { User.sampleData, sendReceipts = remember { mutableStateOf(SendReceipts.Yes) }, setSendReceipts = {}, + chatItemTTL = remember { mutableStateOf(ChatItemTTL.fromSeconds(0)) }, + setChatItemTTL = {}, localAlias = "", connectionCode = "123", developerTools = false, @@ -1338,7 +1428,8 @@ fun PreviewChatInfoLayout() { syncContactConnectionForce = {}, verifyClicked = {}, close = {}, - onSearchClicked = {} + onSearchClicked = {}, + enabled = remember { mutableStateOf(true) } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt index 6419aa884d..562df42a19 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt @@ -28,7 +28,8 @@ suspend fun apiLoadMessages( contentTag: MsgContentTag?, pagination: ChatPagination, search: String = "", - visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 } + visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 }, + replaceChat: Boolean = false ) = coroutineScope { val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, contentTag, pagination, search) ?: return@coroutineScope // For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes @@ -47,6 +48,8 @@ suspend fun apiLoadMessages( withChats { if (getChat(chat.id) == null) { addChat(chat) + } else if (replaceChat) { + replaceChat(chat.remoteHostId, chat.id, chat) } else { updateChatInfo(chat.remoteHostId, chat.chatInfo) updateChatStats(chat.remoteHostId, chat.id, chat.chatStats) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 9b2986ef83..62a2236b76 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.AnnotatedString @@ -39,6 +40,7 @@ import chat.simplex.common.platform.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.chatlist.* +import chat.simplex.common.views.database.TtlOptions import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource import kotlinx.coroutines.* @@ -55,7 +57,11 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin if (chat != null && chat.chatInfo is ChatInfo.Group && currentUser != null) { val groupInfo = chat.chatInfo.groupInfo val sendReceipts = remember { mutableStateOf(SendReceipts.fromBool(groupInfo.chatSettings.sendRcpts, currentUser.sendRcptsSmallGroups)) } + val chatItemTTL = remember(groupInfo.id) { mutableStateOf(if (groupInfo.chatItemTTL != null) ChatItemTTL.fromSeconds(groupInfo.chatItemTTL) else null) } + val progressIndicator = rememberSaveable(groupInfo.id) { mutableStateOf(false) } + // TODO [ttl] disable all interactions while ttl is changing val scope = rememberCoroutineScope() + GroupChatInfoLayout( chat, groupInfo, @@ -66,6 +72,13 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel) sendReceipts.value = sendRcpts }, + chatItemTTL = chatItemTTL, + setChatItemTTL = { + val previousChatTTL = chatItemTTL.value + chatItemTTL.value = it + + setChatTTLAlert(chatModel, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, progressIndicator) + }, members = remember { chatModel.groupMembers }.value .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved } .sortedByDescending { it.memberRole }, @@ -125,8 +138,13 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin manageGroupLink = { ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) } }, - onSearchClicked = onSearchClicked + onSearchClicked = onSearchClicked, + enabled = remember { derivedStateOf { !progressIndicator.value } } ) + + if (progressIndicator.value) { + ProgressIndicator() + } } } @@ -285,6 +303,8 @@ fun ModalData.GroupChatInfoLayout( currentUser: User, sendReceipts: State, setSendReceipts: (SendReceipts) -> Unit, + chatItemTTL: MutableState, + setChatItemTTL: (ChatItemTTL?) -> Unit, members: List, developerTools: Boolean, onLocalAliasChanged: (String) -> Unit, @@ -300,7 +320,8 @@ fun ModalData.GroupChatInfoLayout( leaveGroup: () -> Unit, manageGroupLink: () -> Unit, close: () -> Unit = { ModalManager.closeAllModalsEverywhere()}, - onSearchClicked: () -> Unit + onSearchClicked: () -> Unit, + enabled: State ) { val listState = remember { appBarHandler.listState } val scope = rememberCoroutineScope() @@ -314,7 +335,7 @@ fun ModalData.GroupChatInfoLayout( if (s.isEmpty()) members else members.filter { m -> m.anyNameContains(s) } } } - Box { + Box(Modifier.alpha(if (enabled.value) 1f else 0.6f)) { val oneHandUI = remember { appPrefs.oneHandUI.state } LazyColumnWithScrollBar( state = listState, @@ -382,6 +403,14 @@ fun ModalData.GroupChatInfoLayout( SendReceiptsOptionDisabled() } + TtlOptions( + chatItemTTL, + enabled = remember { mutableStateOf(true) }, + onSelected = setChatItemTTL, + default = chatModel.chatItemTTL, + icon = painterResource(MR.images.ic_delete), + ) + WallpaperButton { ModalManager.end.showModal { val chat = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chat.id } } } @@ -770,12 +799,14 @@ fun PreviewGroupChatInfoLayout() { User.sampleData, sendReceipts = remember { mutableStateOf(SendReceipts.Yes) }, setSendReceipts = {}, + chatItemTTL = remember { mutableStateOf(ChatItemTTL.fromSeconds(0)) }, + setChatItemTTL = {}, members = listOf(GroupMember.sampleData, GroupMember.sampleData, GroupMember.sampleData), developerTools = false, onLocalAliasChanged = {}, groupLink = null, scrollToItemId = remember { mutableStateOf(null) }, - addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {}, onSearchClicked = {}, + addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {}, onSearchClicked = {}, enabled = remember { mutableStateOf(true) } ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index d951f1f812..4d07bb9d4f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.* @@ -108,6 +109,9 @@ fun DatabaseView() { } }, onChatItemTTLSelected = { + if (it == null) { + return@DatabaseLayout + } val oldValue = chatItemTTL.value chatItemTTL.value = it if (it < oldValue) { @@ -158,7 +162,7 @@ fun DatabaseLayout( exportArchive: () -> Unit, deleteChatAlert: () -> Unit, deleteAppFilesAndMedia: () -> Unit, - onChatItemTTLSelected: (ChatItemTTL) -> Unit, + onChatItemTTLSelected: (ChatItemTTL?) -> Unit, disconnectAllHosts: () -> Unit, ) { val operationsDisabled = progressIndicator && !chatModel.desktopNoUserNoRemote @@ -300,27 +304,34 @@ private fun setChatItemTTLAlert( } @Composable -private fun TtlOptions(current: State, enabled: State, onSelected: (ChatItemTTL) -> Unit) { +fun TtlOptions( + current: State, + enabled: State, + onSelected: (ChatItemTTL?) -> Unit, + default: State? = null, + icon: Painter? = null +) { val values = remember { - val all: ArrayList = arrayListOf(ChatItemTTL.None, ChatItemTTL.Month, ChatItemTTL.Week, ChatItemTTL.Day) - if (current.value is ChatItemTTL.Seconds) { - all.add(current.value) + val all: ArrayList = arrayListOf(ChatItemTTL.None, ChatItemTTL.Year, ChatItemTTL.Month, ChatItemTTL.Week, ChatItemTTL.Day) + val currentValue = current.value + if (currentValue is ChatItemTTL.Seconds) { + all.add(currentValue) } - all.map { - when (it) { - is ChatItemTTL.None -> it to generalGetString(MR.strings.chat_item_ttl_none) - is ChatItemTTL.Day -> it to generalGetString(MR.strings.chat_item_ttl_day) - is ChatItemTTL.Week -> it to generalGetString(MR.strings.chat_item_ttl_week) - is ChatItemTTL.Month -> it to generalGetString(MR.strings.chat_item_ttl_month) - is ChatItemTTL.Seconds -> it to String.format(generalGetString(MR.strings.chat_item_ttl_seconds), it.secs) - } + val options = mutableListOf>().apply { + all.mapTo(this) { it to it.text } } + + if (default != null) { + options.add(null to String.format(generalGetString(MR.strings.chat_item_ttl_default), default.value.text)) + } + + options } ExposedDropDownSettingRow( generalGetString(MR.strings.delete_messages_after), values, current, - icon = null, + icon = icon, enabled = enabled, onSelected = onSelected ) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 954c22abee..6d52586198 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -540,6 +540,11 @@ Notifications + Disable automatic message deletion? + Change automatic message deletion? + Messages on this chat will never be deleted. + This action cannot be undone - the messages sent and received on this chat earlier than selected will be deleted. It may take several minutes. + Disable delete messages connect @@ -1396,7 +1401,9 @@ 1 day 1 week 1 month + 1 year %s second(s) + default (%s) Messages This setting applies to messages in your current chat profile Delete messages after