diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvent.kt similarity index 56% rename from features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt rename to features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvent.kt index 3d1c162dd3..b98ab899b9 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvents.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollEvent.kt @@ -10,15 +10,15 @@ package io.element.android.features.poll.impl.create import io.element.android.libraries.matrix.api.poll.PollKind -sealed interface CreatePollEvents { - data object Save : CreatePollEvents - data class Delete(val confirmed: Boolean) : CreatePollEvents - data class SetQuestion(val question: String) : CreatePollEvents - data class SetAnswer(val index: Int, val text: String) : CreatePollEvents - data object AddAnswer : CreatePollEvents - data class RemoveAnswer(val index: Int) : CreatePollEvents - data class SetPollKind(val pollKind: PollKind) : CreatePollEvents - data object NavBack : CreatePollEvents - data object ConfirmNavBack : CreatePollEvents - data object HideConfirmation : CreatePollEvents +sealed interface CreatePollEvent { + data object Save : CreatePollEvent + data class Delete(val confirmed: Boolean) : CreatePollEvent + data class SetQuestion(val question: String) : CreatePollEvent + data class SetAnswer(val index: Int, val text: String) : CreatePollEvent + data object AddAnswer : CreatePollEvent + data class RemoveAnswer(val index: Int) : CreatePollEvent + data class SetPollKind(val pollKind: PollKind) : CreatePollEvent + data object NavBack : CreatePollEvent + data object ConfirmNavBack : CreatePollEvent + data object HideConfirmation : CreatePollEvent } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt index 3da8c3dc53..6138bab2ae 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenter.kt @@ -97,9 +97,9 @@ class CreatePollPresenter( val scope = rememberCoroutineScope() - fun handleEvent(event: CreatePollEvents) { + fun handleEvent(event: CreatePollEvent) { when (event) { - is CreatePollEvents.Save -> scope.launch { + is CreatePollEvent.Save -> scope.launch { if (canSave) { repository.savePoll( existingPollId = when (mode) { @@ -123,7 +123,7 @@ class CreatePollPresenter( Timber.d("Cannot create poll") } } - is CreatePollEvents.Delete -> { + is CreatePollEvent.Delete -> { if (mode !is CreatePollMode.EditPoll) { return } @@ -139,25 +139,25 @@ class CreatePollPresenter( navigateUp() } } - is CreatePollEvents.AddAnswer -> { + is CreatePollEvent.AddAnswer -> { poll = poll.withNewAnswer() } - is CreatePollEvents.RemoveAnswer -> { + is CreatePollEvent.RemoveAnswer -> { poll = poll.withAnswerRemoved(event.index) } - is CreatePollEvents.SetAnswer -> { + is CreatePollEvent.SetAnswer -> { poll = poll.withAnswerChanged(event.index, event.text) } - is CreatePollEvents.SetPollKind -> { + is CreatePollEvent.SetPollKind -> { poll = poll.copy(isDisclosed = event.pollKind.isDisclosed) } - is CreatePollEvents.SetQuestion -> { + is CreatePollEvent.SetQuestion -> { poll = poll.copy(question = event.question) } - is CreatePollEvents.NavBack -> { + is CreatePollEvent.NavBack -> { navigateUp() } - CreatePollEvents.ConfirmNavBack -> { + CreatePollEvent.ConfirmNavBack -> { val shouldConfirm = isDirty if (shouldConfirm) { showBackConfirmation = true @@ -165,7 +165,7 @@ class CreatePollPresenter( navigateUp() } } - is CreatePollEvents.HideConfirmation -> { + is CreatePollEvent.HideConfirmation -> { showBackConfirmation = false showDeleteConfirmation = false } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt index 1046f25bd5..80aa7dc4b0 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollState.kt @@ -20,7 +20,7 @@ data class CreatePollState( val pollKind: PollKind, val showBackConfirmation: Boolean, val showDeleteConfirmation: Boolean, - val eventSink: (CreatePollEvents) -> Unit, + val eventSink: (CreatePollEvent) -> Unit, ) { enum class Mode { New, diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt index 53caf33707..3abf3718af 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/create/CreatePollView.kt @@ -62,20 +62,21 @@ fun CreatePollView( ) { val coroutineScope = rememberCoroutineScope() - val navBack = { state.eventSink(CreatePollEvents.ConfirmNavBack) } + val navBack = { state.eventSink(CreatePollEvent.ConfirmNavBack) } BackHandler(onBack = navBack) if (state.showBackConfirmation) { SaveChangesDialog( - onSubmitClick = { state.eventSink(CreatePollEvents.NavBack) }, - onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } + onSaveClick = { state.eventSink(CreatePollEvent.Save) }, + onDiscardClick = { state.eventSink(CreatePollEvent.NavBack) }, + onDismiss = { state.eventSink(CreatePollEvent.HideConfirmation) }, ) } if (state.showDeleteConfirmation) { ConfirmationDialog( title = stringResource(id = R.string.screen_edit_poll_delete_confirmation_title), content = stringResource(id = R.string.screen_edit_poll_delete_confirmation), - onSubmitClick = { state.eventSink(CreatePollEvents.Delete(confirmed = true)) }, - onDismiss = { state.eventSink(CreatePollEvents.HideConfirmation) } + onSubmitClick = { state.eventSink(CreatePollEvent.Delete(confirmed = true)) }, + onDismiss = { state.eventSink(CreatePollEvent.HideConfirmation) } ) } val questionFocusRequester = remember { FocusRequester() } @@ -90,7 +91,7 @@ fun CreatePollView( mode = state.mode, saveEnabled = state.canSave, onBackClick = navBack, - onSaveClick = { state.eventSink(CreatePollEvents.Save) } + onSaveClick = { state.eventSink(CreatePollEvent.Save) } ) }, ) { paddingValues -> @@ -111,7 +112,7 @@ fun CreatePollView( label = stringResource(id = R.string.screen_create_poll_question_desc), value = state.question, onValueChange = { - state.eventSink(CreatePollEvents.SetQuestion(it)) + state.eventSink(CreatePollEvent.SetQuestion(it)) }, modifier = Modifier .focusRequester(questionFocusRequester) @@ -130,7 +131,7 @@ fun CreatePollView( TextField( value = answer.text, onValueChange = { - state.eventSink(CreatePollEvents.SetAnswer(index, it)) + state.eventSink(CreatePollEvent.SetAnswer(index, it)) }, modifier = Modifier .then(if (isLastItem) Modifier.focusRequester(answerFocusRequester) else Modifier) @@ -144,7 +145,7 @@ fun CreatePollView( imageVector = CompoundIcons.Delete(), contentDescription = stringResource(R.string.screen_create_poll_delete_option_a11y, answer.text), modifier = Modifier.clickable(answer.canDelete) { - state.eventSink(CreatePollEvents.RemoveAnswer(index)) + state.eventSink(CreatePollEvent.RemoveAnswer(index)) }, ) }, @@ -160,7 +161,7 @@ fun CreatePollView( ), style = ListItemStyle.Primary, onClick = { - state.eventSink(CreatePollEvents.AddAnswer) + state.eventSink(CreatePollEvent.AddAnswer) coroutineScope.launch(Dispatchers.Main) { lazyListState.animateScrollToItem(state.answers.size + 1) answerFocusRequester.requestFocus() @@ -180,7 +181,7 @@ fun CreatePollView( ), onClick = { state.eventSink( - CreatePollEvents.SetPollKind( + CreatePollEvent.SetPollKind( if (state.pollKind == PollKind.Disclosed) PollKind.Undisclosed else PollKind.Disclosed ) ) @@ -190,7 +191,7 @@ fun CreatePollView( ListItem( headlineContent = { Text(text = stringResource(id = CommonStrings.action_delete_poll)) }, style = ListItemStyle.Destructive, - onClick = { state.eventSink(CreatePollEvents.Delete(confirmed = false)) }, + onClick = { state.eventSink(CreatePollEvent.Delete(confirmed = false)) }, ) } } diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index 1f916eb670..dee0268c1c 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -104,15 +104,15 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(initial.canSave).isFalse() - initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + initial.eventSink(CreatePollEvent.SetQuestion("A question?")) val questionSet = awaitItem() assertThat(questionSet.canSave).isFalse() - questionSet.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) + questionSet.eventSink(CreatePollEvent.SetAnswer(0, "Answer 1")) val answer1Set = awaitItem() assertThat(answer1Set.canSave).isFalse() - answer1Set.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) + answer1Set.eventSink(CreatePollEvent.SetAnswer(1, "Answer 2")) val answer2Set = awaitItem() assertThat(answer2Set.canSave).isTrue() } @@ -133,11 +133,11 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetQuestion("A question?")) - initial.eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) - initial.eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) + initial.eventSink(CreatePollEvent.SetQuestion("A question?")) + initial.eventSink(CreatePollEvent.SetAnswer(0, "Answer 1")) + initial.eventSink(CreatePollEvent.SetAnswer(1, "Answer 2")) skipItems(3) - initial.eventSink(CreatePollEvents.Save) + initial.eventSink(CreatePollEvent.Save) delay(1) // Wait for the coroutine to finish createPollResult.assertions().isCalledOnce() .with( @@ -182,10 +182,10 @@ class CreatePollPresenterTest { moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { - awaitDefaultItem().eventSink(CreatePollEvents.SetQuestion("A question?")) - awaitItem().eventSink(CreatePollEvents.SetAnswer(0, "Answer 1")) - awaitItem().eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) - awaitItem().eventSink(CreatePollEvents.Save) + awaitDefaultItem().eventSink(CreatePollEvent.SetQuestion("A question?")) + awaitItem().eventSink(CreatePollEvent.SetAnswer(0, "Answer 1")) + awaitItem().eventSink(CreatePollEvent.SetAnswer(1, "Answer 2")) + awaitItem().eventSink(CreatePollEvent.Save) delay(1) // Wait for the coroutine to finish createPollResult.assertions().isCalledOnce() assertThat(fakeAnalyticsService.capturedEvents).isEmpty() @@ -210,20 +210,20 @@ class CreatePollPresenterTest { }.test { awaitDefaultItem() awaitPollLoaded().apply { - eventSink(CreatePollEvents.SetQuestion("Changed question")) + eventSink(CreatePollEvent.SetQuestion("Changed question")) } awaitItem().apply { - eventSink(CreatePollEvents.SetAnswer(0, "Changed answer 1")) + eventSink(CreatePollEvent.SetAnswer(0, "Changed answer 1")) } awaitItem().apply { - eventSink(CreatePollEvents.SetAnswer(1, "Changed answer 2")) + eventSink(CreatePollEvent.SetAnswer(1, "Changed answer 2")) } awaitPollLoaded( newQuestion = "Changed question", newAnswer1 = "Changed answer 1", newAnswer2 = "Changed answer 2", ).apply { - eventSink(CreatePollEvents.Save) + eventSink(CreatePollEvent.Save) } advanceUntilIdle() // Wait for the coroutine to finish @@ -275,8 +275,8 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.SetAnswer(0, "A")) - awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvents.Save) + awaitPollLoaded().eventSink(CreatePollEvent.SetAnswer(0, "A")) + awaitPollLoaded(newAnswer1 = "A").eventSink(CreatePollEvent.Save) advanceUntilIdle() // Wait for the coroutine to finish editPollLambda.assertions().isCalledOnce() assertThat(fakeAnalyticsService.capturedEvents).isEmpty() @@ -296,12 +296,12 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(initial.answers.size).isEqualTo(2) - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) val answerAdded = awaitItem() assertThat(answerAdded.answers.size).isEqualTo(3) assertThat(answerAdded.answers[2].text).isEmpty() - initial.eventSink(CreatePollEvents.RemoveAnswer(2)) + initial.eventSink(CreatePollEvent.RemoveAnswer(2)) val answerRemoved = awaitItem() assertThat(answerRemoved.answers.size).isEqualTo(2) } @@ -314,7 +314,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetQuestion("A question?")) + initial.eventSink(CreatePollEvent.SetQuestion("A question?")) val questionSet = awaitItem() assertThat(questionSet.question).isEqualTo("A question?") } @@ -327,7 +327,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetAnswer(0, "This is answer 1")) + initial.eventSink(CreatePollEvent.SetAnswer(0, "This is answer 1")) val answerSet = awaitItem() assertThat(answerSet.answers.first().text).isEqualTo("This is answer 1") } @@ -340,7 +340,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetPollKind(PollKind.Undisclosed)) + initial.eventSink(CreatePollEvent.SetPollKind(PollKind.Undisclosed)) val kindSet = awaitItem() assertThat(kindSet.pollKind).isEqualTo(PollKind.Undisclosed) } @@ -355,10 +355,10 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(initial.canAddAnswer).isTrue() repeat(17) { - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) assertThat(awaitItem().canAddAnswer).isTrue() } - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) assertThat(awaitItem().canAddAnswer).isFalse() } } @@ -371,7 +371,7 @@ class CreatePollPresenterTest { }.test { val initial = awaitItem() assertThat(initial.answers.all { it.canDelete }).isFalse() - initial.eventSink(CreatePollEvents.AddAnswer) + initial.eventSink(CreatePollEvent.AddAnswer) assertThat(awaitItem().answers.all { it.canDelete }).isTrue() } } @@ -383,7 +383,7 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetAnswer(0, "A".repeat(241))) + initial.eventSink(CreatePollEvent.SetAnswer(0, "A".repeat(241))) assertThat(awaitItem().answers.first().text.length).isEqualTo(240) } } @@ -396,7 +396,7 @@ class CreatePollPresenterTest { }.test { val initial = awaitItem() assertThat(navUpInvocationsCount).isEqualTo(0) - initial.eventSink(CreatePollEvents.NavBack) + initial.eventSink(CreatePollEvent.NavBack) assertThat(navUpInvocationsCount).isEqualTo(1) } } @@ -410,7 +410,7 @@ class CreatePollPresenterTest { val initial = awaitItem() assertThat(navUpInvocationsCount).isEqualTo(0) assertThat(initial.showBackConfirmation).isFalse() - initial.eventSink(CreatePollEvents.ConfirmNavBack) + initial.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(navUpInvocationsCount).isEqualTo(1) } } @@ -422,11 +422,11 @@ class CreatePollPresenterTest { presenter.present() }.test { val initial = awaitItem() - initial.eventSink(CreatePollEvents.SetQuestion("Non blank")) + initial.eventSink(CreatePollEvent.SetQuestion("Non blank")) assertThat(awaitItem().showBackConfirmation).isFalse() - initial.eventSink(CreatePollEvents.ConfirmNavBack) + initial.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(awaitItem().showBackConfirmation).isTrue() - initial.eventSink(CreatePollEvents.HideConfirmation) + initial.eventSink(CreatePollEvent.HideConfirmation) assertThat(awaitItem().showBackConfirmation).isFalse() assertThat(navUpInvocationsCount).isEqualTo(0) } @@ -442,7 +442,7 @@ class CreatePollPresenterTest { val loaded = awaitPollLoaded() assertThat(navUpInvocationsCount).isEqualTo(0) assertThat(loaded.showBackConfirmation).isFalse() - loaded.eventSink(CreatePollEvents.ConfirmNavBack) + loaded.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(navUpInvocationsCount).isEqualTo(1) } } @@ -455,11 +455,11 @@ class CreatePollPresenterTest { }.test { awaitDefaultItem() val loaded = awaitPollLoaded() - loaded.eventSink(CreatePollEvents.SetQuestion("CHANGED")) + loaded.eventSink(CreatePollEvent.SetQuestion("CHANGED")) assertThat(awaitItem().showBackConfirmation).isFalse() - loaded.eventSink(CreatePollEvents.ConfirmNavBack) + loaded.eventSink(CreatePollEvent.ConfirmNavBack) assertThat(awaitItem().showBackConfirmation).isTrue() - loaded.eventSink(CreatePollEvents.HideConfirmation) + loaded.eventSink(CreatePollEvent.HideConfirmation) assertThat(awaitItem().showBackConfirmation).isFalse() assertThat(navUpInvocationsCount).isEqualTo(0) } @@ -474,7 +474,7 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false)) + awaitPollLoaded().eventSink(CreatePollEvent.Delete(confirmed = false)) awaitDeleteConfirmation() assert(redactEventLambda).isNeverCalled() } @@ -489,8 +489,8 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false)) - awaitDeleteConfirmation().eventSink(CreatePollEvents.HideConfirmation) + awaitPollLoaded().eventSink(CreatePollEvent.Delete(confirmed = false)) + awaitDeleteConfirmation().eventSink(CreatePollEvent.HideConfirmation) awaitPollLoaded().apply { assertThat(showDeleteConfirmation).isFalse() } @@ -507,8 +507,8 @@ class CreatePollPresenterTest { presenter.present() }.test { awaitDefaultItem() - awaitPollLoaded().eventSink(CreatePollEvents.Delete(confirmed = false)) - awaitDeleteConfirmation().eventSink(CreatePollEvents.Delete(confirmed = true)) + awaitPollLoaded().eventSink(CreatePollEvent.Delete(confirmed = false)) + awaitDeleteConfirmation().eventSink(CreatePollEvent.Delete(confirmed = true)) awaitPollLoaded().apply { assertThat(showDeleteConfirmation).isFalse() } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvent.kt similarity index 70% rename from features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvents.kt rename to features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvent.kt index f7f2ffceb4..d88eb75963 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileEvent.kt @@ -10,10 +10,10 @@ package io.element.android.features.preferences.impl.user.editprofile import io.element.android.libraries.matrix.ui.media.AvatarAction -sealed interface EditUserProfileEvents { - data class HandleAvatarAction(val action: AvatarAction) : EditUserProfileEvents - data class UpdateDisplayName(val name: String) : EditUserProfileEvents - data object Exit : EditUserProfileEvents - data object Save : EditUserProfileEvents - data object CloseDialog : EditUserProfileEvents +sealed interface EditUserProfileEvent { + data class HandleAvatarAction(val action: AvatarAction) : EditUserProfileEvent + data class UpdateDisplayName(val name: String) : EditUserProfileEvent + data object Exit : EditUserProfileEvent + data object Save : EditUserProfileEvent + data object CloseDialog : EditUserProfileEvent } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt index 59607139d7..0bdbb1031f 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenter.kt @@ -112,15 +112,15 @@ class EditUserProfilePresenter( !userDisplayName.isNullOrBlank() && hasProfileChanged } - fun handleEvent(event: EditUserProfileEvents) { + fun handleEvent(event: EditUserProfileEvent) { when (event) { - is EditUserProfileEvents.Save -> localCoroutineScope.saveChanges( + is EditUserProfileEvent.Save -> localCoroutineScope.saveChanges( name = userDisplayName, avatarUri = userAvatarUri?.toUri(), currentUser = matrixUser, action = saveAction, ) - is EditUserProfileEvents.HandleAvatarAction -> { + is EditUserProfileEvent.HandleAvatarAction -> { when (event.action) { AvatarAction.ChoosePhoto -> galleryImagePicker.launch() AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) { @@ -135,8 +135,8 @@ class EditUserProfilePresenter( } } } - is EditUserProfileEvents.UpdateDisplayName -> userDisplayName = event.name - EditUserProfileEvents.Exit -> { + is EditUserProfileEvent.UpdateDisplayName -> userDisplayName = event.name + EditUserProfileEvent.Exit -> { when (saveAction.value) { is AsyncAction.Confirming -> { // Close the dialog right now @@ -157,7 +157,7 @@ class EditUserProfilePresenter( } } } - EditUserProfileEvents.CloseDialog -> saveAction.value = AsyncAction.Uninitialized + EditUserProfileEvent.CloseDialog -> saveAction.value = AsyncAction.Uninitialized } } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt index 9becf6ce12..a638ed8378 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileState.kt @@ -22,5 +22,5 @@ data class EditUserProfileState( val saveButtonEnabled: Boolean, val saveAction: AsyncAction, val cameraPermissionState: PermissionsState, - val eventSink: (EditUserProfileEvents) -> Unit + val eventSink: (EditUserProfileEvent) -> Unit ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt index 56b734a342..ca9571aea5 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileStateProvider.kt @@ -33,7 +33,7 @@ fun aEditUserProfileState( saveButtonEnabled: Boolean = true, saveAction: AsyncAction = AsyncAction.Uninitialized, cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false), - eventSink: (EditUserProfileEvents) -> Unit = {}, + eventSink: (EditUserProfileEvent) -> Unit = {}, ) = EditUserProfileState( userId = userId, displayName = displayName, diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt index d6f0fcbd2c..5b42440101 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileView.kt @@ -68,7 +68,7 @@ fun EditUserProfileView( fun onBackClick() { focusManager.clearFocus() - state.eventSink(EditUserProfileEvents.Exit) + state.eventSink(EditUserProfileEvent.Exit) } BackHandler( @@ -87,7 +87,7 @@ fun EditUserProfileView( enabled = state.saveButtonEnabled, onClick = { focusManager.clearFocus() - state.eventSink(EditUserProfileEvents.Save) + state.eventSink(EditUserProfileEvent.Save) }, ) } @@ -125,7 +125,7 @@ fun EditUserProfileView( value = state.displayName, placeholder = stringResource(CommonStrings.common_room_name_placeholder), singleLine = true, - onValueChange = { state.eventSink(EditUserProfileEvents.UpdateDisplayName(it)) }, + onValueChange = { state.eventSink(EditUserProfileEvent.UpdateDisplayName(it)) }, ) } @@ -133,7 +133,7 @@ fun EditUserProfileView( actions = state.avatarActions, isVisible = isAvatarActionsSheetVisible.value, onDismiss = { isAvatarActionsSheetVisible.value = false }, - onSelectAction = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) } + onSelectAction = { state.eventSink(EditUserProfileEvent.HandleAvatarAction(it)) } ) AsyncActionView( @@ -147,8 +147,9 @@ fun EditUserProfileView( when (confirming) { is AsyncAction.ConfirmingCancellation -> { SaveChangesDialog( - onSubmitClick = { state.eventSink(EditUserProfileEvents.Exit) }, - onDismiss = { state.eventSink(EditUserProfileEvents.CloseDialog) } + onSaveClick = { state.eventSink(EditUserProfileEvent.Save) }, + onDiscardClick = { state.eventSink(EditUserProfileEvent.Exit) }, + onDismiss = { state.eventSink(EditUserProfileEvent.CloseDialog) }, ) } } @@ -156,7 +157,7 @@ fun EditUserProfileView( onSuccess = { onEditProfileSuccess() }, errorTitle = { stringResource(R.string.screen_edit_profile_error_title) }, errorMessage = { stringResource(R.string.screen_edit_profile_error) }, - onErrorDismiss = { state.eventSink(EditUserProfileEvents.CloseDialog) }, + onErrorDismiss = { state.eventSink(EditUserProfileEvent.CloseDialog) }, ) } PermissionsView( diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt index 3432bac29f..d69101cbba 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt @@ -124,7 +124,7 @@ class EditUserProfilePresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.Exit) + initialState.eventSink(EditUserProfileEvent.Exit) closeLambda.assertions().isCalledOnce() } } @@ -139,21 +139,21 @@ class EditUserProfilePresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("New name")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("New name")) val withUpdatedName = awaitItem() - withUpdatedName.eventSink(EditUserProfileEvents.Exit) + withUpdatedName.eventSink(EditUserProfileEvent.Exit) val withConfirmation = awaitItem() assertThat(withConfirmation.saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) // Cancel - withConfirmation.eventSink(EditUserProfileEvents.CloseDialog) + withConfirmation.eventSink(EditUserProfileEvent.CloseDialog) val afterCancel = awaitItem() assertThat(afterCancel.saveAction).isEqualTo(AsyncAction.Uninitialized) // Try again and confirm - afterCancel.eventSink(EditUserProfileEvents.Exit) + afterCancel.eventSink(EditUserProfileEvent.Exit) val withConfirmation2 = awaitItem() assertThat(withConfirmation2.saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) closeLambda.assertions().isNeverCalled() - withConfirmation2.eventSink(EditUserProfileEvents.Exit) + withConfirmation2.eventSink(EditUserProfileEvent.Exit) // Dialog is closed val finalState = awaitItem() assertThat(finalState.saveAction).isEqualTo(AsyncAction.Uninitialized) @@ -174,17 +174,17 @@ class EditUserProfilePresenterTest { val initialState = awaitItem() assertThat(initialState.displayName).isEqualTo("Name") assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name II")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name II")) awaitItem().apply { assertThat(displayName).isEqualTo("Name II") assertThat(userAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name III")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name III")) awaitItem().apply { assertThat(displayName).isEqualTo("Name III") assertThat(userAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(displayName).isEqualTo("Name III") assertThat(userAvatarUrl).isNull() @@ -205,7 +205,7 @@ class EditUserProfilePresenterTest { presenter.test { val initialState = awaitItem() assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(userAvatarUrl).isEqualTo(ANOTHER_AVATAR_URL) } @@ -229,7 +229,7 @@ class EditUserProfilePresenterTest { val initialState = awaitItem() assertThat(initialState.userAvatarUrl).isEqualTo(AN_AVATAR_URL) assertThat(initialState.cameraPermissionState.permissionGranted).isFalse() - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithAskingPermission = awaitItem() assertThat(stateWithAskingPermission.cameraPermissionState.showDialog).isTrue() fakePermissionsPresenter.setPermissionGranted() @@ -239,7 +239,7 @@ class EditUserProfilePresenterTest { assertThat(stateWithNewAvatar.userAvatarUrl).isEqualTo(ANOTHER_AVATAR_URL) // Do it again, no permission is requested fakePickerProvider.givenResult(userAvatarUri) - stateWithNewAvatar.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + stateWithNewAvatar.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithNewAvatar2 = awaitItem() assertThat(stateWithNewAvatar2.userAvatarUrl).isEqualTo(AN_AVATAR_URL) deleteCallback.assertions().isCalledExactly(2).withSequence( @@ -264,22 +264,22 @@ class EditUserProfilePresenterTest { val initialState = awaitItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name II")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -305,22 +305,22 @@ class EditUserProfilePresenterTest { val initialState = awaitItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name II")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("Name")) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("Name")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -344,9 +344,9 @@ class EditUserProfilePresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("New name")) - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("New name")) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(EditUserProfileEvent.Save) consumeItemsUntilPredicate { matrixClient.setDisplayNameCalled && matrixClient.removeAvatarCalled && !matrixClient.uploadAvatarCalled } assertThat(matrixClient.setDisplayNameCalled).isTrue() assertThat(matrixClient.removeAvatarCalled).isTrue() @@ -365,8 +365,8 @@ class EditUserProfilePresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName(" Name ")) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName(" Name ")) + initialState.eventSink(EditUserProfileEvent.Save) consumeItemsUntilTimeout() assertThat(matrixClient.setDisplayNameCalled).isFalse() assertThat(matrixClient.uploadAvatarCalled).isFalse() @@ -384,8 +384,8 @@ class EditUserProfilePresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("")) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("")) + initialState.eventSink(EditUserProfileEvent.Save) assertThat(matrixClient.setDisplayNameCalled).isFalse() assertThat(matrixClient.uploadAvatarCalled).isFalse() assertThat(matrixClient.removeAvatarCalled).isFalse() @@ -407,8 +407,8 @@ class EditUserProfilePresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.Save) consumeItemsUntilPredicate { matrixClient.uploadAvatarCalled } assertThat(matrixClient.uploadAvatarCalled).isTrue() } @@ -429,8 +429,8 @@ class EditUserProfilePresenterTest { fakeMediaPreProcessor.givenResult(Result.failure(RuntimeException("Oh no"))) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(EditUserProfileEvent.Save) skipItems(2) assertThat(matrixClient.uploadAvatarCalled).isFalse() assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) @@ -443,7 +443,7 @@ class EditUserProfilePresenterTest { val matrixClient = FakeMatrixClient().apply { givenSetDisplayNameResult(Result.failure(RuntimeException("!"))) } - saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.UpdateDisplayName("New name")) + saveAndAssertFailure(user, matrixClient, EditUserProfileEvent.UpdateDisplayName("New name")) } @Test @@ -452,7 +452,7 @@ class EditUserProfilePresenterTest { val matrixClient = FakeMatrixClient().apply { givenRemoveAvatarResult(Result.failure(RuntimeException("!"))) } - saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) + saveAndAssertFailure(user, matrixClient, EditUserProfileEvent.HandleAvatarAction(AvatarAction.Remove)) } @Test @@ -462,7 +462,7 @@ class EditUserProfilePresenterTest { val matrixClient = FakeMatrixClient().apply { givenUploadAvatarResult(Result.failure(RuntimeException("!"))) } - saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + saveAndAssertFailure(user, matrixClient, EditUserProfileEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) } @Test @@ -475,16 +475,16 @@ class EditUserProfilePresenterTest { val presenter = createEditUserProfilePresenter(matrixUser = user, matrixClient = matrixClient) presenter.test { val initialState = awaitItem() - initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("foo")) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.UpdateDisplayName("foo")) + initialState.eventSink(EditUserProfileEvent.Save) skipItems(2) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) - initialState.eventSink(EditUserProfileEvents.CloseDialog) + initialState.eventSink(EditUserProfileEvent.CloseDialog) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } - private suspend fun saveAndAssertFailure(matrixUser: MatrixUser, matrixClient: MatrixClient, event: EditUserProfileEvents) { + private suspend fun saveAndAssertFailure(matrixUser: MatrixUser, matrixClient: MatrixClient, event: EditUserProfileEvent) { val presenter = createEditUserProfilePresenter( matrixUser = matrixUser, matrixClient = matrixClient, @@ -495,7 +495,7 @@ class EditUserProfilePresenterTest { presenter.test { val initialState = awaitItem() initialState.eventSink(event) - initialState.eventSink(EditUserProfileEvents.Save) + initialState.eventSink(EditUserProfileEvent.Save) skipItems(1) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt index f4c7144350..728e05ee7e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfileViewTest.kt @@ -34,45 +34,45 @@ class EditUserProfileViewTest { @Test fun `clicking on back emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setEditUserProfileView( aEditUserProfileState( eventSink = eventsRecorder, ), ) rule.pressBack() - eventsRecorder.assertSingle(EditUserProfileEvents.Exit) + eventsRecorder.assertSingle(EditUserProfileEvent.Exit) } @Test - fun `clicking on cancel exit emits the expected event`() { - val eventsRecorder = EventsRecorder() + fun `clicking on save from the exit confirmation dialog emits the expected event`() { + val eventsRecorder = EventsRecorder() rule.setEditUserProfileView( aEditUserProfileState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(EditUserProfileEvents.CloseDialog) + rule.clickOn(CommonStrings.action_save, inDialog = true) + eventsRecorder.assertSingle(EditUserProfileEvent.Save) } @Test - fun `clicking on OK exit emits the expected event`() { - val eventsRecorder = EventsRecorder() + fun `clicking on discard exit emits the expected event`() { + val eventsRecorder = EventsRecorder() rule.setEditUserProfileView( aEditUserProfileState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(EditUserProfileEvents.Exit) + rule.clickOn(CommonStrings.action_discard) + eventsRecorder.assertSingle(EditUserProfileEvent.Exit) } @Test fun `clicking on save emits the expected event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setEditUserProfileView( aEditUserProfileState( saveButtonEnabled = true, @@ -81,12 +81,12 @@ class EditUserProfileViewTest { ), ) rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertSingle(EditUserProfileEvents.Save) + eventsRecorder.assertSingle(EditUserProfileEvent.Save) } @Test fun `clicking on avatar opens the bottom sheet dialog`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() val actions = listOf( AvatarAction.TakePhoto, AvatarAction.ChoosePhoto, @@ -110,7 +110,7 @@ class EditUserProfileViewTest { @Test fun `success invokes the expected callback`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> rule.setEditUserProfileView( aEditUserProfileState( diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt index b356ca33d2..99f5723bf9 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenter.kt @@ -73,8 +73,7 @@ class ChangeRoomPermissionsPresenter( private var initialPermissions by mutableStateOf(null) private var currentPermissions by mutableStateOf(null) - private var saveAction by mutableStateOf>(AsyncAction.Uninitialized) - private var confirmExitAction by mutableStateOf>(AsyncAction.Uninitialized) + private var saveAction by mutableStateOf>(AsyncAction.Uninitialized) @Composable override fun present(): ChangeRoomPermissionsState { @@ -109,15 +108,14 @@ class ChangeRoomPermissionsPresenter( } is ChangeRoomPermissionsEvent.Save -> coroutineScope.save() is ChangeRoomPermissionsEvent.Exit -> { - confirmExitAction = if (!hasChanges || confirmExitAction.isConfirming()) { - AsyncAction.Success(Unit) + saveAction = if (!hasChanges || saveAction == AsyncAction.ConfirmingCancellation) { + AsyncAction.Success(false) } else { - AsyncAction.ConfirmingNoParams + AsyncAction.ConfirmingCancellation } } is ChangeRoomPermissionsEvent.ResetPendingActions -> { saveAction = AsyncAction.Uninitialized - confirmExitAction = AsyncAction.Uninitialized } } } @@ -126,7 +124,6 @@ class ChangeRoomPermissionsPresenter( itemsBySection = itemsBySection, hasChanges = hasChanges, saveAction = saveAction, - confirmExitAction = confirmExitAction, eventSink = ::handleEvent, ) } @@ -147,7 +144,7 @@ class ChangeRoomPermissionsPresenter( .onSuccess { analyticsService.trackPermissionChangeAnalytics(initialPermissions, updatedRoomPowerLevels) initialPermissions = currentPermissions - saveAction = AsyncAction.Success(Unit) + saveAction = AsyncAction.Success(true) } .onFailure { saveAction = AsyncAction.Failure(it) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt index 2dc2c816d6..0dde54db89 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsState.kt @@ -23,8 +23,7 @@ data class ChangeRoomPermissionsState( val currentPermissions: RoomPowerLevelsValues?, val itemsBySection: ImmutableMap>, val hasChanges: Boolean, - val saveAction: AsyncAction, - val confirmExitAction: AsyncAction, + val saveAction: AsyncAction, val eventSink: (ChangeRoomPermissionsEvent) -> Unit, ) { fun selectedRoleForType(type: RoomPermissionType): SelectableRole? { diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt index d64c85f8cf..6280e4fd8f 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsStateProvider.kt @@ -25,7 +25,7 @@ class ChangeRoomPermissionsStateProvider : PreviewParameterProvider> = ChangeRoomPermissionsPresenter.buildItems(false), hasChanges: Boolean = false, - saveAction: AsyncAction = AsyncAction.Uninitialized, - confirmExitAction: AsyncAction = AsyncAction.Uninitialized, + saveAction: AsyncAction = AsyncAction.Uninitialized, eventSink: (ChangeRoomPermissionsEvent) -> Unit = {}, ) = ChangeRoomPermissionsState( currentPermissions = currentPermissions, itemsBySection = itemsBySection.toImmutableMap(), hasChanges = hasChanges, saveAction = saveAction, - confirmExitAction = confirmExitAction, eventSink = eventSink, ) diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt index 1e88d091c8..5ae8203eb4 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsView.kt @@ -18,9 +18,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.rolesandpermissions.impl.R +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.button.BackButton -import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.dialogs.SaveChangesDialog import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -91,24 +92,19 @@ fun ChangeRoomPermissionsView( AsyncActionView( async = state.saveAction, - onSuccess = { onComplete(true) }, - onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } - ) - - AsyncActionView( - async = state.confirmExitAction, - onSuccess = { onComplete(false) }, - confirmationDialog = { - ConfirmationDialog( - title = stringResource(R.string.screen_room_change_role_unsaved_changes_title), - content = stringResource(R.string.screen_room_change_role_unsaved_changes_description), - submitText = stringResource(CommonStrings.action_save), - cancelText = stringResource(CommonStrings.action_discard), - onSubmitClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, - onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.Exit) } - ) + onSuccess = { onComplete(it) }, + confirmationDialog = { confirming -> + when (confirming) { + is AsyncAction.ConfirmingCancellation -> { + SaveChangesDialog( + onSaveClick = { state.eventSink(ChangeRoomPermissionsEvent.Save) }, + onDiscardClick = { state.eventSink(ChangeRoomPermissionsEvent.Exit) }, + onDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) }, + ) + } + } }, - onErrorDismiss = {}, + onErrorDismiss = { state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions) } ) } diff --git a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt index bab24d3f42..20abc70bb7 100644 --- a/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt +++ b/features/rolesandpermissions/impl/src/main/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesView.kt @@ -172,8 +172,9 @@ fun ChangeRolesView( when (confirming) { is AsyncAction.ConfirmingCancellation -> { SaveChangesDialog( - onSubmitClick = { state.eventSink(ChangeRolesEvent.Exit) }, - onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) } + onSaveClick = { state.eventSink(ChangeRolesEvent.Save) }, + onDiscardClick = { state.eventSink(ChangeRolesEvent.Exit) }, + onDismiss = { state.eventSink(ChangeRolesEvent.CloseDialog) }, ) } is ConfirmingModifyingOwners -> { diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt index 7c328c9bad..d666ec3ccc 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsPresenterTest.kt @@ -39,7 +39,6 @@ class ChangeRoomPermissionsPresenterTest { assertThat(this.itemsBySection).isNotEmpty() assertThat(this.hasChanges).isFalse() assertThat(this.saveAction).isEqualTo(AsyncAction.Uninitialized) - assertThat(this.confirmExitAction).isEqualTo(AsyncAction.Uninitialized) } // Updated state, permissions loaded @@ -162,7 +161,7 @@ class ChangeRoomPermissionsPresenterTest { assertThat(awaitItem().hasChanges).isFalse() awaitItem().run { assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel) - assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) + assertThat(saveAction).isEqualTo(AsyncAction.Success(true)) } assertThat(analyticsService.capturedEvents).containsExactlyElementsIn( listOf( @@ -243,10 +242,10 @@ class ChangeRoomPermissionsPresenterTest { assertThat(awaitItem().hasChanges).isTrue() state.eventSink(ChangeRoomPermissionsEvent.Exit) - assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.ConfirmingNoParams) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) state.eventSink(ChangeRoomPermissionsEvent.Exit) - assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Success(false)) } } @@ -260,7 +259,7 @@ class ChangeRoomPermissionsPresenterTest { state.eventSink(ChangeRoomPermissionsEvent.Exit) - assertThat(awaitItem().confirmExitAction).isEqualTo(AsyncAction.Success(Unit)) + assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Success(false)) } } diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt index 915359cfcf..6b637efbed 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/permissions/ChangeRoomPermissionsViewTest.kt @@ -18,7 +18,6 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn -import io.element.android.tests.testutils.clickOnFirst import io.element.android.tests.testutils.ensureCalledOnceWithParam import io.element.android.tests.testutils.pressBack import io.element.android.tests.testutils.pressBackKey @@ -76,7 +75,7 @@ class ChangeRoomPermissionsViewTest { rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, - confirmExitAction = AsyncAction.ConfirmingNoParams, + saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ), ) @@ -90,11 +89,11 @@ class ChangeRoomPermissionsViewTest { rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, - confirmExitAction = AsyncAction.ConfirmingNoParams, + saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ), ) - rule.clickOnFirst(CommonStrings.action_save) + rule.clickOn(CommonStrings.action_save, inDialog = true) recorder.assertSingle(ChangeRoomPermissionsEvent.Save) } @@ -136,9 +135,23 @@ class ChangeRoomPermissionsViewTest { rule.setChangeRoomPermissionsRule( state = aChangeRoomPermissionsState( hasChanges = true, - saveAction = AsyncAction.Success(Unit), + saveAction = AsyncAction.Success(true), ), - onComplete = callback + onComplete = callback, + ) + rule.clickOn(CommonStrings.action_save) + } + } + + @Test + fun `a cancellation exits the screen`() { + ensureCalledOnceWithParam(false) { callback -> + rule.setChangeRoomPermissionsRule( + state = aChangeRoomPermissionsState( + hasChanges = true, + saveAction = AsyncAction.Success(false), + ), + onComplete = callback, ) rule.clickOn(CommonStrings.action_save) } diff --git a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt index fd45e5408c..39967f9160 100644 --- a/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt +++ b/features/rolesandpermissions/impl/src/test/kotlin/io/element/android/features/rolesandpermissions/impl/roles/ChangeRolesViewTest.kt @@ -119,7 +119,7 @@ class ChangeRolesViewTest { } @Test - fun `exit confirmation dialog - submit exits the screen`() { + fun `exit confirmation dialog - discard exits the screen`() { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( @@ -128,12 +128,12 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) + rule.clickOn(CommonStrings.action_discard) eventsRecorder.assertSingle(ChangeRolesEvent.Exit) } @Test - fun `exit confirmation dialog - cancel removes the dialog`() { + fun `exit confirmation dialog - save emits the save event`() { val eventsRecorder = EventsRecorder() rule.setChangeRolesContent( state = aChangeRolesState( @@ -142,8 +142,8 @@ class ChangeRolesViewTest { eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(ChangeRolesEvent.CloseDialog) + rule.clickOn(CommonStrings.action_save) + eventsRecorder.assertSingle(ChangeRolesEvent.Save) } @Test diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 9071425a72..9ea060ef6c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -383,7 +383,16 @@ class RoomDetailsFlowNode( knockRequestsListEntryPoint.createNode(this, buildContext) } NavTarget.SecurityAndPrivacy -> { - securityAndPrivacyEntryPoint.createNode(this, buildContext) + val callback = object : SecurityAndPrivacyEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } + securityAndPrivacyEntryPoint.createNode( + parentNode = this, + buildContext = buildContext, + callback = callback, + ) } is NavTarget.VerifyUser -> { val params = OutgoingVerificationEntryPoint.Params( diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvents.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvent.kt similarity index 70% rename from features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvents.kt rename to features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvent.kt index 858d08c2a0..40f123401c 100644 --- a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvents.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditEvent.kt @@ -10,11 +10,11 @@ package io.element.android.features.roomdetailsedit.impl import io.element.android.libraries.matrix.ui.media.AvatarAction -sealed interface RoomDetailsEditEvents { - data class HandleAvatarAction(val action: AvatarAction) : RoomDetailsEditEvents - data class UpdateRoomName(val name: String) : RoomDetailsEditEvents - data class UpdateRoomTopic(val topic: String) : RoomDetailsEditEvents - data object OnBackPress : RoomDetailsEditEvents - data object Save : RoomDetailsEditEvents - data object CloseDialog : RoomDetailsEditEvents +sealed interface RoomDetailsEditEvent { + data class HandleAvatarAction(val action: AvatarAction) : RoomDetailsEditEvent + data class UpdateRoomName(val name: String) : RoomDetailsEditEvent + data class UpdateRoomTopic(val topic: String) : RoomDetailsEditEvent + data object OnBackPress : RoomDetailsEditEvent + data object Save : RoomDetailsEditEvent + data object CloseDialog : RoomDetailsEditEvent } diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt index 89af0aa288..4a2dfa3f11 100644 --- a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenter.kt @@ -139,9 +139,9 @@ class RoomDetailsEditPresenter( val saveAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() - fun handleEvent(event: RoomDetailsEditEvents) { + fun handleEvent(event: RoomDetailsEditEvent) { when (event) { - is RoomDetailsEditEvents.Save -> localCoroutineScope.saveChanges( + is RoomDetailsEditEvent.Save -> localCoroutineScope.saveChanges( currentNameTrimmed = roomRawNameTrimmed, newNameTrimmed = roomRawNameEdited.trim(), currentTopicTrimmed = roomTopicTrimmed, @@ -150,7 +150,7 @@ class RoomDetailsEditPresenter( newAvatarUri = roomAvatarUriEdited?.toUri(), action = saveAction, ) - is RoomDetailsEditEvents.HandleAvatarAction -> { + is RoomDetailsEditEvent.HandleAvatarAction -> { when (event.action) { AvatarAction.ChoosePhoto -> galleryImagePicker.launch() AvatarAction.TakePhoto -> if (cameraPermissionState.permissionGranted) { @@ -166,10 +166,10 @@ class RoomDetailsEditPresenter( } } - is RoomDetailsEditEvents.UpdateRoomName -> roomRawNameEdited = event.name - is RoomDetailsEditEvents.UpdateRoomTopic -> roomTopicEdited = event.topic - RoomDetailsEditEvents.CloseDialog -> saveAction.value = AsyncAction.Uninitialized - RoomDetailsEditEvents.OnBackPress -> if (saveButtonEnabled.not() || saveAction.value == AsyncAction.ConfirmingCancellation) { + is RoomDetailsEditEvent.UpdateRoomName -> roomRawNameEdited = event.name + is RoomDetailsEditEvent.UpdateRoomTopic -> roomTopicEdited = event.topic + RoomDetailsEditEvent.CloseDialog -> saveAction.value = AsyncAction.Uninitialized + RoomDetailsEditEvent.OnBackPress -> if (saveButtonEnabled.not() || saveAction.value == AsyncAction.ConfirmingCancellation) { // No changes to save or already confirming exit without saving saveAction.value = AsyncAction.Success(Unit) } else { diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt index 1017c961b1..35f088275f 100644 --- a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditState.kt @@ -28,5 +28,5 @@ data class RoomDetailsEditState( val saveAction: AsyncAction, val cameraPermissionState: PermissionsState, val isSpace: Boolean, - val eventSink: (RoomDetailsEditEvents) -> Unit + val eventSink: (RoomDetailsEditEvent) -> Unit ) diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt index cecdcfa9d5..a6e2e3798b 100644 --- a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditStateProvider.kt @@ -45,7 +45,7 @@ fun aRoomDetailsEditState( saveAction: AsyncAction = AsyncAction.Uninitialized, cameraPermissionState: PermissionsState = aPermissionsState(showDialog = false), isSpace: Boolean = false, - eventSink: (RoomDetailsEditEvents) -> Unit = {}, + eventSink: (RoomDetailsEditEvent) -> Unit = {}, ) = RoomDetailsEditState( roomId = roomId, roomRawName = roomRawName, diff --git a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt index 55a6d038dd..268b30cb31 100644 --- a/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt +++ b/features/roomdetailsedit/impl/src/main/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditView.kt @@ -64,7 +64,7 @@ fun RoomDetailsEditView( } BackHandler { - state.eventSink(RoomDetailsEditEvents.OnBackPress) + state.eventSink(RoomDetailsEditEvent.OnBackPress) } Scaffold( modifier = modifier.clearFocusOnTap(focusManager), @@ -74,7 +74,7 @@ fun RoomDetailsEditView( navigationIcon = { BackButton( onClick = { - state.eventSink(RoomDetailsEditEvents.OnBackPress) + state.eventSink(RoomDetailsEditEvent.OnBackPress) } ) }, @@ -84,7 +84,7 @@ fun RoomDetailsEditView( enabled = state.saveButtonEnabled, onClick = { focusManager.clearFocus() - state.eventSink(RoomDetailsEditEvents.Save) + state.eventSink(RoomDetailsEditEvent.Save) }, ) } @@ -121,7 +121,7 @@ fun RoomDetailsEditView( placeholder = stringResource(CommonStrings.common_room_name_placeholder), singleLine = true, readOnly = !state.canChangeName, - onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) }, + onValueChange = { state.eventSink(RoomDetailsEditEvent.UpdateRoomName(it)) }, ) Spacer(modifier = Modifier.height(32.dp)) @@ -136,7 +136,7 @@ fun RoomDetailsEditView( }, maxLines = 10, readOnly = !state.canChangeTopic, - onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) }, + onValueChange = { state.eventSink(RoomDetailsEditEvent.UpdateRoomTopic(it)) }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.Sentences, ), @@ -147,7 +147,7 @@ fun RoomDetailsEditView( actions = state.avatarActions, isVisible = isAvatarActionsSheetVisible.value, onDismiss = { isAvatarActionsSheetVisible.value = false }, - onSelectAction = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) } + onSelectAction = { state.eventSink(RoomDetailsEditEvent.HandleAvatarAction(it)) } ) AsyncActionView( async = state.saveAction, @@ -159,14 +159,15 @@ fun RoomDetailsEditView( confirmationDialog = { if (state.saveAction == AsyncAction.ConfirmingCancellation) { SaveChangesDialog( - onSubmitClick = { state.eventSink(RoomDetailsEditEvents.OnBackPress) }, - onDismiss = { state.eventSink(RoomDetailsEditEvents.CloseDialog) } + onSaveClick = { state.eventSink(RoomDetailsEditEvent.Save) }, + onDiscardClick = { state.eventSink(RoomDetailsEditEvent.OnBackPress) }, + onDismiss = { state.eventSink(RoomDetailsEditEvent.CloseDialog) } ) } }, onSuccess = { onDone() }, errorMessage = { stringResource(R.string.screen_room_details_edition_error) }, - onErrorDismiss = { state.eventSink(RoomDetailsEditEvents.CloseDialog) } + onErrorDismiss = { state.eventSink(RoomDetailsEditEvent.CloseDialog) } ) PermissionsView( diff --git a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt index d19a07ab04..e438cecf50 100644 --- a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditPresenterTest.kt @@ -241,25 +241,25 @@ class RoomDetailsEditPresenterTest { assertThat(initialState.roomTopic).isEqualTo("My topic") assertThat(initialState.roomRawName).isEqualTo("Name") assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name II")) awaitItem().apply { assertThat(roomTopic).isEqualTo("My topic") assertThat(roomRawName).isEqualTo("Name II") assertThat(roomAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name III")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name III")) awaitItem().apply { assertThat(roomTopic).isEqualTo("My topic") assertThat(roomRawName).isEqualTo("Name III") assertThat(roomAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("Another topic")) awaitItem().apply { assertThat(roomTopic).isEqualTo("Another topic") assertThat(roomRawName).isEqualTo("Name III") assertThat(roomAvatarUrl).isEqualTo(AN_AVATAR_URL) } - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(roomTopic).isEqualTo("Another topic") assertThat(roomRawName).isEqualTo("Name III") @@ -285,7 +285,7 @@ class RoomDetailsEditPresenterTest { presenter.test { val initialState = awaitFirstItem() assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(roomAvatarUrl).isEqualTo(anotherAvatarUri.toString()) } @@ -312,7 +312,7 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) assertThat(initialState.cameraPermissionState.permissionGranted).isFalse() - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithAskingPermission = awaitItem() assertThat(stateWithAskingPermission.cameraPermissionState.showDialog).isTrue() fakePermissionsPresenter.setPermissionGranted() @@ -322,7 +322,7 @@ class RoomDetailsEditPresenterTest { assertThat(stateWithNewAvatar.roomAvatarUrl).isEqualTo(anotherAvatarUri.toString()) // Do it again, no permission is requested fakePickerProvider.givenResult(roomAvatarUri) - stateWithNewAvatar.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto)) + stateWithNewAvatar.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithNewAvatar2 = awaitItem() assertThat(stateWithNewAvatar2.roomAvatarUrl).isEqualTo(AN_AVATAR_URL) deleteCallback.assertions().isCalledExactly(3).withSequence( @@ -351,32 +351,32 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("Another topic")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("My topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("My topic")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -401,32 +401,32 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name II")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name II")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // If it's reverted then the save disables again - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("fallback")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("fallback")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("Another topic")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("Another topic")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("")) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } // Make a change... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() } // Revert it... - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) awaitItem().apply { assertThat(saveButtonEnabled).isFalse() } @@ -454,10 +454,10 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitFirstItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("New name")) - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("New topic")) - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("New name")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("New topic")) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove)) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(5) setNameResult.assertions().isCalledOnce().with(value("New name")) setTopicResult.assertions().isCalledOnce().with(value("New topic")) @@ -480,9 +480,9 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(" Name ")) - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(" My topic ")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName(" Name ")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic(" My topic ")) + initialState.eventSink(RoomDetailsEditEvent.Save) cancelAndIgnoreRemainingEvents() } } @@ -502,8 +502,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("")) + initialState.eventSink(RoomDetailsEditEvent.Save) cancelAndIgnoreRemainingEvents() deleteCallback.assertions().isCalledOnce().with(value(null)) } @@ -524,8 +524,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("")) + initialState.eventSink(RoomDetailsEditEvent.Save) cancelAndIgnoreRemainingEvents() deleteCallback.assertions().isCalledOnce().with(value(null)) } @@ -549,8 +549,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(4) updateAvatarResult.assertions().isCalledOnce().with(value(MimeTypes.Jpeg), value(fakeFileContents)) deleteCallback.assertions().isCalledExactly(2).withSequence( @@ -577,8 +577,8 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto)) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(3) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } @@ -593,7 +593,7 @@ class RoomDetailsEditPresenterTest { setNameResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"), deleteCallbackNumberOfInvocation = 1) + saveAndAssertFailure(room, RoomDetailsEditEvent.UpdateRoomName("New name"), deleteCallbackNumberOfInvocation = 1) } @Test @@ -605,7 +605,7 @@ class RoomDetailsEditPresenterTest { setTopicResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"), deleteCallbackNumberOfInvocation = 1) + saveAndAssertFailure(room, RoomDetailsEditEvent.UpdateRoomTopic("New topic"), deleteCallbackNumberOfInvocation = 1) } @Test @@ -617,7 +617,7 @@ class RoomDetailsEditPresenterTest { removeAvatarResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 2) + saveAndAssertFailure(room, RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 2) } @Test @@ -630,7 +630,7 @@ class RoomDetailsEditPresenterTest { updateAvatarResult = { _, _ -> Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 2) + saveAndAssertFailure(room, RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 2) } @Test @@ -650,11 +650,11 @@ class RoomDetailsEditPresenterTest { ) presenter.test { val initialState = awaitItem() - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("foo")) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomTopic("foo")) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(3) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) - initialState.eventSink(RoomDetailsEditEvents.CloseDialog) + initialState.eventSink(RoomDetailsEditEvent.CloseDialog) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) } } @@ -674,14 +674,14 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name edited")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name edited")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() - eventSink(RoomDetailsEditEvents.OnBackPress) + eventSink(RoomDetailsEditEvent.OnBackPress) } awaitItem().apply { assertThat(saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) - eventSink(RoomDetailsEditEvents.CloseDialog) + eventSink(RoomDetailsEditEvent.CloseDialog) } awaitItem().apply { assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) @@ -702,7 +702,7 @@ class RoomDetailsEditPresenterTest { presenter.test { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() - initialState.eventSink(RoomDetailsEditEvents.OnBackPress) + initialState.eventSink(RoomDetailsEditEvent.OnBackPress) assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Success(Unit)) } } @@ -721,14 +721,14 @@ class RoomDetailsEditPresenterTest { val initialState = awaitFirstItem() assertThat(initialState.saveButtonEnabled).isFalse() // Once a change is made, the save button is enabled - initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("Name edited")) + initialState.eventSink(RoomDetailsEditEvent.UpdateRoomName("Name edited")) awaitItem().apply { assertThat(saveButtonEnabled).isTrue() - eventSink(RoomDetailsEditEvents.OnBackPress) + eventSink(RoomDetailsEditEvent.OnBackPress) } awaitItem().apply { assertThat(saveAction).isEqualTo(AsyncAction.ConfirmingCancellation) - eventSink(RoomDetailsEditEvents.OnBackPress) + eventSink(RoomDetailsEditEvent.OnBackPress) } awaitItem().apply { assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit)) @@ -738,7 +738,7 @@ class RoomDetailsEditPresenterTest { private suspend fun saveAndAssertFailure( room: JoinedRoom, - event: RoomDetailsEditEvents, + event: RoomDetailsEditEvent, deleteCallbackNumberOfInvocation: Int = 2, ) { val deleteCallback = lambdaRecorder {} @@ -749,7 +749,7 @@ class RoomDetailsEditPresenterTest { presenter.test { val initialState = awaitFirstItem() initialState.eventSink(event) - initialState.eventSink(RoomDetailsEditEvents.Save) + initialState.eventSink(RoomDetailsEditEvent.Save) skipItems(1) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) diff --git a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt index dcf8a94d66..71fb143074 100644 --- a/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt +++ b/features/roomdetailsedit/impl/src/test/kotlin/io/element/android/features/roomdetailsedit/impl/RoomDetailsEditViewTest.kt @@ -39,45 +39,45 @@ class RoomDetailsEditViewTest { @Test fun `clicking on back emits the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder ), ) rule.pressBack() - eventsRecorder.assertSingle(RoomDetailsEditEvents.OnBackPress) + eventsRecorder.assertSingle(RoomDetailsEditEvent.OnBackPress) } @Test - fun `clicking on OK when confirming exit emits the expected Event`() { - val eventsRecorder = EventsRecorder() + fun `clicking on discard when confirming exit emits the expected Event`() { + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(RoomDetailsEditEvents.OnBackPress) + rule.clickOn(CommonStrings.action_discard) + eventsRecorder.assertSingle(RoomDetailsEditEvent.OnBackPress) } @Test - fun `clicking on cancel when confirming exit emits the expected Event`() { - val eventsRecorder = EventsRecorder() + fun `clicking on save when confirming exit emits the expected Event`() { + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( saveAction = AsyncAction.ConfirmingCancellation, eventSink = eventsRecorder, ), ) - rule.clickOn(CommonStrings.action_cancel) - eventsRecorder.assertSingle(RoomDetailsEditEvents.CloseDialog) + rule.clickOn(CommonStrings.action_save, inDialog = true) + eventsRecorder.assertSingle(RoomDetailsEditEvent.Save) } @Test fun `when edition is successful, the expected callback is invoked`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) ensureCalledOnce { callback -> rule.setRoomDetailsEditView( aRoomDetailsEditState( @@ -91,7 +91,7 @@ class RoomDetailsEditViewTest { @Test fun `when name is changed, the expected Event is emitted`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -99,12 +99,12 @@ class RoomDetailsEditViewTest { ), ) rule.onNodeWithText("Marketing").performTextInput("A") - eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomName("AMarketing")) + eventsRecorder.assertSingle(RoomDetailsEditEvent.UpdateRoomName("AMarketing")) } @Test fun `when user cannot change name, nothing happen`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -117,7 +117,7 @@ class RoomDetailsEditViewTest { @Test fun `when topic is changed, the expected Event is emitted`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -125,12 +125,12 @@ class RoomDetailsEditViewTest { ), ) rule.onNodeWithText("My Topic").performTextInput("A") - eventsRecorder.assertSingle(RoomDetailsEditEvents.UpdateRoomTopic("AMy Topic")) + eventsRecorder.assertSingle(RoomDetailsEditEvent.UpdateRoomTopic("AMy Topic")) } @Test fun `when user cannot change topic, nothing happen`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -146,7 +146,7 @@ class RoomDetailsEditViewTest { fun `when avatar is changed with action to take photo, the expected Event is emitted`() { testAvatarChange( stringActionRes = CommonStrings.action_take_photo, - expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto), + expectedEvent = RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.TakePhoto), ) } @@ -155,7 +155,7 @@ class RoomDetailsEditViewTest { fun `when avatar is changed with action to choose photo, the expected Event is emitted`() { testAvatarChange( stringActionRes = CommonStrings.action_choose_photo, - expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), + expectedEvent = RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.ChoosePhoto), ) } @@ -164,15 +164,15 @@ class RoomDetailsEditViewTest { fun `when avatar is changed with action to remove photo, the expected Event is emitted`() { testAvatarChange( stringActionRes = CommonStrings.action_remove, - expectedEvent = RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), + expectedEvent = RoomDetailsEditEvent.HandleAvatarAction(AvatarAction.Remove), ) } private fun testAvatarChange( @StringRes stringActionRes: Int, - expectedEvent: RoomDetailsEditEvents.HandleAvatarAction, + expectedEvent: RoomDetailsEditEvent.HandleAvatarAction, ) { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -187,7 +187,7 @@ class RoomDetailsEditViewTest { @Test fun `when user cannot change avatar, nothing happen`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -200,7 +200,7 @@ class RoomDetailsEditViewTest { @Test fun `when save is clicked, the expected Event is emitted`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -208,12 +208,12 @@ class RoomDetailsEditViewTest { ), ) rule.clickOn(CommonStrings.action_save) - eventsRecorder.assertSingle(RoomDetailsEditEvents.Save) + eventsRecorder.assertSingle(RoomDetailsEditEvent.Save) } @Test fun `when save is clicked, but nothing need to be saved, nothing happens`() { - val eventsRecorder = EventsRecorder(expectEvents = false) + val eventsRecorder = EventsRecorder(expectEvents = false) rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -225,7 +225,7 @@ class RoomDetailsEditViewTest { @Test fun `when error is shown, closing the dialog emit the expected Event`() { - val eventsRecorder = EventsRecorder() + val eventsRecorder = EventsRecorder() rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, @@ -233,7 +233,7 @@ class RoomDetailsEditViewTest { ), ) rule.clickOn(CommonStrings.action_ok) - eventsRecorder.assertSingle(RoomDetailsEditEvents.CloseDialog) + eventsRecorder.assertSingle(RoomDetailsEditEvent.CloseDialog) } } diff --git a/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt index 2c7c1cfd41..be2a4dc9e2 100644 --- a/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt +++ b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyEntryPoint.kt @@ -8,6 +8,19 @@ package io.element.android.features.securityandprivacy.api -import io.element.android.libraries.architecture.SimpleFeatureEntryPoint +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint -fun interface SecurityAndPrivacyEntryPoint : SimpleFeatureEntryPoint +fun interface SecurityAndPrivacyEntryPoint : FeatureEntryPoint { + interface Callback : Plugin { + fun onDone() + } + + fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: Callback, + ): Node +} diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt index 2d01ed4a83..d2a40c6836 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/DefaultSecurityAndPrivacyEntryPoint.kt @@ -17,7 +17,11 @@ import io.element.android.libraries.di.RoomScope @ContributesBinding(RoomScope::class) class DefaultSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext): Node { - return parentNode.createNode(buildContext) + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: SecurityAndPrivacyEntryPoint.Callback, + ): Node { + return parentNode.createNode(buildContext, listOf(callback)) } } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt index 5dbc8dbb45..13c5b454a2 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyFlowNode.kt @@ -18,10 +18,12 @@ import com.bumble.appyx.navmodel.backstack.BackStack import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.RoomScope import kotlinx.parcelize.Parcelize @@ -47,7 +49,8 @@ class SecurityAndPrivacyFlowNode( data object EditRoomAddress : NavTarget } - private val navigator = BackstackSecurityAndPrivacyNavigator(backstack) + private val callback: SecurityAndPrivacyEntryPoint.Callback = callback() + private val navigator = BackstackSecurityAndPrivacyNavigator(callback, backstack) override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt index 3b71868bbf..092da87943 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyNavigator.kt @@ -12,15 +12,22 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint interface SecurityAndPrivacyNavigator : Plugin { + fun onDone() fun openEditRoomAddress() fun closeEditRoomAddress() } class BackstackSecurityAndPrivacyNavigator( + private val callback: SecurityAndPrivacyEntryPoint.Callback, private val backStack: BackStack ) : SecurityAndPrivacyNavigator { + override fun onDone() { + callback.onDone() + } + override fun openEditRoomAddress() { backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress) } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvent.kt similarity index 55% rename from features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt rename to features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvent.kt index b1d739c45b..39abedab8c 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvents.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyEvent.kt @@ -8,16 +8,16 @@ package io.element.android.features.securityandprivacy.impl.root -sealed interface SecurityAndPrivacyEvents { - data object EditRoomAddress : SecurityAndPrivacyEvents - data object Save : SecurityAndPrivacyEvents - data object Exit : SecurityAndPrivacyEvents - data object DismissExitConfirmation : SecurityAndPrivacyEvents - data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvents - data object ToggleEncryptionState : SecurityAndPrivacyEvents - data object CancelEnableEncryption : SecurityAndPrivacyEvents - data object ConfirmEnableEncryption : SecurityAndPrivacyEvents - data class ChangeHistoryVisibility(val historyVisibility: SecurityAndPrivacyHistoryVisibility) : SecurityAndPrivacyEvents - data object ToggleRoomVisibility : SecurityAndPrivacyEvents - data object DismissSaveError : SecurityAndPrivacyEvents +sealed interface SecurityAndPrivacyEvent { + data object EditRoomAddress : SecurityAndPrivacyEvent + data object Save : SecurityAndPrivacyEvent + data object Exit : SecurityAndPrivacyEvent + data object DismissExitConfirmation : SecurityAndPrivacyEvent + data class ChangeRoomAccess(val roomAccess: SecurityAndPrivacyRoomAccess) : SecurityAndPrivacyEvent + data object ToggleEncryptionState : SecurityAndPrivacyEvent + data object CancelEnableEncryption : SecurityAndPrivacyEvent + data object ConfirmEnableEncryption : SecurityAndPrivacyEvent + data class ChangeHistoryVisibility(val historyVisibility: SecurityAndPrivacyHistoryVisibility) : SecurityAndPrivacyEvent + data object ToggleRoomVisibility : SecurityAndPrivacyEvent + data object DismissSaveError : SecurityAndPrivacyEvent } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt index 5e329a06f6..e173117431 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyNode.kt @@ -40,7 +40,6 @@ class SecurityAndPrivacyNode( val state by stateFlow.collectAsState() SecurityAndPrivacyView( state = state, - onBackClick = this::navigateUp, modifier = modifier ) } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt index 43d8383b76..b77ad0e7d6 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt @@ -64,7 +64,6 @@ class SecurityAndPrivacyPresenter( featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) }.collectAsState(false) val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - var confirmExitAction by remember { mutableStateOf>(AsyncAction.Uninitialized) } val homeserverName = remember { matrixClient.userIdServerName() } val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomInfo by room.roomInfoFlow.collectAsState() @@ -109,9 +108,9 @@ class SecurityAndPrivacyPresenter( var showEnableEncryptionConfirmation by remember(savedSettings.isEncrypted) { mutableStateOf(false) } val permissions by room.securityAndPrivacyPermissionsAsState(syncUpdateFlow.value) - fun handleEvent(event: SecurityAndPrivacyEvents) { + fun handleEvent(event: SecurityAndPrivacyEvent) { when (event) { - SecurityAndPrivacyEvents.Save -> { + SecurityAndPrivacyEvent.Save -> { coroutineScope.save( saveAction = saveAction, isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory, @@ -119,49 +118,55 @@ class SecurityAndPrivacyPresenter( editedSettings = editedSettings ) } - is SecurityAndPrivacyEvents.ChangeRoomAccess -> { + is SecurityAndPrivacyEvent.ChangeRoomAccess -> { editedRoomAccess = event.roomAccess } - is SecurityAndPrivacyEvents.ToggleEncryptionState -> { + is SecurityAndPrivacyEvent.ToggleEncryptionState -> { if (editedIsEncrypted) { editedIsEncrypted = false } else { showEnableEncryptionConfirmation = true } } - is SecurityAndPrivacyEvents.ChangeHistoryVisibility -> { + is SecurityAndPrivacyEvent.ChangeHistoryVisibility -> { editedHistoryVisibility = event.historyVisibility } - SecurityAndPrivacyEvents.ToggleRoomVisibility -> { + SecurityAndPrivacyEvent.ToggleRoomVisibility -> { editedVisibleInRoomDirectory = when (val edited = editedVisibleInRoomDirectory) { is AsyncData.Success -> AsyncData.Success(!edited.data) else -> edited } } - SecurityAndPrivacyEvents.EditRoomAddress -> navigator.openEditRoomAddress() - SecurityAndPrivacyEvents.CancelEnableEncryption -> { + SecurityAndPrivacyEvent.EditRoomAddress -> navigator.openEditRoomAddress() + SecurityAndPrivacyEvent.CancelEnableEncryption -> { showEnableEncryptionConfirmation = false } - SecurityAndPrivacyEvents.ConfirmEnableEncryption -> { + SecurityAndPrivacyEvent.ConfirmEnableEncryption -> { showEnableEncryptionConfirmation = false editedIsEncrypted = true } - SecurityAndPrivacyEvents.DismissSaveError -> { + SecurityAndPrivacyEvent.DismissSaveError -> { saveAction.value = AsyncAction.Uninitialized } - SecurityAndPrivacyEvents.Exit -> { - confirmExitAction = if (savedSettings == editedSettings || confirmExitAction.isConfirming()) { + SecurityAndPrivacyEvent.Exit -> { + saveAction.value = if (savedSettings == editedSettings || saveAction.value == AsyncAction.ConfirmingCancellation) { AsyncAction.Success(Unit) } else { - AsyncAction.ConfirmingNoParams + AsyncAction.ConfirmingCancellation } } - SecurityAndPrivacyEvents.DismissExitConfirmation -> { - confirmExitAction = AsyncAction.Uninitialized + SecurityAndPrivacyEvent.DismissExitConfirmation -> { + saveAction.value = AsyncAction.Uninitialized } } } + LaunchedEffect(saveAction.value.isSuccess()) { + if (saveAction.value.isSuccess()) { + navigator.onDone() + } + } + val state = SecurityAndPrivacyState( savedSettings = savedSettings, editedSettings = editedSettings, @@ -171,7 +176,6 @@ class SecurityAndPrivacyPresenter( saveAction = saveAction.value, permissions = permissions, isSpace = roomInfo.isSpace, - confirmExitAction = confirmExitAction, eventSink = ::handleEvent, ) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt index 0671bbf163..2c6bd59e12 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt @@ -22,10 +22,9 @@ data class SecurityAndPrivacyState( val showEnableEncryptionConfirmation: Boolean, val isKnockEnabled: Boolean, val saveAction: AsyncAction, - val confirmExitAction: AsyncAction, val isSpace: Boolean, private val permissions: SecurityAndPrivacyPermissions, - val eventSink: (SecurityAndPrivacyEvents) -> Unit + val eventSink: (SecurityAndPrivacyEvent) -> Unit ) { val canBeSaved = savedSettings != editedSettings diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt index 11e5665c1c..b5347a753f 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt @@ -27,7 +27,7 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider = AsyncAction.Uninitialized, - confirmExitAction: AsyncAction = AsyncAction.Uninitialized, permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions( canChangeRoomAccess = true, canChangeHistoryVisibility = true, @@ -118,14 +117,13 @@ fun aSecurityAndPrivacyState( ), isKnockEnabled: Boolean = true, isSpace: Boolean = false, - eventSink: (SecurityAndPrivacyEvents) -> Unit = {} + eventSink: (SecurityAndPrivacyEvent) -> Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, savedSettings = savedSettings, homeserverName = homeserverName, showEnableEncryptionConfirmation = showEncryptionConfirmation, saveAction = saveAction, - confirmExitAction = confirmExitAction, isKnockEnabled = isKnockEnabled, permissions = permissions, isSpace = isSpace, diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt index ad75580031..add719153a 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.securityandprivacy.impl.R +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.designsystem.components.async.AsyncActionView @@ -56,11 +57,10 @@ import kotlinx.collections.immutable.ImmutableSet @Composable fun SecurityAndPrivacyView( state: SecurityAndPrivacyState, - onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { BackHandler { - state.eventSink(SecurityAndPrivacyEvents.Exit) + state.eventSink(SecurityAndPrivacyEvent.Exit) } Scaffold( modifier = modifier, @@ -68,10 +68,10 @@ fun SecurityAndPrivacyView( SecurityAndPrivacyToolbar( isSaveActionEnabled = state.canBeSaved, onBackClick = { - state.eventSink(SecurityAndPrivacyEvents.Exit) + state.eventSink(SecurityAndPrivacyEvent.Exit) }, onSaveClick = { - state.eventSink(SecurityAndPrivacyEvents.Save) + state.eventSink(SecurityAndPrivacyEvent.Save) }, ) } @@ -90,7 +90,7 @@ fun SecurityAndPrivacyView( edited = state.editedSettings.roomAccess, saved = state.savedSettings.roomAccess, isKnockEnabled = state.isKnockEnabled, - onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(it)) }, + onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(it)) }, ) } if (state.showRoomVisibilitySections) { @@ -98,10 +98,10 @@ fun SecurityAndPrivacyView( RoomAddressSection( roomAddress = state.editedSettings.address, homeserverName = state.homeserverName, - onRoomAddressClick = { state.eventSink(SecurityAndPrivacyEvents.EditRoomAddress) }, + onRoomAddressClick = { state.eventSink(SecurityAndPrivacyEvent.EditRoomAddress) }, isVisibleInRoomDirectory = state.editedSettings.isVisibleInRoomDirectory, onVisibilityChange = { - state.eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + state.eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) }, ) } @@ -110,10 +110,10 @@ fun SecurityAndPrivacyView( isRoomEncrypted = state.editedSettings.isEncrypted, // encryption can't be disabled once enabled canToggleEncryption = !state.savedSettings.isEncrypted, - onToggleEncryption = { state.eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) }, + onToggleEncryption = { state.eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) }, showConfirmation = state.showEnableEncryptionConfirmation, - onDismissConfirmation = { state.eventSink(SecurityAndPrivacyEvents.CancelEnableEncryption) }, - onConfirmEncryption = { state.eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) }, + onDismissConfirmation = { state.eventSink(SecurityAndPrivacyEvent.CancelEnableEncryption) }, + onConfirmEncryption = { state.eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) }, ) } if (state.showHistoryVisibilitySection) { @@ -121,7 +121,7 @@ fun SecurityAndPrivacyView( editedOption = state.editedSettings.historyVisibility, savedOptions = state.savedSettings.historyVisibility, availableOptions = state.availableHistoryVisibilities, - onSelectOption = { state.eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(it)) }, + onSelectOption = { state.eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(it)) }, ) } } @@ -129,25 +129,24 @@ fun SecurityAndPrivacyView( AsyncActionView( async = state.saveAction, onSuccess = { }, - onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) }, + onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvent.DismissSaveError) }, + confirmationDialog = { confirming -> + when (confirming) { + is AsyncAction.ConfirmingCancellation -> + SaveChangesDialog( + onSaveClick = { state.eventSink(SecurityAndPrivacyEvent.Save) }, + onDiscardClick = { state.eventSink(SecurityAndPrivacyEvent.Exit) }, + onDismiss = { state.eventSink(SecurityAndPrivacyEvent.DismissExitConfirmation) } + ) + } + }, errorMessage = { stringResource(CommonStrings.error_unknown) }, progressDialog = { AsyncActionViewDefaults.ProgressDialog( progressText = stringResource(CommonStrings.common_saving), ) }, - onRetry = { state.eventSink(SecurityAndPrivacyEvents.Save) }, - ) - AsyncActionView( - async = state.confirmExitAction, - onSuccess = { onBackClick() }, - onErrorDismiss = { }, - confirmationDialog = { - SaveChangesDialog( - onSubmitClick = { state.eventSink(SecurityAndPrivacyEvents.Exit) }, - onDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissExitConfirmation) } - ) - }, + onRetry = { state.eventSink(SecurityAndPrivacyEvent.Save) }, ) } @@ -425,6 +424,5 @@ internal fun SecurityAndPrivacyViewDarkPreview(@PreviewParameter(SecurityAndPriv private fun ContentToPreview(state: SecurityAndPrivacyState) { SecurityAndPrivacyView( state = state, - onBackClick = {}, ) } diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt index 9ca3cc5288..f90040d3fb 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/FakeSecurityAndPrivacyNavigator.kt @@ -11,9 +11,14 @@ package io.element.android.features.securityandprivacy.impl import io.element.android.tests.testutils.lambda.lambdaError class FakeSecurityAndPrivacyNavigator( + private val onDoneLambda: () -> Unit = { lambdaError() }, private val openEditRoomAddressLambda: () -> Unit = { lambdaError() }, private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() }, ) : SecurityAndPrivacyNavigator { + override fun onDone() { + onDoneLambda() + } + override fun openEditRoomAddress() { openEditRoomAddressLambda() } diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt index bdae5274f0..7d88706996 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyPresenterTest.kt @@ -9,7 +9,7 @@ package io.element.android.features.securityandprivacy.impl import com.google.common.truth.Truth.assertThat -import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyEvents +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyEvent import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyHistoryVisibility import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyPresenter import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyRoomAccess @@ -96,13 +96,13 @@ class SecurityAndPrivacyPresenterTest { with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) assertThat(showRoomVisibilitySections).isFalse() - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) } with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.Anyone) assertThat(showRoomVisibilitySections).isTrue() assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) } with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) @@ -119,12 +119,12 @@ class SecurityAndPrivacyPresenterTest { skipItems(1) with(awaitItem()) { assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.SinceSelection) - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceInvite)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceInvite)) } with(awaitItem()) { assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.SinceInvite) assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceSelection)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceSelection)) } with(awaitItem()) { assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.SinceSelection) @@ -140,26 +140,26 @@ class SecurityAndPrivacyPresenterTest { skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isFalse() - eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) + eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) } with(awaitItem()) { assertThat(showEnableEncryptionConfirmation).isTrue() - eventSink(SecurityAndPrivacyEvents.CancelEnableEncryption) + eventSink(SecurityAndPrivacyEvent.CancelEnableEncryption) } with(awaitItem()) { assertThat(showEnableEncryptionConfirmation).isFalse() - eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) + eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) } with(awaitItem()) { assertThat(showEnableEncryptionConfirmation).isTrue() - eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isTrue() assertThat(showEnableEncryptionConfirmation).isFalse() assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) + eventSink(SecurityAndPrivacyEvent.ToggleEncryptionState) } skipItems(1) with(awaitItem()) { @@ -186,12 +186,12 @@ class SecurityAndPrivacyPresenterTest { } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(false)) - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(true)) assertThat(canBeSaved).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(false)) @@ -203,12 +203,12 @@ class SecurityAndPrivacyPresenterTest { @Test fun `present - edit room address`() = runTest { val openEditRoomAddressLambda = lambdaRecorder { } - val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda) + val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda = openEditRoomAddressLambda) val presenter = createSecurityAndPrivacyPresenter(navigator = navigator) presenter.test { skipItems(1) with(awaitItem()) { - eventSink(SecurityAndPrivacyEvents.EditRoomAddress) + eventSink(SecurityAndPrivacyEvent.EditRoomAddress) } assert(openEditRoomAddressLambda).isCalledOnce() } @@ -231,28 +231,35 @@ class SecurityAndPrivacyPresenterTest { updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomHistoryVisibilityResult = updateRoomHistoryVisibilityLambda, ) - val presenter = createSecurityAndPrivacyPresenter(room = room) + val onDoneLambda = lambdaRecorder { } + val navigator = FakeSecurityAndPrivacyNavigator( + onDoneLambda = onDoneLambda, + ) + val presenter = createSecurityAndPrivacyPresenter( + room = room, + navigator = navigator, + ) presenter.test { skipItems(2) with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) } with(awaitItem()) { - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Anyone)) } with(awaitItem()) { assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Anyone) - eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(true)) - eventSink(SecurityAndPrivacyEvents.Save) + eventSink(SecurityAndPrivacyEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -276,6 +283,7 @@ class SecurityAndPrivacyPresenterTest { assert(updateJoinRuleLambda).isCalledOnce() assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomHistoryVisibilityLambda).isCalledOnce() + onDoneLambda.assertions().isCalledOnce() } } @@ -303,23 +311,23 @@ class SecurityAndPrivacyPresenterTest { skipItems(2) with(awaitItem()) { assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.InviteOnly) - eventSink(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.Anyone)) } with(awaitItem()) { - eventSink(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Anyone)) + eventSink(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.Anyone)) } with(awaitItem()) { assertThat(editedSettings.historyVisibility).isEqualTo(SecurityAndPrivacyHistoryVisibility.Anyone) - eventSink(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + eventSink(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isTrue() - eventSink(SecurityAndPrivacyEvents.ToggleRoomVisibility) + eventSink(SecurityAndPrivacyEvent.ToggleRoomVisibility) } with(awaitItem()) { assertThat(editedSettings.isVisibleInRoomDirectory).isEqualTo(AsyncData.Success(true)) - eventSink(SecurityAndPrivacyEvents.Save) + eventSink(SecurityAndPrivacyEvent.Save) } with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Loading) @@ -344,7 +352,7 @@ class SecurityAndPrivacyPresenterTest { assert(updateRoomVisibilityLambda).isCalledOnce() assert(updateRoomHistoryVisibilityLambda).isCalledOnce() // Clear error - state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) + state.eventSink(SecurityAndPrivacyEvent.DismissSaveError) with(awaitItem()) { assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized) } diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt index 78b840d823..6b429c04ea 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/SecurityAndPrivacyViewTest.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyEvents +import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyEvent import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyHistoryVisibility import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyRoomAccess import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyState @@ -24,7 +24,6 @@ import io.element.android.features.securityandprivacy.impl.root.aSecurityAndPriv import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.pressBack @@ -40,53 +39,53 @@ class SecurityAndPrivacyViewTest { @Test fun `click on back invokes emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, ) rule.setSecurityAndPrivacyView(state) rule.pressBack() - recorder.assertSingle(SecurityAndPrivacyEvents.Exit) + recorder.assertSingle(SecurityAndPrivacyEvent.Exit) } @Test - fun `confirm cancellation emits the expected event`() { - val recorder = EventsRecorder() + fun `discard cancellation emits the expected event`() { + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( - confirmExitAction = AsyncAction.ConfirmingCancellation, + saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ) rule.setSecurityAndPrivacyView(state) - rule.clickOn(CommonStrings.action_ok) - recorder.assertSingle(SecurityAndPrivacyEvents.Exit) + rule.clickOn(CommonStrings.action_discard) + recorder.assertSingle(SecurityAndPrivacyEvent.Exit) } @Test - fun `dismiss cancellation confirmation emits the expected event`() { - val recorder = EventsRecorder() + fun `save cancellation confirmation emits the expected event`() { + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( - confirmExitAction = AsyncAction.ConfirmingCancellation, + saveAction = AsyncAction.ConfirmingCancellation, eventSink = recorder, ) rule.setSecurityAndPrivacyView(state) - rule.clickOn(CommonStrings.action_cancel) - recorder.assertSingle(SecurityAndPrivacyEvents.DismissExitConfirmation) + rule.clickOn(CommonStrings.action_save, inDialog = true) + recorder.assertSingle(SecurityAndPrivacyEvent.Save) } @Test fun `click on room access item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_room_access_invite_only_option_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) + recorder.assertSingle(SecurityAndPrivacyEvent.ChangeRoomAccess(SecurityAndPrivacyRoomAccess.InviteOnly)) } @Test fun `click on disabled save doesn't emit event`() { - val recorder = EventsRecorder(expectEvents = false) + val recorder = EventsRecorder(expectEvents = false) val state = aSecurityAndPrivacyState(eventSink = recorder) rule.setSecurityAndPrivacyView(state) rule.clickOn(CommonStrings.action_save) @@ -95,7 +94,7 @@ class SecurityAndPrivacyViewTest { @Test fun `click on enabled save emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -104,14 +103,14 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.clickOn(CommonStrings.action_save) - recorder.assertSingle(SecurityAndPrivacyEvents.Save) + recorder.assertSingle(SecurityAndPrivacyEvent.Save) } @Test @Config(qualifiers = "h640dp") fun `click on room address item emits the expected event`() { val address = "@alias:matrix.org" - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -121,13 +120,13 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.onNodeWithText(address).performClick() - recorder.assertSingle(SecurityAndPrivacyEvents.EditRoomAddress) + recorder.assertSingle(SecurityAndPrivacyEvent.EditRoomAddress) } @Test @Config(qualifiers = "h1024dp") fun `click on room visibility item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -137,13 +136,13 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_room_directory_visibility_toggle_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ToggleRoomVisibility) + recorder.assertSingle(SecurityAndPrivacyEvent.ToggleRoomVisibility) } @Test @Config(qualifiers = "h640dp") fun `click on history visibility item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, editedSettings = aSecurityAndPrivacySettings( @@ -152,32 +151,32 @@ class SecurityAndPrivacyViewTest { ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_room_history_since_selecting_option_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceSelection)) + recorder.assertSingle(SecurityAndPrivacyEvent.ChangeHistoryVisibility(SecurityAndPrivacyHistoryVisibility.SinceSelection)) } @Test @Config(qualifiers = "h640dp") fun `click on encryption item emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, savedSettings = aSecurityAndPrivacySettings(isEncrypted = false), ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_encryption_toggle_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ToggleEncryptionState) + recorder.assertSingle(SecurityAndPrivacyEvent.ToggleEncryptionState) } @Test fun `click on encryption confirm emits the expected event`() { - val recorder = EventsRecorder() + val recorder = EventsRecorder() val state = aSecurityAndPrivacyState( eventSink = recorder, showEncryptionConfirmation = true, ) rule.setSecurityAndPrivacyView(state) rule.clickOn(R.string.screen_security_and_privacy_enable_encryption_alert_confirm_button_title) - recorder.assertSingle(SecurityAndPrivacyEvents.ConfirmEnableEncryption) + recorder.assertSingle(SecurityAndPrivacyEvent.ConfirmEnableEncryption) } } @@ -185,12 +184,10 @@ private fun AndroidComposeTestRule.setSecur state: SecurityAndPrivacyState = aSecurityAndPrivacyState( eventSink = EventsRecorder(expectEvents = false), ), - onBackClick: () -> Unit = EnsureNeverCalled(), ) { setContent { SecurityAndPrivacyView( state = state, - onBackClick = onBackClick, ) } } diff --git a/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt b/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt index f316b2fe96..a66fe55bff 100644 --- a/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt +++ b/features/securityandprivacy/test/src/main/kotlin/io/element/android/features/securityandprivacy/test/FakeSecurityAndPrivacyEntryPoint.kt @@ -13,7 +13,11 @@ import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntr import io.element.android.tests.testutils.lambda.lambdaError class FakeSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext): Node { + override fun createNode( + parentNode: Node, + buildContext: BuildContext, + callback: SecurityAndPrivacyEntryPoint.Callback, + ): Node { lambdaError() } } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt index b52da111b3..d299a43791 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsFlowNode.kt @@ -14,6 +14,7 @@ import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject @@ -95,9 +96,15 @@ class SpaceSettingsFlowNode( ) } is NavTarget.SecurityAndPrivacy -> { + val callback = object : SecurityAndPrivacyEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + } securityAndPrivacyEntryPoint.createNode( parentNode = this, buildContext = buildContext, + callback = callback, ) } is NavTarget.RolesAndPermissions -> { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt index b722480bb1..dc30b191a0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/SaveChangesDialog.kt @@ -17,16 +17,22 @@ import io.element.android.libraries.ui.strings.CommonStrings @Composable fun SaveChangesDialog( - onSubmitClick: () -> Unit, + onSaveClick: () -> Unit, + onDiscardClick: () -> Unit, onDismiss: () -> Unit, modifier: Modifier = Modifier, title: String = stringResource(CommonStrings.dialog_unsaved_changes_title), - content: String = stringResource(CommonStrings.dialog_unsaved_changes_description_android), + content: String = stringResource(CommonStrings.dialog_unsaved_changes_description), + submitText: String = stringResource(CommonStrings.action_save), + cancelText: String = stringResource(CommonStrings.action_discard), ) = ConfirmationDialog( modifier = modifier, title = title, content = content, - onSubmitClick = onSubmitClick, + submitText = submitText, + cancelText = cancelText, + onSubmitClick = onSaveClick, + onCancelClick = onDiscardClick, onDismiss = onDismiss, ) @@ -34,7 +40,8 @@ fun SaveChangesDialog( @Composable internal fun SaveChangesDialogPreview() = ElementPreview { SaveChangesDialog( - onSubmitClick = {}, + onSaveClick = {}, + onDiscardClick = {}, onDismiss = {} ) } diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index 0764851647..935c3e600c 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -38,6 +38,7 @@ "%1$s invited you to join the room" "Me" "%1$s mentioned or replied" + "Invited you to join the space" "You are viewing the notification! Click me!" "Thread in %1$s" "%1$s: %2$s" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 8be01eb439..d422a4c35c 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -392,6 +392,7 @@ Are you sure you want to continue?" "Error" "Success" "Warning" + "You have unsaved changes." "Your changes have not been saved. Are you sure you want to go back?" "Save changes?" "The max file size allowed is: %1$s" diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt index f81b03b10b..6502882d7d 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/SemanticsNodeInteractionsProviderExtensions.kt @@ -10,34 +10,33 @@ package io.element.android.tests.testutils import androidx.activity.ComponentActivity import androidx.annotation.StringRes +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.SemanticsNodeInteractionsProvider +import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasClickAction import androidx.compose.ui.test.hasContentDescription import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.junit4.AndroidComposeTestRule -import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import io.element.android.libraries.ui.strings.CommonStrings import org.junit.rules.TestRule -fun AndroidComposeTestRule.clickOn(@StringRes res: Int) { +val trueMatcher = SemanticsMatcher("true matcher") { true } + +fun AndroidComposeTestRule.clickOn( + @StringRes res: Int, + inDialog: Boolean = false, +) { val text = activity.getString(res) - onNode(hasText(text) and hasClickAction()) + onNode( + hasText(text) and hasClickAction() and if (inDialog) hasAnyAncestor(isDialog()) else trueMatcher + ) .performClick() } -fun AndroidComposeTestRule.clickOnFirst(@StringRes res: Int) { - val text = activity.getString(res) - onAllNodes(hasText(text) and hasClickAction()).onFirst().performClick() -} - -fun AndroidComposeTestRule.clickOnLast(@StringRes res: Int) { - val text = activity.getString(res) - onAllNodes(hasText(text) and hasClickAction()).onFirst().performClick() -} - /** * Press the back button in the app bar. */ diff --git a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png index 12e42e376c..d083e1d3e1 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:657d1e0eff5254eb3d36a64212cca38ec129c240cbd61b8cfdeb8399a00a5251 -size 39103 +oid sha256:2d60cf2028add0c159867b252f429ec92575b0ee4e89088046ecfbd3c58997df +size 38027 diff --git a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png index 5d4488f53a..0788da663d 100644 --- a/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.poll.impl.create_CreatePollView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3407623ce83d0c3f710ea45d693823d6125b1bc625c34e2922916110d8a2a442 -size 36717 +oid sha256:223002dda410268fbe0c3305ed08749c7fcaabf4d5c3803503525811da2a2841 +size 35821 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en.png index d27f554281..17eb214c80 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Day_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2a443d3d6733e4fb9c618c4a689c8fb95195f47d3b08513a955f18251028b2f -size 34203 +oid sha256:9b6869e6026df038d739cc14a0a585563d595b1430ba23a985ec57680506ead2 +size 29857 diff --git a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en.png index 4d93498ed8..8ee822b18b 100644 --- a/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en.png +++ b/tests/uitests/src/test/snapshots/images/features.preferences.impl.user.editprofile_EditUserProfileView_Night_2_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2166579c462025a6bff38b05da7574502f7200d5f0a4949b31ea4e4fbc25a67 -size 32467 +oid sha256:2b3eb9e8908deb84153b19d3735dd2338fac2a7998793915eeec32d7607a0b94 +size 28671 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en.png index dbf2be2d17..1af85a90ae 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Day_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8301124f0464ecd17016d93160899f6a4f3d4bb14ebd8dcd4bc3c3bd77e41996 -size 58980 +oid sha256:6109391197169d423341ba95d8f3de581b6b9663f8a017c0b727bf7d9181b228 +size 56993 diff --git a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en.png index bdb1f920d1..93d5b14f5f 100644 --- a/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en.png +++ b/tests/uitests/src/test/snapshots/images/features.rolesandpermissions.impl.roles_ChangeRolesView_Night_6_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be70d554880dec4eb3816360dbdbb6b678f2300afc10c91e741418cfac93cfb4 -size 56852 +oid sha256:71a4998d420d5cad0a799db7f51223393cca34218a1a23520606667ea320331b +size 55323 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en.png index c9523fb650..a1da37b1de 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Day_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:25d60c4025a15e283978fb15abf8417bc896714ebc1faf198a3735978bbbfb18 -size 32392 +oid sha256:e3d196d9b4946165718d8a4ad37869d1d81c6134e94e05dcc1b23b71338482cb +size 28941 diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en.png index 5f62ef9369..1643569702 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetailsedit.impl_RoomDetailsEditView_Night_9_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29cea35b4105715561f34e4c6fe80a57272444b34b01bd9dd1f0adec67ea8878 -size 30270 +oid sha256:b9187a1e5fc63707a2c1a9804411e15439469f8562eac2c9766a4e033e214166 +size 27289 diff --git a/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en.png b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en.png index e8e5a2019d..f642f7050b 100644 --- a/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b1ce86ad21d9ccd5b170a1628985eadbc5ac92be521ab55e9a1b22eb48c1476 -size 32552 +oid sha256:72be11ed273a790b0ab8e5023c1e21384c020e1b0fc0e3950de8f041c4004e64 +size 33758 diff --git a/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en.png b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en.png index 6625789361..5e318df2ca 100644 --- a/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en.png +++ b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_18_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da39e0496083394c522d40ed4cc62edc614ddca54ccfb05d5b7c68cfb030960a -size 34559 +oid sha256:c1cbcce698faad80969cd00d2680afd7cd61b6ba649f0b67e8c7fb2fe0dbe746 +size 35884 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png index 090d6dce7d..7d4045ec02 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d89a6c9a49b2b0e4fa37e219cec8e0dbe90ed8ca6e71f0ec5e8788076f91526 -size 23679 +oid sha256:9849f4591df3218c78c91e6731a864b72abaa699caa6321c818197faaf6b85af +size 18326 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png index 3963c95375..e2700312ba 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.dialogs_SaveChangesDialog_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e496c8d67a678a7d4c729a3368c145afa080252dd68708b1ba95d98706cd614c -size 22265 +oid sha256:d47c650eae62a550c80aeed85775062304132debf33e5279fddc74c787ad40b3 +size 17211