Rename AsyncView to AsyncActionView

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

View file

@ -23,7 +23,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
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.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.MatrixClient
@ -52,7 +52,7 @@ class NotificationSettingsPresenter @Inject constructor(
val systemNotificationsEnabled: MutableState<Boolean> = remember {
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 appNotificationsEnabled = userPushStore
@ -87,7 +87,7 @@ class NotificationSettingsPresenter @Inject constructor(
NotificationSettingsEvents.RefreshSystemNotificationsEnabled -> {
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 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)
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 {
notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow()
}.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 {
notificationSettingsService.setCallEnabled(enabled).getOrThrow()
}.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 {
notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow()
}.runCatchingUpdatingState(action)

View file

@ -17,18 +17,18 @@
package io.element.android.features.preferences.impl.notifications
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
@Immutable
data class NotificationSettingsState(
val matrixSettings: MatrixSettings,
val appSettings: AppSettings,
val changeNotificationSettingAction: AsyncData<Unit>,
val changeNotificationSettingAction: AsyncAction<Unit>,
val eventSink: (NotificationSettingsEvents) -> Unit,
) {
sealed interface MatrixSettings {
data object Uninitialized : MatrixSettings
data object Uninitialized : MatrixSettings
data class Valid(
val atRoomNotificationsEnabled: Boolean,
val callNotificationsEnabled: Boolean,
@ -39,7 +39,7 @@ data class NotificationSettingsState(
data class Invalid(
val fixFailed: Boolean
) : MatrixSettings
) : MatrixSettings
}
data class AppSettings(

View file

@ -17,20 +17,20 @@
package io.element.android.features.preferences.impl.notifications
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
open class NotificationSettingsStateProvider : PreviewParameterProvider<NotificationSettingsState> {
override val values: Sequence<NotificationSettingsState>
get() = sequenceOf(
aNotificationSettingsState(),
aNotificationSettingsState(changeNotificationSettingAction = AsyncData.Loading(Unit)),
aNotificationSettingsState(changeNotificationSettingAction = AsyncData.Failure(Throwable("error"))),
aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading),
aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
)
}
fun aNotificationSettingsState(
changeNotificationSettingAction: AsyncData<Unit> = AsyncData.Uninitialized,
changeNotificationSettingAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
) = NotificationSettingsState(
matrixSettings = NotificationSettingsState.MatrixSettings.Valid(
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.libraries.androidutils.system.startNotificationSettingsIntent
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.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
@ -80,7 +80,7 @@ fun NotificationSettingsView(
onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) },
)
}
AsyncView(
AsyncActionView(
async = state.changeNotificationSettingAction,
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) },

View file

@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
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.runCatchingUpdatingState
import io.element.android.libraries.matrix.api.MatrixClient
@ -55,6 +55,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
interface Factory {
fun create(oneToOne: Boolean): EditDefaultNotificationSettingPresenter
}
@Composable
override fun present(): EditDefaultNotificationSettingState {
var displayMentionsOnlyDisclaimer by remember { mutableStateOf(false) }
@ -63,7 +64,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
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 {
mutableStateOf(listOf())
@ -82,7 +83,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor(
is EditDefaultNotificationSettingStateEvents.SetNotificationMode -> {
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
}
// locale sensitive sorting
.sortedWith(compareBy(Collator.getInstance()){ it.details.name })
.sortedWith(compareBy(Collator.getInstance()) { it.details.name })
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 {
// 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 = false, mode = mode, isOneToOne = isOneToOne).getOrThrow()
}.runCatchingUpdatingState(action)
}
}

View file

@ -16,7 +16,7 @@
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.roomlist.RoomSummary
import kotlinx.collections.immutable.ImmutableList
@ -25,7 +25,7 @@ data class EditDefaultNotificationSettingState(
val isOneToOne: Boolean,
val mode: RoomNotificationMode?,
val roomsWithUserDefinedMode: ImmutableList<RoomSummary.Filled>,
val changeNotificationSettingAction: AsyncData<Unit>,
val changeNotificationSettingAction: AsyncAction<Unit>,
val displayMentionsOnlyDisclaimer: Boolean,
val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit,
)

View file

@ -17,27 +17,27 @@
package io.element.android.features.preferences.impl.notifications.edit
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.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import kotlinx.collections.immutable.persistentListOf
open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider<EditDefaultNotificationSettingState> {
open class EditDefaultNotificationSettingStateProvider : PreviewParameterProvider<EditDefaultNotificationSettingState> {
override val values: Sequence<EditDefaultNotificationSettingState>
get() = sequenceOf(
anEditDefaultNotificationSettingsState(),
anEditDefaultNotificationSettingsState(isOneToOne = true),
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncData.Loading(Unit)),
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncData.Failure(Throwable("error"))),
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading),
anEditDefaultNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))),
anEditDefaultNotificationSettingsState(displayMentionsOnlyDisclaimer = true),
)
}
private fun anEditDefaultNotificationSettingsState(
isOneToOne: Boolean = false,
changeNotificationSettingAction: AsyncData<Unit> = AsyncData.Uninitialized,
changeNotificationSettingAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
displayMentionsOnlyDisclaimer: Boolean = false,
) = EditDefaultNotificationSettingState(
isOneToOne = isOneToOne,

View file

@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
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.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@ -117,7 +117,7 @@ fun EditDefaultNotificationSettingView(
}
}
}
AsyncView(
AsyncActionView(
async = state.changeNotificationSettingAction,
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) },

