Skip to content

Commit

Permalink
feat: Delete group conversation locally [#WPB-11556] (#3774)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-zagorski authored Dec 31, 2024
1 parent b74b88e commit 06d0b4d
Show file tree
Hide file tree
Showing 18 changed files with 385 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ fun ConversationSheetContent(
unblockUser: (UnblockUserDialogState) -> Unit,
leaveGroup: (GroupDialogState) -> Unit,
deleteGroup: (GroupDialogState) -> Unit,
deleteGroupLocally: (GroupDialogState) -> Unit,
isBottomSheetVisible: () -> Boolean = { true }
) {
// it may be null as initial state
Expand All @@ -63,6 +64,7 @@ fun ConversationSheetContent(
unblockUserClick = unblockUser,
leaveGroup = leaveGroup,
deleteGroup = deleteGroup,
deleteGroupLocally = deleteGroupLocally,
navigateToNotification = conversationSheetState::toMutingNotificationOption
)
}
Expand Down Expand Up @@ -125,7 +127,8 @@ data class ConversationSheetContent(
val mlsVerificationStatus: Conversation.VerificationStatus,
val proteusVerificationStatus: Conversation.VerificationStatus,
val isUnderLegalHold: Boolean,
val isFavorite: Boolean?
val isFavorite: Boolean?,
val isDeletingConversationLocallyRunning: Boolean
) {

private val isSelfUserMember: Boolean get() = selfRole != null
Expand All @@ -144,6 +147,8 @@ data class ConversationSheetContent(

fun canLeaveTheGroup(): Boolean = conversationTypeDetail is ConversationTypeDetail.Group && isSelfUserMember

fun canDeleteGroupLocally(): Boolean = !isSelfUserMember && !isDeletingConversationLocallyRunning

fun canBlockUser(): Boolean {
return conversationTypeDetail is ConversationTypeDetail.Private
&& conversationTypeDetail.blockingState == BlockingState.NOT_BLOCKED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ class ConversationSheetState(
@Composable
fun rememberConversationSheetState(
conversationItem: ConversationItem,
conversationOptionNavigation: ConversationOptionNavigation
conversationOptionNavigation: ConversationOptionNavigation,
isConversationDeletionLocallyRunning: Boolean
): ConversationSheetState {
val conversationSheetContent: ConversationSheetContent = when (conversationItem) {
is ConversationItem.GroupConversation -> {
Expand All @@ -79,7 +80,8 @@ fun rememberConversationSheetState(
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
isUnderLegalHold = showLegalHoldIndicator,
isFavorite = isFavorite
isFavorite = isFavorite,
isDeletingConversationLocallyRunning = isConversationDeletionLocallyRunning
)
}
}
Expand All @@ -105,7 +107,8 @@ fun rememberConversationSheetState(
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
isUnderLegalHold = showLegalHoldIndicator,
isFavorite = isFavorite
isFavorite = isFavorite,
isDeletingConversationLocallyRunning = false
)
}
}
Expand All @@ -126,13 +129,14 @@ fun rememberConversationSheetState(
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
isUnderLegalHold = showLegalHoldIndicator,
isFavorite = null
isFavorite = null,
isDeletingConversationLocallyRunning = false
)
}
}
}

return remember(conversationItem, conversationOptionNavigation) {
return remember(conversationItem, conversationOptionNavigation, isConversationDeletionLocallyRunning) {
ConversationSheetState(
conversationSheetContent = conversationSheetContent,
conversationOptionNavigation = conversationOptionNavigation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ internal fun ConversationMainSheetContent(
unblockUserClick: (UnblockUserDialogState) -> Unit,
leaveGroup: (GroupDialogState) -> Unit,
deleteGroup: (GroupDialogState) -> Unit,
deleteGroupLocally: (GroupDialogState) -> Unit,
navigateToNotification: () -> Unit
) {
WireMenuModalSheetContent(
Expand Down Expand Up @@ -270,6 +271,28 @@ internal fun ConversationMainSheetContent(
)
}
}
if (conversationSheetContent.canDeleteGroupLocally()) {
add {
MenuBottomSheetItem(
leading = {
MenuItemIcon(
id = R.drawable.ic_close,
contentDescription = null
)
},
title = stringResource(R.string.label_delete_group_locally),
itemProvidedColor = MaterialTheme.colorScheme.error,
onItemClick = {
deleteGroupLocally(
GroupDialogState(
conversationSheetContent.conversationId,
conversationSheetContent.title
)
)
}
)
}
}
if (conversationSheetContent.canDeleteGroup()) {
add {
MenuBottomSheetItem(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ sealed class HomeSnackBarMessage(override val uiText: UIText) : SnackBarMessage
groupName
)
)
data class DeleteConversationGroupLocallySuccess(val groupName: String) : HomeSnackBarMessage(
UIText.StringResource(
R.string.conversation_group_removed_locally_success,
groupName
)
)

data object DeleteConversationGroupError : HomeSnackBarMessage(UIText.StringResource(R.string.delete_group_conversation_error))
data object LeftConversationSuccess : HomeSnackBarMessage(UIText.StringResource(R.string.left_conversation_group_success))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,8 @@ private fun GroupConversationDetailsContent(
blockUser = {},
unblockUser = {},
leaveGroup = leaveGroupDialogState::show,
deleteGroup = deleteGroupDialogState::show
deleteGroup = deleteGroupDialogState::show,
deleteGroupLocally = {}
)
}
)
Expand Down Expand Up @@ -609,7 +610,8 @@ fun PreviewGroupConversationDetails() {
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
isUnderLegalHold = false,
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
isFavorite = false
isFavorite = false,
isDeletingConversationLocallyRunning = false
),
bottomSheetEventsHandler = GroupConversationDetailsBottomSheetEventsHandler.PREVIEW,
onBackPressed = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ class GroupConversationDetailsViewModel @Inject constructor(
mlsVerificationStatus = groupDetails.conversation.mlsVerificationStatus,
proteusVerificationStatus = groupDetails.conversation.proteusVerificationStatus,
isUnderLegalHold = groupDetails.conversation.legalHoldStatus.showLegalHoldIndicator(),
isFavorite = groupDetails.isFavorite
isFavorite = groupDetails.isFavorite,
isDeletingConversationLocallyRunning = false
)

updateState(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.conversations.details.menu

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.wire.android.R
import com.wire.android.ui.common.VisibilityState
import com.wire.android.ui.common.WireDialog
import com.wire.android.ui.common.WireDialogButtonProperties
import com.wire.android.ui.common.WireDialogButtonType
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.common.visbility.VisibilityState
import com.wire.android.ui.home.conversationslist.model.GroupDialogState

@Composable
internal fun DeleteConversationGroupLocallyDialog(
dialogState: VisibilityState<GroupDialogState>,
isLoading: Boolean,
onDeleteGroupLocally: (GroupDialogState) -> Unit,
) {
VisibilityState(dialogState) {
WireDialog(
title = stringResource(id = R.string.delete_group_locally_conversation_dialog_title, it.conversationName),
text = stringResource(id = R.string.delete_group_locally_conversation_dialog_description),
buttonsHorizontalAlignment = true,
onDismiss = dialogState::dismiss,
dismissButtonProperties = WireDialogButtonProperties(
onClick = dialogState::dismiss,
text = stringResource(id = R.string.label_cancel),
state = WireButtonState.Default
),
optionButton1Properties = WireDialogButtonProperties(
onClick = { onDeleteGroupLocally(it) },
text = stringResource(id = R.string.delete_group_locally_delete_for_me_label),
type = WireDialogButtonType.Primary,
state = if (isLoading) {
WireButtonState.Disabled
} else {
WireButtonState.Error
},
loading = isLoading
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.insertSeparators
import androidx.paging.map
import androidx.work.WorkManager
import com.wire.android.BuildConfig
import com.wire.android.appLogger
import com.wire.android.di.CurrentAccount
Expand All @@ -47,6 +48,9 @@ import com.wire.android.ui.home.conversationslist.model.ConversationsSource
import com.wire.android.ui.home.conversationslist.model.DialogState
import com.wire.android.ui.home.conversationslist.model.GroupDialogState
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.workmanager.worker.ConversationDeletionLocallyStatus
import com.wire.android.workmanager.worker.enqueueConversationDeletionLocally
import com.wire.android.workmanager.worker.observeConversationDeletionStatusLocally
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.ConversationFilter
import com.wire.kalium.logic.data.conversation.MutedConversationStatus
Expand Down Expand Up @@ -88,6 +92,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
Expand All @@ -109,6 +114,8 @@ interface ConversationListViewModel {
fun blockUser(blockUserState: BlockUserDialogState) {}
fun unblockUser(userId: UserId) {}
fun deleteGroup(groupDialogState: GroupDialogState) {}
fun deleteGroupLocally(groupDialogState: GroupDialogState) {}
fun observeIsDeletingConversationLocally(conversationId: ConversationId): Flow<Boolean>
fun leaveGroup(leaveGroupState: GroupDialogState) {}
fun clearConversationContent(dialogState: DialogState) {}
fun muteConversation(conversationId: ConversationId?, mutedConversationStatus: MutedConversationStatus) {}
Expand All @@ -120,6 +127,7 @@ class ConversationListViewModelPreview(
foldersWithConversations: Flow<PagingData<ConversationFolderItem>> = previewConversationFoldersFlow(),
) : ConversationListViewModel {
override val conversationListState = ConversationListState.Paginated(foldersWithConversations)
override fun observeIsDeletingConversationLocally(conversationId: ConversationId): Flow<Boolean> = flowOf(false)
}

@Suppress("MagicNumber", "TooManyFunctions", "LongParameterList")
Expand All @@ -142,7 +150,8 @@ class ConversationListViewModelImpl @AssistedInject constructor(
private val observeLegalHoldStateForSelfUser: ObserveLegalHoldStateForSelfUserUseCase,
@CurrentAccount val currentAccount: UserId,
private val userTypeMapper: UserTypeMapper,
private val observeSelfUser: GetSelfUserUseCase
private val observeSelfUser: GetSelfUserUseCase,
private val workManager: WorkManager
) : ConversationListViewModel, ViewModel() {

@AssistedFactory
Expand Down Expand Up @@ -367,6 +376,35 @@ class ConversationListViewModelImpl @AssistedInject constructor(
}
}

override fun deleteGroupLocally(groupDialogState: GroupDialogState) {
viewModelScope.launch {
closeBottomSheet.emit(Unit)
workManager.enqueueConversationDeletionLocally(groupDialogState.conversationId)
.collect { status ->
when (status) {
ConversationDeletionLocallyStatus.SUCCEEDED -> {
_infoMessage.emit(HomeSnackBarMessage.DeleteConversationGroupLocallySuccess(groupDialogState.conversationName))
}

ConversationDeletionLocallyStatus.FAILED -> {
_infoMessage.emit(HomeSnackBarMessage.DeleteConversationGroupError)
}

ConversationDeletionLocallyStatus.RUNNING,
ConversationDeletionLocallyStatus.IDLE -> {
// nop
}
}
}
}
}

override fun observeIsDeletingConversationLocally(conversationId: ConversationId): Flow<Boolean> {
return workManager.observeConversationDeletionStatusLocally(conversationId)
.map { status -> status == ConversationDeletionLocallyStatus.RUNNING }
.distinctUntilChanged()
}

// TODO: needs to be implemented
@Suppress("EmptyFunctionBlock")
override fun moveConversationToFolder() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.wire.android.ui.home.conversationslist.model.GroupDialogState
class ConversationsDialogsState(
val leaveGroupDialogState: VisibilityState<GroupDialogState>,
val deleteGroupDialogState: VisibilityState<GroupDialogState>,
val deleteGroupLocallyDialogState: VisibilityState<GroupDialogState>,
val blockUserDialogState: VisibilityState<BlockUserDialogState>,
val unblockUserDialogState: VisibilityState<UnblockUserDialogState>,
val clearContentDialogState: VisibilityState<DialogState>,
Expand All @@ -48,6 +49,7 @@ fun rememberConversationsDialogsState(requestInProgress: Boolean): Conversations

val leaveGroupDialogState = rememberVisibilityState<GroupDialogState>()
val deleteGroupDialogState = rememberVisibilityState<GroupDialogState>()
val deleteGroupLocallyDialogState = rememberVisibilityState<GroupDialogState>()
val blockUserDialogState = rememberVisibilityState<BlockUserDialogState>()
val unblockUserDialogState = rememberVisibilityState<UnblockUserDialogState>()
val clearContentDialogState = rememberVisibilityState<DialogState>()
Expand All @@ -57,6 +59,7 @@ fun rememberConversationsDialogsState(requestInProgress: Boolean): Conversations
ConversationsDialogsState(
leaveGroupDialogState,
deleteGroupDialogState,
deleteGroupLocallyDialogState,
blockUserDialogState,
unblockUserDialogState,
clearContentDialogState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.wire.android.R
Expand Down Expand Up @@ -63,6 +64,7 @@ import com.wire.android.ui.destinations.OtherUserProfileScreenDestination
import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState
import com.wire.android.ui.home.conversations.details.dialog.ClearConversationContentDialog
import com.wire.android.ui.home.conversations.details.menu.DeleteConversationGroupDialog
import com.wire.android.ui.home.conversations.details.menu.DeleteConversationGroupLocallyDialog
import com.wire.android.ui.home.conversations.details.menu.LeaveConversationGroupDialog
import com.wire.android.ui.home.conversationslist.common.ConversationList
import com.wire.android.ui.home.conversationslist.model.ConversationItem
Expand Down Expand Up @@ -273,6 +275,15 @@ fun ConversationsScreenContent(
onDeleteGroup = conversationListViewModel::deleteGroup
)

DeleteConversationGroupLocallyDialog(
isLoading = requestInProgress,
dialogState = deleteGroupLocallyDialogState,
onDeleteGroupLocally = { state ->
conversationListViewModel.deleteGroupLocally(state)
deleteGroupLocallyDialogState.dismiss()
}
)

LeaveConversationGroupDialog(
dialogState = leaveGroupDialogState,
isLoading = requestInProgress,
Expand All @@ -299,9 +310,12 @@ fun ConversationsScreenContent(
WireModalSheetLayout(
sheetState = sheetState,
sheetContent = {
val conversationDeletionInProgress by conversationListViewModel.observeIsDeletingConversationLocally(it.conversationId)
.collectAsStateWithLifecycle(false)
val conversationState = rememberConversationSheetState(
conversationItem = it,
conversationOptionNavigation = currentConversationOptionNavigation
conversationOptionNavigation = currentConversationOptionNavigation,
isConversationDeletionLocallyRunning = conversationDeletionInProgress
)

ConversationSheetContent(
Expand All @@ -320,6 +334,7 @@ fun ConversationsScreenContent(
unblockUser = unblockUserDialogState::show,
leaveGroup = leaveGroupDialogState::show,
deleteGroup = deleteGroupDialogState::show,
deleteGroupLocally = deleteGroupLocallyDialogState::show
)
},
)
Expand Down
Loading

0 comments on commit 06d0b4d

Please sign in to comment.