Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.wire.kalium.logic.feature.conversation.IsOneToOneConversationCreatedU
import com.wire.kalium.logic.feature.conversation.JoinConversationViaCodeUseCase
import com.wire.kalium.logic.feature.conversation.CheckConversationLeaveConditionsUseCase
import com.wire.kalium.logic.feature.conversation.LeaveConversationUseCase
import com.wire.kalium.logic.feature.conversation.ObserveEligibleMembersForConversationAdminRoleUseCase
import com.wire.kalium.logic.feature.conversation.NotifyConversationIsOpenUseCase
import com.wire.kalium.logic.feature.conversation.ObserveArchivedUnreadConversationsCountUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase
Expand Down Expand Up @@ -209,6 +210,13 @@ class ConversationModule {
fun provideCheckConversationLeaveConditionsUseCase(conversationScope: ConversationScope): CheckConversationLeaveConditionsUseCase =
conversationScope.checkConversationLeaveConditions

@ViewModelScoped
@Provides
fun provideObserveEligibleMembersForConversationAdminRoleUseCase(
conversationScope: ConversationScope
): ObserveEligibleMembersForConversationAdminRoleUseCase =
conversationScope.observeEligibleMembersForConversationAdminRole

@ViewModelScoped
@Provides
fun provideUpdateConversationMutedStatusUseCase(conversationScope: ConversationScope): UpdateConversationMutedStatusUseCase =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import com.wire.android.ui.home.HomeSnackBarMessage
import com.wire.android.ui.home.conversationslist.model.DeleteGroupDialogState
import com.wire.android.ui.home.conversationslist.model.DialogState
import com.wire.android.ui.home.conversationslist.model.LeaveGroupDialogState
import com.wire.android.ui.home.conversationslist.model.LeaveGroupOptionsDialogState
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.workmanager.worker.enqueueConversationDeletionLocally
import com.wire.kalium.logic.data.conversation.ConversationFolder
Expand All @@ -46,9 +47,9 @@ import com.wire.kalium.logic.feature.connection.BlockUserUseCase
import com.wire.kalium.logic.feature.connection.UnblockUserResult
import com.wire.kalium.logic.feature.connection.UnblockUserUseCase
import com.wire.kalium.logic.feature.conversation.ArchiveStatusUpdateResult
import com.wire.kalium.logic.feature.conversation.CheckConversationLeaveConditionsUseCase
import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCase
import com.wire.kalium.logic.feature.conversation.ConversationUpdateStatusResult
import com.wire.kalium.logic.feature.conversation.CheckConversationLeaveConditionsUseCase
import com.wire.kalium.logic.feature.conversation.LeaveConversationUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationDetailsUseCase
import com.wire.kalium.logic.feature.conversation.RemoveMemberFromConversationUseCase
Expand All @@ -70,6 +71,8 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onCompletion
Expand All @@ -82,6 +85,7 @@ import javax.inject.Inject
@ViewModelScopedPreview
interface ConversationOptionsMenuViewModel : ActionsManager<ConversationOptionsMenuViewAction> {
val leaveGroupDialogState: VisibilityState<LeaveGroupDialogState> get() = VisibilityState()
val leaveGroupOptionsDialogState: VisibilityState<LeaveGroupOptionsDialogState> get() = VisibilityState()
val deleteGroupDialogState: VisibilityState<DeleteGroupDialogState> get() = VisibilityState()
val deleteGroupLocallyDialogState: VisibilityState<DeleteGroupDialogState> get() = VisibilityState()
val blockUserDialogState: VisibilityState<BlockUserDialogState> get() = VisibilityState()
Expand Down Expand Up @@ -129,6 +133,7 @@ class ConversationOptionsMenuViewModelImpl @Inject constructor(
private val nonCancellableIOContext = NonCancellable + dispatchers.io()
private val conversationStateFlow: ConcurrentHashMap<ConversationId, StateFlow<ConversationOptionsMenuState>> = ConcurrentHashMap()
override val leaveGroupDialogState: VisibilityState<LeaveGroupDialogState> by mutableStateOf(VisibilityState())
override val leaveGroupOptionsDialogState: VisibilityState<LeaveGroupOptionsDialogState> by mutableStateOf(VisibilityState())
override val deleteGroupDialogState: VisibilityState<DeleteGroupDialogState> by mutableStateOf(VisibilityState())
override val deleteGroupLocallyDialogState: VisibilityState<DeleteGroupDialogState> by mutableStateOf(VisibilityState())
override val blockUserDialogState: VisibilityState<BlockUserDialogState> by mutableStateOf(VisibilityState())
Expand Down Expand Up @@ -253,7 +258,14 @@ class ConversationOptionsMenuViewModelImpl @Inject constructor(
when (val result = checkConversationLeaveConditions(leaveGroupState.conversationId)) {
CheckConversationLeaveConditionsUseCase.Result.Allow -> leaveGroupDialogState.show(leaveGroupState)
is CheckConversationLeaveConditionsUseCase.Result.DoNotAllow -> {
appLogger.i("TODO: Show new leave options dialog: $result")
leaveGroupOptionsDialogState.show(
LeaveGroupOptionsDialogState(
conversationId = leaveGroupState.conversationId,
conversationName = leaveGroupState.conversationName,
showPromoteOption = result.eligibleUsersAvailable,
canDeleteGroup = canDeleteGroup(leaveGroupState.conversationId),
)
)
}
is CheckConversationLeaveConditionsUseCase.Result.Error -> {
onMessage(HomeSnackBarMessage.LeaveConversationError)
Expand All @@ -265,6 +277,12 @@ class ConversationOptionsMenuViewModelImpl @Inject constructor(
}
}

private suspend fun canDeleteGroup(conversationId: ConversationId) = observeConversationStateFlow(conversationId)
.filterIsInstance<ConversationOptionsMenuState.Conversation>()
.firstOrNull()
?.conversation
?.canDeleteGroup() ?: false

override fun leaveGroup(conversationId: ConversationId, conversationName: String, shouldDelete: Boolean) {
viewModelScope.launch {
leaveGroupDialogState.update { it.copy(loading = true) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,23 @@
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.LeaveConversationAdminOptionsDialog
import com.wire.android.ui.home.conversations.details.menu.LeaveConversationGroupDialog
import com.wire.android.ui.home.conversations.folder.ConversationFoldersNavArgs
import com.wire.android.ui.home.conversationslist.model.DeleteGroupDialogState
import com.wire.android.ui.theme.WireTheme
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.id.ConversationId

@SuppressLint("ComposeModifierMissing")
@Composable
fun ConversationOptionsModalSheetLayout(

Check warning on line 52 in app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsModalSheetLayout.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This function has 8 parameters, which is greater than the 7 authorized.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-android&issues=AZ4gLZz_2wV6EN9GqSDb&open=AZ4gLZz_2wV6EN9GqSDb&pullRequest=4819
sheetState: WireModalSheetState<ConversationSheetState>,
openConversationFolders: (ConversationFoldersNavArgs) -> Unit,
onLeftConversation: () -> Unit = {},
onDeletedConversation: () -> Unit = {},
onDeletedConversationLocally: () -> Unit = {},
onPromoteAdmin: (ConversationId) -> Unit = {},
openConversationDebugMenu: (ConversationId) -> Unit = {},
viewModel: ConversationOptionsMenuViewModel =
hiltViewModelScoped<ConversationOptionsMenuViewModelImpl, ConversationOptionsMenuViewModel>()
Expand All @@ -63,6 +66,17 @@
dialogState = viewModel.leaveGroupDialogState,
onLeaveGroup = { viewModel.leaveGroup(it.conversationId, it.conversationName, it.shouldDelete) }
)
LeaveConversationAdminOptionsDialog(
dialogState = viewModel.leaveGroupOptionsDialogState,
onPromoteAdmin = { state ->
viewModel.leaveGroupOptionsDialogState.dismiss()
onPromoteAdmin(state.conversationId)
},
onDeleteGroup = { state ->
viewModel.leaveGroupOptionsDialogState.dismiss()
viewModel.deleteGroupDialogState.show(DeleteGroupDialogState(state.conversationId, state.conversationName))
},
)
DeleteConversationGroupDialog(
dialogState = viewModel.deleteGroupDialogState,
onDeleteGroup = { viewModel.deleteGroup(it.conversationId, it.conversationName) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ import com.ramcosta.composedestinations.generated.app.destinations.SearchConvers
import com.ramcosta.composedestinations.generated.app.destinations.SelfUserProfileScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.ServiceDetailsScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.UpdateAppsAccessScreenDestination
import com.ramcosta.composedestinations.generated.app.destinations.PromoteAdminScreenDestination
import com.wire.android.ui.home.conversations.details.editguestaccess.EditGuestAccessParams
import com.wire.android.ui.home.conversations.promoteadmin.PromoteAdminNavArgs
import com.wire.android.ui.home.conversations.details.options.GroupConversationOptions
import com.wire.android.ui.home.conversations.details.options.GroupConversationOptionsState
import com.wire.android.ui.home.conversations.details.options.LoadingGroupConversation
Expand Down Expand Up @@ -299,6 +301,9 @@ fun GroupConversationDetailsScreen(
)
)
},
onPromoteAdmin = { conversationId ->
navigator.navigate(NavigationCommand(PromoteAdminScreenDestination(PromoteAdminNavArgs(conversationId))))
},
openConversationDebugMenu = {
navigator.navigate(
NavigationCommand(
Expand Down Expand Up @@ -374,6 +379,7 @@ private fun GroupConversationDetailsContent(
onMoveToFolder: (ConversationFoldersNavArgs) -> Unit = {},
onLeftConversation: () -> Unit = {},
onDeletedConversation: () -> Unit = {},
onPromoteAdmin: (ConversationId) -> Unit = {},
openConversationDebugMenu: (ConversationId) -> Unit = {},
initialPageIndex: GroupConversationDetailsTabItem = GroupConversationDetailsTabItem.OPTIONS,
isScreenLoading: StateFlow<Boolean> = MutableStateFlow(false),
Expand Down Expand Up @@ -559,6 +565,7 @@ private fun GroupConversationDetailsContent(
openConversationFolders = onMoveToFolder,
onLeftConversation = onLeftConversation,
onDeletedConversation = onDeletedConversation,
onPromoteAdmin = onPromoteAdmin,
openConversationDebugMenu = openConversationDebugMenu,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Wire
* Copyright (C) 2025 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.visbility.VisibilityState
import com.wire.android.ui.home.conversationslist.model.LeaveGroupOptionsDialogState

@Composable
internal fun LeaveConversationAdminOptionsDialog(
dialogState: VisibilityState<LeaveGroupOptionsDialogState>,
onPromoteAdmin: (LeaveGroupOptionsDialogState) -> Unit,
onDeleteGroup: (LeaveGroupOptionsDialogState) -> Unit,
) {
VisibilityState(dialogState) { state ->
val isInformationalOnly = !state.showPromoteOption && !state.canDeleteGroup
WireDialog(
title = stringResource(id = titleRes(state), state.conversationName),
text = stringResource(id = descriptionRes(state)),
buttonsHorizontalAlignment = false,
onDismiss = dialogState::dismiss,
optionButton1Properties = when {
isInformationalOnly -> WireDialogButtonProperties(
onClick = dialogState::dismiss,
text = stringResource(id = R.string.label_ok),
type = WireDialogButtonType.Primary,
)
state.showPromoteOption -> WireDialogButtonProperties(
onClick = { onPromoteAdmin(state) },
text = stringResource(id = R.string.leave_conversation_admin_options_dialog_promote_button),
type = WireDialogButtonType.Primary,
)
else -> null
},
optionButton2Properties = when {
!isInformationalOnly && state.canDeleteGroup -> WireDialogButtonProperties(
onClick = { onDeleteGroup(state) },
text = stringResource(id = R.string.leave_conversation_admin_options_dialog_delete_button),
type = WireDialogButtonType.Primary,
)
else -> null
},
dismissButtonProperties = if (isInformationalOnly) {
null
} else {
WireDialogButtonProperties(
onClick = dialogState::dismiss,
text = stringResource(id = R.string.label_cancel),
)
},
)
}
}

@Composable
private fun descriptionRes(state: LeaveGroupOptionsDialogState): Int = when {
state.showPromoteOption && state.canDeleteGroup ->
R.string.leave_conversation_admin_options_dialog_description_with_promote

state.showPromoteOption && !state.canDeleteGroup ->
R.string.leave_conversation_admin_options_dialog_description_with_promote_no_delete

!state.showPromoteOption && state.canDeleteGroup ->
R.string.leave_conversation_admin_options_dialog_description_no_promote

else ->
R.string.leave_conversation_admin_options_dialog_description_no_promote_no_delete
}

@Composable
private fun titleRes(state: LeaveGroupOptionsDialogState): Int = when {
state.canDeleteGroup || state.showPromoteOption -> R.string.leave_conversation_dialog_title
else -> R.string.cannot_leave_conversation_dialog_title
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Wire
* Copyright (C) 2025 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.promoteadmin

import android.os.Parcelable
import com.wire.android.ui.home.conversations.QualifiedIdParceler
import com.wire.kalium.logic.data.id.ConversationId
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.TypeParceler

@Parcelize
@TypeParceler<ConversationId, QualifiedIdParceler>()
data class PromoteAdminNavArgs(val conversationId: ConversationId) : Parcelable
Loading
Loading