Skip to content

Commit 06d0b4d

Browse files
authored
feat: Delete group conversation locally [#WPB-11556] (#3774)
1 parent b74b88e commit 06d0b4d

File tree

18 files changed

+385
-19
lines changed

18 files changed

+385
-19
lines changed

app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ fun ConversationSheetContent(
4545
unblockUser: (UnblockUserDialogState) -> Unit,
4646
leaveGroup: (GroupDialogState) -> Unit,
4747
deleteGroup: (GroupDialogState) -> Unit,
48+
deleteGroupLocally: (GroupDialogState) -> Unit,
4849
isBottomSheetVisible: () -> Boolean = { true }
4950
) {
5051
// it may be null as initial state
@@ -63,6 +64,7 @@ fun ConversationSheetContent(
6364
unblockUserClick = unblockUser,
6465
leaveGroup = leaveGroup,
6566
deleteGroup = deleteGroup,
67+
deleteGroupLocally = deleteGroupLocally,
6668
navigateToNotification = conversationSheetState::toMutingNotificationOption
6769
)
6870
}
@@ -125,7 +127,8 @@ data class ConversationSheetContent(
125127
val mlsVerificationStatus: Conversation.VerificationStatus,
126128
val proteusVerificationStatus: Conversation.VerificationStatus,
127129
val isUnderLegalHold: Boolean,
128-
val isFavorite: Boolean?
130+
val isFavorite: Boolean?,
131+
val isDeletingConversationLocallyRunning: Boolean
129132
) {
130133

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

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

150+
fun canDeleteGroupLocally(): Boolean = !isSelfUserMember && !isDeletingConversationLocallyRunning
151+
147152
fun canBlockUser(): Boolean {
148153
return conversationTypeDetail is ConversationTypeDetail.Private
149154
&& conversationTypeDetail.blockingState == BlockingState.NOT_BLOCKED

app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetState.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class ConversationSheetState(
5959
@Composable
6060
fun rememberConversationSheetState(
6161
conversationItem: ConversationItem,
62-
conversationOptionNavigation: ConversationOptionNavigation
62+
conversationOptionNavigation: ConversationOptionNavigation,
63+
isConversationDeletionLocallyRunning: Boolean
6364
): ConversationSheetState {
6465
val conversationSheetContent: ConversationSheetContent = when (conversationItem) {
6566
is ConversationItem.GroupConversation -> {
@@ -79,7 +80,8 @@ fun rememberConversationSheetState(
7980
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
8081
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
8182
isUnderLegalHold = showLegalHoldIndicator,
82-
isFavorite = isFavorite
83+
isFavorite = isFavorite,
84+
isDeletingConversationLocallyRunning = isConversationDeletionLocallyRunning
8385
)
8486
}
8587
}
@@ -105,7 +107,8 @@ fun rememberConversationSheetState(
105107
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
106108
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
107109
isUnderLegalHold = showLegalHoldIndicator,
108-
isFavorite = isFavorite
110+
isFavorite = isFavorite,
111+
isDeletingConversationLocallyRunning = false
109112
)
110113
}
111114
}
@@ -126,13 +129,14 @@ fun rememberConversationSheetState(
126129
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
127130
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
128131
isUnderLegalHold = showLegalHoldIndicator,
129-
isFavorite = null
132+
isFavorite = null,
133+
isDeletingConversationLocallyRunning = false
130134
)
131135
}
132136
}
133137
}
134138

