diff --git a/app/src/main/kotlin/com/wire/android/di/accountScoped/UserModule.kt b/app/src/main/kotlin/com/wire/android/di/accountScoped/UserModule.kt index 44cb55315e9..7398312b525 100644 --- a/app/src/main/kotlin/com/wire/android/di/accountScoped/UserModule.kt +++ b/app/src/main/kotlin/com/wire/android/di/accountScoped/UserModule.kt @@ -42,6 +42,7 @@ import com.wire.kalium.logic.feature.user.GetSelfTeamIdUseCase import com.wire.kalium.logic.feature.user.GetSelfUserUseCase import com.wire.kalium.logic.feature.user.GetUserInfoUseCase import com.wire.kalium.logic.feature.user.IsPasswordRequiredUseCase +import com.wire.kalium.logic.feature.user.IsPreventAdminlessGroupsEnabledUseCase import com.wire.kalium.logic.feature.user.IsReadOnlyAccountUseCase import com.wire.kalium.logic.feature.user.ObserveSelfUserUseCase import com.wire.kalium.logic.feature.user.ObserveSelfUserWithTeamUseCase @@ -161,6 +162,14 @@ class UserModule { userScope: UserScope ): IsPasswordRequiredUseCase = userScope.isPasswordRequired + @ViewModelScoped + @Provides + fun provideIsPreventAdminlessGroupsEnabledUseCase( + @KaliumCoreLogic coreLogic: CoreLogic, + @CurrentAccount currentAccount: UserId, + ): IsPreventAdminlessGroupsEnabledUseCase = + coreLogic.getSessionScope(currentAccount).isPreventAdminlessGroupsEnabled + @ViewModelScoped @Provides fun provideIsReadOnlyAccountUseCase( diff --git a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModel.kt index 74ea32b1794..bf61f52d073 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModel.kt @@ -21,7 +21,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewModelScope import androidx.work.WorkManager -import com.wire.android.BuildConfig import com.wire.android.appLogger import com.wire.android.di.CurrentAccount import com.wire.android.di.ViewModelScopedPreview @@ -62,6 +61,7 @@ import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromF import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromFolderUseCase import com.wire.kalium.logic.feature.team.DeleteTeamConversationUseCase import com.wire.kalium.logic.feature.team.Result +import com.wire.kalium.logic.feature.user.IsPreventAdminlessGroupsEnabledUseCase import com.wire.kalium.logic.feature.user.ObserveSelfUserUseCase import com.wire.kalium.util.DateTimeUtil import dagger.hilt.android.lifecycle.HiltViewModel @@ -124,6 +124,7 @@ class ConversationOptionsMenuViewModelImpl @Inject constructor( private val markConversationAsDeletedLocally: MarkConversationAsDeletedLocallyUseCase, private val leaveConversation: LeaveConversationUseCase, private val checkConversationLeaveConditions: CheckConversationLeaveConditionsUseCase, + private val isPreventAdminlessGroupsEnabled: IsPreventAdminlessGroupsEnabledUseCase, private val blockUser: BlockUserUseCase, private val unblockUser: UnblockUserUseCase, private val clearConversationContent: ClearConversationContentUseCase, @@ -253,8 +254,8 @@ class ConversationOptionsMenuViewModelImpl @Inject constructor( } override fun onLeaveGroup(leaveGroupState: LeaveGroupDialogState) { - if (BuildConfig.ADMINLESS_GROUP_HANDLING_ENABLED) { - viewModelScope.launch { + viewModelScope.launch { + if (isPreventAdminlessGroupsEnabled()) { when (val result = checkConversationLeaveConditions(leaveGroupState.conversationId)) { CheckConversationLeaveConditionsUseCase.Result.Allow -> leaveGroupDialogState.show(leaveGroupState) is CheckConversationLeaveConditionsUseCase.Result.DoNotAllow -> { @@ -271,9 +272,9 @@ class ConversationOptionsMenuViewModelImpl @Inject constructor( onMessage(HomeSnackBarMessage.LeaveConversationError) } } + } else { + leaveGroupDialogState.show(leaveGroupState) } - } else { - leaveGroupDialogState.show(leaveGroupState) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/debug/featureflags/DebugFeatureFlagsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/debug/featureflags/DebugFeatureFlagsViewModel.kt index 0ccf0cd9bd6..92818b6f684 100644 --- a/app/src/main/kotlin/com/wire/android/ui/debug/featureflags/DebugFeatureFlagsViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/debug/featureflags/DebugFeatureFlagsViewModel.kt @@ -69,6 +69,7 @@ class DebugFeatureFlagsViewModel @Inject constructor( addFeature("Allowed Global Operations", allowedGlobalOperationsModel?.status, allowedGlobalOperationsModel) addFeature("Wire Cells", cellsModel?.status) addFeature("User Profile QR code", enableUserProfileQRCodeConfigModel?.status) + addFeature("Prevent Adminless Groups", preventAdminlessGroupsModel?.status) add( Feature( name = "Channels", diff --git a/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModelTest.kt index d56cfd15d1c..f813ccfb455 100644 --- a/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/common/bottomsheet/conversation/ConversationOptionsMenuViewModelTest.kt @@ -23,6 +23,7 @@ import com.wire.android.assertIs import com.wire.android.config.CoroutineTestExtension import com.wire.android.config.TestDispatcherProvider import com.wire.android.ui.home.HomeSnackBarMessage +import com.wire.android.ui.home.conversationslist.model.LeaveGroupDialogState import com.wire.android.workmanager.worker.ConversationDeletionLocallyStatus import com.wire.android.workmanager.worker.enqueueConversationDeletionLocally import com.wire.kalium.common.error.CoreFailure @@ -51,6 +52,7 @@ import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromF import com.wire.kalium.logic.feature.conversation.folder.RemoveConversationFromFolderUseCase import com.wire.kalium.logic.feature.team.DeleteTeamConversationUseCase import com.wire.kalium.logic.feature.team.Result +import com.wire.kalium.logic.feature.user.IsPreventAdminlessGroupsEnabledUseCase import com.wire.kalium.logic.feature.user.ObserveSelfUserUseCase import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -589,6 +591,57 @@ class ConversationOptionsMenuViewModelTest { } } + @Test + fun `given preventAdminlessGroups disabled, when onLeaveGroup, then show simple leave dialog`() = + runTest(dispatcherProvider.main()) { + val (arrangement, viewModel) = Arrangement() + .withPreventAdminlessGroupsEnabled(false) + .arrange() + + viewModel.onLeaveGroup(LeaveGroupDialogState(conversationId, "name")) + + coVerify(exactly = 0) { arrangement.checkConversationLeaveConditions(any()) } + assertEquals(true, viewModel.leaveGroupDialogState.isVisible) + assertEquals(false, viewModel.leaveGroupOptionsDialogState.isVisible) + } + + @Test + fun `given preventAdminlessGroups enabled and conditions allow, when onLeaveGroup, then show simple leave dialog`() = + runTest(dispatcherProvider.main()) { + val (arrangement, viewModel) = Arrangement() + .withPreventAdminlessGroupsEnabled(true) + .withCheckConversationLeaveConditions(CheckConversationLeaveConditionsUseCase.Result.Allow) + .arrange() + + viewModel.onLeaveGroup(LeaveGroupDialogState(conversationId, "name")) + + coVerify(exactly = 1) { arrangement.checkConversationLeaveConditions(conversationId) } + assertEquals(true, viewModel.leaveGroupDialogState.isVisible) + assertEquals(false, viewModel.leaveGroupOptionsDialogState.isVisible) + } + + @Test + fun `given preventAdminlessGroups enabled and conditions error, when onLeaveGroup, then show snackbar error`() = + runTest(dispatcherProvider.main()) { + val (arrangement, viewModel) = Arrangement() + .withPreventAdminlessGroupsEnabled(true) + .withCheckConversationLeaveConditions( + CheckConversationLeaveConditionsUseCase.Result.Error(CoreFailure.Unknown(null)) + ) + .arrange() + + viewModel.actions.test { + viewModel.onLeaveGroup(LeaveGroupDialogState(conversationId, "name")) + + coVerify(exactly = 1) { arrangement.checkConversationLeaveConditions(conversationId) } + assertIs(awaitItem()).also { + assertIs(it.message) + } + assertEquals(false, viewModel.leaveGroupDialogState.isVisible) + cancelAndIgnoreRemainingEvents() + } + } + inner class Arrangement { @MockK lateinit var observeConversationDetails: ObserveConversationDetailsUseCase @@ -623,6 +676,9 @@ class ConversationOptionsMenuViewModelTest { @MockK lateinit var checkConversationLeaveConditions: CheckConversationLeaveConditionsUseCase + @MockK + lateinit var isPreventAdminlessGroupsEnabled: IsPreventAdminlessGroupsEnabledUseCase + @MockK lateinit var blockUser: BlockUserUseCase @@ -638,6 +694,7 @@ class ConversationOptionsMenuViewModelTest { init { MockKAnnotations.init(this, relaxUnitFun = true) mockkStatic("com.wire.android.workmanager.worker.DeleteConversationLocallyWorkerKt") + coEvery { isPreventAdminlessGroupsEnabled() } returns true } fun arrange() = this to ConversationOptionsMenuViewModelImpl( @@ -653,6 +710,7 @@ class ConversationOptionsMenuViewModelTest { markConversationAsDeletedLocally = markConversationAsDeletedLocally, leaveConversation = leaveConversation, checkConversationLeaveConditions = checkConversationLeaveConditions, + isPreventAdminlessGroupsEnabled = isPreventAdminlessGroupsEnabled, blockUser = blockUser, unblockUser = unblockUser, clearConversationContent = clearConversationContent, @@ -708,6 +766,14 @@ class ConversationOptionsMenuViewModelTest { fun withClearConversationContent(result: ClearConversationContentUseCase.Result) = apply { coEvery { clearConversationContent(any(), any()) } returns result } + + fun withPreventAdminlessGroupsEnabled(enabled: Boolean) = apply { + coEvery { isPreventAdminlessGroupsEnabled() } returns enabled + } + + fun withCheckConversationLeaveConditions(result: CheckConversationLeaveConditionsUseCase.Result) = apply { + coEvery { checkConversationLeaveConditions(any()) } returns result + } } companion object { diff --git a/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt b/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt index 782aa604028..ce3c7a501f1 100644 --- a/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt +++ b/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt @@ -155,7 +155,5 @@ enum class FeatureConfigs(val value: String, val configType: ConfigType) { CALL_QUALITY_MENU_ENABLED("call_quality_menu_enabled", ConfigType.BOOLEAN), - CALL_REACTIONS_ENABLED("call_reactions_enabled", ConfigType.BOOLEAN), - - ADMINLESS_GROUP_HANDLING_ENABLED("adminless_group_handling_enabled", ConfigType.BOOLEAN) + CALL_REACTIONS_ENABLED("call_reactions_enabled", ConfigType.BOOLEAN) } diff --git a/default.json b/default.json index ed613d987e3..1588be90a2c 100644 --- a/default.json +++ b/default.json @@ -73,7 +73,7 @@ "analytics_app_key": "8ffae535f1836ed5f58fd5c8a11c00eca07c5438", "analytics_server_url": "https://wire.count.ly/", "enable_new_registration": true, - "enforce_configuration_signature": true, + "enforce_configuration_signature": true }, "internal": { "application_id": "com.wire.internal", @@ -88,8 +88,7 @@ "enforce_configuration_signature": true, "use_strict_mls_filter": false, "conversation_feeder_enabled": true, - "db_invalidation_control_enabled": false, - "adminless_group_handling_enabled": true + "db_invalidation_control_enabled": false }, "fdroid": { "application_id": "com.wire", @@ -175,6 +174,5 @@ "enforce_configuration_signature": true, "call_quality_menu_enabled": true, "call_reactions_enabled": true, - "nomad_profiles_enabled": true, - "adminless_group_handling_enabled": false + "nomad_profiles_enabled": true } diff --git a/kalium b/kalium index d8ec16d3d0d..9a6221f139b 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit d8ec16d3d0d4f5cb6a4a6bd0fb196be286c309df +Subproject commit 9a6221f139ba9d995180f9afd563c948094f9ed0