android, desktop: support chat item ttl per chat (#5546)

* android, desktop: support chat item ttl per chat

* disable all intereactions when reloading ttl

* changes matching ios

* changes

* simplify

* changes

* divided apiLoadMessages

* change

* stacktrace

* unneeded coroutineScope

* only disable ttl

* remove delay

* look

* look

* Revert "look"

This reverts commit 86a5d1d511.

---------

Co-authored-by: Avently <7953703+avently@users.noreply.github.com>
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
Diogo
2025-01-22 19:32:09 +00:00
committed by GitHub
co-authored by Avently Evgeny Poberezkin
parent 8e609ac507
commit 969a7c433d
8 changed files with 242 additions and 36 deletions
@@ -1484,6 +1484,7 @@ data class Contact(
val contactGroupMemberId: Long? = null,
val contactGrpInvSent: Boolean,
val chatTags: List<Long>,
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<Long>,
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<ChatItemTTL?> {
sealed class ChatItemTTL: Comparable<ChatItemTTL> {
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 neverExpires: 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)
}
}
@@ -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) {
return if (r.chatItemTTL != null) {
ChatItemTTL.fromSeconds(r.chatItemTTL)
} else {
ChatItemTTL.None
}
}
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<Long>): 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()
}
@@ -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,14 @@ 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.ChatTTLSection
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 +78,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 deletingItems = rememberSaveable(contact.id) { mutableStateOf(false) }
ChatInfoLayout(
chat,
contact,
@@ -84,6 +91,16 @@ fun ChatInfoView(
updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel)
sendReceipts.value = sendRcpts
},
chatItemTTL = chatItemTTL,
setChatItemTTL = {
if (it == chatItemTTL.value) {
return@ChatInfoLayout
}
val previousChatTTL = chatItemTTL.value
chatItemTTL.value = it
setChatTTLAlert(chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems)
},
connStats = connStats,
contactNetworkStatus.value,
customUserProfile,
@@ -173,7 +190,8 @@ fun ChatInfoView(
}
},
close = close,
onSearchClicked = onSearchClicked
onSearchClicked = onSearchClicked,
deletingItems = deletingItems
)
}
}
@@ -504,6 +522,8 @@ fun ChatInfoLayout(
currentUser: User,
sendReceipts: State<SendReceipts>,
setSendReceipts: (SendReceipts) -> Unit,
chatItemTTL: MutableState<ChatItemTTL?>,
setChatItemTTL: (ChatItemTTL?) -> Unit,
connStats: MutableState<ConnectionStats?>,
contactNetworkStatus: NetworkStatus,
customUserProfile: Profile?,
@@ -520,7 +540,8 @@ fun ChatInfoLayout(
syncContactConnectionForce: () -> Unit,
verifyClicked: () -> Unit,
close: () -> Unit,
onSearchClicked: () -> Unit
onSearchClicked: () -> Unit,
deletingItems: State<Boolean>
) {
val cStats = connStats.value
val scrollState = rememberScrollState()
@@ -597,6 +618,9 @@ fun ChatInfoLayout(
}
SectionDividerSpaced(maxBottomPadding = false)
ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
val conn = contact.activeConn
if (conn != null) {
SectionView {
@@ -1308,6 +1332,80 @@ fun queueInfoText(info: Pair<RcvMsgInfo?, ServerQueueInfo>): String {
return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo)
}
fun setChatTTLAlert(
rhId: Long?,
chatInfo: ChatInfo,
selectedChatTTL: MutableState<ChatItemTTL?>,
previousChatTTL: ChatItemTTL?,
progressIndicator: MutableState<Boolean>
) {
val defaultTTL = chatModel.chatItemTTL.value
val previouslyUsedTTL = previousChatTTL ?: defaultTTL
val newTTLToUse = selectedChatTTL.value ?: defaultTTL
AlertManager.shared.showAlertDialog(
title = generalGetString(
if (newTTLToUse.neverExpires) {
MR.strings.disable_automatic_deletion_question
} else if (!previouslyUsedTTL.neverExpires || selectedChatTTL.value == null) {
MR.strings.change_automatic_deletion_question
} else MR.strings.enable_automatic_deletion_question),
text = generalGetString(if (newTTLToUse.neverExpires) MR.strings.disable_automatic_deletion_message else MR.strings.change_automatic_chat_deletion_message),
confirmText = generalGetString(if (newTTLToUse.neverExpires) MR.strings.disable_automatic_deletion else MR.strings.delete_messages),
onConfirm = { setChatTTL(rhId, chatInfo, selectedChatTTL, progressIndicator, previousChatTTL) },
onDismiss = { selectedChatTTL.value = previousChatTTL },
onDismissRequest = { selectedChatTTL.value = previousChatTTL },
destructive = true,
)
}
private fun setChatTTL(
rhId: Long?,
chatInfo: ChatInfo,
chatTTL: MutableState<ChatItemTTL?>,
progressIndicator: MutableState<Boolean>,
previousChatTTL: ChatItemTTL?
) {
progressIndicator.value = true
withBGApi {
try {
chatModel.controller.setChatTTL(rhId, chatInfo.chatType, chatInfo.apiId, chatTTL.value)
afterSetChatTTL(rhId, chatInfo, progressIndicator)
} catch (e: Exception) {
chatTTL.value = previousChatTTL
afterSetChatTTL(rhId, chatInfo, progressIndicator)
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_changing_message_deletion), e.stackTraceToString())
}
}
}
private suspend fun afterSetChatTTL(rhId: Long?, chatInfo: ChatInfo, progressIndicator: MutableState<Boolean>) {
try {
val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT)
val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return
if (chat.chatItems.isEmpty()) {
// replacing old chat with the same old chat but without items. Less intrusive way of clearing a preview
withChats {
val oldChat = getChat(chat.id)
if (oldChat != null) {
replaceChat(oldChat.remoteHostId, oldChat.id, oldChat.copy(chatItems = emptyList()))
}
}
}
if (chat.remoteHostId != chatModel.remoteHostId() || chat.id != chatModel.chatId.value) return
processLoadedChat(
chat,
navInfo,
contentTag = null,
pagination = pagination
)
} catch (e: Exception) {
Log.e(TAG, "apiGetChat error: ${e.stackTraceToString()}")
} finally {
progressIndicator.value = false
}
}
@Preview
@Composable
fun PreviewChatInfoLayout() {
@@ -1322,6 +1420,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 +1438,8 @@ fun PreviewChatInfoLayout() {
syncContactConnectionForce = {},
verifyClicked = {},
close = {},
onSearchClicked = {}
onSearchClicked = {},
deletingItems = remember { mutableStateOf(false) }
)
}
}
@@ -34,7 +34,16 @@ suspend fun apiLoadMessages(
// 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
if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last)
|| !isActive) return@coroutineScope
processLoadedChat(chat, navInfo, contentTag, pagination, visibleItemIndexesNonReversed)
}
suspend fun processLoadedChat(
chat: Chat,
navInfo: NavigationInfo,
contentTag: MsgContentTag?,
pagination: ChatPagination,
visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 }
) {
val chatState = chatModel.chatStateForContent(contentTag)
val (splits, unreadAfterItemId, totalAfter, unreadTotal, unreadAfter, unreadAfterNewestLoaded) = chatState
val oldItems = chatModel.chatItemsForContent(contentTag).value
@@ -70,7 +79,7 @@ suspend fun apiLoadMessages(
is ChatPagination.Before -> {
newItems.addAll(oldItems)
val indexInCurrentItems: Int = oldItems.indexOfFirst { it.id == pagination.chatItemId }
if (indexInCurrentItems == -1) return@coroutineScope
if (indexInCurrentItems == -1) return
val (newIds, _) = mapItemsToIds(chat.chatItems)
val wasSize = newItems.size
val (oldUnreadSplitIndex, newUnreadSplitIndex, trimmedIds, newSplits) = removeDuplicatesAndModifySplitsOnBeforePagination(
@@ -87,7 +96,7 @@ suspend fun apiLoadMessages(
is ChatPagination.After -> {
newItems.addAll(oldItems)
val indexInCurrentItems: Int = oldItems.indexOfFirst { it.id == pagination.chatItemId }
if (indexInCurrentItems == -1) return@coroutineScope
if (indexInCurrentItems == -1) return
val mappedItems = mapItemsToIds(chat.chatItems)
val newIds = mappedItems.first
@@ -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,10 @@ 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 deletingItems = rememberSaveable(groupInfo.id) { mutableStateOf(false) }
val scope = rememberCoroutineScope()
GroupChatInfoLayout(
chat,
groupInfo,
@@ -66,6 +71,16 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin
updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel)
sendReceipts.value = sendRcpts
},
chatItemTTL = chatItemTTL,
setChatItemTTL = {
if (it == chatItemTTL.value) {
return@GroupChatInfoLayout
}
val previousChatTTL = chatItemTTL.value
chatItemTTL.value = it
setChatTTLAlert(chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems)
},
members = remember { chatModel.groupMembers }.value
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
.sortedByDescending { it.memberRole },
@@ -125,7 +140,8 @@ fun ModalData.GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: Strin
manageGroupLink = {
ModalManager.end.showModal { GroupLinkView(chatModel, rhId, groupInfo, groupLink, groupLinkMemberRole, onGroupLinkUpdated) }
},
onSearchClicked = onSearchClicked
onSearchClicked = onSearchClicked,
deletingItems = deletingItems
)
}
}
@@ -285,6 +301,8 @@ fun ModalData.GroupChatInfoLayout(
currentUser: User,
sendReceipts: State<SendReceipts>,
setSendReceipts: (SendReceipts) -> Unit,
chatItemTTL: MutableState<ChatItemTTL?>,
setChatItemTTL: (ChatItemTTL?) -> Unit,
members: List<GroupMember>,
developerTools: Boolean,
onLocalAliasChanged: (String) -> Unit,
@@ -300,7 +318,8 @@ fun ModalData.GroupChatInfoLayout(
leaveGroup: () -> Unit,
manageGroupLink: () -> Unit,
close: () -> Unit = { ModalManager.closeAllModalsEverywhere()},
onSearchClicked: () -> Unit
onSearchClicked: () -> Unit,
deletingItems: State<Boolean>
) {
val listState = remember { appBarHandler.listState }
val scope = rememberCoroutineScope()
@@ -394,7 +413,10 @@ fun ModalData.GroupChatInfoLayout(
}
val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs
SectionTextFooter(stringResource(footerId))
SectionDividerSpaced(maxTopPadding = true)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = true)
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), members.count() + 1)) {
if (groupInfo.canAddMembers) {
@@ -463,6 +485,26 @@ fun ModalData.GroupChatInfoLayout(
}
}
@Composable
fun ChatTTLSection(chatItemTTL: State<ChatItemTTL?>, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State<Boolean>) {
Box {
SectionView {
TtlOptions(
chatItemTTL,
enabled = remember { derivedStateOf { !deletingItems.value } },
onSelected = setChatItemTTL,
default = chatModel.chatItemTTL
)
SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
if (deletingItems.value) {
Box(Modifier.matchParentSize()) {
ProgressIndicator()
}
}
}
}
@Composable
private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
Column(
@@ -770,12 +812,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 = {}, deletingItems = remember { mutableStateOf(true) }
)
}
}
@@ -1,9 +1,8 @@
package chat.simplex.common.views.chat.group
import SectionBottomSpacer
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -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,21 +304,25 @@ private fun setChatItemTTLAlert(
}
@Composable
private fun TtlOptions(current: State<ChatItemTTL>, enabled: State<Boolean>, onSelected: (ChatItemTTL) -> Unit) {
fun TtlOptions(
current: State<ChatItemTTL?>,
enabled: State<Boolean>,
onSelected: (ChatItemTTL?) -> Unit,
default: State<ChatItemTTL>? = null
) {
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)
val all: ArrayList<ChatItemTTL> = 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: MutableList<Pair<ChatItemTTL?, String>> = all.map { it to it.text }.toMutableList()
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),
@@ -540,6 +540,12 @@
<!-- Chat Info Settings - ChatInfoView.kt -->
<string name="notifications">Notifications</string>
<string name="disable_automatic_deletion_question">Disable automatic message deletion?</string>
<string name="change_automatic_deletion_question">Change automatic message deletion?</string>
<string name="disable_automatic_deletion_message">Messages in this chat will never be deleted.</string>
<string name="change_automatic_chat_deletion_message">This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted.</string>
<string name="disable_automatic_deletion">Disable delete messages</string>
<string name="chat_ttl_options_footer">Delete chat messages from your device.</string>
<!-- Chat Info Actions - ChatInfoView.kt -->
<string name="info_view_connect_button">connect</string>
@@ -1396,7 +1402,9 @@
<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_year">1 year</string>
<string name="chat_item_ttl_seconds">%s second(s)</string>
<string name="chat_item_ttl_default">default (%s)</string>
<string name="messages_section_title">Messages</string>
<string name="messages_section_description">This setting applies to messages in your current chat profile</string>
<string name="delete_messages_after">Delete messages after</string>