Rename AsyncView to AsyncActionView

This commit is contained in:
Benoit Marty 2024-01-04 16:49:22 +01:00
parent bd91dd64e2
commit d78ce21939
71 changed files with 369 additions and 315 deletions

View file

@ -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>>)
} }

View file

@ -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)
} }
} }
} }

View file

@ -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) }

View file

@ -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
) { ) {

View file

@ -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 = { },
) )

View file

@ -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) },

View file

@ -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
} }
} }

View file

@ -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,
) )

View file

@ -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(),
) )

View file

@ -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) },

View file

@ -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(

View file

@ -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)
} }
} }
} }

View file

@ -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)
} }

View file

@ -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
} }

View file

@ -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
} }
} }

View file

@ -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
) )

View file

@ -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 = {}
) )

View file

@ -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) }

View file

@ -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)
} }
} }
} }

View file

@ -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 }

View file

@ -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
) )

View file

@ -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,

View file

@ -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() },

View file

@ -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)
} }
} }

View file

@ -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)

View file

@ -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(

View file

@ -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,

View file

@ -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) },

View file

@ -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)
} }
} }

View file

@ -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,
) )

View file

@ -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,

View file

@ -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) },

View file

@ -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()) {

View file

@ -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
) )

View file

@ -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 = {}

View file

@ -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() },

View file

@ -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)
} }
} }

View file

@ -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
} }
} }
} }

View file

@ -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

View file

@ -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 = {}
) )

View file

@ -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 = {

View file

@ -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)
} }
} }
} }

View file

@ -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()) {

View file

@ -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
) )

View file

@ -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 = {}
) )

View file

@ -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() },

View file

@ -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)
} }
} }

View file

@ -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
} }
} }
} }

View file

@ -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

View file

@ -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 = {},

View file

@ -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,

View file

@ -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 {

View file

@ -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
) )

View file

@ -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(

View file

@ -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) },

View file

@ -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 = { },
), ),

View file

@ -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) },

View file

@ -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)
} }
} }

View file

@ -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)
} }

View file

@ -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()

View file

@ -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

View file

@ -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,

View file

@ -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) },

View file

@ -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()

View file

@ -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
) )

View file

@ -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,

View file

@ -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,

View file

@ -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)
} }
} }

View file

@ -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()
} }
} }

View file

@ -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),
) )
} }

View file

@ -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 = {},