View file

@ -31,7 +31,7 @@ import androidx.core.net.toUri
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
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.runCatchingUpdatingState
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()
fun handleEvents(event: EditUserProfileEvents) {
when (event) {
@ -111,7 +111,7 @@ class EditUserProfilePresenter @AssistedInject constructor(
}
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(),
userAvatarUrl = userAvatarUri,
avatarActions = avatarActions,
saveButtonEnabled = canSave && saveAction.value !is AsyncData.Loading,
saveButtonEnabled = canSave && saveAction.value !is AsyncAction.Loading,
saveAction = saveAction.value,
cameraPermissionState = cameraPermissionState,
eventSink = { handleEvents(it) },
@ -140,7 +140,12 @@ class EditUserProfilePresenter @AssistedInject constructor(
// Need to call `toUri()?.toString()` to make the test pass (we mockk Uri)
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>>()
suspend {
if (!name.isNullOrEmpty() && name.trim() != currentUser.displayName.orEmpty().trim()) {

View file

@ -17,7 +17,7 @@
package io.element.android.features.preferences.impl.user.editprofile
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.ui.media.AvatarAction
import io.element.android.libraries.permissions.api.PermissionsState
@ -29,7 +29,7 @@ data class EditUserProfileState(
val userAvatarUrl: Uri?,
val avatarActions: ImmutableList<AvatarAction>,
val saveButtonEnabled: Boolean,
val saveAction: AsyncData<Unit>,
val saveAction: AsyncAction<Unit>,
val cameraPermissionState: PermissionsState,
val eventSink: (EditUserProfileEvents) -> Unit
)

View file

@ -17,7 +17,7 @@
package io.element.android.features.preferences.impl.user.editprofile
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.permissions.api.aPermissionsState
import kotlinx.collections.immutable.persistentListOf
@ -35,7 +35,7 @@ fun aEditUserProfileState() = EditUserProfileState(
displayName = "John Doe",
userAvatarUrl = null,
avatarActions = persistentListOf(),
saveAction = AsyncData.Uninitialized,
saveAction = AsyncAction.Uninitialized,
saveButtonEnabled = true,
cameraPermissionState = aPermissionsState(showDialog = false),
eventSink = {}

View file

@ -43,7 +43,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.preferences.impl.R
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.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview
@ -147,7 +147,7 @@ fun EditUserProfileView(
onActionSelected = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) }
)
AsyncView(
AsyncActionView(
async = state.saveAction,
progressText = stringResource(R.string.screen_edit_profile_updating_details),
onSuccess = { onProfileEdited() },

View file

@ -21,7 +21,7 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
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.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
@ -110,7 +110,7 @@ class EditUserProfilePresenterTest {
AvatarAction.Remove
)
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)
skipItems(2)
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.Save)
skipItems(2)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
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(EditUserProfileEvents.Save)
skipItems(1)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Loading::class.java)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncData.Failure::class.java)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java)
}
}