ui: archive multiple reports (#5619)

* android, desktop: archive multiple reports

* ios

* change

* changes

* fix changing counter

* fix changing counter2

* fix changing counter3

* unused

* fix android

* android notification

* simplify

* ios notification

* orange

* orange

* core: update api

* buttons

* ios api

* android api

* fix 4 buttons

* buttons and check for member active status

* android colors and member active

* show delete group button when not in the group anymore

* title

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
Stanislav Dmitrenko
2025-02-10 15:07:14 +00:00
committed by GitHub
co-authored by Evgeny Poberezkin
parent c5bb2c4ca2
commit e7361cf025
19 changed files with 465 additions and 147 deletions
@@ -851,8 +851,8 @@ object ChatModel {
changeGroupReportsCounter(rhId, chatId, 1)
}
fun decreaseGroupReportsCounter(rhId: Long?, chatId: ChatId) {
changeGroupReportsCounter(rhId, chatId, -1)
fun decreaseGroupReportsCounter(rhId: Long?, chatId: ChatId, by: Int = 1) {
changeGroupReportsCounter(rhId, chatId, -by)
}
private fun changeGroupReportsCounter(rhId: Long?, chatId: ChatId, by: Int = 0) {
@@ -1071,6 +1071,20 @@ object ChatController {
return null
}
suspend fun apiArchiveReceivedReports(rh: Long?, groupId: Long): CR.GroupChatItemsDeleted? {
val r = sendCmd(rh, CC.ApiArchiveReceivedReports(groupId))
if (r is CR.GroupChatItemsDeleted) return r
Log.e(TAG, "apiArchiveReceivedReports bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun apiDeleteReceivedReports(rh: Long?, groupId: Long, itemIds: List<Long>, mode: CIDeleteMode): List<ChatItemDeletion>? {
val r = sendCmd(rh, CC.ApiDeleteReceivedReports(groupId, itemIds, mode))
if (r is CR.ChatItemsDeleted) return r.chatItemDeletions
Log.e(TAG, "apiDeleteReceivedReports bad response: ${r.responseType} ${r.details}")
return null
}
suspend fun testProtoServer(rh: Long?, server: String): ProtocolTestFailure? {
val userId = currentUserId("testProtoServer")
val r = sendCmd(rh, CC.APITestProtoServer(userId, server))
@@ -2606,6 +2620,9 @@ object ChatController {
} else {
upsertChatItem(rhId, cInfo, toChatItem.chatItem)
}
if (cItem.isActiveReport) {
decreaseGroupReportsCounter(rhId, cInfo.id)
}
}
withReportsChatsIfOpen {
if (cItem.isReport) {
@@ -2619,54 +2636,7 @@ object ChatController {
}
}
is CR.GroupChatItemsDeleted -> {
if (!active(r.user)) {
val users = chatController.listUsers(rhId)
chatModel.users.clear()
chatModel.users.addAll(users)
return
}
val cInfo = ChatInfo.Group(r.groupInfo)
withChats {
r.chatItemIDs.forEach { itemId ->
decreaseGroupReportsCounter(rhId, cInfo.id)
val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
}
val isLastChatItem = getChat(cInfo.id)?.chatItems?.lastOrNull()?.id == cItem.id
if (isLastChatItem && ntfManager.hasNotificationsForChat(cInfo.id)) {
ntfManager.cancelNotificationsForChat(cInfo.id)
ntfManager.displayNotification(
r.user,
cInfo.id,
cInfo.displayName,
generalGetString(MR.strings.marked_deleted_description)
)
}
val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) {
CIDeleted.Moderated(Clock.System.now(), r.member_)
} else {
CIDeleted.Deleted(Clock.System.now())
}
upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
}
withReportsChatsIfOpen {
r.chatItemIDs.forEach { itemId ->
val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
}
val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) {
CIDeleted.Moderated(Clock.System.now(), r.member_)
} else {
CIDeleted.Deleted(Clock.System.now())
}
upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
}
groupChatItemsDeleted(rhId, r)
}
is CR.ReceivedGroupInvitation -> {
if (active(r.user)) {
@@ -3150,6 +3120,57 @@ object ChatController {
}
}
suspend fun groupChatItemsDeleted(rhId: Long?, r: CR.GroupChatItemsDeleted) {
if (!activeUser(rhId, r.user)) {
val users = chatController.listUsers(rhId)
chatModel.users.clear()
chatModel.users.addAll(users)
return
}
val cInfo = ChatInfo.Group(r.groupInfo)
withChats {
r.chatItemIDs.forEach { itemId ->
decreaseGroupReportsCounter(rhId, cInfo.id)
val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
}
val isLastChatItem = getChat(cInfo.id)?.chatItems?.lastOrNull()?.id == cItem.id
if (isLastChatItem && ntfManager.hasNotificationsForChat(cInfo.id)) {
ntfManager.cancelNotificationsForChat(cInfo.id)
ntfManager.displayNotification(
r.user,
cInfo.id,
cInfo.displayName,
generalGetString(MR.strings.marked_deleted_description)
)
}
val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) {
CIDeleted.Moderated(Clock.System.now(), r.member_)
} else {
CIDeleted.Deleted(Clock.System.now())
}
upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
}
withReportsChatsIfOpen {
r.chatItemIDs.forEach { itemId ->
val cItem = chatItems.value.lastOrNull { it.id == itemId } ?: return@forEach
if (chatModel.chatId.value != null) {
// Stop voice playback only inside a chat, allow to play in a chat list
AudioPlayer.stop(cItem)
}
val deleted = if (r.member_ != null && (cItem.chatDir as CIDirection.GroupRcv?)?.groupMember?.groupMemberId != r.member_.groupMemberId) {
CIDeleted.Moderated(Clock.System.now(), r.member_)
} else {
CIDeleted.Deleted(Clock.System.now())
}
upsertChatItem(rhId, cInfo, cItem.copy(meta = cItem.meta.copy(itemDeleted = deleted)))
}
}
}
private suspend fun chatItemUpdateNotify(rh: Long?, user: UserLike, aChatItem: AChatItem) {
val cInfo = aChatItem.chatInfo
val cItem = aChatItem.chatItem
@@ -3375,6 +3396,8 @@ sealed class CC {
class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC()
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List<Long>, val mode: CIDeleteMode): CC()
class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List<Long>): CC()
class ApiArchiveReceivedReports(val groupId: Long): CC()
class ApiDeleteReceivedReports(val groupId: Long, val itemIds: List<Long>, val mode: CIDeleteMode): CC()
class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
class ApiGetReactionMembers(val userId: Long, val groupId: Long, val itemId: Long, val reaction: MsgReaction): CC()
class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List<Long>): CC()
@@ -3553,6 +3576,8 @@ sealed class CC {
is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}"
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}"
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}"
is ApiArchiveReceivedReports -> "/_archive reports #$groupId"
is ApiDeleteReceivedReports -> "/_delete reports #$groupId ${itemIds.joinToString(",")} ${mode.deleteMode}"
is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
is ApiGetReactionMembers -> "/_reaction members $userId #$groupId $itemId ${json.encodeToString(reaction)}"
is ApiForwardChatItems -> {
@@ -3719,6 +3744,8 @@ sealed class CC {
is ApiUpdateChatItem -> "apiUpdateChatItem"
is ApiDeleteChatItem -> "apiDeleteChatItem"
is ApiDeleteMemberChatItem -> "apiDeleteMemberChatItem"
is ApiArchiveReceivedReports -> "apiArchiveReceivedReports"
is ApiDeleteReceivedReports -> "apiDeleteReceivedReports"
is ApiChatItemReaction -> "apiChatItemReaction"
is ApiGetReactionMembers -> "apiGetReactionMembers"
is ApiForwardChatItems -> "apiForwardChatItems"
@@ -134,7 +134,12 @@ abstract class NtfManager {
}
res
} else {
cItem.text
val mc = cItem.content.msgContent
if (mc is MsgContent.MCReport) {
generalGetString(MR.strings.notification_group_report).format(cItem.text.ifEmpty { mc.reason.text })
} else {
cItem.text
}
}
}
}
@@ -196,6 +196,7 @@ fun ChatView(
)
}
},
archiveItems = { archiveItems(chatRh, chatInfo, selectedChatItems) },
moderateItems = {
if (chatInfo is ChatInfo.Group) {
val itemIds = selectedChatItems.value
@@ -397,6 +398,7 @@ fun ChatView(
}
},
deleteMessages = { itemIds -> deleteMessages(chatRh, chatInfo, itemIds, false, moderate = false) },
archiveReports = { itemIds, forAll -> archiveReports(chatRh, chatInfo, itemIds, forAll) },
receiveFile = { fileId ->
withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) }
},
@@ -673,6 +675,7 @@ fun ChatLayout(
loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
deleteMessages: (List<Long>) -> Unit,
archiveReports: (List<Long>, Boolean) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long, () -> Unit) -> Unit,
@@ -745,7 +748,7 @@ fun ChatLayout(
}) {
ChatItemsList(
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
useLinkPreviews, linkMode, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages,
useLinkPreviews, linkMode, scrollToItemId, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages, archiveReports,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
@@ -792,6 +795,7 @@ fun ChatLayout(
})
}
},
archiveItems = { archiveItems(remoteHostId, chatInfo, selectedChatItems) },
moderateItems = {},
forwardItems = {}
)
@@ -1138,6 +1142,7 @@ fun BoxScope.ChatItemsList(
loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
deleteMessages: (List<Long>) -> Unit,
archiveReports: (List<Long>, Boolean) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long, () -> Unit) -> Unit,
@@ -1291,7 +1296,7 @@ fun BoxScope.ChatItemsList(
highlightedItems.value = setOf()
}
}
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
}
}
@@ -2368,6 +2373,59 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List<Long
}
}
private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List<Long>, forAll: Boolean, onSuccess: () -> Unit = {}) {
if (itemIds.isNotEmpty()) {
withBGApi {
val deleted = chatModel.controller.apiDeleteReceivedReports(
chatRh,
groupId = chatInfo.apiId,
itemIds = itemIds,
mode = if (forAll) CIDeleteMode.cidmBroadcast else CIDeleteMode.cidmInternalMark
)
if (deleted != null) {
withChats {
for (di in deleted) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
val deletedItem = di.deletedChatItem.chatItem
if (deletedItem.isActiveReport) {
decreaseGroupReportsCounter(chatRh, chatInfo.id)
}
}
}
withReportsChatsIfOpen {
for (di in deleted) {
if (di.deletedChatItem.chatItem.isReport) {
val toChatItem = di.toChatItem?.chatItem
if (toChatItem != null) {
upsertChatItem(chatRh, chatInfo, toChatItem)
} else {
removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
}
}
}
}
onSuccess()
}
}
}
}
private fun archiveItems(rhId: Long?, chatInfo: ChatInfo, selectedChatItems: MutableState<Set<Long>?>) {
val itemIds = selectedChatItems.value
if (itemIds != null) {
showArchiveReportsAlert(itemIds.sorted(), chatInfo is ChatInfo.Group && chatInfo.groupInfo.membership.memberActive, archiveReports = { ids, forAll ->
archiveReports(rhId, chatInfo, ids, forAll) {
selectedChatItems.value = null
}
})
}
}
private fun markUnreadChatAsRead(chatId: String) {
val chat = chatModel.chats.value.firstOrNull { it.id == chatId }
if (chat?.chatStats?.unreadChat != true) return
@@ -2716,6 +2774,7 @@ fun PreviewChatLayout() {
loadMessages = { _, _, _ -> },
deleteMessage = { _, _ -> },
deleteMessages = { _ -> },
archiveReports = { _, _ -> },
receiveFile = { _ -> },
cancelFile = {},
joinGroup = { _, _ -> },
@@ -2791,6 +2850,7 @@ fun PreviewGroupChatLayout() {
loadMessages = { _, _, _ -> },
deleteMessage = { _, _ -> },
deleteMessages = {},
archiveReports = { _, _ -> },
receiveFile = { _ -> },
cancelFile = {},
joinGroup = { _, _ -> },
@@ -53,11 +53,13 @@ fun SelectedItemsBottomToolbar(
contentTag: MsgContentTag?,
selectedChatItems: MutableState<Set<Long>?>,
deleteItems: (Boolean) -> Unit, // Boolean - delete for everyone is possible
archiveItems: () -> Unit,
moderateItems: () -> Unit,
forwardItems: () -> Unit,
) {
val deleteEnabled = remember { mutableStateOf(false) }
val deleteForEveryoneEnabled = remember { mutableStateOf(false) }
val canArchiveReports = remember { mutableStateOf(false) }
val canModerate = remember { mutableStateOf(false) }
val moderateEnabled = remember { mutableStateOf(false) }
val forwardEnabled = remember { mutableStateOf(false) }
@@ -80,7 +82,7 @@ fun SelectedItemsBottomToolbar(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton({ deleteItems(deleteForEveryoneEnabled.value) }, enabled = deleteEnabled.value && !deleteCountProhibited.value) {
IconButton({ if (canArchiveReports.value) archiveItems() else deleteItems(deleteForEveryoneEnabled.value) }, enabled = deleteEnabled.value && !deleteCountProhibited.value) {
Icon(
painterResource(MR.images.ic_delete),
null,
@@ -111,7 +113,7 @@ fun SelectedItemsBottomToolbar(
}
val chatItems = remember { derivedStateOf { chatModel.chatItemsForContent(contentTag).value } }
LaunchedEffect(chatInfo, chatItems.value, selectedChatItems.value) {
recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
recheckItems(chatInfo, chatItems.value, selectedChatItems, deleteEnabled, deleteForEveryoneEnabled, canArchiveReports, canModerate, moderateEnabled, forwardEnabled, deleteCountProhibited, forwardCountProhibited)
}
}
@@ -120,6 +122,7 @@ private fun recheckItems(chatInfo: ChatInfo,
selectedChatItems: MutableState<Set<Long>?>,
deleteEnabled: MutableState<Boolean>,
deleteForEveryoneEnabled: MutableState<Boolean>,
canArchiveReports: MutableState<Boolean>,
canModerate: MutableState<Boolean>,
moderateEnabled: MutableState<Boolean>,
forwardEnabled: MutableState<Boolean>,
@@ -133,6 +136,7 @@ private fun recheckItems(chatInfo: ChatInfo,
val selected = selectedChatItems.value ?: return
var rDeleteEnabled = true
var rDeleteForEveryoneEnabled = true
var rCanArchiveReports = true
var rModerateEnabled = true
var rOnlyOwnGroupItems = true
var rForwardEnabled = true
@@ -141,6 +145,7 @@ private fun recheckItems(chatInfo: ChatInfo,
if (selected.contains(ci.id)) {
rDeleteEnabled = rDeleteEnabled && ci.canBeDeletedForSelf
rDeleteForEveryoneEnabled = rDeleteForEveryoneEnabled && ci.meta.deletable && !ci.localNote && !ci.isReport
rCanArchiveReports = rCanArchiveReports && ci.isActiveReport && ci.chatDir !is CIDirection.GroupSnd && chatInfo is ChatInfo.Group && chatInfo.groupInfo.membership.memberRole >= GroupMemberRole.Moderator
rOnlyOwnGroupItems = rOnlyOwnGroupItems && ci.chatDir is CIDirection.GroupSnd && !ci.isReport
rModerateEnabled = rModerateEnabled && ci.content.msgContent != null && ci.memberToModerate(chatInfo) != null && !ci.isReport
rForwardEnabled = rForwardEnabled && ci.content.msgContent != null && ci.meta.itemDeleted == null && !ci.isLiveDummy && !ci.isReport
@@ -150,6 +155,7 @@ private fun recheckItems(chatInfo: ChatInfo,
rModerateEnabled = rModerateEnabled && !rOnlyOwnGroupItems
deleteEnabled.value = rDeleteEnabled
deleteForEveryoneEnabled.value = rDeleteForEveryoneEnabled
canArchiveReports.value = rCanArchiveReports
moderateEnabled.value = rModerateEnabled
forwardEnabled.value = rForwardEnabled
selectedChatItems.value = rSelectedChatItems
@@ -77,6 +77,7 @@ fun ChatItemView(
selectChatItem: () -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
deleteMessages: (List<Long>) -> Unit,
archiveReports: (List<Long>, Boolean) -> Unit,
receiveFile: (Long) -> Unit,
cancelFile: (Long) -> Unit,
joinGroup: (Long, () -> Unit) -> Unit,
@@ -301,7 +302,7 @@ fun ChatItemView(
cItem.isReport && cItem.meta.itemDeleted == null && cInfo is ChatInfo.Group -> {
DefaultDropdownMenu(showMenu) {
if (cItem.chatDir !is CIDirection.GroupSnd && cInfo.groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
ArchiveReportItemAction(cItem, showMenu, deleteMessage)
ArchiveReportItemAction(cItem.id, cInfo.groupInfo.membership.memberActive, showMenu, archiveReports)
}
DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages, buttonText = stringResource(MR.strings.delete_report))
Divider()
@@ -914,23 +915,53 @@ private fun ReportItemAction(
}
@Composable
private fun ArchiveReportItemAction(cItem: ChatItem, showMenu: MutableState<Boolean>, deleteMessage: (Long, CIDeleteMode) -> Unit) {
private fun ArchiveReportItemAction(id: Long, allowForAll: Boolean, showMenu: MutableState<Boolean>, archiveReports: (List<Long>, Boolean) -> Unit) {
ItemAction(
stringResource(MR.strings.archive_report),
painterResource(MR.images.ic_inventory_2),
onClick = {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.report_archive_alert_title),
text = generalGetString(MR.strings.report_archive_alert_desc),
onConfirm = {
deleteMessage(cItem.id, CIDeleteMode.cidmInternalMark)
},
destructive = true,
confirmText = generalGetString(MR.strings.archive_verb),
)
showArchiveReportsAlert(listOf(id), allowForAll, archiveReports)
showMenu.value = false
}
)
}
fun showArchiveReportsAlert(ids: List<Long>, allowForAll: Boolean, archiveReports: (List<Long>, Boolean) -> Unit) {
AlertManager.shared.showAlertDialogButtonsColumn(
title = if (ids.size == 1) {
generalGetString(MR.strings.report_archive_alert_title)
} else {
generalGetString(MR.strings.report_archive_alert_title_nth).format(ids.size)
},
color = Color.Red
text = null,
buttons = {
// Archive for me
SectionItemView({
AlertManager.shared.hideAlert()
archiveReports(ids, false)
}) {
Text(
generalGetString(MR.strings.report_archive_for_me),
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.error
)
}
if (allowForAll) {
// Archive for all moderators
SectionItemView({
AlertManager.shared.hideAlert()
archiveReports(ids, true)
}) {
Text(
stringResource(MR.strings.report_archive_for_all_moderators),
Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.error
)
}
}
}
)
}
@@ -1310,6 +1341,7 @@ fun PreviewChatItemView(
selectChatItem = {},
deleteMessage = { _, _ -> },
deleteMessages = { _ -> },
archiveReports = { _, _ -> },
receiveFile = { _ -> },
cancelFile = {},
joinGroup = { _, _ -> },
@@ -1356,6 +1388,7 @@ fun PreviewChatItemViewDeletedContent() {
selectChatItem = {},
deleteMessage = { _, _ -> },
deleteMessages = { _ -> },
archiveReports = { _, _ -> },
receiveFile = { _ -> },
cancelFile = {},
joinGroup = { _, _ -> },
@@ -19,6 +19,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.model.*
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.platform.*
@@ -299,6 +300,11 @@ fun GroupMenuItems(
ToggleFavoritesChatAction(chat, chatModel, chat.chatInfo.chatSettings?.favorite == true, showMenu)
ToggleNotificationsChatAction(chat, chatModel, groupInfo.chatSettings.enableNtfs.nextMode(true), showMenu)
TagListAction(chat, showMenu)
if (chat.chatStats.reportsCount > 0 && groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
ArchiveAllReportsItemAction(showMenu) {
archiveAllReportsForMe(chat.remoteHostId, chat.chatInfo.apiId)
}
}
ClearChatAction(chat, showMenu)
if (groupInfo.membership.memberCurrent) {
LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu)
@@ -563,6 +569,18 @@ private fun InvalidDataView() {
}
}
@Composable
private fun ArchiveAllReportsItemAction(showMenu: MutableState<Boolean>, archiveReports: () -> Unit) {
ItemAction(
stringResource(MR.strings.archive_reports),
painterResource(MR.images.ic_inventory_2),
onClick = {
showArchiveAllReportsForMeAlert(archiveReports)
showMenu.value = false
}
)
}
fun markChatRead(c: Chat) {
var chat = c
withApi {
@@ -886,6 +904,25 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch
}
}
private fun showArchiveAllReportsForMeAlert(archiveReports: () -> Unit) {
AlertManager.shared.showAlertDialog(
title = generalGetString(MR.strings.report_archive_alert_title_all),
text = generalGetString(MR.strings.report_archive_alert_desc_all),
onConfirm = archiveReports,
destructive = true,
confirmText = generalGetString(MR.strings.archive_verb),
)
}
private fun archiveAllReportsForMe(chatRh: Long?, apiId: Long) {
withBGApi {
val r = chatModel.controller.apiArchiveReceivedReports(chatRh, apiId)
if (r != null) {
controller.groupChatItemsDeleted(chatRh, r)
}
}
}
@Composable
expect fun ChatListNavLinkLayout(
chatLinkPreview: @Composable () -> Unit,
@@ -312,7 +312,12 @@
<string name="message_deleted_or_not_received_error_desc">This message was deleted or not received yet.</string>
<string name="report_reason_alert_title">Report reason?</string>
<string name="report_archive_alert_title">Archive report?</string>
<string name="report_archive_alert_title_nth">Archive %d reports?</string>
<string name="report_archive_alert_title_all">Archive all reports?</string>
<string name="report_archive_alert_desc">The report will be archived for you.</string>
<string name="report_archive_alert_desc_all">All reports will be archived for you.</string>
<string name="report_archive_for_me">For me</string>
<string name="report_archive_for_all_moderators">For all moderators</string>
<!-- CIStatus errors -->
<string name="ci_status_other_error">Error: %1$s</string>
@@ -341,6 +346,7 @@
<string name="search_verb">Search</string>
<string name="archive_verb">Archive</string>
<string name="archive_report">Archive report</string>
<string name="archive_reports">Archive reports</string>
<string name="delete_report">Delete report</string>
<string name="sent_message">Sent message</string>
<string name="received_message">Received message</string>
@@ -447,6 +453,7 @@
<string name="chat_list_businesses">Businesses</string>
<string name="chat_list_notes">Notes</string>
<string name="chat_list_group_reports">Reports</string>
<string name="notification_group_report">Report: %s</string>
<string name="chat_list_all">All</string>
<string name="chat_list_add_list">Add list</string>
<string name="group_reports_active_one">1 report</string>