135-
return remember(conversationItem, conversationOptionNavigation) {
139+
return remember(conversationItem, conversationOptionNavigation, isConversationDeletionLocallyRunning) {
136140
ConversationSheetState(
137141
conversationSheetContent = conversationSheetContent,
138142
conversationOptionNavigation = conversationOptionNavigation

app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ internal fun ConversationMainSheetContent(
6767
unblockUserClick: (UnblockUserDialogState) -> Unit,
6868
leaveGroup: (GroupDialogState) -> Unit,
6969
deleteGroup: (GroupDialogState) -> Unit,
70+
deleteGroupLocally: (GroupDialogState) -> Unit,
7071
navigateToNotification: () -> Unit
7172
) {
7273
WireMenuModalSheetContent(
@@ -270,6 +271,28 @@ internal fun ConversationMainSheetContent(
270271
)
271272
}
272273
}
274+
if (conversationSheetContent.canDeleteGroupLocally()) {
275+
add {
276+
MenuBottomSheetItem(
277+
leading = {
278+
MenuItemIcon(
279+
id = R.drawable.ic_close,
280+
contentDescription = null
281+
)
282+
},
283+
title = stringResource(R.string.label_delete_group_locally),
284+
itemProvidedColor = MaterialTheme.colorScheme.error,
285+
onItemClick = {
286+
deleteGroupLocally(
287+
GroupDialogState(
288+
conversationSheetContent.conversationId,
289+
conversationSheetContent.title
290+
)
291+
)
292+
}
293+
)
294+
}
295+
}
273296
if (conversationSheetContent.canDeleteGroup()) {
274297
add {
275298
MenuBottomSheetItem(

app/src/main/kotlin/com/wire/android/ui/home/HomeSnackBarMessage.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ sealed class HomeSnackBarMessage(override val uiText: UIText) : SnackBarMessage
5959
groupName
6060
)
6161
)
62+
data class DeleteConversationGroupLocallySuccess(val groupName: String) : HomeSnackBarMessage(
63+
UIText.StringResource(
64+
R.string.conversation_group_removed_locally_success,
65+
groupName
66+
)
67+
)
6268

6369
data object DeleteConversationGroupError : HomeSnackBarMessage(UIText.StringResource(R.string.delete_group_conversation_error))
6470
data object LeftConversationSuccess : HomeSnackBarMessage(UIText.StringResource(R.string.left_conversation_group_success))

app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,8 @@ private fun GroupConversationDetailsContent(
489489
blockUser = {},
490490
unblockUser = {},
491491
leaveGroup = leaveGroupDialogState::show,
492-
deleteGroup = deleteGroupDialogState::show
492+
deleteGroup = deleteGroupDialogState::show,
493+
deleteGroupLocally = {}
493494
)
494495
}
495496
)
@@ -609,7 +610,8 @@ fun PreviewGroupConversationDetails() {
609610
mlsVerificationStatus = Conversation.VerificationStatus.VERIFIED,
610611
isUnderLegalHold = false,
611612
proteusVerificationStatus = Conversation.VerificationStatus.VERIFIED,
612-
isFavorite = false
613+
isFavorite = false,
614+
isDeletingConversationLocallyRunning = false
613615
),
614616
bottomSheetEventsHandler = GroupConversationDetailsBottomSheetEventsHandler.PREVIEW,
615617
onBackPressed = {},

app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ class GroupConversationDetailsViewModel @Inject constructor(
162162
mlsVerificationStatus = groupDetails.conversation.mlsVerificationStatus,
163163
proteusVerificationStatus = groupDetails.conversation.proteusVerificationStatus,
164164
isUnderLegalHold = groupDetails.conversation.legalHoldStatus.showLegalHoldIndicator(),
165-
isFavorite = groupDetails.isFavorite
165+
isFavorite = groupDetails.isFavorite,
166+
isDeletingConversationLocallyRunning = false
166167
)
167168

168169
updateState(
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2024 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
package com.wire.android.ui.home.conversations.details.menu
19+
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.ui.res.stringResource
22+
import com.wire.android.R
23+
import com.wire.android.ui.common.VisibilityState
24+
import com.wire.android.ui.common.WireDialog
25+
import com.wire.android.ui.common.WireDialogButtonProperties
26+
import com.wire.android.ui.common.WireDialogButtonType
27+
import com.wire.android.ui.common.button.WireButtonState
28+
import com.wire.android.ui.common.visbility.VisibilityState
29+
import com.wire.android.ui.home.conversationslist.model.GroupDialogState
30+
31+
@Composable
32+
internal fun DeleteConversationGroupLocallyDialog(
33+
dialogState: VisibilityState<GroupDialogState>,
34+
isLoading: Boolean,
35+
onDeleteGroupLocally: (GroupDialogState) -> Unit,
36+
) {
37+
VisibilityState(dialogState) {
38+
WireDialog(
39+
title = stringResource(id = R.string.delete_group_locally_conversation_dialog_title, it.conversationName),
40+
text = stringResource(id = R.string.delete_group_locally_conversation_dialog_description),
41+
buttonsHorizontalAlignment = true,
42+
onDismiss = dialogState::dismiss,
43+
dismissButtonProperties = WireDialogButtonProperties(
44+
onClick = dialogState::dismiss,
45+
text = stringResource(id = R.string.label_cancel),
46+
state = WireButtonState.Default
47+
),
48+
optionButton1Properties = WireDialogButtonProperties(
49+
onClick = { onDeleteGroupLocally(it) },
50+
text = stringResource(id = R.string.delete_group_locally_delete_for_me_label),
51+
type = WireDialogButtonType.Primary,
52+
state = if (isLoading) {
53+
WireButtonState.Disabled
54+
} else {
55+
WireButtonState.Error
56+
},
57+
loading = isLoading
58+
)
59+
)
60+
}
61+
}

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationListViewModel.kt

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.paging.PagingData
2727
import androidx.paging.cachedIn
2828
import androidx.paging.insertSeparators
2929
import androidx.paging.map
30+
import androidx.work.WorkManager
3031
import com.wire.android.BuildConfig
3132
import com.wire.android.appLogger
3233
import com.wire.android.di.CurrentAccount
@@ -47,6 +48,9 @@ import com.wire.android.ui.home.conversationslist.model.ConversationsSource
4748
import com.wire.android.ui.home.conversationslist.model.DialogState
4849
import com.wire.android.ui.home.conversationslist.model.GroupDialogState
4950
import com.wire.android.util.dispatchers.DispatcherProvider
51+
import com.wire.android.workmanager.worker.ConversationDeletionLocallyStatus
52+
import com.wire.android.workmanager.worker.enqueueConversationDeletionLocally
53+
import com.wire.android.workmanager.worker.observeConversationDeletionStatusLocally
5054
import com.wire.kalium.logic.data.conversation.Conversation
5155
import com.wire.kalium.logic.data.conversation.ConversationFilter
5256
import com.wire.kalium.logic.data.conversation.MutedConversationStatus
@@ -88,6 +92,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
8892
import kotlinx.coroutines.flow.emptyFlow
8993
import kotlinx.coroutines.flow.firstOrNull
9094
import kotlinx.coroutines.flow.flatMapLatest
95+
import kotlinx.coroutines.flow.flowOf
9196
import kotlinx.coroutines.flow.flowOn
9297
import kotlinx.coroutines.flow.map
9398
import kotlinx.coroutines.flow.onStart
@@ -109,6 +114,8 @@ interface ConversationListViewModel {
109114
fun blockUser(blockUserState: BlockUserDialogState) {}
110115
fun unblockUser(userId: UserId) {}
111116
fun deleteGroup(groupDialogState: GroupDialogState) {}
117+
fun deleteGroupLocally(groupDialogState: GroupDialogState) {}
118+
fun observeIsDeletingConversationLocally(conversationId: ConversationId): Flow<Boolean>
112119
fun leaveGroup(leaveGroupState: GroupDialogState) {}
113120
fun clearConversationContent(dialogState: DialogState) {}
114121
fun muteConversation(conversationId: ConversationId?, mutedConversationStatus: MutedConversationStatus) {}
@@ -120,6 +127,7 @@ class ConversationListViewModelPreview(
120127
foldersWithConversations: Flow<PagingData<ConversationFolderItem>> = previewConversationFoldersFlow(),
121128
) : ConversationListViewModel {
122129
override val conversationListState = ConversationListState.Paginated(foldersWithConversations)
130+
override fun observeIsDeletingConversationLocally(conversationId: ConversationId): Flow<Boolean> = flowOf(false)
123131
}
124132

125133
@Suppress("MagicNumber", "TooManyFunctions", "LongParameterList")
@@ -142,7 +150,8 @@ class ConversationListViewModelImpl @AssistedInject constructor(
142150
private val observeLegalHoldStateForSelfUser: ObserveLegalHoldStateForSelfUserUseCase,
143151
@CurrentAccount val currentAccount: UserId,
144152
private val userTypeMapper: UserTypeMapper,
145-
private val observeSelfUser: GetSelfUserUseCase
153+
private val observeSelfUser: GetSelfUserUseCase,
154+
private val workManager: WorkManager
146155
) : ConversationListViewModel, ViewModel() {
147156

148157
@AssistedFactory
@@ -367,6 +376,35 @@ class ConversationListViewModelImpl @AssistedInject constructor(
367376
}
368377
}
369378

379+
override fun deleteGroupLocally(groupDialogState: GroupDialogState) {
380+
viewModelScope.launch {
381+
closeBottomSheet.emit(Unit)
382+
workManager.enqueueConversationDeletionLocally(groupDialogState.conversationId)
383+
.collect { status ->
384+
when (status) {
385+
ConversationDeletionLocallyStatus.SUCCEEDED -> {
386+
_infoMessage.emit(HomeSnackBarMessage.DeleteConversationGroupLocallySuccess(groupDialogState.conversationName))
387+
}
388+
389+
ConversationDeletionLocallyStatus.FAILED -> {
390+
_infoMessage.emit(HomeSnackBarMessage.DeleteConversationGroupError)
391+
}
392+
393+
ConversationDeletionLocallyStatus.RUNNING,
394+
ConversationDeletionLocallyStatus.IDLE -> {
395+
// nop
396+
}
397+
}
398+
}
399+
}
400+
}
401+
402+
override fun observeIsDeletingConversationLocally(conversationId: ConversationId): Flow<Boolean> {
403+
return workManager.observeConversationDeletionStatusLocally(conversationId)
404+
.map { status -> status == ConversationDeletionLocallyStatus.RUNNING }
405+
.distinctUntilChanged()
406+
}
407+
370408
// TODO: needs to be implemented
371409
@Suppress("EmptyFunctionBlock")
372410
override fun moveConversationToFolder() {

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationsDialogsState.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import com.wire.android.ui.home.conversationslist.model.GroupDialogState
3434
class ConversationsDialogsState(
3535
val leaveGroupDialogState: VisibilityState<GroupDialogState>,
3636
val deleteGroupDialogState: VisibilityState<GroupDialogState>,
37+
val deleteGroupLocallyDialogState: VisibilityState<GroupDialogState>,
3738
val blockUserDialogState: VisibilityState<BlockUserDialogState>,
3839
val unblockUserDialogState: VisibilityState<UnblockUserDialogState>,
3940
val clearContentDialogState: VisibilityState<DialogState>,
@@ -48,6 +49,7 @@ fun rememberConversationsDialogsState(requestInProgress: Boolean): Conversations
4849

4950
val leaveGroupDialogState = rememberVisibilityState<GroupDialogState>()
5051
val deleteGroupDialogState = rememberVisibilityState<GroupDialogState>()
52+
val deleteGroupLocallyDialogState = rememberVisibilityState<GroupDialogState>()
5153
val blockUserDialogState = rememberVisibilityState<BlockUserDialogState>()
5254
val unblockUserDialogState = rememberVisibilityState<UnblockUserDialogState>()
5355
val clearContentDialogState = rememberVisibilityState<DialogState>()
@@ -57,6 +59,7 @@ fun rememberConversationsDialogsState(requestInProgress: Boolean): Conversations
5759
ConversationsDialogsState(
5860
leaveGroupDialogState,
5961
deleteGroupDialogState,
62+
deleteGroupLocallyDialogState,
6063
blockUserDialogState,
6164
unblockUserDialogState,
6265
clearContentDialogState,

app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationsScreenContent.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import androidx.compose.runtime.setValue
2929
import androidx.compose.ui.platform.LocalContext
3030
import androidx.compose.ui.platform.LocalInspectionMode
3131
import androidx.hilt.navigation.compose.hiltViewModel
32+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3233
import androidx.paging.LoadState
3334
import androidx.paging.compose.collectAsLazyPagingItems
3435
import com.wire.android.R
@@ -63,6 +64,7 @@ import com.wire.android.ui.destinations.OtherUserProfileScreenDestination
6364
import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState
6465
import com.wire.android.ui.home.conversations.details.dialog.ClearConversationContentDialog
6566
import com.wire.android.ui.home.conversations.details.menu.DeleteConversationGroupDialog
67+
import com.wire.android.ui.home.conversations.details.menu.DeleteConversationGroupLocallyDialog
6668
import com.wire.android.ui.home.conversations.details.menu.LeaveConversationGroupDialog
6769
import com.wire.android.ui.home.conversationslist.common.ConversationList
6870
import com.wire.android.ui.home.conversationslist.model.ConversationItem
@@ -273,6 +275,15 @@ fun ConversationsScreenContent(
273275
onDeleteGroup = conversationListViewModel::deleteGroup
274276
)
275277

278+
DeleteConversationGroupLocallyDialog(
279+
isLoading = requestInProgress,
280+
dialogState = deleteGroupLocallyDialogState,
281+
onDeleteGroupLocally = { state ->
282+
conversationListViewModel.deleteGroupLocally(state)
283+
deleteGroupLocallyDialogState.dismiss()
284+
}
285+
)
286+
276287
LeaveConversationGroupDialog(
277288
dialogState = leaveGroupDialogState,
278289
isLoading = requestInProgress,
@@ -299,9 +310,12 @@ fun ConversationsScreenContent(
299310
WireModalSheetLayout(
300311
sheetState = sheetState,
301312
sheetContent = {
313+
val conversationDeletionInProgress by conversationListViewModel.observeIsDeletingConversationLocally(it.conversationId)
314+
.collectAsStateWithLifecycle(false)
302315
val conversationState = rememberConversationSheetState(
303316
conversationItem = it,
304-
conversationOptionNavigation = currentConversationOptionNavigation
317+
conversationOptionNavigation = currentConversationOptionNavigation,
318+
isConversationDeletionLocallyRunning = conversationDeletionInProgress
305319
)
306320

307321
ConversationSheetContent(
@@ -320,6 +334,7 @@ fun ConversationsScreenContent(
320334
unblockUser = unblockUserDialogState::show,
321335
leaveGroup = leaveGroupDialogState::show,
322336
deleteGroup = deleteGroupDialogState::show,
337+
deleteGroupLocally = deleteGroupLocallyDialogState::show
323338
)
324339
},
325340
)

0 commit comments

Comments
 (0)