mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-15 04:30:38 +00:00
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:
co-authored by
Avently
Evgeny Poberezkin
parent
8e609ac507
commit
969a7c433d
+29
-8
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-3
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+105
-4
@@ -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) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -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
|
||||
|
||||
+48
-4
@@ -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
-2
@@ -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
|
||||
|
||||
+21
-13
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user