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