From d0c0e95767602e87df98f540742cedfcd8d5d23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Mon, 31 Aug 2026 17:20:26 +0200 Subject: [PATCH 1/3] feat: delete meeting for me [WPB-27932] --- app/src/main/baseline-prof.txt | 26 ++-- .../android/di/accountScoped/MeetingModule.kt | 10 +- app/src/main/startup-prof.txt | 26 ++-- .../ui/options/DeleteMeetingDialog.kt | 24 +++- .../ui/options/MeetingOptionsMenuViewModel.kt | 49 +++++--- .../options/MeetingOptionsModalSheetLayout.kt | 30 ++++- .../meetings/src/main/res/values/strings.xml | 2 + .../MeetingOptionsMenuViewModelTest.kt | 111 ++++++++++++++---- kalium | 2 +- 9 files changed, 216 insertions(+), 64 deletions(-) diff --git a/app/src/main/baseline-prof.txt b/app/src/main/baseline-prof.txt index e26296854b8..dd4dded2070 100644 --- a/app/src/main/baseline-prof.txt +++ b/app/src/main/baseline-prof.txt @@ -29502,14 +29502,22 @@ Lcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseM SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory$Companion;->()V SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory; -Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory; -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory;->()V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V -Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion; -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion;->()V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory; +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory; +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory; Lcom/wire/android/di/accountScoped/MeetingModule$ProvideGetNextMeetingOccurrenceUseCaseMetroFactory; SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideGetNextMeetingOccurrenceUseCaseMetroFactory;->()V SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideGetNextMeetingOccurrenceUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V @@ -48900,4 +48908,4 @@ SPLorg/slf4j/helpers/Util;->()V SPLorg/slf4j/helpers/Util;->safeGetBooleanSystemProperty(Ljava/lang/String;)Z SPLorg/slf4j/helpers/Util;->safeGetSystemProperty(Ljava/lang/String;)Ljava/lang/String; Lorg/slf4j/spi/MDCAdapter; -Lorg/slf4j/spi/SLF4JServiceProvider; \ No newline at end of file +Lorg/slf4j/spi/SLF4JServiceProvider; diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/MeetingModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/MeetingModule.kt index 7f74075609e..c9575e0ab62 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/MeetingModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/MeetingModule.kt @@ -22,6 +22,8 @@ import com.wire.android.di.KaliumCoreLogic import com.wire.kalium.logic.CoreLogic import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.meeting.CreateNewMeetingUseCase +import com.wire.kalium.logic.feature.meeting.DeleteMeetingForEveryoneUseCase +import com.wire.kalium.logic.feature.meeting.DeleteMeetingForMeUseCase import com.wire.kalium.logic.feature.meeting.EnsureMeetingIsMLSEstablishedUseCase import com.wire.kalium.logic.feature.meeting.GetNextUnfinishedMeetingOccurrenceUseCase import com.wire.kalium.logic.feature.meeting.GetPaginatedMeetingOccurrencesUseCase @@ -48,8 +50,12 @@ class MeetingModule { meetingScope.observeMeetingOccurrence @Provides - fun provideDeleteMeetingUseCase(meetingScope: MeetingScope): com.wire.kalium.logic.feature.meeting.DeleteMeetingUseCase = - meetingScope.deleteMeeting + fun provideDeleteMeetingForEveryoneUseCase(meetingScope: MeetingScope): DeleteMeetingForEveryoneUseCase = + meetingScope.deleteMeetingForEveryone + + @Provides + fun provideDeleteMeetingForMeUseCase(meetingScope: MeetingScope): DeleteMeetingForMeUseCase = + meetingScope.deleteMeetingForMe @Provides fun provideCreateNewMeetingUseCase(meetingScope: MeetingScope): CreateNewMeetingUseCase = diff --git a/app/src/main/startup-prof.txt b/app/src/main/startup-prof.txt index e26296854b8..dd4dded2070 100644 --- a/app/src/main/startup-prof.txt +++ b/app/src/main/startup-prof.txt @@ -29502,14 +29502,22 @@ Lcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseM SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory$Companion;->()V SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideCreateNewMeetingUseCaseMetroFactory; -Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory; -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory;->()V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V -Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion; -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion;->()V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V -SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingUseCaseMetroFactory; +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForEveryoneUseCaseMetroFactory; +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;Lkotlin/jvm/internal/DefaultConstructorMarker;)V +Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion; +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion;->()V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion;->(Lkotlin/jvm/internal/DefaultConstructorMarker;)V +SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory$Companion;->create(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)Lcom/wire/android/di/accountScoped/MeetingModule$ProvideDeleteMeetingForMeUseCaseMetroFactory; Lcom/wire/android/di/accountScoped/MeetingModule$ProvideGetNextMeetingOccurrenceUseCaseMetroFactory; SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideGetNextMeetingOccurrenceUseCaseMetroFactory;->()V SPLcom/wire/android/di/accountScoped/MeetingModule$ProvideGetNextMeetingOccurrenceUseCaseMetroFactory;->(Lcom/wire/android/di/accountScoped/MeetingModule;Ldev/zacsweers/metro/Provider;)V @@ -48900,4 +48908,4 @@ SPLorg/slf4j/helpers/Util;->()V SPLorg/slf4j/helpers/Util;->safeGetBooleanSystemProperty(Ljava/lang/String;)Z SPLorg/slf4j/helpers/Util;->safeGetSystemProperty(Ljava/lang/String;)Ljava/lang/String; Lorg/slf4j/spi/MDCAdapter; -Lorg/slf4j/spi/SLF4JServiceProvider; \ No newline at end of file +Lorg/slf4j/spi/SLF4JServiceProvider; diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/DeleteMeetingDialog.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/DeleteMeetingDialog.kt index b338d41e113..07c960faaac 100644 --- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/DeleteMeetingDialog.kt +++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/DeleteMeetingDialog.kt @@ -20,7 +20,6 @@ package com.wire.android.feature.meetings.ui.options import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource -import com.wire.android.ui.common.R as commonR import com.wire.android.feature.meetings.R import com.wire.android.ui.common.VisibilityState import com.wire.android.ui.common.WireDialog @@ -30,6 +29,7 @@ import com.wire.android.ui.common.button.WireButtonState import com.wire.android.ui.common.visbility.VisibilityState import com.wire.android.ui.common.wireDialogPropertiesBuilder import com.wire.kalium.logic.data.id.MeetingId +import com.wire.android.ui.common.R as commonR @Composable internal fun DeleteMeetingDialog( @@ -37,10 +37,21 @@ internal fun DeleteMeetingDialog( onDelete: (DeleteMeetingDialogState) -> Unit, ) { VisibilityState(dialogState) { state -> + val (titleResId, textResId) = when (state.deleteType) { + DeleteMeetingType.ForEveryone -> Pair( + R.string.delete_meeting_for_everyone_title, + R.string.delete_meeting_for_everyone_description, + ) + + DeleteMeetingType.ForMe -> Pair( + R.string.delete_meeting_for_me_title, + R.string.delete_meeting_for_me_description, + ) + } WireDialog( properties = wireDialogPropertiesBuilder(dismissOnBackPress = !state.loading, dismissOnClickOutside = !state.loading), - title = stringResource(id = R.string.delete_meeting_for_everyone_title, state.meetingTitle), - text = stringResource(id = R.string.delete_meeting_for_everyone_description), + title = stringResource(id = titleResId), + text = stringResource(id = textResId), buttonsHorizontalAlignment = true, onDismiss = dialogState::dismiss, dismissButtonProperties = WireDialogButtonProperties( @@ -61,8 +72,13 @@ internal fun DeleteMeetingDialog( } } +enum class DeleteMeetingType { + ForMe, + ForEveryone, +} + data class DeleteMeetingDialogState( - val forEveryone: Boolean, + val deleteType: DeleteMeetingType, val meetingId: MeetingId, val meetingTitle: String, val loading: Boolean = false, diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModel.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModel.kt index 65ead7e9b75..f5b566253bb 100644 --- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModel.kt +++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModel.kt @@ -36,7 +36,8 @@ import com.wire.kalium.logic.data.id.ConversationId import com.wire.kalium.logic.data.id.MeetingId import com.wire.kalium.logic.data.meeting.MeetingOccurrence import com.wire.kalium.logic.feature.call.usecase.ObserveActiveCallsUseCase -import com.wire.kalium.logic.feature.meeting.DeleteMeetingUseCase +import com.wire.kalium.logic.feature.meeting.DeleteMeetingForEveryoneUseCase +import com.wire.kalium.logic.feature.meeting.DeleteMeetingForMeUseCase import com.wire.kalium.logic.feature.meeting.ObserveMeetingOccurrenceUseCase import dev.zacsweers.metro.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -52,9 +53,10 @@ import kotlinx.coroutines.launch import java.util.concurrent.ConcurrentHashMap interface MeetingOptionsMenuViewModel : ActionsManager { - val deleteMeetingForEveryoneDialogState: VisibilityState get() = VisibilityState() + val deleteMeetingDialogState: VisibilityState get() = VisibilityState() fun observeMeetingStateFlow(occurrenceId: String): StateFlow - fun deleteMeeting(meetingId: MeetingId, meetingTitle: String) + fun deleteMeetingForEveryone(meetingId: MeetingId, meetingTitle: String) + fun deleteMeetingForMe(meetingId: MeetingId, meetingTitle: String) fun checkCallStatusAndSendCallAction(conversationId: ConversationId) } @@ -71,18 +73,20 @@ class MeetingOptionsMenuViewModelPreview(currentTimeProvider: CurrentTimeProvide } ?: MeetingOptionsMenuState.NotAvailable ) - override fun deleteMeeting(meetingId: MeetingId, meetingTitle: String) = Unit + override fun deleteMeetingForEveryone(meetingId: MeetingId, meetingTitle: String) = Unit + override fun deleteMeetingForMe(meetingId: MeetingId, meetingTitle: String) = Unit override fun checkCallStatusAndSendCallAction(conversationId: ConversationId) = Unit } class MeetingOptionsMenuViewModelImpl @Inject constructor( private val currentTimeProvider: CurrentTimeProvider, private val observeMeetingOccurrenceUseCase: ObserveMeetingOccurrenceUseCase, - private val deleteMeetingUseCase: DeleteMeetingUseCase, + private val deleteMeetingForEveryoneUseCase: DeleteMeetingForEveryoneUseCase, + private val deleteMeetingForMeUseCase: DeleteMeetingForMeUseCase, private val observeActiveCallsUseCase: ObserveActiveCallsUseCase, ) : MeetingOptionsMenuViewModel, ActionsViewModel() { private val stateFlow: ConcurrentHashMap> = ConcurrentHashMap() - override val deleteMeetingForEveryoneDialogState: VisibilityState by mutableStateOf(VisibilityState()) + override val deleteMeetingDialogState: VisibilityState by mutableStateOf(VisibilityState()) override fun observeMeetingStateFlow(occurrenceId: String): StateFlow = stateFlow.getOrPut(occurrenceId) { flowOf(occurrenceId) @@ -98,8 +102,7 @@ class MeetingOptionsMenuViewModelImpl @Inject constructor( editMeetingEnabled = it.selfRole == MeetingOccurrence.SelfRole.Creator && !hasEnded, deleteOption = when (it.selfRole) { MeetingOccurrence.SelfRole.Creator -> MeetingOptionsMenuState.Meeting.DeleteOption.ForEveryone - // for now, we don't show delete option for members as "delete for me" is not yet implemented - MeetingOccurrence.SelfRole.Member -> MeetingOptionsMenuState.Meeting.DeleteOption.None + MeetingOccurrence.SelfRole.Member -> MeetingOptionsMenuState.Meeting.DeleteOption.ForMe }, ) } ?: MeetingOptionsMenuState.NotAvailable @@ -113,21 +116,39 @@ class MeetingOptionsMenuViewModelImpl @Inject constructor( ) } - override fun deleteMeeting(meetingId: MeetingId, meetingTitle: String) { + override fun deleteMeetingForEveryone(meetingId: MeetingId, meetingTitle: String) { viewModelScope.launch { - deleteMeetingForEveryoneDialogState.update { it.copy(loading = true) } - when (deleteMeetingUseCase.invoke(meetingId = meetingId)) { - is DeleteMeetingUseCase.Result.Success -> { + deleteMeetingDialogState.update { it.copy(loading = true) } + when (deleteMeetingForEveryoneUseCase.invoke(meetingId = meetingId)) { + is DeleteMeetingForEveryoneUseCase.Result.Success -> { UIText.StringResource(R.string.meeting_deleted_success, meetingTitle).asSnackBarMessage() } - is DeleteMeetingUseCase.Result.Failure -> { + is DeleteMeetingForEveryoneUseCase.Result.Failure -> { UIText.StringResource(R.string.meeting_deleted_failure, meetingTitle).asSnackBarMessage() } }.let { sendAction(MeetingOptionsMenuViewAction.Message(it)) } - deleteMeetingForEveryoneDialogState.dismiss() + deleteMeetingDialogState.dismiss() + } + } + + override fun deleteMeetingForMe(meetingId: MeetingId, meetingTitle: String) { + viewModelScope.launch { + deleteMeetingDialogState.update { it.copy(loading = true) } + when (deleteMeetingForMeUseCase.invoke(meetingId = meetingId)) { + is DeleteMeetingForMeUseCase.Result.Success -> { + UIText.StringResource(R.string.meeting_deleted_success, meetingTitle).asSnackBarMessage() + } + + is DeleteMeetingForMeUseCase.Result.Failure -> { + UIText.StringResource(R.string.meeting_deleted_failure, meetingTitle).asSnackBarMessage() + } + }.let { + sendAction(MeetingOptionsMenuViewAction.Message(it)) + } + deleteMeetingDialogState.dismiss() } } diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsModalSheetLayout.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsModalSheetLayout.kt index b9a20e6c2e8..51f37899ac9 100644 --- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsModalSheetLayout.kt +++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/options/MeetingOptionsModalSheetLayout.kt @@ -75,10 +75,25 @@ fun MeetingOptionsModalSheetLayout( when (val state = viewModel.observeMeetingStateFlow(occurrenceId).collectAsStateWithLifecycle().value) { is MeetingOptionsMenuState.Meeting -> MeetingOptionsModalContent( meetingState = state, + onDeleteMeetingForMe = { + sheetState.hide { + viewModel.deleteMeetingDialogState.show( + DeleteMeetingDialogState( + deleteType = DeleteMeetingType.ForMe, + meetingId = state.meetingId, + meetingTitle = state.title + ) + ) + } + }, onDeleteMeetingForEveryone = { sheetState.hide { - viewModel.deleteMeetingForEveryoneDialogState.show( - DeleteMeetingDialogState(forEveryone = true, meetingId = state.meetingId, meetingTitle = state.title) + viewModel.deleteMeetingDialogState.show( + DeleteMeetingDialogState( + deleteType = DeleteMeetingType.ForEveryone, + meetingId = state.meetingId, + meetingTitle = state.title + ) ) } }, @@ -109,8 +124,13 @@ fun MeetingOptionsModalSheetLayout( ) DeleteMeetingDialog( - dialogState = viewModel.deleteMeetingForEveryoneDialogState, - onDelete = { state -> viewModel.deleteMeeting(state.meetingId, state.meetingTitle) } + dialogState = viewModel.deleteMeetingDialogState, + onDelete = { state -> + when (state.deleteType) { + DeleteMeetingType.ForEveryone -> viewModel.deleteMeetingForEveryone(state.meetingId, state.meetingTitle) + DeleteMeetingType.ForMe -> viewModel.deleteMeetingForMe(state.meetingId, state.meetingTitle) + } + } ) HandleActions(viewModel.actions) { action -> @@ -210,7 +230,7 @@ private fun MeetingOptionsModalContent( title = stringResource(R.string.meeting_options_delete_meeting_for_me), leading = { Icon( - painter = painterResource(UICommonR.drawable.ic_close), + painter = painterResource(UICommonR.drawable.ic_delete), contentDescription = null, tint = colorsScheme().error, ) diff --git a/features/meetings/src/main/res/values/strings.xml b/features/meetings/src/main/res/values/strings.xml index 710e7bfe92f..ba8253c479c 100644 --- a/features/meetings/src/main/res/values/strings.xml +++ b/features/meetings/src/main/res/values/strings.xml @@ -73,6 +73,8 @@ To join a meeting, allow Wire to access your microphone in your device settings. Delete meeting for everyone The meeting will be deleted for you and everybody else. All participants will be notified. + Delete meeting for me + The meeting will be deleted from your calendar. This will not affect other participants. ā€œ%sā€ deleted There was an error while deleting meeting ā€œ%sā€ Could not start meeting diff --git a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt index 07534a6d238..471f5120475 100644 --- a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt +++ b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt @@ -33,7 +33,8 @@ import com.wire.kalium.logic.data.meeting.Meeting import com.wire.kalium.logic.data.meeting.MeetingOccurrence import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.call.usecase.ObserveActiveCallsUseCase -import com.wire.kalium.logic.feature.meeting.DeleteMeetingUseCase +import com.wire.kalium.logic.feature.meeting.DeleteMeetingForEveryoneUseCase +import com.wire.kalium.logic.feature.meeting.DeleteMeetingForMeUseCase import com.wire.kalium.logic.feature.meeting.ObserveMeetingOccurrenceUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -96,7 +97,7 @@ class MeetingOptionsMenuViewModelTest { } @Test - fun givenFutureMeeting_andSelfUserIsMember_whenObserving_thenEditAndDeleteIsNotAvailable() = runTest(dispatcher) { + fun givenFutureMeeting_andSelfUserIsMember_whenObserving_thenDeleteForMeIsAvailable() = runTest(dispatcher) { val meeting = meeting( selfRole = MeetingOccurrence.SelfRole.Member, occurrenceStartTime = CURRENT_TIME + 1.hours, @@ -111,7 +112,7 @@ class MeetingOptionsMenuViewModelTest { runCurrent() assertInstanceOf(awaitItem()).also { - assertEquals(MeetingOptionsMenuState.Meeting.DeleteOption.None, it.deleteOption) + assertEquals(MeetingOptionsMenuState.Meeting.DeleteOption.ForMe, it.deleteOption) assertEquals(false, it.editMeetingEnabled) } cancelAndConsumeRemainingEvents() @@ -165,18 +166,66 @@ class MeetingOptionsMenuViewModelTest { } @Test - fun givenSuccess_whenDeletingMeeting_thenSuccessMessageIsSentAndDialogIsDismissed() = runTest(dispatcher) { + fun givenSuccess_whenDeletingMeetingForEveryone_thenSuccessMessageIsSentAndDialogIsDismissed() = runTest(dispatcher) { + val (arrangement, viewModel) = Arrangement() + .withDeleteMeetingForEveryoneResult(DeleteMeetingForEveryoneUseCase.Result.Success) + .arrange() + viewModel.deleteMeetingDialogState.show(deleteMeetingForEveryoneDialogState()) + + viewModel.actions.test { + viewModel.deleteMeetingForEveryone(MEETING_ID, MEETING_TITLE) + advanceUntilIdle() + + coVerify(exactly = 1) { arrangement.deleteMeetingForEveryoneUseCase.invoke(MEETING_ID) } + assertFalse(viewModel.deleteMeetingDialogState.isVisible) + assertEquals( + MeetingOptionsMenuViewAction.Message( + UIText.StringResource(R.string.meeting_deleted_success, MEETING_TITLE).asSnackBarMessage() + ), + awaitItem() + ) + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun givenFailure_whenDeletingMeetingForEveryone_thenFailureMessageIsSentAndDialogIsDismissed() = runTest(dispatcher) { + val (arrangement, viewModel) = Arrangement() + .withDeleteMeetingForEveryoneResult( + DeleteMeetingForEveryoneUseCase.Result.Failure(CoreFailure.Unknown(RuntimeException("delete failed"))) + ) + .arrange() + viewModel.deleteMeetingDialogState.show(deleteMeetingForEveryoneDialogState()) + + viewModel.actions.test { + viewModel.deleteMeetingForEveryone(MEETING_ID, MEETING_TITLE) + advanceUntilIdle() + + coVerify(exactly = 1) { arrangement.deleteMeetingForEveryoneUseCase.invoke(MEETING_ID) } + assertFalse(viewModel.deleteMeetingDialogState.isVisible) + assertEquals( + MeetingOptionsMenuViewAction.Message( + UIText.StringResource(R.string.meeting_deleted_failure, MEETING_TITLE).asSnackBarMessage() + ), + awaitItem() + ) + cancelAndConsumeRemainingEvents() + } + } + + @Test + fun givenSuccess_whenDeletingMeetingForMe_thenSuccessMessageIsSentAndDialogIsDismissed() = runTest(dispatcher) { val (arrangement, viewModel) = Arrangement() - .withDeleteMeetingResult(DeleteMeetingUseCase.Result.Success) + .withDeleteMeetingForMeResult(DeleteMeetingForMeUseCase.Result.Success) .arrange() - viewModel.deleteMeetingForEveryoneDialogState.show(DeleteMeetingDialogState(true, MEETING_ID, MEETING_TITLE)) + viewModel.deleteMeetingDialogState.show(deleteMeetingForMeDialogState()) viewModel.actions.test { - viewModel.deleteMeeting(MEETING_ID, MEETING_TITLE) + viewModel.deleteMeetingForMe(MEETING_ID, MEETING_TITLE) advanceUntilIdle() - coVerify(exactly = 1) { arrangement.deleteMeetingUseCase.invoke(MEETING_ID) } - assertFalse(viewModel.deleteMeetingForEveryoneDialogState.isVisible) + coVerify(exactly = 1) { arrangement.deleteMeetingForMeUseCase.invoke(MEETING_ID) } + assertFalse(viewModel.deleteMeetingDialogState.isVisible) assertEquals( MeetingOptionsMenuViewAction.Message( UIText.StringResource(R.string.meeting_deleted_success, MEETING_TITLE).asSnackBarMessage() @@ -188,18 +237,20 @@ class MeetingOptionsMenuViewModelTest { } @Test - fun givenFailure_whenDeletingMeeting_thenFailureMessageIsSentAndDialogIsDismissed() = runTest(dispatcher) { + fun givenFailure_whenDeletingMeetingForMe_thenFailureMessageIsSentAndDialogIsDismissed() = runTest(dispatcher) { val (arrangement, viewModel) = Arrangement() - .withDeleteMeetingResult(DeleteMeetingUseCase.Result.Failure(CoreFailure.Unknown(RuntimeException("delete failed")))) + .withDeleteMeetingForMeResult( + DeleteMeetingForMeUseCase.Result.Failure(CoreFailure.Unknown(RuntimeException("delete failed"))) + ) .arrange() - viewModel.deleteMeetingForEveryoneDialogState.show(DeleteMeetingDialogState(true, MEETING_ID, MEETING_TITLE)) + viewModel.deleteMeetingDialogState.show(deleteMeetingForMeDialogState()) viewModel.actions.test { - viewModel.deleteMeeting(MEETING_ID, MEETING_TITLE) + viewModel.deleteMeetingForMe(MEETING_ID, MEETING_TITLE) advanceUntilIdle() - coVerify(exactly = 1) { arrangement.deleteMeetingUseCase.invoke(MEETING_ID) } - assertFalse(viewModel.deleteMeetingForEveryoneDialogState.isVisible) + coVerify(exactly = 1) { arrangement.deleteMeetingForMeUseCase.invoke(MEETING_ID) } + assertFalse(viewModel.deleteMeetingDialogState.isVisible) assertEquals( MeetingOptionsMenuViewAction.Message( UIText.StringResource(R.string.meeting_deleted_failure, MEETING_TITLE).asSnackBarMessage() @@ -296,12 +347,27 @@ class MeetingOptionsMenuViewModelTest { callerTeamName = "some_team_name" ) + private fun deleteMeetingForEveryoneDialogState() = DeleteMeetingDialogState( + deleteType = DeleteMeetingType.ForEveryone, + meetingId = MEETING_ID, + meetingTitle = MEETING_TITLE, + ) + + private fun deleteMeetingForMeDialogState() = DeleteMeetingDialogState( + deleteType = DeleteMeetingType.ForMe, + meetingId = MEETING_ID, + meetingTitle = MEETING_TITLE, + ) + private class Arrangement { @MockK lateinit var observeMeetingOccurrenceUseCase: ObserveMeetingOccurrenceUseCase @MockK - lateinit var deleteMeetingUseCase: DeleteMeetingUseCase + lateinit var deleteMeetingForEveryoneUseCase: DeleteMeetingForEveryoneUseCase + + @MockK + lateinit var deleteMeetingForMeUseCase: DeleteMeetingForMeUseCase @MockK lateinit var observeActiveCallsUseCase: ObserveActiveCallsUseCase @@ -311,18 +377,23 @@ class MeetingOptionsMenuViewModelTest { init { MockKAnnotations.init(this) coEvery { observeMeetingOccurrenceUseCase.invoke(OCCURRENCE_ID) } returns flowOf(null) - coEvery { deleteMeetingUseCase.invoke(MEETING_ID) } returns DeleteMeetingUseCase.Result.Success + coEvery { deleteMeetingForEveryoneUseCase.invoke(MEETING_ID) } returns DeleteMeetingForEveryoneUseCase.Result.Success + coEvery { deleteMeetingForMeUseCase.invoke(MEETING_ID) } returns DeleteMeetingForMeUseCase.Result.Success } fun withObservedMeeting(meeting: MeetingOccurrence?) = apply { coEvery { observeMeetingOccurrenceUseCase.invoke(OCCURRENCE_ID) } returns flowOf(meeting) } - fun withDeleteMeetingResult(result: DeleteMeetingUseCase.Result) = apply { - coEvery { deleteMeetingUseCase.invoke(MEETING_ID) } returns result + fun withDeleteMeetingForEveryoneResult(result: DeleteMeetingForEveryoneUseCase.Result) = apply { + coEvery { deleteMeetingForEveryoneUseCase.invoke(MEETING_ID) } returns result + } + fun withDeleteMeetingForMeResult(result: DeleteMeetingForMeUseCase.Result) = apply { + coEvery { deleteMeetingForMeUseCase.invoke(MEETING_ID) } returns result } fun arrange() = this to MeetingOptionsMenuViewModelImpl( currentTimeProvider = currentTimeProvider, observeMeetingOccurrenceUseCase = observeMeetingOccurrenceUseCase, - deleteMeetingUseCase = deleteMeetingUseCase, + deleteMeetingForEveryoneUseCase = deleteMeetingForEveryoneUseCase, + deleteMeetingForMeUseCase = deleteMeetingForMeUseCase, observeActiveCallsUseCase = observeActiveCallsUseCase, ) } diff --git a/kalium b/kalium index 3bbd9d97b58..996816c6804 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 3bbd9d97b584ea9095f326757dc84230acdad08e +Subproject commit 996816c6804dfbaf5a3c6d311e4b85186bc78bcd From c4f84acf1af6243bb93525a5bc67fffb85daab87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Mon, 31 Aug 2026 17:36:30 +0200 Subject: [PATCH 2/3] update tests --- .../meetings/ui/options/MeetingOptionsMenuViewModelTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt index 471f5120475..22c8a47c01c 100644 --- a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt +++ b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/options/MeetingOptionsMenuViewModelTest.kt @@ -143,7 +143,7 @@ class MeetingOptionsMenuViewModelTest { } @Test - fun givenPastMeeting_andSelfUserIsMember_whenObserving_thenEditAndDeleteIsNotAvailable() = runTest(dispatcher) { + fun givenPastMeeting_andSelfUserIsMember_whenObserving_thenEditIsNotAvailableAndDeleteForMeIsAvailable() = runTest(dispatcher) { val meeting = meeting( selfRole = MeetingOccurrence.SelfRole.Member, occurrenceStartTime = CURRENT_TIME - 2.hours, @@ -158,7 +158,7 @@ class MeetingOptionsMenuViewModelTest { runCurrent() assertInstanceOf(awaitItem()).also { - assertEquals(MeetingOptionsMenuState.Meeting.DeleteOption.None, it.deleteOption) + assertEquals(MeetingOptionsMenuState.Meeting.DeleteOption.ForMe, it.deleteOption) assertEquals(false, it.editMeetingEnabled) } cancelAndConsumeRemainingEvents() From a5451c87be4a4f3b70ec36c939fb3ce832398784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Saleniuk?= Date: Wed, 2 Sep 2026 16:26:21 +0200 Subject: [PATCH 3/3] update kalium --- kalium | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalium b/kalium index 996816c6804..44835148681 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit 996816c6804dfbaf5a3c6d311e4b85186bc78bcd +Subproject commit 448351486818f7cfe2719145bfbd4c27929d9c7b