mobile: leave & delete group; ios: fix group preview interaction (#819)

This commit is contained in:
JRoberts
2022-07-19 18:21:15 +04:00
committed by GitHub
parent cf1f921aed
commit 0e84e131cd
16 changed files with 319 additions and 146 deletions
@@ -293,6 +293,7 @@ interface SomeChat {
val id: ChatId
val apiId: Long
val ready: Boolean
val sendMsgEnabled: Boolean
val createdAt: Instant
val updatedAt: Instant
}
@@ -345,6 +346,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val id get() = contact.id
override val apiId get() = contact.apiId
override val ready get() = contact.ready
override val sendMsgEnabled get() = contact.sendMsgEnabled
override val createdAt get() = contact.createdAt
override val updatedAt get() = contact.updatedAt
override val displayName get() = contact.displayName
@@ -363,6 +365,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val id get() = groupInfo.id
override val apiId get() = groupInfo.apiId
override val ready get() = groupInfo.ready
override val sendMsgEnabled get() = groupInfo.sendMsgEnabled
override val createdAt get() = groupInfo.createdAt
override val updatedAt get() = groupInfo.updatedAt
override val displayName get() = groupInfo.displayName
@@ -381,6 +384,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val id get() = contactRequest.id
override val apiId get() = contactRequest.apiId
override val ready get() = contactRequest.ready
override val sendMsgEnabled get() = contactRequest.sendMsgEnabled
override val createdAt get() = contactRequest.createdAt
override val updatedAt get() = contactRequest.updatedAt
override val displayName get() = contactRequest.displayName
@@ -399,6 +403,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val id get() = contactConnection.id
override val apiId get() = contactConnection.apiId
override val ready get() = contactConnection.ready
override val sendMsgEnabled get() = contactConnection.sendMsgEnabled
override val createdAt get() = contactConnection.createdAt
override val updatedAt get() = contactConnection.updatedAt
override val displayName get() = contactConnection.displayName
@@ -426,6 +431,7 @@ class Contact(
override val id get() = "@$contactId"
override val apiId get() = contactId
override val ready get() = activeConn.connStatus == ConnStatus.Ready
override val sendMsgEnabled get() = true
override val displayName get() = profile.displayName
override val fullName get() = profile.fullName
override val image get() = profile.image
@@ -500,10 +506,18 @@ class GroupInfo (
override val id get() = "#$groupId"
override val apiId get() = groupId
override val ready get() = true
override val sendMsgEnabled get() = membership.memberActive
override val displayName get() = groupProfile.displayName
override val fullName get() = groupProfile.fullName
override val image get() = groupProfile.image
val canDelete: Boolean
get() {
val s = membership.memberStatus
return membership.memberRole == GroupMemberRole.Owner
|| (s == GroupMemberStatus.MemRemoved || s == GroupMemberStatus.MemLeft || s == GroupMemberStatus.MemGroupDeleted || s == GroupMemberStatus.MemInvited)
}
companion object {
val sampleData = GroupInfo(
groupId = 1,
@@ -648,6 +662,7 @@ class UserContactRequest (
override val id get() = "<@$contactRequestId"
override val apiId get() = contactRequestId
override val ready get() = true
override val sendMsgEnabled get() = false
override val displayName get() = profile.displayName
override val fullName get() = profile.fullName
override val image get() = profile.image
@@ -676,6 +691,7 @@ class PendingContactConnection(
override val id get () = ":$pccConnId"
override val apiId get() = pccConnId
override val ready get() = false
override val sendMsgEnabled get() = false
override val localDisplayName get() = String.format(generalGetString(R.string.connection_local_display_name), pccConnId)
override val displayName: String get() {
val initiated = pccConnStatus.initiated
@@ -377,6 +377,7 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
when {
r is CR.ContactDeleted && type == ChatType.Direct -> return true
r is CR.ContactConnectionDeleted && type == ChatType.ContactConnection -> return true
r is CR.GroupDeletedUser && type == ChatType.Group -> return true
r is CR.ChatCmdError -> {
val e = r.chatError
if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) {
@@ -520,6 +521,13 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
return null
}
suspend fun apiLeaveGroup(groupId: Long): GroupInfo? {
val r = sendCmd(CC.ApiLeaveGroup(groupId))
if (r is CR.LeftMemberUser) return r.groupInfo
Log.e(TAG, "apiLeaveGroup bad response: ${r.responseType} ${r.details}")
return null
}
fun apiErrorAlert(method: String, title: String, r: CR) {
val errMsg = "${r.responseType}: ${r.details}"
Log.e(TAG, "$method bad response: $errMsg")
@@ -694,6 +702,13 @@ open class ChatController(private val ctrl: ChatCtrl, val ntfManager: NtfManager
}
}
suspend fun leaveGroup(groupId: Long) {
val groupInfo = apiLeaveGroup(groupId)
if (groupInfo != null) {
chatModel.updateGroup(groupInfo)
}
}
private fun chatItemSimpleUpdate(aChatItem: AChatItem) {
val cInfo = aChatItem.chatInfo
val cItem = aChatItem.chatItem
@@ -29,16 +29,16 @@ fun ChatInfoView(chatModel: ChatModel, close: () -> Unit) {
ChatInfoLayout(
chat,
close = close,
deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) },
deleteContact = { deleteChatDialog(chat.chatInfo, chatModel, close) },
clearChat = { clearChatDialog(chat.chatInfo, chatModel, close) }
)
}
}
fun deleteContactDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
fun deleteChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit)? = null) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.delete_contact__question),
text = generalGetString(R.string.delete_contact_all_messages_deleted_cannot_undo_warning),
title = generalGetString(R.string.delete_chat_question),
text = generalGetString(R.string.delete_chat_all_messages_deleted_cannot_undo_warning),
confirmText = generalGetString(R.string.delete_verb),
onConfirm = {
withApi {
@@ -72,6 +72,18 @@ fun clearChatDialog(chatInfo: ChatInfo, chatModel: ChatModel, close: (() -> Unit
)
}
// TODO move to GroupChatInfoView
fun leaveGroupDialog(groupInfo: GroupInfo, chatModel: ChatModel) {
AlertManager.shared.showAlertMsg(
title = generalGetString(R.string.leave_group_question),
text = generalGetString(R.string.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved),
confirmText = generalGetString(R.string.leave_group_button),
onConfirm = {
withApi { chatModel.controller.leaveGroup(groupInfo.groupId) }
}
)
}
@Composable
fun ChatInfoLayout(
chat: Chat,
@@ -75,10 +75,12 @@ fun ChatView(chatModel: ChatModel) {
chat,
composeState,
composeView = {
ComposeView(
chatModel, chat, composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
)
if (chat.chatInfo.sendMsgEnabled) {
ComposeView(
chatModel, chat, composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
)
}
},
attachmentOption,
scope,
@@ -14,6 +14,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.*
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -49,9 +50,9 @@ fun CIGroupInvitationView(
ProfileImage(size = 60.dp, icon = Icons.Filled.SupervisedUserCircle, color = iconColor)
Spacer(Modifier.padding(horizontal = 4.dp))
Column {
Text(p.displayName, style = MaterialTheme.typography.caption, fontWeight = FontWeight.Medium, maxLines = 2)
Text(p.displayName, style = MaterialTheme.typography.caption, fontWeight = FontWeight.Medium, maxLines = 2, overflow = TextOverflow.Ellipsis)
if (p.fullName != "" && p.displayName != p.fullName) {
Text(p.fullName, maxLines = 2)
Text(p.fullName, maxLines = 2, overflow = TextOverflow.Ellipsis)
}
}
}
@@ -17,8 +17,7 @@ import chat.simplex.app.R
import chat.simplex.app.model.*
import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.ui.theme.WarningOrange
import chat.simplex.app.views.chat.clearChatDialog
import chat.simplex.app.views.chat.deleteContactDialog
import chat.simplex.app.views.chat.*
import chat.simplex.app.views.chat.item.ItemAction
import chat.simplex.app.views.helpers.*
import kotlinx.coroutines.delay
@@ -98,16 +97,66 @@ suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
@Composable
fun ContactMenuItems(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
if (showMarkRead) {
ItemAction(
stringResource(R.string.mark_read),
Icons.Outlined.Check,
onClick = {
markChatRead(chat, chatModel)
chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id)
showMenu.value = false
}
)
MarkReadChatAction(chat, chatModel, showMenu)
}
ClearChatAction(chat, chatModel, showMenu)
DeleteChatAction(chat, chatModel, showMenu)
}
@Composable
fun GroupMenuItems(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
when (groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> {
ItemAction(
stringResource(R.string.join_group_button),
Icons.Outlined.Login,
onClick = {
withApi { chatModel.controller.joinGroup(groupInfo.groupId) }
showMenu.value = false
}
)
if (groupInfo.canDelete) {
DeleteChatAction(chat, chatModel, showMenu)
}
}
else -> {
if (showMarkRead) {
MarkReadChatAction(chat, chatModel, showMenu)
}
ClearChatAction(chat, chatModel, showMenu)
if (groupInfo.membership.memberStatus != GroupMemberStatus.MemLeft) {
ItemAction(
stringResource(R.string.leave_group_button),
Icons.Outlined.Logout,
onClick = {
leaveGroupDialog(groupInfo, chatModel)
showMenu.value = false
},
color = Color.Red
)
}
if (groupInfo.canDelete) {
DeleteChatAction(chat, chatModel, showMenu)
}
}
}
}
@Composable
fun MarkReadChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.mark_read),
Icons.Outlined.Check,
onClick = {
markChatRead(chat, chatModel)
chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id)
showMenu.value = false
}
)
}
@Composable
fun ClearChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.clear_verb),
Icons.Outlined.Restore,
@@ -117,54 +166,21 @@ fun ContactMenuItems(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Bo
},
color = WarningOrange
)
}
@Composable
fun DeleteChatAction(chat: Chat, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
stringResource(R.string.delete_verb),
Icons.Outlined.Delete,
onClick = {
deleteContactDialog(chat.chatInfo as ChatInfo.Direct, chatModel)
deleteChatDialog(chat.chatInfo, chatModel)
showMenu.value = false
},
color = Color.Red
)
}
@Composable
fun GroupMenuItems(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, showMenu: MutableState<Boolean>, showMarkRead: Boolean) {
when (groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited ->
ItemAction(
stringResource(R.string.join_button),
Icons.Outlined.Login,
onClick = {
withApi { chatModel.controller.joinGroup(groupInfo.groupId) }
showMenu.value = false
}
)
else -> {
if (showMarkRead) {
ItemAction(
stringResource(R.string.mark_read),
Icons.Outlined.Check,
onClick = {
markChatRead(chat, chatModel)
chatModel.controller.ntfManager.cancelNotificationsForChat(chat.id)
showMenu.value = false
}
)
}
ItemAction(
stringResource(R.string.clear_verb),
Icons.Outlined.Restore,
onClick = {
clearChatDialog(chat.chatInfo, chatModel)
showMenu.value = false
},
color = WarningOrange
)
}
}
}
@Composable
fun ContactRequestMenuItems(chatInfo: ChatInfo.ContactRequest, chatModel: ChatModel, showMenu: MutableState<Boolean>) {
ItemAction(
@@ -311,11 +327,24 @@ fun acceptGroupInvitationAlertDialog(groupInfo: GroupInfo, chatModel: ChatModel)
AlertManager.shared.showAlertDialog(
title = generalGetString(R.string.join_group_question),
text = generalGetString(R.string.you_are_invited_to_group_join_to_connect_with_group_members),
confirmText = generalGetString(R.string.join_button),
onConfirm = { withApi { chatModel.controller.joinGroup(groupInfo.groupId) } }
confirmText = generalGetString(R.string.join_group_button),
onConfirm = { withApi { chatModel.controller.joinGroup(groupInfo.groupId) } },
dismissText = generalGetString(R.string.delete_verb),
onDismiss = { deleteGroup(groupInfo, chatModel) }
)
}
fun deleteGroup(groupInfo: GroupInfo, chatModel: ChatModel) {
withApi {
val r = chatModel.controller.apiDeleteChat(ChatType.Group, groupInfo.apiId)
if (r) {
chatModel.removeChat(groupInfo.id)
chatModel.chatId.value = null
chatModel.controller.ntfManager.cancelNotificationsForChat(groupInfo.id)
}
}
}
fun groupInvitationAcceptedAlert() {
AlertManager.shared.showAlertMsg(
generalGetString(R.string.joining_group),
@@ -30,17 +30,41 @@ fun ChatPreviewView(chat: Chat, stopped: Boolean) {
val cInfo = chat.chatInfo
@Composable
fun chatPreviewTitleColor(): Color {
return when (cInfo) {
fun chatPreviewTitleText(color: Color = Color.Unspecified) {
Text(
cInfo.chatViewName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = color
)
}
@Composable
fun chatPreviewTitle() {
when (cInfo) {
is ChatInfo.Direct ->
if (cInfo.ready) Color.Unspecified else HighOrLowlight
chatPreviewTitleText(if (cInfo.ready) Color.Unspecified else HighOrLowlight)
is ChatInfo.Group ->
when (cInfo.groupInfo.membership.memberStatus) {
GroupMemberStatus.MemInvited -> MaterialTheme.colors.primary
GroupMemberStatus.MemAccepted -> HighOrLowlight
else -> Color.Unspecified
GroupMemberStatus.MemInvited -> chatPreviewTitleText(MaterialTheme.colors.primary)
GroupMemberStatus.MemAccepted -> chatPreviewTitleText(HighOrLowlight)
GroupMemberStatus.MemLeft ->
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
stringResource(R.string.group_left_description),
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = HighOrLowlight
)
chatPreviewTitleText()
}
else -> chatPreviewTitleText()
}
else -> Color.Unspecified
else -> chatPreviewTitleText()
}
}
@@ -77,14 +101,7 @@ fun ChatPreviewView(chat: Chat, stopped: Boolean) {
.padding(horizontal = 8.dp)
.weight(1F)
) {
Text(
cInfo.chatViewName,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h3,
fontWeight = FontWeight.Bold,
color = chatPreviewTitleColor()
)
chatPreviewTitle()
chatPreviewText()
}
val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.updatedAt)
@@ -138,8 +138,8 @@
<string name="error_saving_file">Ошибка сохранения файла</string>
<!-- Chat Info Actions - ChatInfoView.kt -->
<string name="delete_contact__question">Удалить контакт?</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Контакт и все сообщения будут удалены - это действие нельзя отменить!</string>
<string name="delete_chat_question">Удалить чат?</string>
<string name="delete_chat_all_messages_deleted_cannot_undo_warning">Чат и все сообщения будут удалены - это действие нельзя отменить!</string>
<string name="button_delete_contact">Удалить контакт</string>
<string name="icon_descr_server_status_connected">Соединение с сервером установлено</string>
<string name="icon_descr_server_status_disconnected">Соединение с сервером не установлено</string>
@@ -490,9 +490,13 @@
<string name="group_invitation_item_description">приглашение в группу <xliff:g id="group_name">%1$s</xliff:g></string>
<string name="join_group_question">Вступить в группу?</string>
<string name="you_are_invited_to_group_join_to_connect_with_group_members">Вы приглашены в группу. Вступите, чтобы соединиться с членами группы.</string>
<string name="join_button">Вступить</string>
<string name="join_group_button">Вступить</string>
<string name="joining_group">Вступление в группу</string>
<string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Вы вступили в эту группу. Устанавливается соединение с пригласившем членом группы.</string>
<string name="leave_group_button">Выйти</string>
<string name="leave_group_question">Выйти из группы</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Вы перестанете получать сообщения от этой группы. История чата будет сохранена.</string>
<string name="group_left_description">[покинута]</string>
<!-- CIGroupInvitationView.kt -->
<string name="you_sent_group_invitation">Вы отправили приглашение в группу</string>
@@ -138,8 +138,8 @@
<string name="error_saving_file">Error saving file</string>
<!-- Chat Info Actions - ChatInfoView.kt -->
<string name="delete_contact__question">Delete contact?</string>
<string name="delete_contact_all_messages_deleted_cannot_undo_warning">Contact and all messages will be deleted - this cannot be undone!</string>
<string name="delete_chat_question">Delete chat?</string>
<string name="delete_chat_all_messages_deleted_cannot_undo_warning">Chat and all messages will be deleted - this cannot be undone!</string>
<string name="button_delete_contact">Delete contact</string>
<string name="icon_descr_server_status_connected">Connected</string>
<string name="icon_descr_server_status_disconnected">Disconnected</string>
@@ -492,9 +492,13 @@
<string name="group_invitation_item_description">invitation to group <xliff:g id="group_name">%1$s</xliff:g></string>
<string name="join_group_question">Join group?</string>
<string name="you_are_invited_to_group_join_to_connect_with_group_members">You are invited to group. Join to connect with group members.</string>
<string name="join_button">Join</string>
<string name="join_group_button">Join</string>
<string name="joining_group">Joining group</string>
<string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">You joined this group. Connecting to inviting group member.</string>
<string name="leave_group_button">Leave</string>
<string name="leave_group_question">Leave group?</string>
<string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">You will stop receiving messages from this group. Chat history will be preserved.</string>
<string name="group_left_description">[left]</string>
<!-- CIGroupInvitationView.kt -->
<string name="you_sent_group_invitation">You sent group invitation</string>