Rename AsyncView to AsyncActionView
This commit is contained in:
parent
bd91dd64e2
commit
d78ce21939
71 changed files with 369 additions and 315 deletions
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.createroom.api
|
package io.element.android.features.createroom.api
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
||||||
|
|
@ -27,5 +27,5 @@ interface StartDMAction {
|
||||||
* @param userId The user to start a DM with.
|
* @param userId The user to start a DM with.
|
||||||
* @param actionState The state to update with the result of the action.
|
* @param actionState The state to update with the result of the action.
|
||||||
*/
|
*/
|
||||||
suspend fun execute(userId: UserId, actionState: MutableState<AsyncData<RoomId>>)
|
suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import androidx.compose.runtime.MutableState
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.api.StartDMAction
|
import io.element.android.features.createroom.api.StartDMAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -36,17 +36,17 @@ class DefaultStartDMAction @Inject constructor(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
) : StartDMAction {
|
) : StartDMAction {
|
||||||
|
|
||||||
override suspend fun execute(userId: UserId, actionState: MutableState<AsyncData<RoomId>>) {
|
override suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>) {
|
||||||
actionState.value = AsyncData.Loading()
|
actionState.value = AsyncAction.Loading
|
||||||
when (val result = matrixClient.startDM(userId)) {
|
when (val result = matrixClient.startDM(userId)) {
|
||||||
is StartDMResult.Success -> {
|
is StartDMResult.Success -> {
|
||||||
if (result.isNew) {
|
if (result.isNew) {
|
||||||
analyticsService.capture(CreatedRoom(isDM = true))
|
analyticsService.capture(CreatedRoom(isDM = true))
|
||||||
}
|
}
|
||||||
actionState.value = AsyncData.Success(result.roomId)
|
actionState.value = AsyncAction.Success(result.roomId)
|
||||||
}
|
}
|
||||||
is StartDMResult.Failure -> {
|
is StartDMResult.Failure -> {
|
||||||
actionState.value = AsyncData.Failure(result.throwable)
|
actionState.value = AsyncAction.Failure(result.throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
|
|
@ -91,10 +91,10 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val createRoomAction: MutableState<AsyncData<RoomId>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val createRoomAction: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
fun createRoom(config: CreateRoomConfig) {
|
fun createRoom(config: CreateRoomConfig) {
|
||||||
createRoomAction.value = AsyncData.Uninitialized
|
createRoomAction.value = AsyncAction.Uninitialized
|
||||||
localCoroutineScope.createRoom(config, createRoomAction)
|
localCoroutineScope.createRoom(config, createRoomAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncData.Uninitialized
|
ConfigureRoomEvents.CancelCreateRoom -> createRoomAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ class ConfigureRoomPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.createRoom(
|
private fun CoroutineScope.createRoom(
|
||||||
config: CreateRoomConfig,
|
config: CreateRoomConfig,
|
||||||
createRoomAction: MutableState<AsyncData<RoomId>>
|
createRoomAction: MutableState<AsyncAction<RoomId>>
|
||||||
) = launch {
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
val avatarUrl = config.avatarUri?.let { uploadAvatar(it) }
|
val avatarUrl = config.avatarUri?.let { uploadAvatar(it) }
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,17 @@
|
||||||
|
|
||||||
package io.element.android.features.createroom.impl.configureroom
|
package io.element.android.features.createroom.impl.configureroom
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.permissions.api.PermissionsState
|
import io.element.android.libraries.permissions.api.PermissionsState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class ConfigureRoomState(
|
data class ConfigureRoomState(
|
||||||
val config: CreateRoomConfig,
|
val config: CreateRoomConfig,
|
||||||
val avatarActions: ImmutableList<AvatarAction>,
|
val avatarActions: ImmutableList<AvatarAction>,
|
||||||
val createRoomAction: AsyncData<RoomId>,
|
val createRoomAction: AsyncAction<RoomId>,
|
||||||
val cameraPermissionState: PermissionsState,
|
val cameraPermissionState: PermissionsState,
|
||||||
val eventSink: (ConfigureRoomEvents) -> Unit
|
val eventSink: (ConfigureRoomEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package io.element.android.features.createroom.impl.configureroom
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.features.createroom.impl.userlist.aListOfSelectedUsers
|
import io.element.android.features.createroom.impl.userlist.aListOfSelectedUsers
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.permissions.api.aPermissionsState
|
import io.element.android.libraries.permissions.api.aPermissionsState
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ open class ConfigureRoomStateProvider : PreviewParameterProvider<ConfigureRoomSt
|
||||||
fun aConfigureRoomState() = ConfigureRoomState(
|
fun aConfigureRoomState() = ConfigureRoomState(
|
||||||
config = CreateRoomConfig(),
|
config = CreateRoomConfig(),
|
||||||
avatarActions = persistentListOf(),
|
avatarActions = persistentListOf(),
|
||||||
createRoomAction = AsyncData.Uninitialized,
|
createRoomAction = AsyncAction.Uninitialized,
|
||||||
cameraPermissionState = aPermissionsState(showDialog = false),
|
cameraPermissionState = aPermissionsState(showDialog = false),
|
||||||
eventSink = { },
|
eventSink = { },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.createroom.impl.R
|
import io.element.android.features.createroom.impl.R
|
||||||
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
||||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
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.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
|
||||||
|
|
@ -148,7 +148,7 @@ fun ConfigureRoomView(
|
||||||
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.createRoomAction,
|
async = state.createRoomAction,
|
||||||
progressText = stringResource(CommonStrings.common_creating_room),
|
progressText = stringResource(CommonStrings.common_creating_room),
|
||||||
onSuccess = { onRoomCreated(it) },
|
onSuccess = { onRoomCreated(it) },
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import io.element.android.features.createroom.impl.userlist.SelectionMode
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListPresenter
|
import io.element.android.features.createroom.impl.userlist.UserListPresenter
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListPresenterArgs
|
import io.element.android.features.createroom.impl.userlist.UserListPresenterArgs
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -55,14 +55,14 @@ class CreateRoomRootPresenter @Inject constructor(
|
||||||
val userListState = presenter.present()
|
val userListState = presenter.present()
|
||||||
|
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val startDmActionState: MutableState<AsyncData<RoomId>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
fun handleEvents(event: CreateRoomRootEvents) {
|
fun handleEvents(event: CreateRoomRootEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch {
|
is CreateRoomRootEvents.StartDM -> localCoroutineScope.launch {
|
||||||
startDMAction.execute(event.matrixUser.userId, startDmActionState)
|
startDMAction.execute(event.matrixUser.userId, startDmActionState)
|
||||||
}
|
}
|
||||||
CreateRoomRootEvents.CancelStartDM -> startDmActionState.value = AsyncData.Uninitialized
|
CreateRoomRootEvents.CancelStartDM -> startDmActionState.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@
|
||||||
package io.element.android.features.createroom.impl.root
|
package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListState
|
import io.element.android.features.createroom.impl.userlist.UserListState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
data class CreateRoomRootState(
|
data class CreateRoomRootState(
|
||||||
val applicationName: String,
|
val applicationName: String,
|
||||||
val userListState: UserListState,
|
val userListState: UserListState,
|
||||||
val startDmAction: AsyncData<RoomId>,
|
val startDmAction: AsyncAction<RoomId>,
|
||||||
val eventSink: (CreateRoomRootEvents) -> Unit,
|
val eventSink: (CreateRoomRootEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ package io.element.android.features.createroom.impl.root
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.createroom.impl.userlist.aUserListState
|
import io.element.android.features.createroom.impl.userlist.aUserListState
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
import io.element.android.libraries.usersearch.api.UserSearchResult
|
import io.element.android.libraries.usersearch.api.UserSearchResult
|
||||||
|
|
@ -30,7 +30,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aCreateRoomRootState(),
|
aCreateRoomRootState(),
|
||||||
aCreateRoomRootState().copy(
|
aCreateRoomRootState().copy(
|
||||||
startDmAction = AsyncData.Loading(),
|
startDmAction = AsyncAction.Loading,
|
||||||
userListState = aMatrixUser().let {
|
userListState = aMatrixUser().let {
|
||||||
aUserListState().copy(
|
aUserListState().copy(
|
||||||
searchQuery = it.userId.value,
|
searchQuery = it.userId.value,
|
||||||
|
|
@ -41,7 +41,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
aCreateRoomRootState().copy(
|
aCreateRoomRootState().copy(
|
||||||
startDmAction = AsyncData.Failure(Throwable("error")),
|
startDmAction = AsyncAction.Failure(Throwable("error")),
|
||||||
userListState = aMatrixUser().let {
|
userListState = aMatrixUser().let {
|
||||||
aUserListState().copy(
|
aUserListState().copy(
|
||||||
searchQuery = it.userId.value,
|
searchQuery = it.userId.value,
|
||||||
|
|
@ -57,6 +57,6 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
|
||||||
fun aCreateRoomRootState() = CreateRoomRootState(
|
fun aCreateRoomRootState() = CreateRoomRootState(
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
applicationName = "Element X Preview",
|
applicationName = "Element X Preview",
|
||||||
startDmAction = AsyncData.Uninitialized,
|
startDmAction = AsyncAction.Uninitialized,
|
||||||
userListState = aUserListState(),
|
userListState = aUserListState(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ 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.createroom.impl.R
|
import io.element.android.features.createroom.impl.R
|
||||||
import io.element.android.features.createroom.impl.components.UserListView
|
import io.element.android.features.createroom.impl.components.UserListView
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
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.icons.CompoundDrawables
|
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -93,7 +93,7 @@ fun CreateRoomRootView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.startDmAction,
|
async = state.startDmAction,
|
||||||
progressText = stringResource(CommonStrings.common_starting_chat),
|
progressText = stringResource(CommonStrings.common_starting_chat),
|
||||||
onSuccess = { onOpenDM(it) },
|
onSuccess = { onOpenDM(it) },
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ package io.element.android.features.createroom.impl
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.CreatedRoom
|
import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
@ -39,9 +39,9 @@ class DefaultStartDMActionTests {
|
||||||
givenFindDmResult(A_ROOM_ID)
|
givenFindDmResult(A_ROOM_ID)
|
||||||
}
|
}
|
||||||
val action = createStartDMAction(matrixClient)
|
val action = createStartDMAction(matrixClient)
|
||||||
val state = mutableStateOf<AsyncData<RoomId>>(AsyncData.Uninitialized)
|
val state = mutableStateOf<AsyncAction<RoomId>>(AsyncAction.Uninitialized)
|
||||||
action.execute(A_USER_ID, state)
|
action.execute(A_USER_ID, state)
|
||||||
assertThat(state.value).isEqualTo(AsyncData.Success(A_ROOM_ID))
|
assertThat(state.value).isEqualTo(AsyncAction.Success(A_ROOM_ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -52,9 +52,9 @@ class DefaultStartDMActionTests {
|
||||||
}
|
}
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
val action = createStartDMAction(matrixClient, analyticsService)
|
val action = createStartDMAction(matrixClient, analyticsService)
|
||||||
val state = mutableStateOf<AsyncData<RoomId>>(AsyncData.Uninitialized)
|
val state = mutableStateOf<AsyncAction<RoomId>>(AsyncAction.Uninitialized)
|
||||||
action.execute(A_USER_ID, state)
|
action.execute(A_USER_ID, state)
|
||||||
assertThat(state.value).isEqualTo(AsyncData.Success(A_ROOM_ID))
|
assertThat(state.value).isEqualTo(AsyncAction.Success(A_ROOM_ID))
|
||||||
assertThat(analyticsService.capturedEvents).containsExactly(CreatedRoom(isDM = true))
|
assertThat(analyticsService.capturedEvents).containsExactly(CreatedRoom(isDM = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,9 +65,9 @@ class DefaultStartDMActionTests {
|
||||||
givenCreateDmResult(Result.failure(A_THROWABLE))
|
givenCreateDmResult(Result.failure(A_THROWABLE))
|
||||||
}
|
}
|
||||||
val action = createStartDMAction(matrixClient)
|
val action = createStartDMAction(matrixClient)
|
||||||
val state = mutableStateOf<AsyncData<RoomId>>(AsyncData.Uninitialized)
|
val state = mutableStateOf<AsyncAction<RoomId>>(AsyncAction.Uninitialized)
|
||||||
action.execute(A_USER_ID, state)
|
action.execute(A_USER_ID, state)
|
||||||
assertThat(state.value).isEqualTo(AsyncData.Failure<RoomId>(A_THROWABLE))
|
assertThat(state.value).isEqualTo(AsyncAction.Failure(A_THROWABLE))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createStartDMAction(
|
private fun createStartDMAction(
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import im.vector.app.features.analytics.plan.CreatedRoom
|
||||||
import io.element.android.features.createroom.impl.CreateRoomConfig
|
import io.element.android.features.createroom.impl.CreateRoomConfig
|
||||||
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
import io.element.android.features.createroom.impl.CreateRoomDataStore
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||||
|
|
@ -234,9 +234,9 @@ class ConfigureRoomPresenterTests {
|
||||||
fakeMatrixClient.givenCreateRoomResult(createRoomResult)
|
fakeMatrixClient.givenCreateRoomResult(createRoomResult)
|
||||||
|
|
||||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val stateAfterCreateRoom = awaitItem()
|
val stateAfterCreateRoom = awaitItem()
|
||||||
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
assertThat(stateAfterCreateRoom.createRoomAction.dataOrNull()).isEqualTo(createRoomResult.getOrNull())
|
assertThat(stateAfterCreateRoom.createRoomAction.dataOrNull()).isEqualTo(createRoomResult.getOrNull())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,16 +272,16 @@ class ConfigureRoomPresenterTests {
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val stateAfterCreateRoom = awaitItem()
|
val stateAfterCreateRoom = awaitItem()
|
||||||
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
|
assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance<CreatedRoom>()).isEmpty()
|
||||||
|
|
||||||
fakeMatrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL))
|
fakeMatrixClient.givenUploadMediaResult(Result.success(AN_AVATAR_URL))
|
||||||
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,22 +297,22 @@ class ConfigureRoomPresenterTests {
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
initialState.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val stateAfterCreateRoom = awaitItem()
|
val stateAfterCreateRoom = awaitItem()
|
||||||
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(stateAfterCreateRoom.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
assertThat((stateAfterCreateRoom.createRoomAction as? AsyncData.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull())
|
assertThat((stateAfterCreateRoom.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull())
|
||||||
|
|
||||||
// Retry
|
// Retry
|
||||||
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
stateAfterCreateRoom.eventSink(ConfigureRoomEvents.CreateRoom(initialState.config))
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val stateAfterRetry = awaitItem()
|
val stateAfterRetry = awaitItem()
|
||||||
assertThat(stateAfterRetry.createRoomAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(stateAfterRetry.createRoomAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
assertThat((stateAfterRetry.createRoomAction as? AsyncData.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull())
|
assertThat((stateAfterRetry.createRoomAction as? AsyncAction.Failure)?.error).isEqualTo(createRoomResult.exceptionOrNull())
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
stateAfterRetry.eventSink(ConfigureRoomEvents.CancelCreateRoom)
|
stateAfterRetry.eventSink(ConfigureRoomEvents.CancelCreateRoom)
|
||||||
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ import io.element.android.features.createroom.impl.userlist.FakeUserListPresente
|
||||||
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
|
import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory
|
||||||
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
import io.element.android.features.createroom.impl.userlist.UserListDataStore
|
||||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
@ -52,20 +51,20 @@ class CreateRoomRootPresenterTests {
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
assertThat(initialState.startDmAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(initialState.startDmAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName)
|
assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName)
|
||||||
assertThat(initialState.userListState.selectedUsers).isEmpty()
|
assertThat(initialState.userListState.selectedUsers).isEmpty()
|
||||||
assertThat(initialState.userListState.isSearchActive).isFalse()
|
assertThat(initialState.userListState.isSearchActive).isFalse()
|
||||||
assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse()
|
assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse()
|
||||||
|
|
||||||
val matrixUser = MatrixUser(UserId("@name:domain"))
|
val matrixUser = MatrixUser(UserId("@name:domain"))
|
||||||
val startDMSuccessResult = AsyncData.Success(A_ROOM_ID)
|
val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID)
|
||||||
val startDMFailureResult = AsyncData.Failure<RoomId>(A_THROWABLE)
|
val startDMFailureResult = AsyncAction.Failure(A_THROWABLE)
|
||||||
|
|
||||||
// Failure
|
// Failure
|
||||||
startDMAction.givenExecuteResult(startDMFailureResult)
|
startDMAction.givenExecuteResult(startDMFailureResult)
|
||||||
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
assertThat(awaitItem().startDmAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().startDmAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.startDmAction).isEqualTo(startDMFailureResult)
|
assertThat(state.startDmAction).isEqualTo(startDMFailureResult)
|
||||||
state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||||
|
|
@ -74,10 +73,10 @@ class CreateRoomRootPresenterTests {
|
||||||
// Success
|
// Success
|
||||||
startDMAction.givenExecuteResult(startDMSuccessResult)
|
startDMAction.givenExecuteResult(startDMSuccessResult)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.startDmAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(state.startDmAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
state.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
state.eventSink(CreateRoomRootEvents.StartDM(matrixUser))
|
||||||
}
|
}
|
||||||
assertThat(awaitItem().startDmAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().startDmAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.startDmAction).isEqualTo(startDMSuccessResult)
|
assertThat(state.startDmAction).isEqualTo(startDMSuccessResult)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.createroom.test
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import io.element.android.features.createroom.api.StartDMAction
|
import io.element.android.features.createroom.api.StartDMAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
@ -26,14 +26,14 @@ import kotlinx.coroutines.delay
|
||||||
|
|
||||||
class FakeStartDMAction : StartDMAction {
|
class FakeStartDMAction : StartDMAction {
|
||||||
|
|
||||||
private var executeResult: AsyncData<RoomId> = AsyncData.Success(A_ROOM_ID)
|
private var executeResult: AsyncAction<RoomId> = AsyncAction.Success(A_ROOM_ID)
|
||||||
|
|
||||||
fun givenExecuteResult(result: AsyncData<RoomId>) {
|
fun givenExecuteResult(result: AsyncAction<RoomId>) {
|
||||||
executeResult = result
|
executeResult = result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(userId: UserId, actionState: MutableState<AsyncData<RoomId>>) {
|
override suspend fun execute(userId: UserId, actionState: MutableState<AsyncAction<RoomId>>) {
|
||||||
actionState.value = AsyncData.Loading()
|
actionState.value = AsyncAction.Loading
|
||||||
delay(1)
|
delay(1)
|
||||||
actionState.value = executeResult
|
actionState.value = executeResult
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
import io.element.android.features.login.api.oidc.OidcAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
|
@ -44,33 +44,33 @@ class OidcPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): OidcState {
|
override fun present(): OidcState {
|
||||||
var requestState: AsyncData<Unit> by remember {
|
var requestState: AsyncAction<Unit> by remember {
|
||||||
mutableStateOf(AsyncData.Uninitialized)
|
mutableStateOf(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
fun handleCancel() {
|
fun handleCancel() {
|
||||||
requestState = AsyncData.Loading()
|
requestState = AsyncAction.Loading
|
||||||
localCoroutineScope.launch {
|
localCoroutineScope.launch {
|
||||||
authenticationService.cancelOidcLogin()
|
authenticationService.cancelOidcLogin()
|
||||||
.fold(
|
.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
// Then go back
|
// Then go back
|
||||||
requestState = AsyncData.Success(Unit)
|
requestState = AsyncAction.Success(Unit)
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
requestState = AsyncData.Failure(it)
|
requestState = AsyncAction.Failure(it)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleSuccess(url: String) {
|
fun handleSuccess(url: String) {
|
||||||
requestState = AsyncData.Loading()
|
requestState = AsyncAction.Loading
|
||||||
localCoroutineScope.launch {
|
localCoroutineScope.launch {
|
||||||
authenticationService.loginWithOidc(url)
|
authenticationService.loginWithOidc(url)
|
||||||
.onFailure {
|
.onFailure {
|
||||||
requestState = AsyncData.Failure(it)
|
requestState = AsyncAction.Failure(it)
|
||||||
}
|
}
|
||||||
// On success, the node tree will be updated, there is nothing to do
|
// On success, the node tree will be updated, there is nothing to do
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +87,7 @@ class OidcPresenter @AssistedInject constructor(
|
||||||
when (event) {
|
when (event) {
|
||||||
OidcEvents.Cancel -> handleCancel()
|
OidcEvents.Cancel -> handleCancel()
|
||||||
is OidcEvents.OidcActionEvent -> handleAction(event.oidcAction)
|
is OidcEvents.OidcActionEvent -> handleAction(event.oidcAction)
|
||||||
OidcEvents.ClearError -> requestState = AsyncData.Uninitialized
|
OidcEvents.ClearError -> requestState = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
package io.element.android.features.login.impl.oidc.webview
|
package io.element.android.features.login.impl.oidc.webview
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
|
||||||
data class OidcState(
|
data class OidcState(
|
||||||
val oidcDetails: OidcDetails,
|
val oidcDetails: OidcDetails,
|
||||||
val requestState: AsyncData<Unit>,
|
val requestState: AsyncAction<Unit>,
|
||||||
val eventSink: (OidcEvents) -> Unit
|
val eventSink: (OidcEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,20 @@
|
||||||
package io.element.android.features.login.impl.oidc.webview
|
package io.element.android.features.login.impl.oidc.webview
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
import io.element.android.libraries.matrix.api.auth.OidcDetails
|
||||||
|
|
||||||
open class OidcStateProvider : PreviewParameterProvider<OidcState> {
|
open class OidcStateProvider : PreviewParameterProvider<OidcState> {
|
||||||
override val values: Sequence<OidcState>
|
override val values: Sequence<OidcState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aOidcState(),
|
aOidcState(),
|
||||||
aOidcState().copy(requestState = AsyncData.Loading()),
|
aOidcState().copy(requestState = AsyncAction.Loading),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aOidcState() = OidcState(
|
fun aOidcState() = OidcState(
|
||||||
oidcDetails = aOidcDetails(),
|
oidcDetails = aOidcDetails(),
|
||||||
requestState = AsyncData.Uninitialized,
|
requestState = AsyncAction.Uninitialized,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import io.element.android.features.login.impl.oidc.OidcUrlParser
|
import io.element.android.features.login.impl.oidc.OidcUrlParser
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -76,7 +76,7 @@ fun OidcView(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.requestState,
|
async = state.requestState,
|
||||||
onSuccess = { onNavigateBack() },
|
onSuccess = { onNavigateBack() },
|
||||||
onErrorDismiss = { state.eventSink(OidcEvents.ClearError) }
|
onErrorDismiss = { state.eventSink(OidcEvents.ClearError) }
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.login.api.oidc.OidcAction
|
import io.element.android.features.login.api.oidc.OidcAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||||
import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA
|
import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||||
|
|
@ -49,7 +49,7 @@ class OidcPresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.oidcDetails).isEqualTo(A_OIDC_DATA)
|
assertThat(initialState.oidcDetails).isEqualTo(A_OIDC_DATA)
|
||||||
assertThat(initialState.requestState).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.requestState).isEqualTo(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,9 +65,9 @@ class OidcPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(OidcEvents.Cancel)
|
initialState.eventSink.invoke(OidcEvents.Cancel)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.requestState).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.requestState).isEqualTo(AsyncData.Success(Unit))
|
assertThat(finalState.requestState).isEqualTo(AsyncAction.Success(Unit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,9 +85,9 @@ class OidcPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(OidcEvents.Cancel)
|
initialState.eventSink.invoke(OidcEvents.Cancel)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.requestState).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.requestState).isEqualTo(AsyncData.Failure<Unit>(A_THROWABLE))
|
assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE))
|
||||||
// Note: in real life I do not think this can happen, and the app should not block the user.
|
// Note: in real life I do not think this can happen, and the app should not block the user.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -104,9 +104,9 @@ class OidcPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.GoBack))
|
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.GoBack))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.requestState).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.requestState).isEqualTo(AsyncData.Success(Unit))
|
assertThat(finalState.requestState).isEqualTo(AsyncAction.Success(Unit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +122,7 @@ class OidcPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL")))
|
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL")))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.requestState).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
|
||||||
// In this case, no success, the session is created and the node get destroyed.
|
// In this case, no success, the session is created and the node get destroyed.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,12 +141,12 @@ class OidcPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL")))
|
initialState.eventSink.invoke(OidcEvents.OidcActionEvent(OidcAction.Success("A_URL")))
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.requestState).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading)
|
||||||
val errorState = awaitItem()
|
val errorState = awaitItem()
|
||||||
assertThat(errorState.requestState).isEqualTo(AsyncData.Failure<Unit>(A_THROWABLE))
|
assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE))
|
||||||
errorState.eventSink.invoke(OidcEvents.ClearError)
|
errorState.eventSink.invoke(OidcEvents.ClearError)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.requestState).isEqualTo(AsyncData.Uninitialized)
|
assertThat(finalState.requestState).isEqualTo(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runUpdatingState
|
import io.element.android.libraries.architecture.runUpdatingState
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
|
|
@ -60,14 +60,14 @@ class ReportMessagePresenter @AssistedInject constructor(
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var reason by rememberSaveable { mutableStateOf("") }
|
var reason by rememberSaveable { mutableStateOf("") }
|
||||||
var blockUser by rememberSaveable { mutableStateOf(false) }
|
var blockUser by rememberSaveable { mutableStateOf(false) }
|
||||||
var result: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
var result: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
fun handleEvents(event: ReportMessageEvents) {
|
fun handleEvents(event: ReportMessageEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is ReportMessageEvents.UpdateReason -> reason = event.reason
|
is ReportMessageEvents.UpdateReason -> reason = event.reason
|
||||||
ReportMessageEvents.ToggleBlockUser -> blockUser = !blockUser
|
ReportMessageEvents.ToggleBlockUser -> blockUser = !blockUser
|
||||||
ReportMessageEvents.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result)
|
ReportMessageEvents.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result)
|
||||||
ReportMessageEvents.ClearError -> result.value = AsyncData.Uninitialized
|
ReportMessageEvents.ClearError -> result.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ class ReportMessagePresenter @AssistedInject constructor(
|
||||||
userId: UserId,
|
userId: UserId,
|
||||||
reason: String,
|
reason: String,
|
||||||
blockUser: Boolean,
|
blockUser: Boolean,
|
||||||
result: MutableState<AsyncData<Unit>>,
|
result: MutableState<AsyncAction<Unit>>,
|
||||||
) = launch {
|
) = launch {
|
||||||
result.runUpdatingState {
|
result.runUpdatingState {
|
||||||
val userIdToBlock = userId.takeIf { blockUser }
|
val userIdToBlock = userId.takeIf { blockUser }
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.report
|
package io.element.android.features.messages.impl.report
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
data class ReportMessageState(
|
data class ReportMessageState(
|
||||||
val reason: String,
|
val reason: String,
|
||||||
val blockUser: Boolean,
|
val blockUser: Boolean,
|
||||||
val result: AsyncData<Unit>,
|
val result: AsyncAction<Unit>,
|
||||||
val eventSink: (ReportMessageEvents) -> Unit
|
val eventSink: (ReportMessageEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.messages.impl.report
|
package io.element.android.features.messages.impl.report
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
open class ReportMessageStateProvider : PreviewParameterProvider<ReportMessageState> {
|
open class ReportMessageStateProvider : PreviewParameterProvider<ReportMessageState> {
|
||||||
override val values: Sequence<ReportMessageState>
|
override val values: Sequence<ReportMessageState>
|
||||||
|
|
@ -25,9 +25,9 @@ open class ReportMessageStateProvider : PreviewParameterProvider<ReportMessageSt
|
||||||
aReportMessageState(),
|
aReportMessageState(),
|
||||||
aReportMessageState(reason = "This user is making the chat very toxic."),
|
aReportMessageState(reason = "This user is making the chat very toxic."),
|
||||||
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true),
|
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true),
|
||||||
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true, result = AsyncData.Loading()),
|
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true, result = AsyncAction.Loading),
|
||||||
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true, result = AsyncData.Failure(Throwable("error"))),
|
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true, result = AsyncAction.Failure(Throwable("error"))),
|
||||||
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true, result = AsyncData.Success(Unit)),
|
aReportMessageState(reason = "This user is making the chat very toxic.", blockUser = true, result = AsyncAction.Success(Unit)),
|
||||||
// Add other states here
|
// Add other states here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +35,7 @@ open class ReportMessageStateProvider : PreviewParameterProvider<ReportMessageSt
|
||||||
fun aReportMessageState(
|
fun aReportMessageState(
|
||||||
reason: String = "",
|
reason: String = "",
|
||||||
blockUser: Boolean = false,
|
blockUser: Boolean = false,
|
||||||
result: AsyncData<Unit> = AsyncData.Uninitialized,
|
result: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
) = ReportMessageState(
|
) = ReportMessageState(
|
||||||
reason = reason,
|
reason = reason,
|
||||||
blockUser = blockUser,
|
blockUser = blockUser,
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.messages.impl.R
|
import io.element.android.features.messages.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.designsystem.components.async.AsyncView
|
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.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
|
||||||
|
|
@ -62,8 +63,8 @@ fun ReportMessageView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
val isSending = state.result is AsyncData.Loading
|
val isSending = state.result is AsyncAction.Loading
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.result,
|
async = state.result,
|
||||||
showProgressDialog = false,
|
showProgressDialog = false,
|
||||||
onSuccess = { onBackClicked() },
|
onSuccess = { onBackClicked() },
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
|
@ -45,7 +45,7 @@ class ReportMessagePresenterTests {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.reason).isEmpty()
|
assertThat(initialState.reason).isEmpty()
|
||||||
assertThat(initialState.blockUser).isFalse()
|
assertThat(initialState.blockUser).isFalse()
|
||||||
assertThat(initialState.result).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(initialState.result).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,8 +91,8 @@ class ReportMessagePresenterTests {
|
||||||
initialState.eventSink(ReportMessageEvents.ToggleBlockUser)
|
initialState.eventSink(ReportMessageEvents.ToggleBlockUser)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
initialState.eventSink(ReportMessageEvents.Report)
|
initialState.eventSink(ReportMessageEvents.Report)
|
||||||
assertThat(awaitItem().result).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().result).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
assertThat(room.reportedContentCount).isEqualTo(1)
|
assertThat(room.reportedContentCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -106,8 +106,8 @@ class ReportMessagePresenterTests {
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ReportMessageEvents.Report)
|
initialState.eventSink(ReportMessageEvents.Report)
|
||||||
assertThat(awaitItem().result).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().result).isInstanceOf(AsyncData.Success::class.java)
|
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
|
||||||
assertThat(room.reportedContentCount).isEqualTo(1)
|
assertThat(room.reportedContentCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,13 +123,13 @@ class ReportMessagePresenterTests {
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ReportMessageEvents.Report)
|
initialState.eventSink(ReportMessageEvents.Report)
|
||||||
assertThat(awaitItem().result).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val resultState = awaitItem()
|
val resultState = awaitItem()
|
||||||
assertThat(resultState.result).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
assertThat(room.reportedContentCount).isEqualTo(1)
|
assertThat(room.reportedContentCount).isEqualTo(1)
|
||||||
|
|
||||||
resultState.eventSink(ReportMessageEvents.ClearError)
|
resultState.eventSink(ReportMessageEvents.ClearError)
|
||||||
assertThat(awaitItem().result).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
@ -52,7 +52,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
val systemNotificationsEnabled: MutableState<Boolean> = remember {
|
val systemNotificationsEnabled: MutableState<Boolean> = remember {
|
||||||
mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled())
|
mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled())
|
||||||
}
|
}
|
||||||
val changeNotificationSettingAction: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val changeNotificationSettingAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val appNotificationsEnabled = userPushStore
|
val appNotificationsEnabled = userPushStore
|
||||||
|
|
@ -87,7 +87,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
|
||||||
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
|
systemNotificationsEnabled.value = systemNotificationsEnabledProvider.notificationsEnabled()
|
||||||
}
|
}
|
||||||
NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncData.Uninitialized
|
NotificationSettingsEvents.ClearNotificationChangeError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
val oneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = true).getOrThrow()
|
val oneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = true).getOrThrow()
|
||||||
val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow()
|
val encryptedOneToOneDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = true).getOrThrow()
|
||||||
|
|
||||||
if(groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) {
|
if (groupDefaultMode != encryptedGroupDefaultMode || oneToOneDefaultMode != encryptedOneToOneDefaultMode) {
|
||||||
target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
|
target.value = NotificationSettingsState.MatrixSettings.Invalid(fixFailed = false)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
@ -168,19 +168,19 @@ class NotificationSettingsPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow()
|
notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow()
|
||||||
}.runCatchingUpdatingState(action)
|
}.runCatchingUpdatingState(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
notificationSettingsService.setCallEnabled(enabled).getOrThrow()
|
notificationSettingsService.setCallEnabled(enabled).getOrThrow()
|
||||||
}.runCatchingUpdatingState(action)
|
}.runCatchingUpdatingState(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow()
|
notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow()
|
||||||
}.runCatchingUpdatingState(action)
|
}.runCatchingUpdatingState(action)
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,18 @@
|
||||||
package io.element.android.features.preferences.impl.notifications
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class NotificationSettingsState(
|
data class NotificationSettingsState(
|
||||||
val matrixSettings: MatrixSettings,
|
val matrixSettings: MatrixSettings,
|
||||||
val appSettings: AppSettings,
|
val appSettings: AppSettings,
|
||||||
val changeNotificationSettingAction: AsyncData<Unit>,
|
val changeNotificationSettingAction: AsyncAction<Unit>,
|
||||||
val eventSink: (NotificationSettingsEvents) -> Unit,
|
val eventSink: (NotificationSettingsEvents) -> Unit,
|
||||||
) {
|
) {
|
||||||
sealed interface MatrixSettings {
|
sealed interface MatrixSettings {
|
||||||
data object Uninitialized : MatrixSettings
|
data object Uninitialized : MatrixSettings
|
||||||
data class Valid(
|
data class Valid(
|
||||||
val atRoomNotificationsEnabled: Boolean,
|
val atRoomNotificationsEnabled: Boolean,
|
||||||
val callNotificationsEnabled: Boolean,
|
val callNotificationsEnabled: Boolean,
|
||||||
|
|
@ -39,7 +39,7 @@ data class NotificationSettingsState(
|
||||||
|
|
||||||
data class Invalid(
|
data class Invalid(
|
||||||
val fixFailed: Boolean
|
val fixFailed: Boolean
|
||||||
) : MatrixSettings
|
) : MatrixSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
data class AppSettings(
|
data class AppSettings(
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,20 @@
|
||||||
package io.element.android.features.preferences.impl.notifications
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
|
|
||||||
open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> {
|
open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> {
|
||||||
override val values: Sequence<NotificationSettingsState>
|
override val values: Sequence<NotificationSettingsState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aNotificationSettingsState(),
|
aNotificationSettingsState(),
|
||||||
aNotificationSettingsState(changeNotificationSettingAction = AsyncData.Loading(Unit)),
|
aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading),
|
||||||
aNotificationSettingsState(changeNotificationSettingAction = AsyncData.Failure(Throwable("error"))),
|
aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aNotificationSettingsState(
|
fun aNotificationSettingsState(
|
||||||
changeNotificationSettingAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
changeNotificationSettingAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
) = NotificationSettingsState(
|
) = NotificationSettingsState(
|
||||||
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
|
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
|
||||||
atRoomNotificationsEnabled = true,
|
atRoomNotificationsEnabled = true,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent
|
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
|
|
@ -80,7 +80,7 @@ fun NotificationSettingsView(
|
||||||
onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
|
onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.changeNotificationSettingAction,
|
async = state.changeNotificationSettingAction,
|
||||||
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||||
onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) },
|
onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) },
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
@ -55,6 +55,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(oneToOne: Boolean): EditDefaultNotificationSettingPresenter
|
fun create(oneToOne: Boolean): EditDefaultNotificationSettingPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): EditDefaultNotificationSettingState {
|
override fun present(): EditDefaultNotificationSettingState {
|
||||||
var displayMentionsOnlyDisclaimer by remember { mutableStateOf(false) }
|
var displayMentionsOnlyDisclaimer by remember { mutableStateOf(false) }
|
||||||
|
|
@ -63,7 +64,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
mutableStateOf(null)
|
mutableStateOf(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val changeNotificationSettingAction: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val changeNotificationSettingAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
val roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>> = remember {
|
val roomsWithUserDefinedMode: MutableState<List<RoomSummary.Filled>> = remember {
|
||||||
mutableStateOf(listOf())
|
mutableStateOf(listOf())
|
||||||
|
|
@ -82,7 +83,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> {
|
is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> {
|
||||||
localCoroutineScope.setDefaultNotificationMode(event.mode, changeNotificationSettingAction)
|
localCoroutineScope.setDefaultNotificationMode(event.mode, changeNotificationSettingAction)
|
||||||
}
|
}
|
||||||
EditDefaultNotificationSettingStateEvents.ClearError -> changeNotificationSettingAction.value = AsyncData.Uninitialized
|
EditDefaultNotificationSettingStateEvents.ClearError -> changeNotificationSettingAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,17 +133,16 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
|
||||||
roomWithUserDefinedRules.contains(it.identifier()) && isOneToOne == room.isOneToOne
|
roomWithUserDefinedRules.contains(it.identifier()) && isOneToOne == room.isOneToOne
|
||||||
}
|
}
|
||||||
// locale sensitive sorting
|
// locale sensitive sorting
|
||||||
.sortedWith(compareBy(Collator.getInstance()){ it.details.name })
|
.sortedWith(compareBy(Collator.getInstance()) { it.details.name })
|
||||||
|
|
||||||
roomsWithUserDefinedMode.value = sortedSummaries
|
roomsWithUserDefinedMode.value = sortedSummaries
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
// On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did).
|
// On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did).
|
||||||
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow()
|
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow()
|
||||||
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow()
|
notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow()
|
||||||
}.runCatchingUpdatingState(action)
|
}.runCatchingUpdatingState(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.notifications.edit
|
package io.element.android.features.preferences.impl.notifications.edit
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -25,7 +25,7 @@ data class EditDefaultNotificationSettingState(
|
||||||
val isOneToOne: Boolean,
|
val isOneToOne: Boolean,
|
||||||
val mode: RoomNotificationMode?,
|
val mode: RoomNotificationMode?,
|
||||||
val roomsWithUserDefinedMode: ImmutableList<RoomSummary.Filled>,
|
val roomsWithUserDefinedMode: ImmutableList<RoomSummary.Filled>,
|
||||||
val changeNotificationSettingAction: AsyncData<Unit>,
|
val changeNotificationSettingAction: AsyncAction<Unit>,
|
||||||
val displayMentionsOnlyDisclaimer: Boolean,
|
val displayMentionsOnlyDisclaimer: Boolean,
|
||||||
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
|
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,27 +17,27 @@
|
||||||
package io.element.android.features.preferences.impl.notifications.edit
|
package io.element.android.features.preferences.impl.notifications.edit
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider<EditDefaultNotificationSettingState> {
|
open class EditDefaultNotificationSettingStateProvider : PreviewParameterProvider<EditDefaultNotificationSettingState> {
|
||||||
override val values: Sequence<EditDefaultNotificationSettingState>
|
override val values: Sequence<EditDefaultNotificationSettingState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
anEditDefaultNotificationSettingsState(),
|
anEditDefaultNotificationSettingsState(),
|
||||||
anEditDefaultNotificationSettingsState(isOneToOne = true),
|
anEditDefaultNotificationSettingsState(isOneToOne = true),
|
||||||
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncData.Loading(Unit)),
|
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading),
|
||||||
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncData.Failure(Throwable("error"))),
|
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
|
||||||
anEditDefaultNotificationSettingsState(displayMentionsOnlyDisclaimer = true),
|
anEditDefaultNotificationSettingsState(displayMentionsOnlyDisclaimer = true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun anEditDefaultNotificationSettingsState(
|
private fun anEditDefaultNotificationSettingsState(
|
||||||
isOneToOne: Boolean = false,
|
isOneToOne: Boolean = false,
|
||||||
changeNotificationSettingAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
changeNotificationSettingAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
displayMentionsOnlyDisclaimer: Boolean = false,
|
displayMentionsOnlyDisclaimer: Boolean = false,
|
||||||
) = EditDefaultNotificationSettingState(
|
) = EditDefaultNotificationSettingState(
|
||||||
isOneToOne = isOneToOne,
|
isOneToOne = isOneToOne,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ 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.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
|
@ -117,7 +117,7 @@ fun EditDefaultNotificationSettingView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.changeNotificationSettingAction,
|
async = state.changeNotificationSettingAction,
|
||||||
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||||
onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) },
|
onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) },
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import androidx.core.net.toUri
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
|
|
@ -92,7 +92,7 @@ class EditUserProfilePresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val saveAction: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val saveAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
fun handleEvents(event: EditUserProfileEvents) {
|
fun handleEvents(event: EditUserProfileEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
@ -111,7 +111,7 @@ class EditUserProfilePresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
is EditUserProfileEvents.UpdateDisplayName -> userDisplayName = event.name
|
is EditUserProfileEvents.UpdateDisplayName -> userDisplayName = event.name
|
||||||
EditUserProfileEvents.CancelSaveChanges -> saveAction.value = AsyncData.Uninitialized
|
EditUserProfileEvents.CancelSaveChanges -> saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +126,7 @@ class EditUserProfilePresenter @AssistedInject constructor(
|
||||||
displayName = userDisplayName.orEmpty(),
|
displayName = userDisplayName.orEmpty(),
|
||||||
userAvatarUrl = userAvatarUri,
|
userAvatarUrl = userAvatarUri,
|
||||||
avatarActions = avatarActions,
|
avatarActions = avatarActions,
|
||||||
saveButtonEnabled = canSave && saveAction.value !is AsyncData.Loading,
|
saveButtonEnabled = canSave && saveAction.value !is AsyncAction.Loading,
|
||||||
saveAction = saveAction.value,
|
saveAction = saveAction.value,
|
||||||
cameraPermissionState = cameraPermissionState,
|
cameraPermissionState = cameraPermissionState,
|
||||||
eventSink = { handleEvents(it) },
|
eventSink = { handleEvents(it) },
|
||||||
|
|
@ -140,7 +140,12 @@ class EditUserProfilePresenter @AssistedInject constructor(
|
||||||
// Need to call `toUri()?.toString()` to make the test pass (we mockk Uri)
|
// Need to call `toUri()?.toString()` to make the test pass (we mockk Uri)
|
||||||
avatarUri?.toString()?.trim() != currentUser.avatarUrl?.toUri()?.toString()?.trim()
|
avatarUri?.toString()?.trim() != currentUser.avatarUrl?.toUri()?.toString()?.trim()
|
||||||
|
|
||||||
private fun CoroutineScope.saveChanges(name: String?, avatarUri: Uri?, currentUser: MatrixUser, action: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.saveChanges(
|
||||||
|
name: String?,
|
||||||
|
avatarUri: Uri?,
|
||||||
|
currentUser: MatrixUser,
|
||||||
|
action: MutableState<AsyncAction<Unit>>,
|
||||||
|
) = launch {
|
||||||
val results = mutableListOf<Result<Unit>>()
|
val results = mutableListOf<Result<Unit>>()
|
||||||
suspend {
|
suspend {
|
||||||
if (!name.isNullOrEmpty() && name.trim() != currentUser.displayName.orEmpty().trim()) {
|
if (!name.isNullOrEmpty() && name.trim() != currentUser.displayName.orEmpty().trim()) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.preferences.impl.user.editprofile
|
package io.element.android.features.preferences.impl.user.editprofile
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.permissions.api.PermissionsState
|
import io.element.android.libraries.permissions.api.PermissionsState
|
||||||
|
|
@ -29,7 +29,7 @@ data class EditUserProfileState(
|
||||||
val userAvatarUrl: Uri?,
|
val userAvatarUrl: Uri?,
|
||||||
val avatarActions: ImmutableList<AvatarAction>,
|
val avatarActions: ImmutableList<AvatarAction>,
|
||||||
val saveButtonEnabled: Boolean,
|
val saveButtonEnabled: Boolean,
|
||||||
val saveAction: AsyncData<Unit>,
|
val saveAction: AsyncAction<Unit>,
|
||||||
val cameraPermissionState: PermissionsState,
|
val cameraPermissionState: PermissionsState,
|
||||||
val eventSink: (EditUserProfileEvents) -> Unit
|
val eventSink: (EditUserProfileEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.preferences.impl.user.editprofile
|
package io.element.android.features.preferences.impl.user.editprofile
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.permissions.api.aPermissionsState
|
import io.element.android.libraries.permissions.api.aPermissionsState
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -35,7 +35,7 @@ fun aEditUserProfileState() = EditUserProfileState(
|
||||||
displayName = "John Doe",
|
displayName = "John Doe",
|
||||||
userAvatarUrl = null,
|
userAvatarUrl = null,
|
||||||
avatarActions = persistentListOf(),
|
avatarActions = persistentListOf(),
|
||||||
saveAction = AsyncData.Uninitialized,
|
saveAction = AsyncAction.Uninitialized,
|
||||||
saveButtonEnabled = true,
|
saveButtonEnabled = true,
|
||||||
cameraPermissionState = aPermissionsState(showDialog = false),
|
cameraPermissionState = aPermissionsState(showDialog = false),
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.LabelledOutlinedTextField
|
import io.element.android.libraries.designsystem.components.LabelledOutlinedTextField
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -147,7 +147,7 @@ fun EditUserProfileView(
|
||||||
onActionSelected = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) }
|
onActionSelected = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.saveAction,
|
async = state.saveAction,
|
||||||
progressText = stringResource(R.string.screen_edit_profile_updating_details),
|
progressText = stringResource(R.string.screen_edit_profile_updating_details),
|
||||||
onSuccess = { onProfileEdited() },
|
onSuccess = { onProfileEdited() },
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
|
|
@ -110,7 +110,7 @@ class EditUserProfilePresenterTest {
|
||||||
AvatarAction.Remove
|
AvatarAction.Remove
|
||||||
)
|
)
|
||||||
assertThat(initialState.saveButtonEnabled).isFalse()
|
assertThat(initialState.saveButtonEnabled).isFalse()
|
||||||
assertThat(initialState.saveAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(initialState.saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -359,7 +359,7 @@ class EditUserProfilePresenterTest {
|
||||||
initialState.eventSink(EditUserProfileEvents.Save)
|
initialState.eventSink(EditUserProfileEvents.Save)
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
assertThat(matrixClient.uploadAvatarCalled).isFalse()
|
assertThat(matrixClient.uploadAvatarCalled).isFalse()
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -406,9 +406,9 @@ class EditUserProfilePresenterTest {
|
||||||
initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("foo"))
|
initialState.eventSink(EditUserProfileEvents.UpdateDisplayName("foo"))
|
||||||
initialState.eventSink(EditUserProfileEvents.Save)
|
initialState.eventSink(EditUserProfileEvents.Save)
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
initialState.eventSink(EditUserProfileEvents.CancelSaveChanges)
|
initialState.eventSink(EditUserProfileEvents.CancelSaveChanges)
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,8 +421,8 @@ class EditUserProfilePresenterTest {
|
||||||
initialState.eventSink(event)
|
initialState.eventSink(event)
|
||||||
initialState.eventSink(EditUserProfileEvents.Save)
|
initialState.eventSink(EditUserProfileEvents.Save)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import io.element.android.features.rageshake.api.reporter.BugReporter
|
||||||
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
import io.element.android.features.rageshake.api.reporter.BugReporterListener
|
||||||
import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder
|
import io.element.android.features.rageshake.api.screenshot.ScreenshotHolder
|
||||||
import io.element.android.features.rageshake.impl.logs.VectorFileLogger
|
import io.element.android.features.rageshake.impl.logs.VectorFileLogger
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -45,27 +45,27 @@ class BugReportPresenter @Inject constructor(
|
||||||
|
|
||||||
private class BugReporterUploadListener(
|
private class BugReporterUploadListener(
|
||||||
private val sendingProgress: MutableFloatState,
|
private val sendingProgress: MutableFloatState,
|
||||||
private val sendingAction: MutableState<AsyncData<Unit>>
|
private val sendingAction: MutableState<AsyncAction<Unit>>
|
||||||
) : BugReporterListener {
|
) : BugReporterListener {
|
||||||
|
|
||||||
override fun onUploadCancelled() {
|
override fun onUploadCancelled() {
|
||||||
sendingProgress.floatValue = 0f
|
sendingProgress.floatValue = 0f
|
||||||
sendingAction.value = AsyncData.Uninitialized
|
sendingAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUploadFailed(reason: String?) {
|
override fun onUploadFailed(reason: String?) {
|
||||||
sendingProgress.floatValue = 0f
|
sendingProgress.floatValue = 0f
|
||||||
sendingAction.value = AsyncData.Failure(Exception(reason))
|
sendingAction.value = AsyncAction.Failure(Exception(reason))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onProgress(progress: Int) {
|
override fun onProgress(progress: Int) {
|
||||||
sendingProgress.floatValue = progress.toFloat() / 100
|
sendingProgress.floatValue = progress.toFloat() / 100
|
||||||
sendingAction.value = AsyncData.Loading()
|
sendingAction.value = AsyncAction.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUploadSucceed() {
|
override fun onUploadSucceed() {
|
||||||
sendingProgress.floatValue = 0f
|
sendingProgress.floatValue = 0f
|
||||||
sendingAction.value = AsyncData.Success(Unit)
|
sendingAction.value = AsyncAction.Success(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,8 +83,8 @@ class BugReportPresenter @Inject constructor(
|
||||||
val sendingProgress = remember {
|
val sendingProgress = remember {
|
||||||
mutableFloatStateOf(0f)
|
mutableFloatStateOf(0f)
|
||||||
}
|
}
|
||||||
val sendingAction: MutableState<AsyncData<Unit>> = remember {
|
val sendingAction: MutableState<AsyncAction<Unit>> = remember {
|
||||||
mutableStateOf(AsyncData.Uninitialized)
|
mutableStateOf(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
val formState: MutableState<BugReportFormState> = remember {
|
val formState: MutableState<BugReportFormState> = remember {
|
||||||
mutableStateOf(BugReportFormState.Default)
|
mutableStateOf(BugReportFormState.Default)
|
||||||
|
|
@ -109,7 +109,7 @@ class BugReportPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
BugReportEvents.ClearError -> {
|
BugReportEvents.ClearError -> {
|
||||||
sendingProgress.floatValue = 0f
|
sendingProgress.floatValue = 0f
|
||||||
sendingAction.value = AsyncData.Uninitialized
|
sendingAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.rageshake.impl.bugreport
|
package io.element.android.features.rageshake.impl.bugreport
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
data class BugReportState(
|
data class BugReportState(
|
||||||
|
|
@ -25,11 +25,11 @@ data class BugReportState(
|
||||||
val hasCrashLogs: Boolean,
|
val hasCrashLogs: Boolean,
|
||||||
val screenshotUri: String?,
|
val screenshotUri: String?,
|
||||||
val sendingProgress: Float,
|
val sendingProgress: Float,
|
||||||
val sending: AsyncData<Unit>,
|
val sending: AsyncAction<Unit>,
|
||||||
val eventSink: (BugReportEvents) -> Unit
|
val eventSink: (BugReportEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
val submitEnabled =
|
val submitEnabled =
|
||||||
formState.description.length > 10 && sending !is AsyncData.Loading
|
formState.description.length > 10 && sending !is AsyncAction.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.rageshake.impl.bugreport
|
package io.element.android.features.rageshake.impl.bugreport
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
open class BugReportStateProvider : PreviewParameterProvider<BugReportState> {
|
open class BugReportStateProvider : PreviewParameterProvider<BugReportState> {
|
||||||
override val values: Sequence<BugReportState>
|
override val values: Sequence<BugReportState>
|
||||||
|
|
@ -31,8 +31,8 @@ open class BugReportStateProvider : PreviewParameterProvider<BugReportState> {
|
||||||
hasCrashLogs = true,
|
hasCrashLogs = true,
|
||||||
screenshotUri = "aUri"
|
screenshotUri = "aUri"
|
||||||
),
|
),
|
||||||
aBugReportState().copy(sending = AsyncData.Loading()),
|
aBugReportState().copy(sending = AsyncAction.Loading),
|
||||||
aBugReportState().copy(sending = AsyncData.Success(Unit)),
|
aBugReportState().copy(sending = AsyncAction.Success(Unit)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,6 +41,6 @@ fun aBugReportState() = BugReportState(
|
||||||
hasCrashLogs = false,
|
hasCrashLogs = false,
|
||||||
screenshotUri = null,
|
screenshotUri = null,
|
||||||
sendingProgress = 0F,
|
sendingProgress = 0F,
|
||||||
sending = AsyncData.Uninitialized,
|
sending = AsyncAction.Uninitialized,
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ import androidx.compose.ui.unit.dp
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
import io.element.android.features.rageshake.impl.R
|
import io.element.android.features.rageshake.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.form.textFieldState
|
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
|
||||||
|
|
@ -67,7 +67,7 @@ fun BugReportView(
|
||||||
title = stringResource(id = CommonStrings.common_report_a_problem),
|
title = stringResource(id = CommonStrings.common_report_a_problem),
|
||||||
onBackPressed = onBackPressed
|
onBackPressed = onBackPressed
|
||||||
) {
|
) {
|
||||||
val isFormEnabled = state.sending !is AsyncData.Loading
|
val isFormEnabled = state.sending !is AsyncAction.Loading
|
||||||
var descriptionFieldState by textFieldState(
|
var descriptionFieldState by textFieldState(
|
||||||
stateValue = state.formState.description
|
stateValue = state.formState.description
|
||||||
)
|
)
|
||||||
|
|
@ -150,7 +150,7 @@ fun BugReportView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.sending,
|
async = state.sending,
|
||||||
showProgressDialog = false,
|
showProgressDialog = false,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import io.element.android.features.rageshake.test.crash.A_CRASH_DATA
|
||||||
import io.element.android.features.rageshake.test.crash.FakeCrashDataStore
|
import io.element.android.features.rageshake.test.crash.FakeCrashDataStore
|
||||||
import io.element.android.features.rageshake.test.screenshot.A_SCREENSHOT_URI
|
import io.element.android.features.rageshake.test.screenshot.A_SCREENSHOT_URI
|
||||||
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
|
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
import io.element.android.libraries.matrix.test.A_FAILURE_REASON
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -53,7 +53,7 @@ class BugReportPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.hasCrashLogs).isFalse()
|
assertThat(initialState.hasCrashLogs).isFalse()
|
||||||
assertThat(initialState.formState).isEqualTo(BugReportFormState.Default)
|
assertThat(initialState.formState).isEqualTo(BugReportFormState.Default)
|
||||||
assertThat(initialState.sending).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.sending).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assertThat(initialState.screenshotUri).isNull()
|
assertThat(initialState.screenshotUri).isNull()
|
||||||
assertThat(initialState.sendingProgress).isEqualTo(0f)
|
assertThat(initialState.sendingProgress).isEqualTo(0f)
|
||||||
assertThat(initialState.submitEnabled).isFalse()
|
assertThat(initialState.submitEnabled).isFalse()
|
||||||
|
|
@ -174,13 +174,13 @@ class BugReportPresenterTest {
|
||||||
initialState.eventSink.invoke(BugReportEvents.SendBugReport)
|
initialState.eventSink.invoke(BugReportEvents.SendBugReport)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val progressState = awaitItem()
|
val progressState = awaitItem()
|
||||||
assertThat(progressState.sending).isEqualTo(AsyncData.Loading(null))
|
assertThat(progressState.sending).isEqualTo(AsyncAction.Loading)
|
||||||
assertThat(progressState.sendingProgress).isEqualTo(0f)
|
assertThat(progressState.sendingProgress).isEqualTo(0f)
|
||||||
assertThat(progressState.submitEnabled).isFalse()
|
assertThat(progressState.submitEnabled).isFalse()
|
||||||
assertThat(awaitItem().sendingProgress).isEqualTo(0.5f)
|
assertThat(awaitItem().sendingProgress).isEqualTo(0.5f)
|
||||||
assertThat(awaitItem().sendingProgress).isEqualTo(1f)
|
assertThat(awaitItem().sendingProgress).isEqualTo(1f)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
assertThat(awaitItem().sending).isEqualTo(AsyncData.Success(Unit))
|
assertThat(awaitItem().sending).isEqualTo(AsyncAction.Success(Unit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,17 +199,17 @@ class BugReportPresenterTest {
|
||||||
initialState.eventSink.invoke(BugReportEvents.SendBugReport)
|
initialState.eventSink.invoke(BugReportEvents.SendBugReport)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val progressState = awaitItem()
|
val progressState = awaitItem()
|
||||||
assertThat(progressState.sending).isEqualTo(AsyncData.Loading(null))
|
assertThat(progressState.sending).isEqualTo(AsyncAction.Loading)
|
||||||
assertThat(progressState.sendingProgress).isEqualTo(0f)
|
assertThat(progressState.sendingProgress).isEqualTo(0f)
|
||||||
assertThat(awaitItem().sendingProgress).isEqualTo(0.5f)
|
assertThat(awaitItem().sendingProgress).isEqualTo(0.5f)
|
||||||
// Failure
|
// Failure
|
||||||
assertThat(awaitItem().sendingProgress).isEqualTo(0f)
|
assertThat(awaitItem().sendingProgress).isEqualTo(0f)
|
||||||
assertThat((awaitItem().sending as AsyncData.Failure).error.message).isEqualTo(A_FAILURE_REASON)
|
assertThat((awaitItem().sending as AsyncAction.Failure).error.message).isEqualTo(A_FAILURE_REASON)
|
||||||
// Reset failure
|
// Reset failure
|
||||||
initialState.eventSink.invoke(BugReportEvents.ClearError)
|
initialState.eventSink.invoke(BugReportEvents.ClearError)
|
||||||
val lastItem = awaitItem()
|
val lastItem = awaitItem()
|
||||||
assertThat(lastItem.sendingProgress).isEqualTo(0f)
|
assertThat(lastItem.sendingProgress).isEqualTo(0f)
|
||||||
assertThat(lastItem.sending).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(lastItem.sending).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,12 +228,12 @@ class BugReportPresenterTest {
|
||||||
initialState.eventSink.invoke(BugReportEvents.SendBugReport)
|
initialState.eventSink.invoke(BugReportEvents.SendBugReport)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val progressState = awaitItem()
|
val progressState = awaitItem()
|
||||||
assertThat(progressState.sending).isEqualTo(AsyncData.Loading(null))
|
assertThat(progressState.sending).isEqualTo(AsyncAction.Loading)
|
||||||
assertThat(progressState.sendingProgress).isEqualTo(0f)
|
assertThat(progressState.sendingProgress).isEqualTo(0f)
|
||||||
assertThat(awaitItem().sendingProgress).isEqualTo(0.5f)
|
assertThat(awaitItem().sendingProgress).isEqualTo(0.5f)
|
||||||
// Cancelled
|
// Cancelled
|
||||||
assertThat(awaitItem().sendingProgress).isEqualTo(0f)
|
assertThat(awaitItem().sendingProgress).isEqualTo(0f)
|
||||||
assertThat(awaitItem().sending).isEqualTo(AsyncData.Uninitialized)
|
assertThat(awaitItem().sending).isEqualTo(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
|
|
@ -116,7 +116,7 @@ class RoomDetailsEditPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val saveAction: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val saveAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
fun handleEvents(event: RoomDetailsEditEvents) {
|
fun handleEvents(event: RoomDetailsEditEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|
@ -136,7 +136,7 @@ class RoomDetailsEditPresenter @Inject constructor(
|
||||||
|
|
||||||
is RoomDetailsEditEvents.UpdateRoomName -> roomName = event.name
|
is RoomDetailsEditEvents.UpdateRoomName -> roomName = event.name
|
||||||
is RoomDetailsEditEvents.UpdateRoomTopic -> roomTopic = event.topic.takeUnless { it.isEmpty() }
|
is RoomDetailsEditEvents.UpdateRoomTopic -> roomTopic = event.topic.takeUnless { it.isEmpty() }
|
||||||
RoomDetailsEditEvents.CancelSaveChanges -> saveAction.value = AsyncData.Uninitialized
|
RoomDetailsEditEvents.CancelSaveChanges -> saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +156,12 @@ class RoomDetailsEditPresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.saveChanges(name: String, topic: String?, avatarUri: Uri?, action: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.saveChanges(
|
||||||
|
name: String,
|
||||||
|
topic: String?,
|
||||||
|
avatarUri: Uri?,
|
||||||
|
action: MutableState<AsyncAction<Unit>>,
|
||||||
|
) = launch {
|
||||||
val results = mutableListOf<Result<Unit>>()
|
val results = mutableListOf<Result<Unit>>()
|
||||||
suspend {
|
suspend {
|
||||||
if (topic.orEmpty().trim() != room.topic.orEmpty().trim()) {
|
if (topic.orEmpty().trim() != room.topic.orEmpty().trim()) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.roomdetails.impl.edit
|
package io.element.android.features.roomdetails.impl.edit
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
import io.element.android.libraries.permissions.api.PermissionsState
|
import io.element.android.libraries.permissions.api.PermissionsState
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
@ -32,7 +32,7 @@ data class RoomDetailsEditState(
|
||||||
val canChangeAvatar: Boolean,
|
val canChangeAvatar: Boolean,
|
||||||
val avatarActions: ImmutableList<AvatarAction>,
|
val avatarActions: ImmutableList<AvatarAction>,
|
||||||
val saveButtonEnabled: Boolean,
|
val saveButtonEnabled: Boolean,
|
||||||
val saveAction: AsyncData<Unit>,
|
val saveAction: AsyncAction<Unit>,
|
||||||
val cameraPermissionState: PermissionsState,
|
val cameraPermissionState: PermissionsState,
|
||||||
val eventSink: (RoomDetailsEditEvents) -> Unit
|
val eventSink: (RoomDetailsEditEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ package io.element.android.features.roomdetails.impl.edit
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.permissions.api.aPermissionsState
|
import io.element.android.libraries.permissions.api.aPermissionsState
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
|
@ -30,8 +30,8 @@ open class RoomDetailsEditStateProvider : PreviewParameterProvider<RoomDetailsEd
|
||||||
aRoomDetailsEditState().copy(roomAvatarUrl = Uri.parse("example://uri")),
|
aRoomDetailsEditState().copy(roomAvatarUrl = Uri.parse("example://uri")),
|
||||||
aRoomDetailsEditState().copy(canChangeName = true, canChangeTopic = false, canChangeAvatar = true, saveButtonEnabled = false),
|
aRoomDetailsEditState().copy(canChangeName = true, canChangeTopic = false, canChangeAvatar = true, saveButtonEnabled = false),
|
||||||
aRoomDetailsEditState().copy(canChangeName = false, canChangeTopic = true, canChangeAvatar = false, saveButtonEnabled = false),
|
aRoomDetailsEditState().copy(canChangeName = false, canChangeTopic = true, canChangeAvatar = false, saveButtonEnabled = false),
|
||||||
aRoomDetailsEditState().copy(saveAction = AsyncData.Loading()),
|
aRoomDetailsEditState().copy(saveAction = AsyncAction.Loading),
|
||||||
aRoomDetailsEditState().copy(saveAction = AsyncData.Failure(Throwable("Whelp")))
|
aRoomDetailsEditState().copy(saveAction = AsyncAction.Failure(Throwable("Whelp")))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ fun aRoomDetailsEditState() = RoomDetailsEditState(
|
||||||
canChangeAvatar = true,
|
canChangeAvatar = true,
|
||||||
avatarActions = persistentListOf(),
|
avatarActions = persistentListOf(),
|
||||||
saveButtonEnabled = true,
|
saveButtonEnabled = true,
|
||||||
saveAction = AsyncData.Uninitialized,
|
saveAction = AsyncAction.Uninitialized,
|
||||||
cameraPermissionState = aPermissionsState(showDialog = false),
|
cameraPermissionState = aPermissionsState(showDialog = false),
|
||||||
eventSink = {}
|
eventSink = {}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
|
@ -171,7 +171,7 @@ fun RoomDetailsEditView(
|
||||||
onActionSelected = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) }
|
onActionSelected = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) }
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.saveAction,
|
async = state.saveAction,
|
||||||
progressText = stringResource(R.string.screen_room_details_updating_room),
|
progressText = stringResource(R.string.screen_room_details_updating_room),
|
||||||
onSuccess = { onRoomEdited() },
|
onSuccess = { onRoomEdited() },
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.anvilannotations.ContributesNode
|
import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
||||||
|
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.NodeInputs
|
import io.element.android.libraries.architecture.NodeInputs
|
||||||
import io.element.android.libraries.architecture.inputs
|
import io.element.android.libraries.architecture.inputs
|
||||||
|
|
@ -95,7 +96,7 @@ class RoomMemberDetailsNode @AssistedInject constructor(
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
|
|
||||||
LaunchedEffect(state.startDmActionState) {
|
LaunchedEffect(state.startDmActionState) {
|
||||||
if (state.startDmActionState is AsyncData.Success) {
|
if (state.startDmActionState is AsyncAction.Success) {
|
||||||
onStartDM(state.startDmActionState.data)
|
onStartDM(state.startDmActionState.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import io.element.android.features.createroom.api.StartDMAction
|
import io.element.android.features.createroom.api.StartDMAction
|
||||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog
|
||||||
|
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.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
|
|
@ -56,7 +57,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
|
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
|
||||||
val roomMember by room.getRoomMemberAsState(roomMemberId)
|
val roomMember by room.getRoomMemberAsState(roomMemberId)
|
||||||
val startDmActionState: MutableState<AsyncData<RoomId>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
// the room member is not really live...
|
// the room member is not really live...
|
||||||
val isBlocked: MutableState<AsyncData<Boolean>> = remember(roomMember) {
|
val isBlocked: MutableState<AsyncData<Boolean>> = remember(roomMember) {
|
||||||
val isIgnored = roomMember?.isIgnored
|
val isIgnored = roomMember?.isIgnored
|
||||||
|
|
@ -98,7 +99,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomMemberDetailsEvents.ClearStartDMState -> {
|
RoomMemberDetailsEvents.ClearStartDMState -> {
|
||||||
startDmActionState.value = AsyncData.Uninitialized
|
startDmActionState.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.members.details
|
package io.element.android.features.roomdetails.impl.members.details
|
||||||
|
|
||||||
|
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.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ data class RoomMemberDetailsState(
|
||||||
val userName: String?,
|
val userName: String?,
|
||||||
val avatarUrl: String?,
|
val avatarUrl: String?,
|
||||||
val isBlocked: AsyncData<Boolean>,
|
val isBlocked: AsyncData<Boolean>,
|
||||||
val startDmActionState: AsyncData<RoomId>,
|
val startDmActionState: AsyncAction<RoomId>,
|
||||||
val displayConfirmationDialog: ConfirmationDialog?,
|
val displayConfirmationDialog: ConfirmationDialog?,
|
||||||
val isCurrentUser: Boolean,
|
val isCurrentUser: Boolean,
|
||||||
val eventSink: (RoomMemberDetailsEvents) -> Unit
|
val eventSink: (RoomMemberDetailsEvents) -> Unit
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.roomdetails.impl.members.details
|
package io.element.android.features.roomdetails.impl.members.details
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberDetailsState> {
|
open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberDetailsState> {
|
||||||
|
|
@ -28,7 +29,7 @@ open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberD
|
||||||
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
||||||
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
||||||
aRoomMemberDetailsState().copy(isBlocked = AsyncData.Loading(true)),
|
aRoomMemberDetailsState().copy(isBlocked = AsyncData.Loading(true)),
|
||||||
aRoomMemberDetailsState().copy(startDmActionState = AsyncData.Loading()),
|
aRoomMemberDetailsState().copy(startDmActionState = AsyncAction.Loading),
|
||||||
// Add other states here
|
// Add other states here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +39,7 @@ fun aRoomMemberDetailsState() = RoomMemberDetailsState(
|
||||||
userName = "Daniel",
|
userName = "Daniel",
|
||||||
avatarUrl = null,
|
avatarUrl = null,
|
||||||
isBlocked = AsyncData.Success(false),
|
isBlocked = AsyncData.Success(false),
|
||||||
startDmActionState = AsyncData.Uninitialized,
|
startDmActionState = AsyncAction.Uninitialized,
|
||||||
displayConfirmationDialog = null,
|
displayConfirmationDialog = null,
|
||||||
isCurrentUser = false,
|
isCurrentUser = false,
|
||||||
eventSink = {},
|
eventSink = {},
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
||||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
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.list.ListItemContent
|
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||||
|
|
@ -88,7 +88,7 @@ fun RoomMemberDetailsView(
|
||||||
BlockUserSection(state)
|
BlockUserSection(state)
|
||||||
BlockUserDialogs(state)
|
BlockUserDialogs(state)
|
||||||
}
|
}
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.startDmActionState,
|
async = state.startDmActionState,
|
||||||
progressText = stringResource(CommonStrings.common_starting_chat),
|
progressText = stringResource(CommonStrings.common_starting_chat),
|
||||||
onSuccess = onDMStarted,
|
onSuccess = onDMStarted,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.runtime.setValue
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
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.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
|
|
@ -61,8 +62,8 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor(
|
||||||
mutableStateOf(null)
|
mutableStateOf(null)
|
||||||
}
|
}
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
val setNotificationSettingAction: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val setNotificationSettingAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val restoreDefaultAction: MutableState<AsyncData<Unit>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
val restoreDefaultAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
val roomNotificationSettings: MutableState<AsyncData<RoomNotificationSettings>> = remember {
|
val roomNotificationSettings: MutableState<AsyncData<RoomNotificationSettings>> = remember {
|
||||||
mutableStateOf(AsyncData.Uninitialized)
|
mutableStateOf(AsyncData.Uninitialized)
|
||||||
|
|
@ -103,7 +104,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor(
|
||||||
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
|
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
|
||||||
} else {
|
} else {
|
||||||
defaultRoomNotificationMode.value?.let {
|
defaultRoomNotificationMode.value?.let {
|
||||||
localCoroutineScope.setRoomNotificationMode(it, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction)
|
localCoroutineScope.setRoomNotificationMode(it, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,10 +112,10 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor(
|
||||||
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
|
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
|
||||||
}
|
}
|
||||||
RoomNotificationSettingsEvents.ClearSetNotificationError -> {
|
RoomNotificationSettingsEvents.ClearSetNotificationError -> {
|
||||||
setNotificationSettingAction.value = AsyncData.Uninitialized
|
setNotificationSettingAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
RoomNotificationSettingsEvents.ClearRestoreDefaultError -> {
|
RoomNotificationSettingsEvents.ClearRestoreDefaultError -> {
|
||||||
restoreDefaultAction.value = AsyncData.Uninitialized
|
restoreDefaultAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +170,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor(
|
||||||
mode: RoomNotificationMode,
|
mode: RoomNotificationMode,
|
||||||
pendingModeState: MutableState<RoomNotificationMode?>,
|
pendingModeState: MutableState<RoomNotificationMode?>,
|
||||||
pendingDefaultState: MutableState<Boolean?>,
|
pendingDefaultState: MutableState<Boolean?>,
|
||||||
action: MutableState<AsyncData<Unit>>
|
action: MutableState<AsyncAction<Unit>>
|
||||||
) = launch {
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
pendingModeState.value = mode
|
pendingModeState.value = mode
|
||||||
|
|
@ -184,7 +185,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.restoreDefaultRoomNotificationMode(
|
private fun CoroutineScope.restoreDefaultRoomNotificationMode(
|
||||||
action: MutableState<AsyncData<Unit>>,
|
action: MutableState<AsyncAction<Unit>>,
|
||||||
pendingDefaultState: MutableState<Boolean?>
|
pendingDefaultState: MutableState<Boolean?>
|
||||||
) = launch {
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.roomdetails.impl.notificationsettings
|
package io.element.android.features.roomdetails.impl.notificationsettings
|
||||||
|
|
||||||
|
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.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
|
|
@ -27,8 +28,8 @@ data class RoomNotificationSettingsState(
|
||||||
val pendingRoomNotificationMode: RoomNotificationMode?,
|
val pendingRoomNotificationMode: RoomNotificationMode?,
|
||||||
val pendingSetDefault: Boolean?,
|
val pendingSetDefault: Boolean?,
|
||||||
val defaultRoomNotificationMode: RoomNotificationMode?,
|
val defaultRoomNotificationMode: RoomNotificationMode?,
|
||||||
val setNotificationSettingAction: AsyncData<Unit>,
|
val setNotificationSettingAction: AsyncAction<Unit>,
|
||||||
val restoreDefaultAction: AsyncData<Unit>,
|
val restoreDefaultAction: AsyncAction<Unit>,
|
||||||
val displayMentionsOnlyDisclaimer: Boolean,
|
val displayMentionsOnlyDisclaimer: Boolean,
|
||||||
val eventSink: (RoomNotificationSettingsEvents) -> Unit
|
val eventSink: (RoomNotificationSettingsEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.roomdetails.impl.notificationsettings
|
package io.element.android.features.roomdetails.impl.notificationsettings
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
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.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
|
|
@ -26,17 +27,17 @@ internal class RoomNotificationSettingsStateProvider : PreviewParameterProvider<
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRoomNotificationSettingsState(),
|
aRoomNotificationSettingsState(),
|
||||||
aRoomNotificationSettingsState(isDefault = false),
|
aRoomNotificationSettingsState(isDefault = false),
|
||||||
aRoomNotificationSettingsState(setNotificationSettingAction = AsyncData.Loading(Unit)),
|
aRoomNotificationSettingsState(setNotificationSettingAction = AsyncAction.Loading),
|
||||||
aRoomNotificationSettingsState(setNotificationSettingAction = AsyncData.Failure(Throwable("error"))),
|
aRoomNotificationSettingsState(setNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
|
||||||
aRoomNotificationSettingsState(restoreDefaultAction = AsyncData.Loading(Unit)),
|
aRoomNotificationSettingsState(restoreDefaultAction = AsyncAction.Loading),
|
||||||
aRoomNotificationSettingsState(restoreDefaultAction = AsyncData.Failure(Throwable("error"))),
|
aRoomNotificationSettingsState(restoreDefaultAction = AsyncAction.Failure(Throwable("error"))),
|
||||||
aRoomNotificationSettingsState(displayMentionsOnlyDisclaimer = true)
|
aRoomNotificationSettingsState(displayMentionsOnlyDisclaimer = true)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aRoomNotificationSettingsState(
|
private fun aRoomNotificationSettingsState(
|
||||||
isDefault: Boolean = true,
|
isDefault: Boolean = true,
|
||||||
setNotificationSettingAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
setNotificationSettingAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
restoreDefaultAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
restoreDefaultAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
displayMentionsOnlyDisclaimer: Boolean = false,
|
displayMentionsOnlyDisclaimer: Boolean = false,
|
||||||
): RoomNotificationSettingsState {
|
): RoomNotificationSettingsState {
|
||||||
return RoomNotificationSettingsState(
|
return RoomNotificationSettingsState(
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.libraries.core.bool.orTrue
|
import io.element.android.libraries.core.bool.orTrue
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
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.preferences.PreferenceCategory
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||||
|
|
@ -157,14 +157,14 @@ private fun RoomSpecificNotificationSettingsView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.setNotificationSettingAction,
|
async = state.setNotificationSettingAction,
|
||||||
onSuccess = {},
|
onSuccess = {},
|
||||||
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||||
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
|
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.restoreDefaultAction,
|
async = state.restoreDefaultAction,
|
||||||
onSuccess = {},
|
onSuccess = {},
|
||||||
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.element.android.features.roomdetails.impl.notificationsettings
|
package io.element.android.features.roomdetails.impl.notificationsettings
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
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.matrix.api.room.RoomNotificationMode
|
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||||
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
|
||||||
|
|
@ -35,8 +36,8 @@ internal class UserDefinedRoomNotificationSettingsStateProvider : PreviewParamet
|
||||||
pendingRoomNotificationMode = null,
|
pendingRoomNotificationMode = null,
|
||||||
pendingSetDefault = null,
|
pendingSetDefault = null,
|
||||||
defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
defaultRoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
|
||||||
setNotificationSettingAction = AsyncData.Uninitialized,
|
setNotificationSettingAction = AsyncAction.Uninitialized,
|
||||||
restoreDefaultAction = AsyncData.Uninitialized,
|
restoreDefaultAction = AsyncAction.Uninitialized,
|
||||||
displayMentionsOnlyDisclaimer = false,
|
displayMentionsOnlyDisclaimer = false,
|
||||||
eventSink = { },
|
eventSink = { },
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.roomdetails.impl.R
|
import io.element.android.features.roomdetails.impl.R
|
||||||
import io.element.android.libraries.core.bool.orTrue
|
import io.element.android.libraries.core.bool.orTrue
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
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.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
|
||||||
|
|
@ -81,14 +81,14 @@ fun UserDefinedRoomNotificationSettingsView(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.setNotificationSettingAction,
|
async = state.setNotificationSettingAction,
|
||||||
onSuccess = {},
|
onSuccess = {},
|
||||||
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||||
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
|
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
|
||||||
)
|
)
|
||||||
|
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.restoreDefaultAction,
|
async = state.restoreDefaultAction,
|
||||||
onSuccess = { onBackPressed() },
|
onSuccess = { onBackPressed() },
|
||||||
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.roomdetails.aMatrixRoom
|
import io.element.android.features.roomdetails.aMatrixRoom
|
||||||
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditEvents
|
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditEvents
|
||||||
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditPresenter
|
import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditPresenter
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.StateEventType
|
import io.element.android.libraries.matrix.api.room.StateEventType
|
||||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||||
|
|
@ -108,7 +108,7 @@ class RoomDetailsEditPresenterTest {
|
||||||
AvatarAction.Remove
|
AvatarAction.Remove
|
||||||
)
|
)
|
||||||
assertThat(initialState.saveButtonEnabled).isFalse()
|
assertThat(initialState.saveButtonEnabled).isFalse()
|
||||||
assertThat(initialState.saveAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(initialState.saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,7 +533,7 @@ class RoomDetailsEditPresenterTest {
|
||||||
assertThat(room.newAvatarData).isNull()
|
assertThat(room.newAvatarData).isNull()
|
||||||
assertThat(room.removedAvatar).isFalse()
|
assertThat(room.removedAvatar).isFalse()
|
||||||
|
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -594,10 +594,10 @@ class RoomDetailsEditPresenterTest {
|
||||||
initialState.eventSink(RoomDetailsEditEvents.Save)
|
initialState.eventSink(RoomDetailsEditEvents.Save)
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
|
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
|
|
||||||
initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges)
|
initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges)
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -613,8 +613,8 @@ class RoomDetailsEditPresenterTest {
|
||||||
initialState.eventSink(RoomDetailsEditEvents.Save)
|
initialState.eventSink(RoomDetailsEditEvents.Save)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||||
|
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.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||||
|
|
@ -204,14 +204,14 @@ class RoomMemberDetailsPresenterTests {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.startDmActionState).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||||
val startDMSuccessResult = AsyncData.Success(A_ROOM_ID)
|
val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID)
|
||||||
val startDMFailureResult = AsyncData.Failure<RoomId>(A_THROWABLE)
|
val startDMFailureResult = AsyncAction.Failure(A_THROWABLE)
|
||||||
|
|
||||||
// Failure
|
// Failure
|
||||||
startDMAction.givenExecuteResult(startDMFailureResult)
|
startDMAction.givenExecuteResult(startDMFailureResult)
|
||||||
initialState.eventSink(RoomMemberDetailsEvents.StartDM)
|
initialState.eventSink(RoomMemberDetailsEvents.StartDM)
|
||||||
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.startDmActionState).isEqualTo(startDMFailureResult)
|
assertThat(state.startDmActionState).isEqualTo(startDMFailureResult)
|
||||||
state.eventSink(RoomMemberDetailsEvents.ClearStartDMState)
|
state.eventSink(RoomMemberDetailsEvents.ClearStartDMState)
|
||||||
|
|
@ -220,10 +220,10 @@ class RoomMemberDetailsPresenterTests {
|
||||||
// Success
|
// Success
|
||||||
startDMAction.givenExecuteResult(startDMSuccessResult)
|
startDMAction.givenExecuteResult(startDMSuccessResult)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.startDmActionState).isEqualTo(AsyncData.Uninitialized)
|
assertThat(state.startDmActionState).isEqualTo(AsyncAction.Uninitialized)
|
||||||
state.eventSink(RoomMemberDetailsEvents.StartDM)
|
state.eventSink(RoomMemberDetailsEvents.StartDM)
|
||||||
}
|
}
|
||||||
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult)
|
assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import io.element.android.features.securebackup.impl.loggerTagDisable
|
import io.element.android.features.securebackup.impl.loggerTagDisable
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
|
@ -44,7 +44,7 @@ class SecureBackupDisablePresenter @Inject constructor(
|
||||||
override fun present(): SecureBackupDisableState {
|
override fun present(): SecureBackupDisableState {
|
||||||
val backupState by encryptionService.backupStateStateFlow.collectAsState()
|
val backupState by encryptionService.backupStateStateFlow.collectAsState()
|
||||||
Timber.tag(loggerTagDisable.value).d("backupState: $backupState")
|
Timber.tag(loggerTagDisable.value).d("backupState: $backupState")
|
||||||
val disableAction = remember { mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized) }
|
val disableAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
fun handleEvents(event: SecureBackupDisableEvents) {
|
fun handleEvents(event: SecureBackupDisableEvents) {
|
||||||
|
|
@ -57,7 +57,7 @@ class SecureBackupDisablePresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
SecureBackupDisableEvents.DismissDialogs -> {
|
SecureBackupDisableEvents.DismissDialogs -> {
|
||||||
showDialog = false
|
showDialog = false
|
||||||
disableAction.value = AsyncData.Uninitialized
|
disableAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +71,7 @@ class SecureBackupDisablePresenter @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CoroutineScope.disableBackup(disableAction: MutableState<AsyncData<Unit>>) = launch {
|
private fun CoroutineScope.disableBackup(disableAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
Timber.tag(loggerTagDisable.value).d("Calling encryptionService.disableRecovery()")
|
Timber.tag(loggerTagDisable.value).d("Calling encryptionService.disableRecovery()")
|
||||||
encryptionService.disableRecovery().getOrThrow()
|
encryptionService.disableRecovery().getOrThrow()
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
|
|
||||||
package io.element.android.features.securebackup.impl.disable
|
package io.element.android.features.securebackup.impl.disable
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||||
|
|
||||||
data class SecureBackupDisableState(
|
data class SecureBackupDisableState(
|
||||||
val backupState: BackupState,
|
val backupState: BackupState,
|
||||||
val disableAction: AsyncData<Unit>,
|
val disableAction: AsyncAction<Unit>,
|
||||||
val showConfirmationDialog: Boolean,
|
val showConfirmationDialog: Boolean,
|
||||||
val appName: String,
|
val appName: String,
|
||||||
val eventSink: (SecureBackupDisableEvents) -> Unit
|
val eventSink: (SecureBackupDisableEvents) -> Unit
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
package io.element.android.features.securebackup.impl.disable
|
package io.element.android.features.securebackup.impl.disable
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||||
|
|
||||||
open class SecureBackupDisableStateProvider : PreviewParameterProvider<SecureBackupDisableState> {
|
open class SecureBackupDisableStateProvider : PreviewParameterProvider<SecureBackupDisableState> {
|
||||||
|
|
@ -25,15 +25,15 @@ open class SecureBackupDisableStateProvider : PreviewParameterProvider<SecureBac
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aSecureBackupDisableState(),
|
aSecureBackupDisableState(),
|
||||||
aSecureBackupDisableState(showConfirmationDialog = true),
|
aSecureBackupDisableState(showConfirmationDialog = true),
|
||||||
aSecureBackupDisableState(disableAction = AsyncData.Loading()),
|
aSecureBackupDisableState(disableAction = AsyncAction.Loading),
|
||||||
aSecureBackupDisableState(disableAction = AsyncData.Failure(Exception("Failed to disable"))),
|
aSecureBackupDisableState(disableAction = AsyncAction.Failure(Exception("Failed to disable"))),
|
||||||
// Add other states here
|
// Add other states here
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aSecureBackupDisableState(
|
fun aSecureBackupDisableState(
|
||||||
backupState: BackupState = BackupState.UNKNOWN,
|
backupState: BackupState = BackupState.UNKNOWN,
|
||||||
disableAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
disableAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
showConfirmationDialog: Boolean = false,
|
showConfirmationDialog: Boolean = false,
|
||||||
) = SecureBackupDisableState(
|
) = SecureBackupDisableState(
|
||||||
backupState = backupState,
|
backupState = backupState,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,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.securebackup.impl.R
|
import io.element.android.features.securebackup.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.designsystem.atomic.pages.FlowStepPage
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
|
@ -53,7 +54,7 @@ fun SecureBackupDisableView(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(state.disableAction) {
|
LaunchedEffect(state.disableAction) {
|
||||||
if (state.disableAction is AsyncData.Success) {
|
if (state.disableAction is AsyncAction.Success) {
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +73,7 @@ fun SecureBackupDisableView(
|
||||||
onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = true)) },
|
onConfirm = { state.eventSink.invoke(SecureBackupDisableEvents.DisableBackup(force = true)) },
|
||||||
onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) },
|
onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) },
|
||||||
)
|
)
|
||||||
} else if (state.disableAction is AsyncData.Failure) {
|
} else if (state.disableAction is AsyncAction.Failure) {
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
content = state.disableAction.error.let { it.message ?: it.toString() },
|
content = state.disableAction.error.let { it.message ?: it.toString() },
|
||||||
onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) },
|
onDismiss = { state.eventSink.invoke(SecureBackupDisableEvents.DismissDialogs) },
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
||||||
import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools
|
import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
|
@ -46,14 +46,14 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor(
|
||||||
var recoveryKey by rememberSaveable {
|
var recoveryKey by rememberSaveable {
|
||||||
mutableStateOf("")
|
mutableStateOf("")
|
||||||
}
|
}
|
||||||
val submitAction = remember {
|
val submitAction: MutableState<AsyncAction<Unit>> = remember {
|
||||||
mutableStateOf<AsyncData<Unit>>(AsyncData.Uninitialized)
|
mutableStateOf(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvents(event: SecureBackupEnterRecoveryKeyEvents) {
|
fun handleEvents(event: SecureBackupEnterRecoveryKeyEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
SecureBackupEnterRecoveryKeyEvents.ClearDialog -> {
|
SecureBackupEnterRecoveryKeyEvents.ClearDialog -> {
|
||||||
submitAction.value = AsyncData.Uninitialized
|
submitAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
is SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange -> {
|
is SecureBackupEnterRecoveryKeyEvents.OnRecoveryKeyChange -> {
|
||||||
val previousRecoveryKey = recoveryKey
|
val previousRecoveryKey = recoveryKey
|
||||||
|
|
@ -86,7 +86,7 @@ class SecureBackupEnterRecoveryKeyPresenter @Inject constructor(
|
||||||
|
|
||||||
private fun CoroutineScope.submitRecoveryKey(
|
private fun CoroutineScope.submitRecoveryKey(
|
||||||
recoveryKey: String,
|
recoveryKey: String,
|
||||||
action: MutableState<AsyncData<Unit>>
|
action: MutableState<AsyncAction<Unit>>
|
||||||
) = launch {
|
) = launch {
|
||||||
suspend {
|
suspend {
|
||||||
encryptionService.recover(recoveryKey).getOrThrow()
|
encryptionService.recover(recoveryKey).getOrThrow()
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,12 @@
|
||||||
package io.element.android.features.securebackup.impl.enter
|
package io.element.android.features.securebackup.impl.enter
|
||||||
|
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
// Do not use default value, so no member get forgotten in the presenters.
|
// Do not use default value, so no member get forgotten in the presenters.
|
||||||
data class SecureBackupEnterRecoveryKeyState(
|
data class SecureBackupEnterRecoveryKeyState(
|
||||||
val recoveryKeyViewState: RecoveryKeyViewState,
|
val recoveryKeyViewState: RecoveryKeyViewState,
|
||||||
val isSubmitEnabled: Boolean,
|
val isSubmitEnabled: Boolean,
|
||||||
val submitAction: AsyncData<Unit>,
|
val submitAction: AsyncAction<Unit>,
|
||||||
val eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit
|
val eventSink: (SecureBackupEnterRecoveryKeyEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
||||||
import io.element.android.features.securebackup.impl.setup.views.aFormattedRecoveryKey
|
import io.element.android.features.securebackup.impl.setup.views.aFormattedRecoveryKey
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
open class SecureBackupEnterRecoveryKeyStateProvider : PreviewParameterProvider<SecureBackupEnterRecoveryKeyState> {
|
open class SecureBackupEnterRecoveryKeyStateProvider : PreviewParameterProvider<SecureBackupEnterRecoveryKeyState> {
|
||||||
|
|
@ -27,15 +28,15 @@ open class SecureBackupEnterRecoveryKeyStateProvider : PreviewParameterProvider<
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aSecureBackupEnterRecoveryKeyState(recoveryKey = ""),
|
aSecureBackupEnterRecoveryKeyState(recoveryKey = ""),
|
||||||
aSecureBackupEnterRecoveryKeyState(),
|
aSecureBackupEnterRecoveryKeyState(),
|
||||||
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncData.Loading()),
|
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Loading),
|
||||||
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncData.Failure(Exception("A Failure"))),
|
aSecureBackupEnterRecoveryKeyState(submitAction = AsyncAction.Failure(Exception("A Failure"))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun aSecureBackupEnterRecoveryKeyState(
|
fun aSecureBackupEnterRecoveryKeyState(
|
||||||
recoveryKey: String = aFormattedRecoveryKey(),
|
recoveryKey: String = aFormattedRecoveryKey(),
|
||||||
isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(),
|
isSubmitEnabled: Boolean = recoveryKey.isNotEmpty(),
|
||||||
submitAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
submitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
) = SecureBackupEnterRecoveryKeyState(
|
) = SecureBackupEnterRecoveryKeyState(
|
||||||
recoveryKeyViewState = RecoveryKeyViewState(
|
recoveryKeyViewState = RecoveryKeyViewState(
|
||||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.features.securebackup.impl.R
|
import io.element.android.features.securebackup.impl.R
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
import io.element.android.libraries.designsystem.atomic.pages.FlowStepPage
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
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
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
|
@ -43,7 +43,7 @@ fun SecureBackupEnterRecoveryKeyView(
|
||||||
onBackClicked: () -> Unit,
|
onBackClicked: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = state.submitAction,
|
async = state.submitAction,
|
||||||
onSuccess = { onDone() },
|
onSuccess = { onDone() },
|
||||||
showProgressDialog = false,
|
showProgressDialog = false,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import app.cash.molecule.RecompositionMode
|
||||||
import app.cash.molecule.moleculeFlow
|
import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
|
|
@ -42,7 +42,7 @@ class SecureBackupDisablePresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN)
|
assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN)
|
||||||
assertThat(initialState.disableAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.disableAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assertThat(initialState.showConfirmationDialog).isFalse()
|
assertThat(initialState.showConfirmationDialog).isFalse()
|
||||||
assertThat(initialState.appName).isEqualTo("Element")
|
assertThat(initialState.appName).isEqualTo("Element")
|
||||||
}
|
}
|
||||||
|
|
@ -80,9 +80,9 @@ class SecureBackupDisablePresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.showConfirmationDialog).isFalse()
|
assertThat(loadingState.showConfirmationDialog).isFalse()
|
||||||
assertThat(loadingState.disableAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.disableAction).isEqualTo(AsyncData.Success(Unit))
|
assertThat(finalState.disableAction).isEqualTo(AsyncAction.Success(Unit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,12 +106,12 @@ class SecureBackupDisablePresenterTest {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.showConfirmationDialog).isFalse()
|
assertThat(loadingState.showConfirmationDialog).isFalse()
|
||||||
assertThat(loadingState.disableAction).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(loadingState.disableAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||||
val errorState = awaitItem()
|
val errorState = awaitItem()
|
||||||
assertThat(errorState.disableAction).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(errorState.disableAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
errorState.eventSink(SecureBackupDisableEvents.DismissDialogs)
|
errorState.eventSink(SecureBackupDisableEvents.DismissDialogs)
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.disableAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(finalState.disableAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyUserStory
|
||||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyViewState
|
||||||
import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools
|
import io.element.android.features.securebackup.impl.tools.RecoveryKeyTools
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||||
|
|
@ -44,7 +44,7 @@ class SecureBackupEnterRecoveryKeyPresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isSubmitEnabled).isFalse()
|
assertThat(initialState.isSubmitEnabled).isFalse()
|
||||||
assertThat(initialState.submitAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.submitAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assertThat(initialState.recoveryKeyViewState).isEqualTo(
|
assertThat(initialState.recoveryKeyViewState).isEqualTo(
|
||||||
RecoveryKeyViewState(
|
RecoveryKeyViewState(
|
||||||
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
recoveryKeyUserStory = RecoveryKeyUserStory.Enter,
|
||||||
|
|
@ -76,22 +76,22 @@ class SecureBackupEnterRecoveryKeyPresenterTest {
|
||||||
encryptionService.givenRecoverFailure(AN_EXCEPTION)
|
encryptionService.givenRecoverFailure(AN_EXCEPTION)
|
||||||
withRecoveryKeyState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit)
|
withRecoveryKeyState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||||
val loadingState = awaitItem()
|
val loadingState = awaitItem()
|
||||||
assertThat(loadingState.submitAction).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState.submitAction).isEqualTo(AsyncAction.Loading)
|
||||||
assertThat(loadingState.isSubmitEnabled).isFalse()
|
assertThat(loadingState.isSubmitEnabled).isFalse()
|
||||||
val errorState = awaitItem()
|
val errorState = awaitItem()
|
||||||
assertThat(errorState.submitAction).isEqualTo(AsyncData.Failure<Unit>(AN_EXCEPTION))
|
assertThat(errorState.submitAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION))
|
||||||
assertThat(errorState.isSubmitEnabled).isFalse()
|
assertThat(errorState.isSubmitEnabled).isFalse()
|
||||||
errorState.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog)
|
errorState.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog)
|
||||||
val clearedState = awaitItem()
|
val clearedState = awaitItem()
|
||||||
assertThat(clearedState.submitAction).isEqualTo(AsyncData.Uninitialized)
|
assertThat(clearedState.submitAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assertThat(clearedState.isSubmitEnabled).isTrue()
|
assertThat(clearedState.isSubmitEnabled).isTrue()
|
||||||
encryptionService.givenRecoverFailure(null)
|
encryptionService.givenRecoverFailure(null)
|
||||||
clearedState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit)
|
clearedState.eventSink(SecureBackupEnterRecoveryKeyEvents.Submit)
|
||||||
val loadingState2 = awaitItem()
|
val loadingState2 = awaitItem()
|
||||||
assertThat(loadingState2.submitAction).isEqualTo(AsyncData.Loading<Unit>())
|
assertThat(loadingState2.submitAction).isEqualTo(AsyncAction.Loading)
|
||||||
assertThat(loadingState2.isSubmitEnabled).isFalse()
|
assertThat(loadingState2.isSubmitEnabled).isFalse()
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.submitAction).isEqualTo(AsyncData.Success(Unit))
|
assertThat(finalState.submitAction).isEqualTo(AsyncAction.Success(Unit))
|
||||||
assertThat(finalState.isSubmitEnabled).isFalse()
|
assertThat(finalState.isSubmitEnabled).isFalse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,15 @@
|
||||||
package io.element.android.libraries.designsystem.components.async
|
package io.element.android.libraries.designsystem.components.async
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
open class AsyncProvider : PreviewParameterProvider<AsyncData<Unit>> {
|
open class AsyncActionProvider : PreviewParameterProvider<AsyncAction<Unit>> {
|
||||||
override val values: Sequence<AsyncData<Unit>>
|
override val values: Sequence<AsyncAction<Unit>>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
AsyncData.Uninitialized,
|
AsyncAction.Uninitialized,
|
||||||
AsyncData.Loading(),
|
AsyncAction.Confirming,
|
||||||
AsyncData.Failure(Exception("An error occurred")),
|
AsyncAction.Loading,
|
||||||
AsyncData.Success(Unit),
|
AsyncAction.Failure(Exception("An error occurred")),
|
||||||
|
AsyncAction.Success(Unit),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -18,40 +18,55 @@ package io.element.android.libraries.designsystem.components.async
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogDefaults
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogDefaults
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||||
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
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render an Async object.
|
* Render an AsyncAction object.
|
||||||
* - If Success, invoke the callback [onSuccess], only once.
|
* - If Success, invoke the callback [onSuccess], only once.
|
||||||
* - If Failure, display a dialog with the error, which can be transformed, using [errorMessage]. When
|
* - If Failure, display a dialog with the error, which can be transformed, using [errorMessage]. When
|
||||||
* closed, [onErrorDismiss] will be invoked. If [onRetry] is not null, a retry button will be displayed.
|
* closed, [onErrorDismiss] will be invoked. If [onRetry] is not null, a retry button will be displayed.
|
||||||
* - When loading, display a loading dialog, if [showProgressDialog] is true, with on optional [progressText].
|
* - When loading, display a loading dialog, if [showProgressDialog] is true, with on optional [progressText].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> AsyncView(
|
fun <T> AsyncActionView(
|
||||||
async: AsyncData<T>,
|
async: AsyncAction<T>,
|
||||||
onSuccess: (T) -> Unit,
|
onSuccess: (T) -> Unit,
|
||||||
onErrorDismiss: () -> Unit,
|
onErrorDismiss: () -> Unit,
|
||||||
|
showConfirmationDialog: Boolean = false,
|
||||||
|
confirmationText: String? = null,
|
||||||
|
confirmationSubmit: String? = null,
|
||||||
|
onConfirmation: (() -> Unit)? = null,
|
||||||
showProgressDialog: Boolean = true,
|
showProgressDialog: Boolean = true,
|
||||||
progressText: String? = null,
|
progressText: String? = null,
|
||||||
errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title },
|
errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title },
|
||||||
errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() },
|
errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() },
|
||||||
onRetry: (() -> Unit)? = null,
|
onRetry: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = async,
|
async = async,
|
||||||
onSuccess = onSuccess,
|
onSuccess = onSuccess,
|
||||||
onErrorDismiss = onErrorDismiss,
|
onErrorDismiss = onErrorDismiss,
|
||||||
|
confirmingDialog = {
|
||||||
|
if (showConfirmationDialog) {
|
||||||
|
AsyncActionViewDefaults.ConfirmationDialog(
|
||||||
|
confirmationText = confirmationText,
|
||||||
|
confirmationSubmit = confirmationSubmit,
|
||||||
|
onConfirmation = onConfirmation,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
progressDialog = {
|
progressDialog = {
|
||||||
if (showProgressDialog) {
|
if (showProgressDialog) {
|
||||||
AsyncViewDefaults.ProgressDialog(progressText)
|
AsyncActionViewDefaults.ProgressDialog(progressText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
errorTitle = errorTitle,
|
errorTitle = errorTitle,
|
||||||
|
|
@ -61,19 +76,21 @@ fun <T> AsyncView(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> AsyncView(
|
fun <T> AsyncActionView(
|
||||||
async: AsyncData<T>,
|
async: AsyncAction<T>,
|
||||||
onSuccess: (T) -> Unit,
|
onSuccess: (T) -> Unit,
|
||||||
onErrorDismiss: () -> Unit,
|
onErrorDismiss: () -> Unit,
|
||||||
progressDialog: @Composable () -> Unit = { AsyncViewDefaults.ProgressDialog() },
|
confirmingDialog: @Composable () -> Unit = { AsyncActionViewDefaults.ConfirmationDialog() },
|
||||||
|
progressDialog: @Composable () -> Unit = { AsyncActionViewDefaults.ProgressDialog() },
|
||||||
errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title },
|
errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title },
|
||||||
errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() },
|
errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() },
|
||||||
onRetry: (() -> Unit)? = null,
|
onRetry: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
when (async) {
|
when (async) {
|
||||||
AsyncData.Uninitialized -> Unit
|
AsyncAction.Uninitialized -> Unit
|
||||||
is AsyncData.Loading -> progressDialog()
|
AsyncAction.Confirming -> confirmingDialog()
|
||||||
is AsyncData.Failure -> {
|
is AsyncAction.Loading -> progressDialog()
|
||||||
|
is AsyncAction.Failure -> {
|
||||||
if (onRetry == null) {
|
if (onRetry == null) {
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
title = errorTitle(async.error),
|
title = errorTitle(async.error),
|
||||||
|
|
@ -89,7 +106,7 @@ fun <T> AsyncView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is AsyncData.Success -> {
|
is AsyncAction.Success -> {
|
||||||
LaunchedEffect(async) {
|
LaunchedEffect(async) {
|
||||||
onSuccess(async.data)
|
onSuccess(async.data)
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +114,23 @@ fun <T> AsyncView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object AsyncViewDefaults {
|
object AsyncActionViewDefaults {
|
||||||
|
@Composable
|
||||||
|
fun ConfirmationDialog(
|
||||||
|
confirmationText: String? = null,
|
||||||
|
confirmationSubmit: String? = null,
|
||||||
|
onConfirmation: (() -> Unit)? = null,
|
||||||
|
onDismiss: (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog(
|
||||||
|
content = confirmationText.orEmpty(),
|
||||||
|
onSubmitClicked = onConfirmation ?: {},
|
||||||
|
onDismiss = onDismiss ?: {},
|
||||||
|
submitText = confirmationSubmit ?: stringResource(CommonStrings.action_continue),
|
||||||
|
cancelText = stringResource(CommonStrings.action_cancel),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProgressDialog(progressText: String? = null) {
|
fun ProgressDialog(progressText: String? = null) {
|
||||||
ProgressDialog(
|
ProgressDialog(
|
||||||
|
|
@ -108,10 +141,10 @@ object AsyncViewDefaults {
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AsyncViewPreview(
|
internal fun AsyncActionViewPreview(
|
||||||
@PreviewParameter(AsyncProvider::class) async: AsyncData<Unit>,
|
@PreviewParameter(AsyncActionProvider::class) async: AsyncAction<Unit>,
|
||||||
) = ElementPreview {
|
) = ElementPreview {
|
||||||
AsyncView(
|
AsyncActionView(
|
||||||
async = async,
|
async = async,
|
||||||
onSuccess = {},
|
onSuccess = {},
|
||||||
onErrorDismiss = {},
|
onErrorDismiss = {},
|
||||||
Loading…
Add table
Add a link
Reference in a new issue