Merge pull request #1738 from vector-im/feature/bma/AsyncView
Introduce AsyncView to avoid repeating ourselves
This commit is contained in:
@@ -36,7 +36,6 @@ import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -49,13 +48,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.createroom.impl.components.RoomPrivacyOption
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@@ -84,12 +81,6 @@ fun ConfigureRoomView(
|
||||
initialValue = ModalBottomSheetValue.Hidden,
|
||||
)
|
||||
|
||||
if (state.createRoomAction is Async.Success) {
|
||||
LaunchedEffect(state.createRoomAction) {
|
||||
onRoomCreated(state.createRoomAction.data)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAvatarClicked() {
|
||||
focusManager.clearFocus()
|
||||
coroutineScope.launch {
|
||||
@@ -158,21 +149,14 @@ fun ConfigureRoomView(
|
||||
onActionSelected = { state.eventSink(ConfigureRoomEvents.HandleAvatarAction(it)) }
|
||||
)
|
||||
|
||||
when (state.createRoomAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog(text = stringResource(CommonStrings.common_creating_room))
|
||||
}
|
||||
|
||||
is Async.Failure -> {
|
||||
RetryDialog(
|
||||
content = stringResource(R.string.screen_create_room_error_creating_room),
|
||||
onDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) },
|
||||
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) },
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.createRoomAction,
|
||||
progressText = stringResource(CommonStrings.common_creating_room),
|
||||
onSuccess = { onRoomCreated(it) },
|
||||
errorMessage = { stringResource(R.string.screen_create_room_error_creating_room) },
|
||||
onRetry = { state.eventSink(ConfigureRoomEvents.CreateRoom(state.config)) },
|
||||
onErrorDismiss = { state.eventSink(ConfigureRoomEvents.CancelCreateRoom) },
|
||||
)
|
||||
|
||||
PermissionsView(
|
||||
state = state.cameraPermissionState,
|
||||
|
||||
@@ -30,7 +30,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -38,12 +37,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.createroom.impl.components.UserListView
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
@@ -64,12 +61,6 @@ fun CreateRoomRootView(
|
||||
onOpenDM: (RoomId) -> Unit = {},
|
||||
onInviteFriendsClicked: () -> Unit = {},
|
||||
) {
|
||||
if (state.startDmAction is Async.Success) {
|
||||
LaunchedEffect(state.startDmAction) {
|
||||
onOpenDM(state.startDmAction.data)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
topBar = {
|
||||
@@ -102,26 +93,19 @@ fun CreateRoomRootView(
|
||||
}
|
||||
}
|
||||
|
||||
when (state.startDmAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog(text = stringResource(id = CommonStrings.common_starting_chat))
|
||||
}
|
||||
|
||||
is Async.Failure -> {
|
||||
RetryDialog(
|
||||
content = stringResource(id = R.string.screen_start_chat_error_starting_chat),
|
||||
onDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
||||
onRetry = {
|
||||
state.userListState.selectedUsers.firstOrNull()
|
||||
?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
|
||||
// Cancel start DM if there is no more selected user (should not happen)
|
||||
?: state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.startDmAction,
|
||||
progressText = stringResource(CommonStrings.common_starting_chat),
|
||||
onSuccess = { onOpenDM(it) },
|
||||
errorMessage = { stringResource(R.string.screen_start_chat_error_starting_chat) },
|
||||
onRetry = {
|
||||
state.userListState.selectedUsers.firstOrNull()
|
||||
?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) }
|
||||
// Cancel start DM if there is no more selected user (should not happen)
|
||||
?: state.eventSink(CreateRoomRootEvents.CancelStartDM)
|
||||
},
|
||||
onErrorDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) },
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
@@ -25,17 +25,14 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.element.android.features.login.impl.oidc.OidcUrlParser
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
@Composable
|
||||
fun OidcView(
|
||||
@@ -79,21 +76,11 @@ fun OidcView(
|
||||
}
|
||||
)
|
||||
|
||||
when (state.requestState) {
|
||||
Async.Uninitialized -> Unit
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
content = state.requestState.error.toString(),
|
||||
onDismiss = { state.eventSink(OidcEvents.ClearError) }
|
||||
)
|
||||
}
|
||||
is Async.Loading -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
is Async.Success -> onNavigateBack()
|
||||
}
|
||||
AsyncView(
|
||||
async = state.requestState,
|
||||
onSuccess = { onNavigateBack() },
|
||||
onErrorDismiss = { state.eventSink(OidcEvents.ClearError) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -42,10 +41,10 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
@@ -64,21 +63,13 @@ fun ReportMessageView(
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val isSending = state.result is Async.Loading
|
||||
when (state.result) {
|
||||
is Async.Success -> {
|
||||
LaunchedEffect(state.result) {
|
||||
onBackClicked()
|
||||
}
|
||||
return
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
content = stringResource(CommonStrings.error_unknown),
|
||||
onDismiss = { state.eventSink(ReportMessageEvents.ClearError) }
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.result,
|
||||
showProgressDialog = false,
|
||||
onSuccess = { onBackClicked() },
|
||||
errorMessage = { stringResource(CommonStrings.error_unknown) },
|
||||
onErrorDismiss = { state.eventSink(ReportMessageEvents.ClearError) }
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
||||
@@ -24,9 +24,8 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
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
|
||||
@@ -79,19 +78,12 @@ fun NotificationSettingsView(
|
||||
// onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) },
|
||||
)
|
||||
}
|
||||
when (state.changeNotificationSettingAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog()
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
title = stringResource(CommonStrings.dialog_title_error),
|
||||
content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode),
|
||||
onDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) },
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.changeNotificationSettingAction,
|
||||
errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||
onErrorDismiss = { state.eventSink(NotificationSettingsEvents.ClearNotificationChangeError) },
|
||||
onSuccess = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,20 +22,18 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
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
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@@ -46,11 +44,10 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun EditDefaultNotificationSettingView(
|
||||
state: EditDefaultNotificationSettingState,
|
||||
openRoomNotificationSettings:(roomId: RoomId) -> Unit,
|
||||
openRoomNotificationSettings: (roomId: RoomId) -> Unit,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
val title = if (state.isOneToOne) {
|
||||
CommonStrings.screen_notification_settings_direct_chats
|
||||
} else {
|
||||
@@ -118,21 +115,15 @@ fun EditDefaultNotificationSettingView(
|
||||
}
|
||||
}
|
||||
}
|
||||
when (state.changeNotificationSettingAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog()
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
title = stringResource(CommonStrings.dialog_title_error),
|
||||
content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode),
|
||||
onDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) },
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.changeNotificationSettingAction,
|
||||
errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||
onErrorDismiss = { state.eventSink(EditDefaultNotificationSettingStateEvents.ClearError) },
|
||||
onSuccess = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun EditDefaultNotificationSettingViewPreview(
|
||||
|
||||
@@ -31,7 +31,6 @@ import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -43,14 +42,12 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
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.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.LabelledOutlinedTextField
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
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.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@@ -150,24 +147,14 @@ fun EditUserProfileView(
|
||||
onActionSelected = { state.eventSink(EditUserProfileEvents.HandleAvatarAction(it)) }
|
||||
)
|
||||
|
||||
when (state.saveAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog(text = stringResource(R.string.screen_edit_profile_updating_details))
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
title = stringResource(R.string.screen_edit_profile_error_title),
|
||||
content = stringResource(R.string.screen_edit_profile_error),
|
||||
onDismiss = { state.eventSink(EditUserProfileEvents.CancelSaveChanges) },
|
||||
)
|
||||
}
|
||||
is Async.Success -> {
|
||||
LaunchedEffect(state.saveAction) {
|
||||
onProfileEdited()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.saveAction,
|
||||
progressText = stringResource(R.string.screen_edit_profile_updating_details),
|
||||
onSuccess = { onProfileEdited() },
|
||||
errorTitle = { stringResource(R.string.screen_edit_profile_error_title) },
|
||||
errorMessage = { stringResource(R.string.screen_edit_profile_error) },
|
||||
onErrorDismiss = { state.eventSink(EditUserProfileEvents.CancelSaveChanges) },
|
||||
)
|
||||
}
|
||||
PermissionsView(
|
||||
state = state.cameraPermissionState,
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -39,16 +38,15 @@ import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import io.element.android.features.rageshake.impl.R
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
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.PreferenceRow
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.LogCompositions
|
||||
@@ -63,13 +61,6 @@ fun BugReportView(
|
||||
) {
|
||||
LogCompositions(tag = "Rageshake", msg = "Root")
|
||||
val eventSink = state.eventSink
|
||||
if (state.sending is Async.Success) {
|
||||
LaunchedEffect(state.sending) {
|
||||
eventSink(BugReportEvents.ResetAll)
|
||||
onDone()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Box(modifier = modifier) {
|
||||
PreferencePage(
|
||||
@@ -151,6 +142,7 @@ fun BugReportView(
|
||||
text = stringResource(id = CommonStrings.action_send),
|
||||
onClick = { eventSink(BugReportEvents.SendBugReport) },
|
||||
enabled = state.submitEnabled,
|
||||
showProgress = state.sending.isLoading(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 24.dp, bottom = 16.dp)
|
||||
@@ -158,19 +150,15 @@ fun BugReportView(
|
||||
}
|
||||
}
|
||||
|
||||
when (state.sending) {
|
||||
is Async.Loading -> {
|
||||
// Indeterminate indicator, to avoid the freeze effect if the connection takes time to initialize.
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
is Async.Failure -> ErrorDialog(
|
||||
content = state.sending.error.toString(),
|
||||
onDismiss = { state.eventSink(BugReportEvents.ClearError) }
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.sending,
|
||||
showProgressDialog = false,
|
||||
onSuccess = {
|
||||
eventSink(BugReportEvents.ResetAll)
|
||||
onDone()
|
||||
},
|
||||
onErrorDismiss = { eventSink(BugReportEvents.ClearError) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ import androidx.compose.material.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusManager
|
||||
@@ -47,14 +46,12 @@ import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.components.LabelledTextField
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
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.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@@ -174,26 +171,13 @@ fun RoomDetailsEditView(
|
||||
onActionSelected = { state.eventSink(RoomDetailsEditEvents.HandleAvatarAction(it)) }
|
||||
)
|
||||
|
||||
when (state.saveAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog(text = stringResource(R.string.screen_room_details_updating_room))
|
||||
}
|
||||
|
||||
is Async.Failure -> {
|
||||
ErrorDialog(
|
||||
content = stringResource(R.string.screen_room_details_edition_error),
|
||||
onDismiss = { state.eventSink(RoomDetailsEditEvents.CancelSaveChanges) },
|
||||
)
|
||||
}
|
||||
|
||||
is Async.Success -> {
|
||||
LaunchedEffect(state.saveAction) {
|
||||
onRoomEdited()
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.saveAction,
|
||||
progressText = stringResource(R.string.screen_room_details_updating_room),
|
||||
onSuccess = { onRoomEdited() },
|
||||
errorMessage = { stringResource(R.string.screen_room_details_edition_error) },
|
||||
onErrorDismiss = { state.eventSink(RoomDetailsEditEvents.CancelSaveChanges) }
|
||||
)
|
||||
|
||||
PermissionsView(
|
||||
state = state.cameraPermissionState,
|
||||
|
||||
@@ -32,9 +32,8 @@ import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.bool.orTrue
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
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.PreferenceSwitch
|
||||
@@ -49,7 +48,6 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
|
||||
@Composable
|
||||
fun RoomNotificationSettingsView(
|
||||
state: RoomNotificationSettingsState,
|
||||
@@ -57,7 +55,7 @@ fun RoomNotificationSettingsView(
|
||||
onShowGlobalNotifications: () -> Unit = {},
|
||||
onBackPressed: () -> Unit = {},
|
||||
) {
|
||||
if(state.showUserDefinedSettingStyle) {
|
||||
if (state.showUserDefinedSettingStyle) {
|
||||
UserDefinedRoomNotificationSettingsView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
@@ -117,7 +115,7 @@ private fun RoomSpecificNotificationSettingsView(
|
||||
ClickableText(
|
||||
text = text,
|
||||
onClick = {
|
||||
onShowGlobalNotifications()
|
||||
onShowGlobalNotifications()
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, bottom = 16.dp, end = 16.dp),
|
||||
@@ -127,7 +125,7 @@ private fun RoomSpecificNotificationSettingsView(
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
)
|
||||
if(state.defaultRoomNotificationMode != null){
|
||||
if (state.defaultRoomNotificationMode != null) {
|
||||
val defaultModeTitle = when (state.defaultRoomNotificationMode) {
|
||||
RoomNotificationMode.ALL_MESSAGES -> stringResource(id = R.string.screen_room_notification_settings_mode_all_messages)
|
||||
RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY -> {
|
||||
@@ -150,29 +148,24 @@ private fun RoomSpecificNotificationSettingsView(
|
||||
enabled = !state.displayIsDefault.orTrue(),
|
||||
onOptionSelected = {
|
||||
state.eventSink(RoomNotificationSettingsEvents.RoomNotificationModeChanged(it.mode))
|
||||
},)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
when (state.setNotificationSettingAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog()
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearSetNotificationError)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.setNotificationSettingAction,
|
||||
onSuccess = {},
|
||||
errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
|
||||
)
|
||||
|
||||
when (state.restoreDefaultAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog()
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearRestoreDefaultError)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.restoreDefaultAction,
|
||||
onSuccess = {},
|
||||
errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.notificationsettings
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun ShowChangeNotificationSettingError(state: RoomNotificationSettingsState, event: RoomNotificationSettingsEvents) {
|
||||
ErrorDialog(
|
||||
title = stringResource(CommonStrings.dialog_title_error),
|
||||
content = stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode),
|
||||
onDismiss = { state.eventSink(event) },
|
||||
)
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -32,9 +31,8 @@ import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.bool.orTrue
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -43,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun UserDefinedRoomNotificationSettingsView(
|
||||
@@ -86,30 +85,19 @@ fun UserDefinedRoomNotificationSettingsView(
|
||||
}
|
||||
)
|
||||
|
||||
when (state.setNotificationSettingAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog()
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearSetNotificationError)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.setNotificationSettingAction,
|
||||
onSuccess = {},
|
||||
errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) },
|
||||
)
|
||||
|
||||
when (state.restoreDefaultAction) {
|
||||
is Async.Loading -> {
|
||||
ProgressDialog()
|
||||
}
|
||||
is Async.Failure -> {
|
||||
ShowChangeNotificationSettingError(state, RoomNotificationSettingsEvents.ClearRestoreDefaultError)
|
||||
}
|
||||
is Async.Success -> {
|
||||
LaunchedEffect(state.restoreDefaultAction) {
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
AsyncView(
|
||||
async = state.restoreDefaultAction,
|
||||
onSuccess = { onBackPressed() },
|
||||
errorMessage = { stringResource(CommonStrings.screen_notification_settings_edit_failed_updating_default_mode) },
|
||||
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,19 +20,17 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.securebackup.impl.R
|
||||
import io.element.android.features.securebackup.impl.setup.views.RecoveryKeyView
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncView
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
@@ -48,19 +46,12 @@ fun SecureBackupEnterRecoveryKeyView(
|
||||
onBackClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (state.submitAction) {
|
||||
Async.Uninitialized -> Unit
|
||||
is Async.Failure -> ErrorDialog(
|
||||
content = state.submitAction.error.message ?: state.submitAction.error.toString(),
|
||||
onDismiss = {
|
||||
state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog)
|
||||
}
|
||||
)
|
||||
is Async.Loading -> Unit
|
||||
is Async.Success -> LaunchedEffect(state.submitAction) {
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
AsyncView(
|
||||
async = state.submitAction,
|
||||
onSuccess = { onDone() },
|
||||
showProgressDialog = false,
|
||||
onErrorDismiss = { state.eventSink(SecureBackupEnterRecoveryKeyEvents.ClearDialog) },
|
||||
)
|
||||
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
|
||||
@@ -39,6 +39,7 @@ android {
|
||||
// Should not be there, but this is a POC
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.vanniktech.blurhash)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.components.async
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.Async
|
||||
|
||||
open class AsyncProvider : PreviewParameterProvider<Async<Unit>> {
|
||||
override val values: Sequence<Async<Unit>>
|
||||
get() = sequenceOf(
|
||||
Async.Uninitialized,
|
||||
Async.Loading(),
|
||||
Async.Failure(Exception("An error occurred")),
|
||||
Async.Success(Unit),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.components.async
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.libraries.architecture.Async
|
||||
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.ErrorDialogDefaults
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
/**
|
||||
* Render an Async object.
|
||||
* - If Success, invoke the callback [onSuccess], only once.
|
||||
* - 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.
|
||||
* - When loading, display a loading dialog, if [showProgressDialog] is true, with on optional [progressText].
|
||||
*/
|
||||
@Composable
|
||||
fun <T> AsyncView(
|
||||
async: Async<T>,
|
||||
onSuccess: (T) -> Unit,
|
||||
onErrorDismiss: () -> Unit,
|
||||
showProgressDialog: Boolean = true,
|
||||
progressText: String? = null,
|
||||
errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title },
|
||||
errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() },
|
||||
onRetry: (() -> Unit)? = null,
|
||||
) {
|
||||
AsyncView(
|
||||
async = async,
|
||||
onSuccess = onSuccess,
|
||||
onErrorDismiss = onErrorDismiss,
|
||||
progressDialog = {
|
||||
if (showProgressDialog) {
|
||||
AsyncViewDefaults.ProgressDialog(progressText)
|
||||
}
|
||||
},
|
||||
errorTitle = errorTitle,
|
||||
errorMessage = errorMessage,
|
||||
onRetry = onRetry,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> AsyncView(
|
||||
async: Async<T>,
|
||||
onSuccess: (T) -> Unit,
|
||||
onErrorDismiss: () -> Unit,
|
||||
progressDialog: @Composable () -> Unit = { AsyncViewDefaults.ProgressDialog() },
|
||||
errorTitle: @Composable (Throwable) -> String = { ErrorDialogDefaults.title },
|
||||
errorMessage: @Composable (Throwable) -> String = { it.message ?: it.toString() },
|
||||
onRetry: (() -> Unit)? = null,
|
||||
) {
|
||||
when (async) {
|
||||
Async.Uninitialized -> Unit
|
||||
is Async.Loading -> progressDialog()
|
||||
is Async.Failure -> {
|
||||
if (onRetry == null) {
|
||||
ErrorDialog(
|
||||
title = errorTitle(async.error),
|
||||
content = errorMessage(async.error),
|
||||
onDismiss = onErrorDismiss
|
||||
)
|
||||
} else {
|
||||
RetryDialog(
|
||||
title = errorTitle(async.error),
|
||||
content = errorMessage(async.error),
|
||||
onDismiss = onErrorDismiss,
|
||||
onRetry = onRetry,
|
||||
)
|
||||
}
|
||||
}
|
||||
is Async.Success -> {
|
||||
LaunchedEffect(async) {
|
||||
onSuccess(async.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AsyncViewDefaults {
|
||||
@Composable
|
||||
fun ProgressDialog(progressText: String? = null) {
|
||||
ProgressDialog(
|
||||
text = progressText,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun AsyncViewPreview(
|
||||
@PreviewParameter(AsyncProvider::class) async: Async<Unit>,
|
||||
) = ElementPreview {
|
||||
AsyncView(
|
||||
async = async,
|
||||
onSuccess = {},
|
||||
onErrorDismiss = {},
|
||||
)
|
||||
}
|
||||
@@ -55,6 +55,8 @@ class KonsistClassNameTest {
|
||||
val providedType = it.text
|
||||
.substringBefore(">")
|
||||
.substringAfter("<")
|
||||
// Get the substring before the first '<' to remove the generic type
|
||||
.substringBefore("<")
|
||||
.removeSuffix("?")
|
||||
.replace(".", "")
|
||||
it.name.endsWith("Provider") && it.name.contains(providedType)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user