diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt index a85838d76b..bdf9c175f7 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/ConversationModule.kt @@ -347,4 +347,9 @@ class ConversationModule { @Provides fun provideMoveConversationToFolderUseCase(conversationScope: ConversationScope) = conversationScope.moveConversationToFolder + + @ViewModelScoped + @Provides + fun provideRemoveConversationFromFolderUseCase(conversationScope: ConversationScope) = + conversationScope.removeConversationFromFolder } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt index afcd2d45ba..54a3d46460 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationSheetContent.kt @@ -41,6 +41,7 @@ fun ConversationSheetContent( onMutingConversationStatusChange: () -> Unit, changeFavoriteState: (GroupDialogState, addToFavorite: Boolean) -> Unit, moveConversationToFolder: ((ConversationFoldersNavArgs) -> Unit)?, + removeFromFolder: (conversationId: ConversationId, conversationName: String, folder: ConversationFolder) -> Unit, updateConversationArchiveStatus: (DialogState) -> Unit, clearConversationContent: (DialogState) -> Unit, blockUser: (BlockUserDialogState) -> Unit, @@ -59,6 +60,7 @@ fun ConversationSheetContent( conversationSheetContent = conversationSheetState.conversationSheetContent!!, changeFavoriteState = changeFavoriteState, moveConversationToFolder = moveConversationToFolder, + removeFromFolder = removeFromFolder, updateConversationArchiveStatus = updateConversationArchiveStatus, clearConversationContent = clearConversationContent, blockUserClick = blockUser, diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt index 42cd48af56..a03fe54ead 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/HomeSheetContent.kt @@ -51,7 +51,9 @@ import com.wire.android.ui.home.conversationslist.model.GroupDialogState import com.wire.android.ui.home.conversationslist.model.getMutedStatusTextResource import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.kalium.logic.data.conversation.ConversationFolder import com.wire.kalium.logic.data.conversation.MutedConversationStatus +import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.user.ConnectionState // items cannot be simplified @@ -61,6 +63,7 @@ internal fun ConversationMainSheetContent( conversationSheetContent: ConversationSheetContent, changeFavoriteState: (dialogState: GroupDialogState, addToFavorite: Boolean) -> Unit, moveConversationToFolder: ((ConversationFoldersNavArgs) -> Unit)?, + removeFromFolder: (conversationId: ConversationId, conversationName: String, folder: ConversationFolder) -> Unit, updateConversationArchiveStatus: (DialogState) -> Unit, clearConversationContent: (DialogState) -> Unit, blockUserClick: (BlockUserDialogState) -> Unit, @@ -164,6 +167,26 @@ internal fun ConversationMainSheetContent( ) } } + if (conversationSheetContent.folder != null) { + add { + MenuBottomSheetItem( + leading = { + MenuItemIcon( + id = R.drawable.ic_folder, + contentDescription = null, + ) + }, + title = stringResource(R.string.label_remove_from_folder, conversationSheetContent.folder.name), + onItemClick = { + removeFromFolder( + conversationSheetContent.conversationId, + conversationSheetContent.title, + conversationSheetContent.folder + ) + } + ) + } + } add { MenuBottomSheetItem( leading = { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index fded5b8f48..9b7b5fb7d7 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -114,6 +114,7 @@ import com.wire.android.ui.home.conversations.details.participants.GroupConversa import com.wire.android.ui.home.conversations.details.participants.model.UIParticipant import com.wire.android.ui.home.conversations.folder.ConversationFoldersNavArgs import com.wire.android.ui.home.conversations.folder.ConversationFoldersNavBackArgs +import com.wire.android.ui.home.conversations.folder.RemoveConversationFromFolderVM import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.GroupDialogState import com.wire.android.ui.legalhold.dialog.subject.LegalHoldSubjectConversationDialog @@ -124,6 +125,7 @@ import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.android.util.ui.UIText import com.wire.kalium.logic.data.conversation.Conversation +import com.wire.kalium.logic.data.conversation.ConversationFolder import com.wire.kalium.logic.data.conversation.MutedConversationStatus import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.GroupID @@ -143,7 +145,8 @@ fun GroupConversationDetailsScreen( groupConversationDetailResultRecipient: ResultRecipient, conversationFoldersScreenResultRecipient: ResultRecipient, - viewModel: GroupConversationDetailsViewModel = hiltViewModel() + viewModel: GroupConversationDetailsViewModel = hiltViewModel(), + removeConversationFromFolderVM: RemoveConversationFromFolderVM = hiltViewModel(), ) { val scope = rememberCoroutineScope() val resources = LocalContext.current.resources @@ -254,7 +257,8 @@ fun GroupConversationDetailsScreen( ) ?: false, onMoveToFolder = { navigator.navigate(NavigationCommand(ConversationFoldersScreenDestination(it))) - } + }, + removeFromFolder = removeConversationFromFolderVM::removeFromFolder ) val tryAgainSnackBarMessage = stringResource(id = R.string.error_unknown_message) @@ -308,6 +312,7 @@ private fun GroupConversationDetailsContent( isAbandonedOneOnOneConversation: Boolean, onSearchConversationMessagesClick: () -> Unit, onConversationMediaClick: () -> Unit, + removeFromFolder: (conversationId: ConversationId, conversationName: String, folder: ConversationFolder) -> Unit, onMoveToFolder: (ConversationFoldersNavArgs) -> Unit = {}, initialPageIndex: GroupConversationDetailsTabItem = GroupConversationDetailsTabItem.OPTIONS, changeConversationFavoriteStateViewModel: ChangeConversationFavoriteVM = @@ -493,6 +498,7 @@ private fun GroupConversationDetailsContent( }, changeFavoriteState = changeConversationFavoriteStateViewModel::changeFavoriteState, moveConversationToFolder = onMoveToFolder, + removeFromFolder = removeFromFolder, updateConversationArchiveStatus = { // Only show the confirmation dialog if the conversation is not archived if (!it.isArchived) { @@ -648,6 +654,7 @@ fun PreviewGroupConversationDetails() { onConversationMediaClick = {}, isAbandonedOneOnOneConversation = false, initialPageIndex = GroupConversationDetailsTabItem.PARTICIPANTS, + removeFromFolder = { _, _, _ -> } ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt index 9ab83d75e6..9949234e62 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsViewModel.kt @@ -382,10 +382,6 @@ class GroupConversationDetailsViewModel @Inject constructor( } } - @Suppress("EmptyFunctionBlock") - override fun onMoveConversationToFolder(conversationId: ConversationId?) { - } - override fun updateConversationArchiveStatus( dialogState: DialogState, timestamp: Long, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/menu/GroupConversationDetailsBottomSheetEventsHandler.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/menu/GroupConversationDetailsBottomSheetEventsHandler.kt index 6a0d8132b1..febc04ed8d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/menu/GroupConversationDetailsBottomSheetEventsHandler.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/menu/GroupConversationDetailsBottomSheetEventsHandler.kt @@ -27,7 +27,6 @@ import com.wire.kalium.util.DateTimeUtil @Suppress("TooManyFunctions") interface GroupConversationDetailsBottomSheetEventsHandler { fun onMutingConversationStatusChange(conversationId: ConversationId?, status: MutedConversationStatus, onMessage: (UIText) -> Unit) - fun onMoveConversationToFolder(conversationId: ConversationId? = null) fun updateConversationArchiveStatus( dialogState: DialogState, timestamp: Long = DateTimeUtil.currentInstant().toEpochMilliseconds(), @@ -46,7 +45,6 @@ interface GroupConversationDetailsBottomSheetEventsHandler { ) { } - override fun onMoveConversationToFolder(conversationId: ConversationId?) {} override fun updateConversationArchiveStatus( dialogState: DialogState, timestamp: Long, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/folder/RemoveConversationFromFolderVM.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/folder/RemoveConversationFromFolderVM.kt new file mode 100644 index 0000000000..1d03f66348 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/folder/RemoveConversationFromFolderVM.kt @@ -0,0 +1,93 @@ +/* + * 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.folder + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.wire.android.R +import com.wire.android.di.ScopedArgs +import com.wire.android.di.ViewModelScopedPreview +import com.wire.android.model.DefaultSnackBarMessage +import com.wire.android.model.SnackBarMessage +import com.wire.android.util.dispatchers.DispatcherProvider +import com.wire.android.util.ui.UIText +import com.wire.kalium.logic.data.conversation.ConversationFolder +import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromFolderUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import javax.inject.Inject + +@ViewModelScopedPreview +interface RemoveConversationFromFolderVM { + val infoMessage: SharedFlow + get() = MutableSharedFlow() + + fun removeFromFolder(conversationId: ConversationId, conversationName: String, folder: ConversationFolder) {} +} + +@HiltViewModel +class RemoveConversationFromFolderVMImpl @Inject constructor( + private val dispatchers: DispatcherProvider, + private val removeConversationFromFolder: RemoveConversationFromFolderUseCase, +) : ViewModel(), RemoveConversationFromFolderVM { + + private val _infoMessage = MutableSharedFlow() + override val infoMessage = _infoMessage.asSharedFlow() + + override fun removeFromFolder(conversationId: ConversationId, conversationName: String, folder: ConversationFolder) { + viewModelScope.launch { + val result = withContext(dispatchers.io()) { + removeConversationFromFolder.invoke( + conversationId, + folder.id + ) + } + when (result) { + is RemoveConversationFromFolderUseCase.Result.Failure -> _infoMessage.emit( + DefaultSnackBarMessage( + UIText.StringResource( + R.string.remove_from_folder_failed, + conversationName, + ) + ) + ) + + RemoveConversationFromFolderUseCase.Result.Success -> _infoMessage.emit( + DefaultSnackBarMessage( + UIText.StringResource( + R.string.remove_from_folder_success, + conversationName, + folder.name + ) + ) + ) + } + } + } +} + +@Serializable +data object RemoveConversationFromFolderArgs : ScopedArgs { + override val key = "RemoveConversationFromFolderArgsKey" +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationsScreenContent.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationsScreenContent.kt index 8c5eb0945c..feba6cc4e5 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationsScreenContent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversationslist/ConversationsScreenContent.kt @@ -67,6 +67,9 @@ import com.wire.android.ui.home.conversations.details.dialog.ClearConversationCo 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.conversations.folder.RemoveConversationFromFolderArgs +import com.wire.android.ui.home.conversations.folder.RemoveConversationFromFolderVM +import com.wire.android.ui.home.conversations.folder.RemoveConversationFromFolderVMImpl import com.wire.android.ui.home.conversationslist.common.ConversationList import com.wire.android.ui.home.conversationslist.model.ConversationItem import com.wire.android.ui.home.conversationslist.model.ConversationsSource @@ -108,6 +111,10 @@ fun ConversationsScreenContent( hiltViewModelScoped( ChangeConversationFavoriteStateArgs ), + removeConversationFromFolderViewModel: RemoveConversationFromFolderVM = + hiltViewModelScoped( + RemoveConversationFromFolderArgs + ) ) { var currentConversationOptionNavigation by remember { mutableStateOf(ConversationOptionNavigation.Home) @@ -119,12 +126,6 @@ fun ConversationsScreenContent( val context = LocalContext.current - LaunchedEffect(Unit) { - conversationListViewModel.infoMessage.collect { - sheetState.hide() - } - } - LaunchedEffect(Unit) { conversationListViewModel.closeBottomSheet.collect { sheetState.hide() @@ -327,6 +328,7 @@ fun ConversationsScreenContent( moveConversationToFolder = { navArgs -> navigator.navigate(NavigationCommand(ConversationFoldersScreenDestination(navArgs))) }, + removeFromFolder = removeConversationFromFolderViewModel::removeFromFolder, updateConversationArchiveStatus = showConfirmationDialogOrUnarchive(), clearConversationContent = clearContentDialogState::show, blockUser = blockUserDialogState::show, @@ -339,7 +341,12 @@ fun ConversationsScreenContent( ) } - SnackBarMessageHandler(infoMessages = conversationListViewModel.infoMessage) + SnackBarMessageHandler(infoMessages = conversationListViewModel.infoMessage, onEmitted = { + sheetState.hide() + }) + SnackBarMessageHandler(infoMessages = removeConversationFromFolderViewModel.infoMessage, onEmitted = { + sheetState.hide() + }) SnackBarMessageHandler(infoMessages = changeConversationFavoriteStateViewModel.infoMessage, onEmitted = { sheetState.hide() }) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt index 9ddd2dc33d..d050920a4d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/OtherUserProfileScreen.kt @@ -100,6 +100,9 @@ import com.wire.android.ui.home.conversations.details.SearchAndMediaRow import com.wire.android.ui.home.conversations.details.dialog.ClearConversationContentDialog import com.wire.android.ui.home.conversations.folder.ConversationFoldersNavArgs import com.wire.android.ui.home.conversations.folder.ConversationFoldersNavBackArgs +import com.wire.android.ui.home.conversations.folder.RemoveConversationFromFolderArgs +import com.wire.android.ui.home.conversations.folder.RemoveConversationFromFolderVM +import com.wire.android.ui.home.conversations.folder.RemoveConversationFromFolderVMImpl import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.legalhold.banner.LegalHoldSubjectBanner @@ -267,6 +270,10 @@ fun OtherProfileScreenContent( changeConversationFavoriteViewModel: ChangeConversationFavoriteVM = hiltViewModelScoped( ChangeConversationFavoriteStateArgs + ), + removeConversationFromFolderViewModel: RemoveConversationFromFolderVM = + hiltViewModelScoped( + RemoveConversationFromFolderArgs ) ) { val otherUserProfileScreenState = rememberOtherUserProfileScreenState() @@ -303,6 +310,7 @@ fun OtherProfileScreenContent( }) } + SnackBarMessageHandler(removeConversationFromFolderViewModel.infoMessage, onEmitted = closeBottomSheet) SnackBarMessageHandler(changeConversationFavoriteViewModel.infoMessage, onEmitted = closeBottomSheet) val tabItems by remember(state) { @@ -392,7 +400,8 @@ fun OtherProfileScreenContent( archivingStatusState = archivingConversationDialogState::show, changeFavoriteState = changeConversationFavoriteViewModel::changeFavoriteState, closeBottomSheet = closeBottomSheet, - onMoveToFolder = onMoveToFolder + onMoveToFolder = onMoveToFolder, + removeFromFolder = removeConversationFromFolderViewModel::removeFromFolder ) } ) diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/bottomsheet/OtherUserProfileBottomSheet.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/bottomsheet/OtherUserProfileBottomSheet.kt index 0dd5c6dd88..ed7f8c7e6a 100644 --- a/app/src/main/kotlin/com/wire/android/ui/userprofile/other/bottomsheet/OtherUserProfileBottomSheet.kt +++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/other/bottomsheet/OtherUserProfileBottomSheet.kt @@ -27,6 +27,8 @@ import com.wire.android.ui.home.conversations.folder.ConversationFoldersNavArgs import com.wire.android.ui.home.conversationslist.model.DialogState import com.wire.android.ui.home.conversationslist.model.GroupDialogState import com.wire.android.ui.userprofile.other.OtherUserProfileBottomSheetEventsHandler +import com.wire.kalium.logic.data.conversation.ConversationFolder +import com.wire.kalium.logic.data.id.ConversationId @Composable fun OtherUserProfileBottomSheetContent( @@ -39,6 +41,7 @@ fun OtherUserProfileBottomSheetContent( changeFavoriteState: (GroupDialogState, addToFavorite: Boolean) -> Unit, closeBottomSheet: () -> Unit, getBottomSheetVisibility: () -> Boolean, + removeFromFolder: (conversationId: ConversationId, conversationName: String, folder: ConversationFolder) -> Unit, onMoveToFolder: ((ConversationFoldersNavArgs) -> Unit)? ) { when (val state = bottomSheetState.bottomSheetContentState) { @@ -56,6 +59,7 @@ fun OtherUserProfileBottomSheetContent( }, changeFavoriteState = changeFavoriteState, moveConversationToFolder = onMoveToFolder, + removeFromFolder = removeFromFolder, updateConversationArchiveStatus = { if (!it.isArchived) { archivingStatusState(it) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c496167f22..184bacf723 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -664,6 +664,7 @@ Add to Favorites Remove from Favorites Move to Folder... + Remove from Folder “%1$s” New Folder Create a new folder by pressing the\n“New Folder” button Move to Archive @@ -1683,4 +1684,7 @@ In group conversations, the group admin can overwrite this setting. “%1$s” was moved to “%2$s” “%1$s” could not be moved + “%1$s” was removed from “%2$s” + “%1$s” could not be removed +