Merge branch 'develop' into feature/bma/removeOldResources

This commit is contained in:
Benoit Marty 2023-06-27 16:09:30 +02:00
commit 5f16297939
85 changed files with 562 additions and 321 deletions

View file

@ -3,6 +3,7 @@
<!--- TOC --> <!--- TOC -->
* [Contributing code to Matrix](#contributing-code-to-matrix) * [Contributing code to Matrix](#contributing-code-to-matrix)
* [Developer onboarding](#developer-onboarding)
* [Android Studio settings](#android-studio-settings) * [Android Studio settings](#android-studio-settings)
* [Compilation](#compilation) * [Compilation](#compilation)
* [Strings](#strings) * [Strings](#strings)
@ -32,6 +33,10 @@ Element X Android support can be found in this room: [![Element Android Matrix r
The rest of the document contains specific rules for Matrix Android projects The rest of the document contains specific rules for Matrix Android projects
## Developer onboarding
For a detailed overview of the project, see [Developer Onboarding](./docs/_developer_onboarding.md).
## Android Studio settings ## Android Studio settings
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`). Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).

View file

@ -21,7 +21,7 @@ import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@ -48,13 +48,13 @@ class LoggedInEventProcessor @Inject constructor(
fun observeEvents(coroutineScope: CoroutineScope) { fun observeEvents(coroutineScope: CoroutineScope) {
observingJob = coroutineScope.launch { observingJob = coroutineScope.launch {
displayLeftRoomMessage.onEach { displayLeftRoomMessage.onEach {
displayMessage(R.string.common_current_user_left_room) displayMessage(CommonStrings.common_current_user_left_room)
}.launchIn(this) }.launchIn(this)
displayVerificationSuccessfulMessage displayVerificationSuccessfulMessage
.drop(1) .drop(1)
.onEach { .onEach {
displayMessage(R.string.common_verification_complete) displayMessage(CommonStrings.common_verification_complete)
}.launchIn(this) }.launchIn(this)
} }
} }

View file

@ -32,7 +32,7 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun AnalyticsPreferencesView( fun AnalyticsPreferencesView(
@ -43,11 +43,11 @@ fun AnalyticsPreferencesView(
state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled)) state.eventSink(AnalyticsOptInEvents.EnableAnalytics(isEnabled = isEnabled))
} }
PreferenceCategory(title = stringResource(id = StringR.string.screen_analytics_settings_share_data)) { PreferenceCategory(title = stringResource(id = CommonStrings.screen_analytics_settings_share_data)) {
val firstPart = stringResource(id = StringR.string.screen_analytics_settings_help_us_improve, state.applicationName) val firstPart = stringResource(id = CommonStrings.screen_analytics_settings_help_us_improve, state.applicationName)
val secondPart = buildAnnotatedStringWithColoredPart( val secondPart = buildAnnotatedStringWithColoredPart(
StringR.string.screen_analytics_settings_read_terms, CommonStrings.screen_analytics_settings_read_terms,
StringR.string.screen_analytics_settings_read_terms_content_link CommonStrings.screen_analytics_settings_read_terms_content_link
) )
val title = "$firstPart\n\n$secondPart" val title = "$firstPart\n\n$secondPart"

View file

@ -61,7 +61,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun AnalyticsOptInView( fun AnalyticsOptInView(
@ -199,13 +199,13 @@ private fun AnalyticsOptInFooter(
onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) }, onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(true)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Text(text = stringResource(id = StringR.string.action_ok)) Text(text = stringResource(id = CommonStrings.action_ok))
} }
TextButton( TextButton(
onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) }, onClick = { eventSink(AnalyticsOptInEvents.EnableAnalytics(false)) },
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { ) {
Text(text = stringResource(id = StringR.string.action_not_now)) Text(text = stringResource(id = CommonStrings.action_not_now))
} }
} }
} }

View file

@ -42,7 +42,7 @@ import io.element.android.libraries.designsystem.theme.components.CenterAlignedT
import io.element.android.libraries.designsystem.theme.components.Scaffold 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.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@ -107,7 +107,7 @@ fun AddPeopleViewTopBar(
modifier = Modifier.padding(horizontal = 8.dp), modifier = Modifier.padding(horizontal = 8.dp),
onClick = onNextPressed, onClick = onNextPressed,
) { ) {
val textActionResId = if (hasSelectedUsers) StringR.string.action_next else StringR.string.action_skip val textActionResId = if (hasSelectedUsers) CommonStrings.action_next else CommonStrings.action_skip
Text( Text(
text = stringResource(id = textActionResId), text = stringResource(id = textActionResId),
fontSize = 16.sp, fontSize = 16.sp,

View file

@ -40,7 +40,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBar
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.SelectedUsersList import io.element.android.libraries.matrix.ui.components.SelectedUsersList
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.usersearch.api.UserSearchResult import io.element.android.libraries.usersearch.api.UserSearchResult
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@ -54,7 +54,7 @@ fun SearchUserBar(
isMultiSelectionEnabled: Boolean, isMultiSelectionEnabled: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showBackButton: Boolean = true, showBackButton: Boolean = true,
placeHolderTitle: String = stringResource(R.string.common_search_for_someone), placeHolderTitle: String = stringResource(CommonStrings.common_search_for_someone),
onActiveChanged: (Boolean) -> Unit = {}, onActiveChanged: (Boolean) -> Unit = {},
onTextChanged: (String) -> Unit = {}, onTextChanged: (String) -> Unit = {},
onUserSelected: (MatrixUser) -> Unit = {}, onUserSelected: (MatrixUser) -> Unit = {},

View file

@ -30,7 +30,7 @@ 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.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
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
@ -131,7 +131,7 @@ class ConfigureRoomPresenter @Inject constructor(
dataStore.clearCachedData() dataStore.clearCachedData()
analyticsService.capture(CreatedRoom(isDM = false)) analyticsService.capture(CreatedRoom(isDM = false))
} }
}.execute(createRoomAction) }.runCatchingUpdatingState(createRoomAction)
} }
private suspend fun uploadAvatar(avatarUri: Uri): String { private suspend fun uploadAvatar(avatarUri: Uri): String {

View file

@ -65,8 +65,8 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.SelectedUsersList import io.element.android.libraries.matrix.ui.components.SelectedUsersList
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterialApi::class)
@Composable @Composable
@ -84,7 +84,7 @@ fun ConfigureRoomView(
if (state.createRoomAction is Async.Success) { if (state.createRoomAction is Async.Success) {
LaunchedEffect(state.createRoomAction) { LaunchedEffect(state.createRoomAction) {
onRoomCreated(state.createRoomAction.state) onRoomCreated(state.createRoomAction.data)
} }
} }
@ -158,7 +158,7 @@ fun ConfigureRoomView(
when (state.createRoomAction) { when (state.createRoomAction) {
is Async.Loading -> { is Async.Loading -> {
ProgressDialog(text = stringResource(StringR.string.common_creating_room)) ProgressDialog(text = stringResource(CommonStrings.common_creating_room))
} }
is Async.Failure -> { is Async.Failure -> {
@ -198,7 +198,7 @@ fun ConfigureRoomToolbar(
onClick = onNextPressed, onClick = onNextPressed,
) { ) {
Text( Text(
text = stringResource(StringR.string.action_create), text = stringResource(CommonStrings.action_create),
fontSize = 16.sp, fontSize = 16.sp,
) )
} }
@ -227,7 +227,7 @@ fun RoomNameWithAvatar(
LabelledTextField( LabelledTextField(
label = stringResource(R.string.screen_create_room_room_name_label), label = stringResource(R.string.screen_create_room_room_name_label),
value = roomName, value = roomName,
placeholder = stringResource(StringR.string.common_room_name_placeholder), placeholder = stringResource(CommonStrings.common_room_name_placeholder),
singleLine = true, singleLine = true,
onValueChange = onRoomNameChanged, onValueChange = onRoomNameChanged,
) )
@ -244,7 +244,7 @@ fun RoomTopic(
modifier = modifier, modifier = modifier,
label = stringResource(R.string.screen_create_room_topic_label), label = stringResource(R.string.screen_create_room_topic_label),
value = topic, value = topic,
placeholder = stringResource(StringR.string.common_topic_placeholder), placeholder = stringResource(CommonStrings.common_topic_placeholder),
onValueChange = onTopicChanged, onValueChange = onTopicChanged,
maxLines = 3, maxLines = 3,
) )

View file

@ -35,7 +35,7 @@ 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
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import timber.log.Timber import timber.log.Timber
@ -91,9 +91,9 @@ class CreateRoomRootNode @AssistedInject constructor(
startSharePlainTextIntent( startSharePlainTextIntent(
context = context, context = context,
activityResultLauncher = null, activityResultLauncher = null,
chooserTitle = context.getString(R.string.action_invite_friends), chooserTitle = context.getString(CommonStrings.action_invite_friends),
text = context.getString(R.string.invite_friends_text, appName, permalink), text = context.getString(CommonStrings.invite_friends_text, appName, permalink),
extraTitle = context.getString(R.string.invite_friends_rich_title, appName), extraTitle = context.getString(CommonStrings.invite_friends_rich_title, appName),
noActivityFoundMessage = context.getString(io.element.android.libraries.androidutils.R.string.error_no_compatible_app_found) noActivityFoundMessage = context.getString(io.element.android.libraries.androidutils.R.string.error_no_compatible_app_found)
) )
}.onFailure { }.onFailure {

View file

@ -28,7 +28,7 @@ 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.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
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
@ -95,6 +95,6 @@ class CreateRoomRootPresenter @Inject constructor(
suspend { suspend {
matrixClient.createDM(user.userId).getOrThrow() matrixClient.createDM(user.userId).getOrThrow()
.also { analyticsService.capture(CreatedRoom(isDM = true)) } .also { analyticsService.capture(CreatedRoom(isDM = true)) }
}.execute(startDmAction) }.runCatchingUpdatingState(startDmAction)
} }
} }

View file

@ -54,8 +54,8 @@ import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Scaffold 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.Text
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.designsystem.R as DrawableR
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@ -69,7 +69,7 @@ fun CreateRoomRootView(
) { ) {
if (state.startDmAction is Async.Success) { if (state.startDmAction is Async.Success) {
LaunchedEffect(state.startDmAction) { LaunchedEffect(state.startDmAction) {
onOpenDM(state.startDmAction.state) onOpenDM(state.startDmAction.data)
} }
} }
@ -107,7 +107,7 @@ fun CreateRoomRootView(
when (state.startDmAction) { when (state.startDmAction) {
is Async.Loading -> { is Async.Loading -> {
ProgressDialog(text = stringResource(id = StringR.string.common_starting_chat)) ProgressDialog(text = stringResource(id = CommonStrings.common_starting_chat))
} }
is Async.Failure -> { is Async.Failure -> {
@ -137,7 +137,7 @@ fun CreateRoomRootViewTopBar(
modifier = modifier, modifier = modifier,
title = { title = {
Text( Text(
text = stringResource(id = StringR.string.action_start_chat), text = stringResource(id = CommonStrings.action_start_chat),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
) )
@ -146,7 +146,7 @@ fun CreateRoomRootViewTopBar(
IconButton(onClick = onClosePressed) { IconButton(onClick = onClosePressed) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(id = StringR.string.action_close), contentDescription = stringResource(id = CommonStrings.action_close),
tint = MaterialTheme.colorScheme.primary, tint = MaterialTheme.colorScheme.primary,
) )
} }
@ -169,7 +169,7 @@ fun CreateRoomActionButtonsList(
) )
CreateRoomActionButton( CreateRoomActionButton(
iconRes = DrawableR.drawable.ic_share, iconRes = DrawableR.drawable.ic_share,
text = stringResource(id = StringR.string.action_invite_friends_to_app, state.applicationName), text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
onClick = onInvitePeopleClicked, onClick = onInvitePeopleClicked,
) )
} }

View file

@ -30,7 +30,7 @@ import io.element.android.features.invitelist.impl.model.InviteListInviteSummary
import io.element.android.features.invitelist.impl.model.InviteSender import io.element.android.features.invitelist.impl.model.InviteSender
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
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
@ -135,7 +135,7 @@ class InviteListPresenter @Inject constructor(
it.acceptInvitation().getOrThrow() it.acceptInvitation().getOrThrow()
} }
roomId roomId
}.execute(acceptedAction) }.runCatchingUpdatingState(acceptedAction)
} }
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<Async<Unit>>) = launch { private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<Async<Unit>>) = launch {
@ -143,7 +143,7 @@ class InviteListPresenter @Inject constructor(
client.getRoom(roomId)?.use { client.getRoom(roomId)?.use {
it.rejectInvitation().getOrThrow() it.rejectInvitation().getOrThrow()
} ?: Unit } ?: Unit
}.execute(declinedAction) }.runCatchingUpdatingState(declinedAction)
} }
private fun RoomSummary.Filled.toInviteSummary(seen: Boolean) = details.run { private fun RoomSummary.Filled.toInviteSummary(seen: Boolean) = details.run {

View file

@ -47,7 +47,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.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun InviteListView( fun InviteListView(
@ -58,7 +58,7 @@ fun InviteListView(
) { ) {
if (state.acceptedAction is Async.Success) { if (state.acceptedAction is Async.Success) {
LaunchedEffect(state.acceptedAction) { LaunchedEffect(state.acceptedAction) {
onInviteAccepted(state.acceptedAction.state) onInviteAccepted(state.acceptedAction.data)
} }
} }
@ -82,8 +82,8 @@ fun InviteListView(
ConfirmationDialog( ConfirmationDialog(
content = stringResource(contentResource, state.declineConfirmationDialog.name), content = stringResource(contentResource, state.declineConfirmationDialog.name),
title = stringResource(titleResource), title = stringResource(titleResource),
submitText = stringResource(StringR.string.action_decline), submitText = stringResource(CommonStrings.action_decline),
cancelText = stringResource(StringR.string.action_cancel), cancelText = stringResource(CommonStrings.action_cancel),
emphasizeSubmitButton = true, emphasizeSubmitButton = true,
onSubmitClicked = { state.eventSink(InviteListEvents.ConfirmDeclineInvite) }, onSubmitClicked = { state.eventSink(InviteListEvents.ConfirmDeclineInvite) },
onDismiss = { state.eventSink(InviteListEvents.CancelDeclineInvite) } onDismiss = { state.eventSink(InviteListEvents.CancelDeclineInvite) }
@ -92,18 +92,18 @@ fun InviteListView(
if (state.acceptedAction is Async.Failure) { if (state.acceptedAction is Async.Failure) {
ErrorDialog( ErrorDialog(
content = stringResource(StringR.string.error_unknown), content = stringResource(CommonStrings.error_unknown),
title = stringResource(StringR.string.common_error), title = stringResource(CommonStrings.common_error),
submitText = stringResource(StringR.string.action_ok), submitText = stringResource(CommonStrings.action_ok),
onDismiss = { state.eventSink(InviteListEvents.DismissAcceptError) } onDismiss = { state.eventSink(InviteListEvents.DismissAcceptError) }
) )
} }
if (state.declinedAction is Async.Failure) { if (state.declinedAction is Async.Failure) {
ErrorDialog( ErrorDialog(
content = stringResource(StringR.string.error_unknown), content = stringResource(CommonStrings.error_unknown),
title = stringResource(StringR.string.common_error), title = stringResource(CommonStrings.common_error),
submitText = stringResource(StringR.string.action_ok), submitText = stringResource(CommonStrings.action_ok),
onDismiss = { state.eventSink(InviteListEvents.DismissDeclineError) } onDismiss = { state.eventSink(InviteListEvents.DismissDeclineError) }
) )
} }
@ -124,7 +124,7 @@ fun InviteListContent(
BackButton(onClick = onBackClicked) BackButton(onClick = onBackClicked)
}, },
title = { title = {
Text(text = stringResource(StringR.string.action_invites_list)) Text(text = stringResource(CommonStrings.action_invites_list))
} }
) )
}, },

View file

@ -61,7 +61,7 @@ import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.noFontPadding import io.element.android.libraries.designsystem.theme.noFontPadding
import io.element.android.libraries.designsystem.theme.roomListUnreadIndicator import io.element.android.libraries.designsystem.theme.roomListUnreadIndicator
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
private val minHeight = 72.dp private val minHeight = 72.dp
@ -143,7 +143,7 @@ internal fun DefaultInviteSummaryRow(
// CTAs // CTAs
Row(Modifier.padding(top = 12.dp)) { Row(Modifier.padding(top = 12.dp)) {
OutlinedButton( OutlinedButton(
content = { Text(stringResource(StringR.string.action_decline), style = ElementTextStyles.Button) }, content = { Text(stringResource(CommonStrings.action_decline), style = ElementTextStyles.Button) },
onClick = onDeclineClicked, onClick = onDeclineClicked,
modifier = Modifier.weight(1f).heightIn(max = 36.dp), modifier = Modifier.weight(1f).heightIn(max = 36.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp), contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp),
@ -152,7 +152,7 @@ internal fun DefaultInviteSummaryRow(
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Button( Button(
content = { Text(stringResource(StringR.string.action_accept), style = ElementTextStyles.Button) }, content = { Text(stringResource(CommonStrings.action_accept), style = ElementTextStyles.Button) },
onClick = onAcceptClicked, onClick = onAcceptClicked,
modifier = Modifier.weight(1f).heightIn(max = 36.dp), modifier = Modifier.weight(1f).heightIn(max = 36.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp), contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp),

View file

@ -31,8 +31,7 @@ import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun LeaveRoomView( fun LeaveRoomView(
@ -50,19 +49,19 @@ private fun LeaveRoomConfirmationDialog(
when (state.confirmation) { when (state.confirmation) {
is LeaveRoomState.Confirmation.Hidden -> {} is LeaveRoomState.Confirmation.Hidden -> {}
is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog( is LeaveRoomState.Confirmation.PrivateRoom -> LeaveRoomConfirmationDialog(
text = StringR.string.leave_room_alert_private_subtitle, text = CommonStrings.leave_room_alert_private_subtitle,
roomId = state.confirmation.roomId, roomId = state.confirmation.roomId,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog( is LeaveRoomState.Confirmation.LastUserInRoom -> LeaveRoomConfirmationDialog(
text = StringR.string.leave_room_alert_empty_subtitle, text = CommonStrings.leave_room_alert_empty_subtitle,
roomId = state.confirmation.roomId, roomId = state.confirmation.roomId,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog( is LeaveRoomState.Confirmation.Generic -> LeaveRoomConfirmationDialog(
text = StringR.string.leave_room_alert_subtitle, text = CommonStrings.leave_room_alert_subtitle,
roomId = state.confirmation.roomId, roomId = state.confirmation.roomId,
eventSink = state.eventSink, eventSink = state.eventSink,
) )
@ -77,7 +76,7 @@ private fun LeaveRoomConfirmationDialog(
) { ) {
ConfirmationDialog( ConfirmationDialog(
content = stringResource(text), content = stringResource(text),
submitText = stringResource(R.string.action_leave), submitText = stringResource(CommonStrings.action_leave),
onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) }, onSubmitClicked = { eventSink(LeaveRoomEvent.LeaveRoom(roomId)) },
onDismiss = { eventSink(LeaveRoomEvent.HideConfirmation) }, onDismiss = { eventSink(LeaveRoomEvent.HideConfirmation) },
) )
@ -90,7 +89,7 @@ private fun LeaveRoomProgressDialog(
when (state.progress) { when (state.progress) {
is LeaveRoomState.Progress.Hidden -> {} is LeaveRoomState.Progress.Hidden -> {}
is LeaveRoomState.Progress.Shown -> ProgressDialog( is LeaveRoomState.Progress.Shown -> ProgressDialog(
text = stringResource(StringR.string.common_leaving_room), text = stringResource(CommonStrings.common_leaving_room),
) )
} }
} }
@ -102,7 +101,7 @@ private fun LeaveRoomErrorDialog(
when (state.error) { when (state.error) {
is LeaveRoomState.Error.Hidden -> {} is LeaveRoomState.Error.Hidden -> {}
is LeaveRoomState.Error.Shown -> ErrorDialog( is LeaveRoomState.Error.Shown -> ErrorDialog(
content = stringResource(StringR.string.error_unknown), content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(LeaveRoomEvent.HideError) } onDismiss = { state.eventSink(LeaveRoomEvent.HideError) }
) )
} }

View file

@ -40,7 +40,7 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.features.location.api.R import io.element.android.features.location.api.R
import io.element.android.libraries.ui.strings.R as StringsR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun StaticMapPlaceholder( internal fun StaticMapPlaceholder(
@ -76,7 +76,7 @@ internal fun StaticMapPlaceholder(
imageVector = Icons.Default.Refresh, imageVector = Icons.Default.Refresh,
contentDescription = null contentDescription = null
) )
Text(text = stringResource(id = StringsR.string.action_static_map_load)) Text(text = stringResource(id = CommonStrings.action_static_map_load))
} }
} }
} }

View file

@ -26,7 +26,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat
import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -71,6 +71,6 @@ class ChangeServerPresenter @Inject constructor(
// Valid, remember user choice // Valid, remember user choice
accountProviderDataSource.userSelection(data) accountProviderDataSource.userSelection(data)
}.getOrThrow() }.getOrThrow()
}.execute(changeServerAction, errorMapping = ChangeServerError::from) }.runCatchingUpdatingState(changeServerAction, errorTransform = ChangeServerError::from)
} }
} }

View file

@ -21,7 +21,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import io.element.android.features.login.impl.R import io.element.android.features.login.impl.R
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun SlidingSyncNotSupportedDialog( internal fun SlidingSyncNotSupportedDialog(
@ -32,11 +32,11 @@ internal fun SlidingSyncNotSupportedDialog(
ConfirmationDialog( ConfirmationDialog(
modifier = modifier, modifier = modifier,
onDismiss = onDismiss, onDismiss = onDismiss,
submitText = stringResource(StringR.string.action_learn_more), submitText = stringResource(CommonStrings.action_learn_more),
onSubmitClicked = onLearnMoreClicked, onSubmitClicked = onLearnMoreClicked,
onCancelClicked = onDismiss, onCancelClicked = onDismiss,
emphasizeSubmitButton = true, emphasizeSubmitButton = true,
title = stringResource(StringR.string.dialog_title_error), title = stringResource(CommonStrings.dialog_title_error),
content = stringResource(R.string.screen_change_server_error_no_sliding_sync_message), content = stringResource(R.string.screen_change_server_error_no_sliding_sync_message),
) )
} }

View file

@ -21,16 +21,16 @@ import io.element.android.features.login.impl.R
import io.element.android.libraries.matrix.api.auth.AuthErrorCode import io.element.android.libraries.matrix.api.auth.AuthErrorCode
import io.element.android.libraries.matrix.api.auth.AuthenticationException import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.matrix.api.auth.errorCode import io.element.android.libraries.matrix.api.auth.errorCode
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@StringRes @StringRes
fun loginError( fun loginError(
throwable: Throwable throwable: Throwable
): Int { ): Int {
val authException = throwable as? AuthenticationException ?: return StringR.string.error_unknown val authException = throwable as? AuthenticationException ?: return CommonStrings.error_unknown
return when (authException.errorCode) { return when (authException.errorCode) {
AuthErrorCode.FORBIDDEN -> R.string.screen_login_error_invalid_credentials AuthErrorCode.FORBIDDEN -> R.string.screen_login_error_invalid_credentials
AuthErrorCode.USER_DEACTIVATED -> R.string.screen_login_error_deactivated_account AuthErrorCode.USER_DEACTIVATED -> R.string.screen_login_error_deactivated_account
AuthErrorCode.UNKNOWN -> StringR.string.error_unknown AuthErrorCode.UNKNOWN -> CommonStrings.error_unknown
} }
} }

View file

@ -30,7 +30,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat
import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -95,6 +95,6 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
throw IllegalStateException("Unsupported login flow") throw IllegalStateException("Unsupported login flow")
} }
}.getOrThrow() }.getOrThrow()
}.execute(loginFlowAction, errorMapping = ChangeServerError::from) }.runCatchingUpdatingState(loginFlowAction, errorTransform = ChangeServerError::from)
} }
} }

View file

@ -131,7 +131,7 @@ fun ConfirmAccountProviderView(
} }
is Async.Loading -> Unit // The Continue button shows the loading state is Async.Loading -> Unit // The Continue button shows the loading state
is Async.Success -> { is Async.Success -> {
when (val loginFlowState = state.loginFlow.state) { when (val loginFlowState = state.loginFlow.data) {
is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails) is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails)
LoginFlow.PasswordLogin -> onLoginPasswordNeeded() LoginFlow.PasswordLogin -> onLoginPasswordNeeded()
} }

View file

@ -76,7 +76,7 @@ import io.element.android.libraries.designsystem.theme.components.autofill
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
@ -213,7 +213,7 @@ internal fun LoginForm(
IconButton(onClick = { IconButton(onClick = {
loginFieldState = "" loginFieldState = ""
}) { }) {
Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(StringR.string.action_clear)) Icon(imageVector = Icons.Filled.Close, contentDescription = stringResource(CommonStrings.action_clear))
} }
} }
} else null, } else null,
@ -248,7 +248,7 @@ internal fun LoginForm(
val image = val image =
if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
val description = val description =
if (passwordVisible) stringResource(StringR.string.a11y_hide_password) else stringResource(StringR.string.a11y_show_password) if (passwordVisible) stringResource(CommonStrings.a11y_hide_password) else stringResource(CommonStrings.a11y_show_password)
IconButton(onClick = { passwordVisible = !passwordVisible }) { IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, description) Icon(imageVector = image, description)

View file

@ -72,7 +72,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKeyFocusNext
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
/** /**
* https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=611-61435 * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=611-61435
@ -143,7 +143,7 @@ fun SearchAccountProviderView(
}) { }) {
Icon( Icon(
imageVector = Icons.Filled.Close, imageVector = Icons.Filled.Close,
contentDescription = stringResource(StringR.string.action_clear) contentDescription = stringResource(CommonStrings.action_clear)
) )
} }
} }
@ -171,7 +171,7 @@ fun SearchAccountProviderView(
} }
} }
is Async.Success -> { is Async.Success -> {
items(state.userInputResult.state) { homeserverData -> items(state.userInputResult.data) { homeserverData ->
val item = homeserverData.toAccountProvider() val item = homeserverData.toAccountProvider()
AccountProviderView( AccountProviderView(
item = item, item = item,

View file

@ -19,8 +19,8 @@ package io.element.android.features.login.impl.error
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.R import io.element.android.features.login.impl.R
import io.element.android.libraries.matrix.api.auth.AuthenticationException import io.element.android.libraries.matrix.api.auth.AuthenticationException
import io.element.android.libraries.ui.strings.CommonStrings
import org.junit.Test import org.junit.Test
import io.element.android.libraries.ui.strings.R as StringR
class ErrorFormatterTests { class ErrorFormatterTests {
@ -28,19 +28,19 @@ class ErrorFormatterTests {
@Test @Test
fun `loginError - invalid unknown error returns unknown error message`() { fun `loginError - invalid unknown error returns unknown error message`() {
val error = Throwable("Some unknown error") val error = Throwable("Some unknown error")
assertThat(loginError(error)).isEqualTo(StringR.string.error_unknown) assertThat(loginError(error)).isEqualTo(CommonStrings.error_unknown)
} }
@Test @Test
fun `loginError - invalid auth error returns unknown error message`() { fun `loginError - invalid auth error returns unknown error message`() {
val error = AuthenticationException.SlidingSyncNotAvailable("Some message. Also contains M_FORBIDDEN, but won't be parsed") val error = AuthenticationException.SlidingSyncNotAvailable("Some message. Also contains M_FORBIDDEN, but won't be parsed")
assertThat(loginError(error)).isEqualTo(StringR.string.error_unknown) assertThat(loginError(error)).isEqualTo(CommonStrings.error_unknown)
} }
@Test @Test
fun `loginError - unknown error returns unknown error message`() { fun `loginError - unknown error returns unknown error message`() {
val error = AuthenticationException.Generic("M_UNKNOWN") val error = AuthenticationException.Generic("M_UNKNOWN")
assertThat(loginError(error)).isEqualTo(StringR.string.error_unknown) assertThat(loginError(error)).isEqualTo(CommonStrings.error_unknown)
} }
@Test @Test

View file

@ -31,7 +31,7 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun LogoutPreferenceView( fun LogoutPreferenceView(
@ -81,7 +81,7 @@ fun LogoutPreferenceView(
fun LogoutPreferenceContent( fun LogoutPreferenceContent(
onClick: () -> Unit = {}, onClick: () -> Unit = {},
) { ) {
PreferenceCategory(title = stringResource(id = StringR.string.settings_title_general)) { PreferenceCategory(title = stringResource(id = CommonStrings.settings_title_general)) {
PreferenceText( PreferenceText(
title = stringResource(id = R.string.screen_signout_preference_item), title = stringResource(id = R.string.screen_signout_preference_item),
icon = Icons.Default.Logout, icon = Icons.Default.Logout,

View file

@ -26,7 +26,7 @@ import io.element.android.features.logout.api.LogoutPreferenceEvents
import io.element.android.features.logout.api.LogoutPreferencePresenter import io.element.android.features.logout.api.LogoutPreferencePresenter
import io.element.android.features.logout.api.LogoutPreferenceState import io.element.android.features.logout.api.LogoutPreferenceState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -59,6 +59,6 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli
private fun CoroutineScope.logout(logoutAction: MutableState<Async<Unit>>) = launch { private fun CoroutineScope.logout(logoutAction: MutableState<Async<Unit>>) = launch {
suspend { suspend {
matrixClient.logout() matrixClient.logout()
}.execute(logoutAction) }.runCatchingUpdatingState(logoutAction)
} }
} }

View file

@ -77,9 +77,9 @@ import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
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.timeline.item.event.EventSendState import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import timber.log.Timber import timber.log.Timber
import io.element.android.libraries.ui.strings.R as StringsR
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
@ -195,7 +195,7 @@ private fun AttachmentStateView(
is AttachmentsState.Previewing -> LaunchedEffect(state) { is AttachmentsState.Previewing -> LaunchedEffect(state) {
onPreviewAttachments(state.attachments) onPreviewAttachments(state.attachments)
} }
is AttachmentsState.Sending -> ProgressDialog(text = stringResource(id = StringsR.string.common_loading)) is AttachmentsState.Sending -> ProgressDialog(text = stringResource(id = CommonStrings.common_loading))
} }
} }

View file

@ -27,7 +27,7 @@ import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.executeResult import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaSender
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -83,8 +83,8 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
mediaAttachment: Attachment.Media, mediaAttachment: Attachment.Media,
sendActionState: MutableState<Async<Unit>>, sendActionState: MutableState<Async<Unit>>,
) { ) {
suspend { sendActionState.runUpdatingState {
mediaSender.sendMedia(mediaAttachment.localMedia.uri, mediaAttachment.localMedia.info.mimeType, mediaAttachment.compressIfPossible) mediaSender.sendMedia(mediaAttachment.localMedia.uri, mediaAttachment.localMedia.info.mimeType, mediaAttachment.compressIfPossible)
}.executeResult(sendActionState) }
} }
} }

View file

@ -41,8 +41,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.theme.components.Scaffold 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.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.strings.R as StringsR
@Composable @Composable
fun AttachmentsPreviewView( fun AttachmentsPreviewView(
@ -92,7 +91,7 @@ private fun AttachmentSendStateView(
) { ) {
when (sendActionState) { when (sendActionState) {
is Async.Loading -> { is Async.Loading -> {
ProgressDialog(text = stringResource(id = R.string.common_loading)) ProgressDialog(text = stringResource(id = CommonStrings.common_loading))
} }
is Async.Failure -> { is Async.Failure -> {
@ -151,10 +150,10 @@ private fun AttachmentsPreviewBottomActions(
modifier = modifier, modifier = modifier,
) { ) {
TextButton(onClick = onCancelClicked) { TextButton(onClick = onCancelClicked) {
Text(stringResource(id = StringsR.string.action_cancel)) Text(stringResource(id = CommonStrings.action_cancel))
} }
TextButton(onClick = onSendClicked) { TextButton(onClick = onSendClicked) {
Text(stringResource(id = StringsR.string.action_send)) Text(stringResource(id = CommonStrings.action_send))
} }
} }
} }

View file

@ -17,14 +17,14 @@
package io.element.android.features.messages.impl.attachments.preview.error package io.element.android.features.messages.impl.attachments.preview.error
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
fun sendAttachmentError( fun sendAttachmentError(
throwable: Throwable throwable: Throwable
): Int { ): Int {
return if (throwable is MediaPreProcessor.Failure) { return if (throwable is MediaPreProcessor.Failure) {
R.string.screen_media_upload_preview_error_failed_processing CommonStrings.screen_media_upload_preview_error_failed_processing
} else { } else {
R.string.screen_media_upload_preview_error_failed_sending CommonStrings.screen_media_upload_preview_error_failed_sending
} }
} }

View file

@ -24,14 +24,12 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
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.setValue 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.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.isLoading
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId

View file

@ -66,8 +66,8 @@ import io.element.android.libraries.designsystem.theme.roomListRoomName
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.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.ui.components.SelectedRoom import io.element.android.libraries.matrix.ui.components.SelectedRoom
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
@ -111,7 +111,7 @@ fun ForwardMessagesView(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = { Text(stringResource(StringR.string.common_forward_message), style = ElementTextStyles.Bold.callout) }, title = { Text(stringResource(CommonStrings.common_forward_message), style = ElementTextStyles.Bold.callout) },
navigationIcon = { navigationIcon = {
BackButton(onClick = { onBackButton(state) }) BackButton(onClick = { onBackButton(state) })
}, },
@ -120,7 +120,7 @@ fun ForwardMessagesView(
enabled = state.selectedRooms.isNotEmpty(), enabled = state.selectedRooms.isNotEmpty(),
onClick = { state.eventSink(ForwardMessagesEvents.ForwardEvent) } onClick = { state.eventSink(ForwardMessagesEvents.ForwardEvent) }
) { ) {
Text(text = stringResource(StringR.string.action_send)) Text(text = stringResource(CommonStrings.action_send))
} }
} }
) )
@ -132,7 +132,7 @@ fun ForwardMessagesView(
.consumeWindowInsets(paddingValues) .consumeWindowInsets(paddingValues)
) { ) {
SearchBar<ImmutableList<RoomSummaryDetails>>( SearchBar<ImmutableList<RoomSummaryDetails>>(
placeHolderTitle = stringResource(StringR.string.action_search), placeHolderTitle = stringResource(CommonStrings.action_search),
query = state.query, query = state.query,
onQueryChange = { state.eventSink(ForwardMessagesEvents.UpdateQuery(it)) }, onQueryChange = { state.eventSink(ForwardMessagesEvents.UpdateQuery(it)) },
active = state.isSearchActive, active = state.isSearchActive,

View file

@ -38,10 +38,10 @@ import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState import io.element.android.libraries.designsystem.utils.collectSnackbarMessageAsState
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.media.MediaFile
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import io.element.android.libraries.androidutils.R as UtilsR import io.element.android.libraries.androidutils.R as UtilsR
import io.element.android.libraries.ui.strings.R as StringR
class MediaViewerPresenter @AssistedInject constructor( class MediaViewerPresenter @AssistedInject constructor(
@Assisted private val inputs: MediaViewerNode.Inputs, @Assisted private val inputs: MediaViewerNode.Inputs,
@ -117,9 +117,9 @@ class MediaViewerPresenter @AssistedInject constructor(
private fun CoroutineScope.saveOnDisk(localMedia: Async<LocalMedia>) = launch { private fun CoroutineScope.saveOnDisk(localMedia: Async<LocalMedia>) = launch {
if (localMedia is Async.Success) { if (localMedia is Async.Success) {
localMediaActions.saveOnDisk(localMedia.state) localMediaActions.saveOnDisk(localMedia.data)
.onSuccess { .onSuccess {
val snackbarMessage = SnackbarMessage(StringR.string.common_file_saved_on_disk_android) val snackbarMessage = SnackbarMessage(CommonStrings.common_file_saved_on_disk_android)
snackbarDispatcher.post(snackbarMessage) snackbarDispatcher.post(snackbarMessage)
} }
.onFailure { .onFailure {
@ -131,7 +131,7 @@ class MediaViewerPresenter @AssistedInject constructor(
private fun CoroutineScope.share(localMedia: Async<LocalMedia>) = launch { private fun CoroutineScope.share(localMedia: Async<LocalMedia>) = launch {
if (localMedia is Async.Success) { if (localMedia is Async.Success) {
localMediaActions.share(localMedia.state) localMediaActions.share(localMedia.data)
.onFailure { .onFailure {
val snackbarMessage = SnackbarMessage(mediaActionsError(it)) val snackbarMessage = SnackbarMessage(mediaActionsError(it))
snackbarDispatcher.post(snackbarMessage) snackbarDispatcher.post(snackbarMessage)
@ -141,7 +141,7 @@ class MediaViewerPresenter @AssistedInject constructor(
private fun CoroutineScope.open(localMedia: Async<LocalMedia>) = launch { private fun CoroutineScope.open(localMedia: Async<LocalMedia>) = launch {
if (localMedia is Async.Success) { if (localMedia is Async.Success) {
localMediaActions.open(localMedia.state) localMediaActions.open(localMedia.data)
.onFailure { .onFailure {
val snackbarMessage = SnackbarMessage(mediaActionsError(it)) val snackbarMessage = SnackbarMessage(mediaActionsError(it))
snackbarDispatcher.post(snackbarMessage) snackbarDispatcher.post(snackbarMessage)
@ -153,7 +153,7 @@ class MediaViewerPresenter @AssistedInject constructor(
return if (throwable is ActivityNotFoundException) { return if (throwable is ActivityNotFoundException) {
UtilsR.string.error_no_compatible_app_found UtilsR.string.error_no_compatible_app_found
} else { } else {
StringR.string.error_unknown CommonStrings.error_unknown
} }
} }
} }

View file

@ -57,7 +57,6 @@ import io.element.android.features.messages.impl.media.local.LocalMediaView
import io.element.android.features.messages.impl.media.local.MediaInfo import io.element.android.features.messages.impl.media.local.MediaInfo
import io.element.android.features.messages.impl.media.local.rememberLocalMediaViewState import io.element.android.features.messages.impl.media.local.rememberLocalMediaViewState
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.isLoading
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
@ -69,8 +68,8 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun MediaViewerView( fun MediaViewerView(
@ -131,7 +130,7 @@ fun MediaViewerView(
) { ) {
if (state.downloadedMedia is Async.Failure) { if (state.downloadedMedia is Async.Failure) {
ErrorView( ErrorView(
errorMessage = stringResource(id = StringR.string.error_unknown), errorMessage = stringResource(id = CommonStrings.error_unknown),
onRetry = ::onRetry, onRetry = ::onRetry,
onDismiss = ::onDismissError onDismiss = ::onDismissError
) )
@ -188,7 +187,7 @@ private fun MediaViewerTopBar(
eventSink(MediaViewerEvents.OpenWith) eventSink(MediaViewerEvents.OpenWith)
}, },
) { ) {
Icon(imageVector = Icons.Default.OpenInNew, contentDescription = stringResource(id = StringR.string.action_open_with)) Icon(imageVector = Icons.Default.OpenInNew, contentDescription = stringResource(id = CommonStrings.action_open_with))
} }
IconButton( IconButton(
enabled = actionsEnabled, enabled = actionsEnabled,
@ -196,7 +195,7 @@ private fun MediaViewerTopBar(
eventSink(MediaViewerEvents.SaveOnDisk) eventSink(MediaViewerEvents.SaveOnDisk)
}, },
) { ) {
Icon(imageVector = Icons.Default.Download, contentDescription = stringResource(id = StringR.string.action_save)) Icon(imageVector = Icons.Default.Download, contentDescription = stringResource(id = CommonStrings.action_save))
} }
IconButton( IconButton(
enabled = actionsEnabled, enabled = actionsEnabled,
@ -204,7 +203,7 @@ private fun MediaViewerTopBar(
eventSink(MediaViewerEvents.Share) eventSink(MediaViewerEvents.Share)
}, },
) { ) {
Icon(imageVector = Icons.Default.Share, contentDescription = stringResource(id = StringR.string.action_share)) Icon(imageVector = Icons.Default.Share, contentDescription = stringResource(id = CommonStrings.action_share))
} }
} }
) )

View file

@ -29,16 +29,15 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.executeResult import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.SnackbarMessage import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
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.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import io.element.android.libraries.ui.strings.R as StringR
class ReportMessagePresenter @AssistedInject constructor( class ReportMessagePresenter @AssistedInject constructor(
private val room: MatrixRoom, private val room: MatrixRoom,
@ -87,12 +86,12 @@ class ReportMessagePresenter @AssistedInject constructor(
blockUser: Boolean, blockUser: Boolean,
result: MutableState<Async<Unit>>, result: MutableState<Async<Unit>>,
) = launch { ) = launch {
suspend { result.runUpdatingState {
val userIdToBlock = userId.takeIf { blockUser } val userIdToBlock = userId.takeIf { blockUser }
room.reportContent(eventId, reason, userIdToBlock) room.reportContent(eventId, reason, userIdToBlock)
.onSuccess { .onSuccess {
snackbarDispatcher.post(SnackbarMessage(StringR.string.common_report_submitted)) snackbarDispatcher.post(SnackbarMessage(CommonStrings.common_report_submitted))
} }
}.executeResult(result) }
} }
} }

View file

@ -54,7 +54,7 @@ import io.element.android.libraries.designsystem.theme.components.CenterAlignedT
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Scaffold 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.Text
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
@ -74,7 +74,7 @@ fun ReportMessageView(
} }
is Async.Failure -> { is Async.Failure -> {
ErrorDialog( ErrorDialog(
content = stringResource(StringR.string.error_unknown), content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(ReportMessageEvents.ClearError) } onDismiss = { state.eventSink(ReportMessageEvents.ClearError) }
) )
} }
@ -86,7 +86,7 @@ fun ReportMessageView(
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = { title = {
Text( Text(
stringResource(StringR.string.action_report_content), stringResource(CommonStrings.action_report_content),
style = ElementTextStyles.Regular.callout, style = ElementTextStyles.Regular.callout,
fontWeight = FontWeight.Medium, fontWeight = FontWeight.Medium,
) )
@ -112,14 +112,14 @@ fun ReportMessageView(
OutlinedTextField( OutlinedTextField(
value = state.reason, value = state.reason,
onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) }, onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) },
placeholder = { Text(stringResource(StringR.string.report_content_hint)) }, placeholder = { Text(stringResource(CommonStrings.report_content_hint)) },
enabled = !isSending, enabled = !isSending,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.heightIn(min = 90.dp) .heightIn(min = 90.dp)
) )
Text( Text(
text = stringResource(StringR.string.report_content_explanation), text = stringResource(CommonStrings.report_content_explanation),
style = ElementTextStyles.Regular.caption1, style = ElementTextStyles.Regular.caption1,
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
@ -133,11 +133,11 @@ fun ReportMessageView(
) { ) {
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text( Text(
text = stringResource(StringR.string.screen_report_content_block_user), text = stringResource(CommonStrings.screen_report_content_block_user),
style = ElementTextStyles.Regular.callout, style = ElementTextStyles.Regular.callout,
) )
Text( Text(
text = stringResource(StringR.string.screen_report_content_block_user_hint), text = stringResource(CommonStrings.screen_report_content_block_user_hint),
style = ElementTextStyles.Regular.bodyMD, style = ElementTextStyles.Regular.bodyMD,
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
) )
@ -152,7 +152,7 @@ fun ReportMessageView(
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
ButtonWithProgress( ButtonWithProgress(
text = stringResource(StringR.string.action_send), text = stringResource(CommonStrings.action_send),
enabled = state.reason.isNotBlank() && !isSending, enabled = state.reason.isNotBlank() && !isSending,
showProgress = isSending, showProgress = isSending,
onClick = { onClick = {

View file

@ -44,7 +44,7 @@ import io.element.android.libraries.designsystem.theme.ElementTheme
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState import io.element.android.libraries.matrix.api.timeline.item.event.EventSendState
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun TimelineEventTimestampView( fun TimelineEventTimestampView(
@ -70,7 +70,7 @@ fun TimelineEventTimestampView(
) { ) {
if (isMessageEdited) { if (isMessageEdited) {
Text( Text(
stringResource(R.string.common_edited_suffix), stringResource(CommonStrings.common_edited_suffix),
style = ElementTextStyles.Regular.caption2, style = ElementTextStyles.Regular.caption2,
color = tint ?: MaterialTheme.colorScheme.secondary, color = tint ?: MaterialTheme.colorScheme.secondary,
) )

View file

@ -26,8 +26,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun TimelineItemEncryptedView( fun TimelineItemEncryptedView(
@ -35,8 +34,8 @@ fun TimelineItemEncryptedView(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TimelineItemInformativeView( TimelineItemInformativeView(
text = stringResource(id = StringR.string.common_decryption_error), text = stringResource(id = CommonStrings.common_decryption_error),
iconDescription = stringResource(id = StringR.string.dialog_title_warning), iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
icon = Icons.Default.Warning, icon = Icons.Default.Warning,
modifier = modifier modifier = modifier
) )

View file

@ -25,8 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun TimelineItemRedactedView( fun TimelineItemRedactedView(
@ -34,8 +33,8 @@ fun TimelineItemRedactedView(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TimelineItemInformativeView( TimelineItemInformativeView(
text = stringResource(id = StringR.string.common_message_removed), text = stringResource(id = CommonStrings.common_message_removed),
iconDescription = stringResource(id = StringR.string.common_message_removed), iconDescription = stringResource(id = CommonStrings.common_message_removed),
icon = Icons.Default.Delete, icon = Icons.Default.Delete,
modifier = modifier modifier = modifier
) )

View file

@ -25,8 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun TimelineItemUnknownView( fun TimelineItemUnknownView(
@ -34,8 +33,8 @@ fun TimelineItemUnknownView(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
TimelineItemInformativeView( TimelineItemInformativeView(
text = stringResource(id = StringR.string.common_unsupported_event), text = stringResource(id = CommonStrings.common_unsupported_event),
iconDescription = stringResource(id = StringR.string.dialog_title_warning), iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
icon = Icons.Default.Info, icon = Icons.Default.Info,
modifier = modifier modifier = modifier
) )

View file

@ -30,7 +30,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import javax.inject.Inject import javax.inject.Inject
@ContributesBinding(RoomScope::class) @ContributesBinding(RoomScope::class)
@ -42,12 +42,12 @@ class MessageSummaryFormatterImpl @Inject constructor(
is TimelineItemTextBasedContent -> event.content.body is TimelineItemTextBasedContent -> event.content.body
is TimelineItemStateContent -> event.content.body is TimelineItemStateContent -> event.content.body
is TimelineItemProfileChangeContent -> event.content.body is TimelineItemProfileChangeContent -> event.content.body
is TimelineItemEncryptedContent -> context.getString(R.string.common_unable_to_decrypt) is TimelineItemEncryptedContent -> context.getString(CommonStrings.common_unable_to_decrypt)
is TimelineItemRedactedContent -> context.getString(R.string.common_message_removed) is TimelineItemRedactedContent -> context.getString(CommonStrings.common_message_removed)
is TimelineItemUnknownContent -> context.getString(R.string.common_unsupported_event) is TimelineItemUnknownContent -> context.getString(CommonStrings.common_unsupported_event)
is TimelineItemImageContent -> context.getString(R.string.common_image) is TimelineItemImageContent -> context.getString(CommonStrings.common_image)
is TimelineItemVideoContent -> context.getString(R.string.common_video) is TimelineItemVideoContent -> context.getString(CommonStrings.common_video)
is TimelineItemFileContent -> context.getString(R.string.common_file) is TimelineItemFileContent -> context.getString(CommonStrings.common_file)
} }
} }
} }

View file

@ -49,7 +49,7 @@ import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.LocalColors
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun ConnectivityIndicatorView( fun ConnectivityIndicatorView(
@ -98,7 +98,7 @@ private fun Indicator(modifier: Modifier = Modifier) {
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = stringResource(StringR.string.common_offline), text = stringResource(CommonStrings.common_offline),
style = ElementTextStyles.Regular.bodyMD.copy(fontWeight = FontWeight.Medium), style = ElementTextStyles.Regular.bodyMD.copy(fontWeight = FontWeight.Medium),
color = tint, color = tint,
) )

View file

@ -29,7 +29,7 @@ import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.featureflag.api.Feature import io.element.android.libraries.featureflag.api.Feature
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
@ -128,13 +128,13 @@ class DeveloperSettingsPresenter @Inject constructor(
private fun CoroutineScope.computeCacheSize(cacheSize: MutableState<Async<String>>) = launch { private fun CoroutineScope.computeCacheSize(cacheSize: MutableState<Async<String>>) = launch {
suspend { suspend {
computeCacheSizeUseCase() computeCacheSizeUseCase()
}.execute(cacheSize) }.runCatchingUpdatingState(cacheSize)
} }
private fun CoroutineScope.clearCache(clearCacheAction: MutableState<Async<Unit>>) = launch { private fun CoroutineScope.clearCache(clearCacheAction: MutableState<Async<Unit>>) = launch {
suspend { suspend {
clearCacheUseCase() clearCacheUseCase()
}.execute(clearCacheAction) }.runCatchingUpdatingState(clearCacheAction)
} }
} }

View file

@ -23,8 +23,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.isLoading
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.PreferenceText import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.components.preferences.PreferenceView import io.element.android.libraries.designsystem.components.preferences.PreferenceView
@ -32,7 +30,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.featureflag.ui.FeatureListView import io.element.android.libraries.featureflag.ui.FeatureListView
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun DeveloperSettingsView( fun DeveloperSettingsView(
@ -44,7 +42,7 @@ fun DeveloperSettingsView(
PreferenceView( PreferenceView(
modifier = modifier, modifier = modifier,
onBackPressed = onBackPressed, onBackPressed = onBackPressed,
title = stringResource(id = R.string.common_developer_options) title = stringResource(id = CommonStrings.common_developer_options)
) { ) {
// Note: this is OK to hardcode strings in this debug screen. // Note: this is OK to hardcode strings in this debug screen.
PreferenceCategory(title = "Feature flags") { PreferenceCategory(title = "Feature flags") {

View file

@ -35,7 +35,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.LargeHeightPreview import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserProvider import io.element.android.libraries.matrix.ui.components.MatrixUserProvider
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun PreferencesRootView( fun PreferencesRootView(
@ -50,7 +50,7 @@ fun PreferencesRootView(
PreferenceView( PreferenceView(
modifier = modifier, modifier = modifier,
onBackPressed = onBackPressed, onBackPressed = onBackPressed,
title = stringResource(id = StringR.string.common_settings) title = stringResource(id = CommonStrings.common_settings)
) { ) {
UserPreferences(state.myUser) UserPreferences(state.myUser)
AnalyticsPreferencesView( AnalyticsPreferencesView(
@ -71,9 +71,9 @@ fun PreferencesRootView(
@Composable @Composable
fun DeveloperPreferencesView(onOpenDeveloperSettings: () -> Unit) { fun DeveloperPreferencesView(onOpenDeveloperSettings: () -> Unit) {
PreferenceCategory(title = stringResource(id = StringR.string.common_developer_options)) { PreferenceCategory(title = stringResource(id = CommonStrings.common_developer_options)) {
PreferenceText( PreferenceText(
title = stringResource(id = StringR.string.common_developer_options), title = stringResource(id = CommonStrings.common_developer_options),
icon = Icons.Default.DeveloperMode, icon = Icons.Default.DeveloperMode,
onClick = onOpenDeveloperSettings onClick = onOpenDeveloperSettings
) )

View file

@ -24,7 +24,7 @@ import io.element.android.libraries.designsystem.components.dialogs.Confirmation
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun CrashDetectionView( fun CrashDetectionView(
@ -56,10 +56,10 @@ fun CrashDetectionContent(
onDismiss: () -> Unit = { }, onDismiss: () -> Unit = { },
) { ) {
ConfirmationDialog( ConfirmationDialog(
title = stringResource(id = StringR.string.action_report_bug), title = stringResource(id = CommonStrings.action_report_bug),
content = stringResource(id = R.string.crash_detection_dialog_content, /* TODO App name */ "Element"), content = stringResource(id = R.string.crash_detection_dialog_content, /* TODO App name */ "Element"),
submitText = stringResource(id = StringR.string.action_yes), submitText = stringResource(id = CommonStrings.action_yes),
cancelText = stringResource(id = StringR.string.action_no), cancelText = stringResource(id = CommonStrings.action_no),
onCancelClicked = onNoClicked, onCancelClicked = onNoClicked,
onSubmitClicked = onYesClicked, onSubmitClicked = onYesClicked,
onDismiss = onDismiss, onDismiss = onDismiss,

View file

@ -32,7 +32,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun RageshakeDetectionView( fun RageshakeDetectionView(
@ -88,11 +88,11 @@ fun RageshakeDialogContent(
onYesClicked: () -> Unit = { }, onYesClicked: () -> Unit = { },
) { ) {
ConfirmationDialog( ConfirmationDialog(
title = stringResource(id = StringR.string.action_report_bug), title = stringResource(id = CommonStrings.action_report_bug),
content = stringResource(id = R.string.rageshake_detection_dialog_content), content = stringResource(id = R.string.rageshake_detection_dialog_content),
thirdButtonText = stringResource(id = StringR.string.action_disable), thirdButtonText = stringResource(id = CommonStrings.action_disable),
submitText = stringResource(id = StringR.string.action_yes), submitText = stringResource(id = CommonStrings.action_yes),
cancelText = stringResource(id = StringR.string.action_no), cancelText = stringResource(id = CommonStrings.action_no),
onCancelClicked = onNoClicked, onCancelClicked = onNoClicked,
onThirdButtonClicked = onDisableClicked, onThirdButtonClicked = onDisableClicked,
onSubmitClicked = onYesClicked, onSubmitClicked = onYesClicked,

View file

@ -30,7 +30,7 @@ import io.element.android.libraries.designsystem.components.preferences.Preferen
import io.element.android.libraries.designsystem.components.preferences.PreferenceText import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun RageshakePreferencesView( fun RageshakePreferencesView(
@ -47,23 +47,23 @@ fun RageshakePreferencesView(
} }
Column(modifier = modifier) { Column(modifier = modifier) {
PreferenceCategory(title = stringResource(id = StringR.string.action_report_bug)) { PreferenceCategory(title = stringResource(id = CommonStrings.action_report_bug)) {
PreferenceText( PreferenceText(
title = stringResource(id = StringR.string.action_report_bug), title = stringResource(id = CommonStrings.action_report_bug),
icon = Icons.Default.BugReport, icon = Icons.Default.BugReport,
onClick = onOpenRageshake onClick = onOpenRageshake
) )
} }
PreferenceCategory(title = stringResource(id = StringR.string.settings_rageshake)) { PreferenceCategory(title = stringResource(id = CommonStrings.settings_rageshake)) {
if (state.isSupported) { if (state.isSupported) {
PreferenceSwitch( PreferenceSwitch(
title = stringResource(id = StringR.string.preference_rageshake), title = stringResource(id = CommonStrings.preference_rageshake),
isChecked = state.isEnabled, isChecked = state.isEnabled,
onCheckedChange = ::onEnabledChanged onCheckedChange = ::onEnabledChanged
) )
PreferenceSlide( PreferenceSlide(
title = stringResource(id = StringR.string.settings_rageshake_detection_threshold), title = stringResource(id = CommonStrings.settings_rageshake_detection_threshold),
// summary = stringResource(id = StringR.string.settings_rageshake_detection_threshold_summary), // summary = stringResource(id = CommonStrings.settings_rageshake_detection_threshold_summary),
value = state.sensitivity, value = state.sensitivity,
enabled = state.isEnabled, enabled = state.isEnabled,
steps = 3 /* 5 possible values - steps are in ]0, 1[ */, steps = 3 /* 5 possible values - steps are in ]0, 1[ */,

View file

@ -58,7 +58,7 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun BugReportView( fun BugReportView(
@ -90,7 +90,7 @@ fun BugReportView(
val isFormEnabled = state.sending !is Async.Loading val isFormEnabled = state.sending !is Async.Loading
// Title // Title
Text( Text(
text = stringResource(id = StringR.string.action_report_bug), text = stringResource(id = CommonStrings.action_report_bug),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp), .padding(horizontal = 16.dp, vertical = 16.dp),
@ -190,7 +190,7 @@ fun BugReportView(
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 32.dp) .padding(vertical = 32.dp)
) { ) {
Text(text = stringResource(id = StringR.string.action_send)) Text(text = stringResource(id = CommonStrings.action_send))
} }
} }
when (state.sending) { when (state.sending) {

View file

@ -77,7 +77,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.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@ -198,7 +198,7 @@ internal fun RoomDetailsTopBar(
onDismissRequest = { showMenu = false }, onDismissRequest = { showMenu = false },
) { ) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(id = StringR.string.action_edit)) }, text = { Text(stringResource(id = CommonStrings.action_edit)) },
onClick = { onClick = {
// Explicitly close the menu before handling the action, as otherwise it stays open during the // Explicitly close the menu before handling the action, as otherwise it stays open during the
// transition and renders really badly. // transition and renders really badly.
@ -250,7 +250,7 @@ internal fun TopicSection(
onActionClicked: (RoomDetailsAction) -> Unit, onActionClicked: (RoomDetailsAction) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
PreferenceCategory(title = stringResource(StringR.string.common_topic), modifier = modifier) { PreferenceCategory(title = stringResource(CommonStrings.common_topic), modifier = modifier) {
if (roomTopic is RoomTopicState.CanAddTopic) { if (roomTopic is RoomTopicState.CanAddTopic) {
PreferenceText( PreferenceText(
title = stringResource(R.string.screen_room_details_add_topic_title), title = stringResource(R.string.screen_room_details_add_topic_title),

View file

@ -31,7 +31,7 @@ import androidx.compose.runtime.setValue
import androidx.core.net.toUri import androidx.core.net.toUri
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
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
@ -154,7 +154,7 @@ class RoomDetailsEditPresenter @Inject constructor(
}) })
} }
if (results.all { it.isSuccess }) Unit else results.first { it.isFailure }.getOrThrow() if (results.all { it.isSuccess }) Unit else results.first { it.isFailure }.getOrThrow()
}.execute(action) }.runCatchingUpdatingState(action)
} }
private suspend fun updateAvatar(avatarUri: Uri?): Result<Unit> { private suspend fun updateAvatar(avatarUri: Uri?): Result<Unit> {

View file

@ -76,8 +76,8 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet import io.element.android.libraries.matrix.ui.components.AvatarActionBottomSheet
import io.element.android.libraries.matrix.ui.components.UnsavedAvatar import io.element.android.libraries.matrix.ui.components.UnsavedAvatar
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
@ -121,7 +121,7 @@ fun RoomDetailsEditView(
}, },
) { ) {
Text( Text(
text = stringResource(StringR.string.action_save), text = stringResource(CommonStrings.action_save),
fontSize = 16.sp, fontSize = 16.sp,
) )
} }
@ -145,7 +145,7 @@ fun RoomDetailsEditView(
LabelledTextField( LabelledTextField(
label = stringResource(id = R.string.screen_room_details_room_name_label), label = stringResource(id = R.string.screen_room_details_room_name_label),
value = state.roomName, value = state.roomName,
placeholder = stringResource(StringR.string.common_room_name_placeholder), placeholder = stringResource(CommonStrings.common_room_name_placeholder),
singleLine = true, singleLine = true,
onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) }, onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomName(it)) },
) )
@ -160,9 +160,9 @@ fun RoomDetailsEditView(
if (state.canChangeTopic) { if (state.canChangeTopic) {
LabelledTextField( LabelledTextField(
label = stringResource(StringR.string.common_topic), label = stringResource(CommonStrings.common_topic),
value = state.roomTopic, value = state.roomTopic,
placeholder = stringResource(StringR.string.common_topic_placeholder), placeholder = stringResource(CommonStrings.common_topic_placeholder),
maxLines = 10, maxLines = 10,
onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) }, onValueChange = { state.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(it)) },
) )

View file

@ -28,11 +28,11 @@ import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.apperror.api.AppErrorStateService import io.element.android.services.apperror.api.AppErrorStateService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import io.element.android.libraries.ui.strings.R as StringR
@ContributesNode(RoomScope::class) @ContributesNode(RoomScope::class)
class RoomInviteMembersNode @AssistedInject constructor( class RoomInviteMembersNode @AssistedInject constructor(
@ -65,8 +65,8 @@ class RoomInviteMembersNode @AssistedInject constructor(
if (anyInviteFailed) { if (anyInviteFailed) {
appErrorStateService.showError( appErrorStateService.showError(
title = context.getString(StringR.string.common_unable_to_invite_title), title = context.getString(CommonStrings.common_unable_to_invite_title),
body = context.getString(StringR.string.common_unable_to_invite_message), body = context.getString(CommonStrings.common_unable_to_invite_message),
) )
} }

View file

@ -27,7 +27,7 @@ import androidx.compose.runtime.setValue
import io.element.android.features.roomdetails.impl.members.RoomMemberListDataSource import io.element.android.features.roomdetails.impl.members.RoomMemberListDataSource
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.execute import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
@ -147,7 +147,7 @@ class RoomInviteMembersPresenter @Inject constructor(
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
roomMemberListDataSource.search("").toImmutableList() roomMemberListDataSource.search("").toImmutableList()
} }
}.execute(roomMembers) }.runCatchingUpdatingState(roomMembers)
} }
} }

View file

@ -52,8 +52,8 @@ import io.element.android.libraries.matrix.ui.components.CheckableUserRow
import io.element.android.libraries.matrix.ui.components.SelectedUsersList import io.element.android.libraries.matrix.ui.components.SelectedUsersList
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@ -131,7 +131,7 @@ fun RoomInviteMembersTopBar(
TextButton( TextButton(
onClick = onSendPressed, onClick = onSendPressed,
content = { content = {
Text(stringResource(StringR.string.action_send)) Text(stringResource(CommonStrings.action_send))
}, },
enabled = canSend, enabled = canSend,
) )
@ -147,7 +147,7 @@ private fun RoomInviteMembersSearchBar(
selectedUsers: ImmutableList<MatrixUser>, selectedUsers: ImmutableList<MatrixUser>,
active: Boolean, active: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
placeHolderTitle: String = stringResource(StringR.string.common_search_for_someone), placeHolderTitle: String = stringResource(CommonStrings.common_search_for_someone),
onActiveChanged: (Boolean) -> Unit = {}, onActiveChanged: (Boolean) -> Unit = {},
onTextChanged: (String) -> Unit = {}, onTextChanged: (String) -> Unit = {},
onUserToggled: (MatrixUser) -> Unit = {}, onUserToggled: (MatrixUser) -> Unit = {},

View file

@ -44,7 +44,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import io.element.android.features.roomdetails.impl.R import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.isLoading
import io.element.android.libraries.designsystem.ElementTextStyles import io.element.android.libraries.designsystem.ElementTextStyles
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
@ -61,8 +60,8 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.components.MatrixUserRow import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
@ -100,7 +99,7 @@ fun RoomMemberListView(
query = state.searchQuery, query = state.searchQuery,
state = state.searchResults, state = state.searchResults,
active = state.isSearchActive, active = state.isSearchActive,
placeHolderTitle = stringResource(StringR.string.common_search_for_someone), placeHolderTitle = stringResource(CommonStrings.common_search_for_someone),
onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) }, onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) },
onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) }, onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) },
onUserSelected = ::onUserSelected, onUserSelected = ::onUserSelected,
@ -110,7 +109,7 @@ fun RoomMemberListView(
if (!state.isSearchActive) { if (!state.isSearchActive) {
if (state.roomMembers is Async.Success) { if (state.roomMembers is Async.Success) {
RoomMemberList( RoomMemberList(
roomMembers = state.roomMembers.state, roomMembers = state.roomMembers.data,
showMembersCount = true, showMembersCount = true,
onUserSelected = ::onUserSelected onUserSelected = ::onUserSelected
) )
@ -221,7 +220,7 @@ private fun RoomMemberListTopBar(
onClick = onInvitePressed, onClick = onInvitePressed,
) { ) {
Text( Text(
text = stringResource(StringR.string.action_invite), text = stringResource(CommonStrings.action_invite),
fontSize = 16.sp, fontSize = 16.sp,
) )
} }

View file

@ -57,7 +57,7 @@ import io.element.android.libraries.designsystem.preview.LargeHeightPreview
import io.element.android.libraries.designsystem.theme.components.Scaffold 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.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable @Composable
@ -129,7 +129,7 @@ internal fun RoomMemberHeaderSection(
@Composable @Composable
internal fun RoomMemberMainActionsSection(onShareUser: () -> Unit, modifier: Modifier = Modifier) { internal fun RoomMemberMainActionsSection(onShareUser: () -> Unit, modifier: Modifier = Modifier) {
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
MainActionButton(title = stringResource(StringR.string.action_share), icon = Icons.Outlined.Share, onClick = onShareUser) MainActionButton(title = stringResource(CommonStrings.action_share), icon = Icons.Outlined.Share, onClick = onShareUser)
} }
} }
@ -137,7 +137,7 @@ internal fun RoomMemberMainActionsSection(onShareUser: () -> Unit, modifier: Mod
internal fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier = Modifier) { internal fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(modifier = modifier) { PreferenceCategory(modifier = modifier) {
PreferenceText( PreferenceText(
title = stringResource(StringR.string.action_send_message), title = stringResource(CommonStrings.action_send_message),
icon = Icons.Outlined.ChatBubbleOutline, icon = Icons.Outlined.ChatBubbleOutline,
onClick = onSendMessage, onClick = onSendMessage,
) )

View file

@ -55,8 +55,8 @@ class RoomMemberListPresenterTests {
val loadedState = awaitItem() val loadedState = awaitItem()
Truth.assertThat(loadedState.roomMembers).isInstanceOf(Async.Success::class.java) Truth.assertThat(loadedState.roomMembers).isInstanceOf(Async.Success::class.java)
Truth.assertThat((loadedState.roomMembers as Async.Success).state.invited).isEqualTo(listOf(aVictor(), aWalter())) Truth.assertThat((loadedState.roomMembers as Async.Success).data.invited).isEqualTo(listOf(aVictor(), aWalter()))
Truth.assertThat((loadedState.roomMembers as Async.Success).state.joined).isNotEmpty() Truth.assertThat((loadedState.roomMembers as Async.Success).data.joined).isNotEmpty()
} }
} }

View file

@ -41,7 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -86,13 +86,13 @@ private fun RoomListModalBottomSheetContent(
) )
ListItem( ListItem(
headlineContent = { headlineContent = {
Text(text = stringResource(id = StringR.string.common_settings)) Text(text = stringResource(id = CommonStrings.common_settings))
}, },
modifier = Modifier.clickable { onRoomSettingsClicked(contextMenu.roomId) }, modifier = Modifier.clickable { onRoomSettingsClicked(contextMenu.roomId) },
leadingContent = { leadingContent = {
Icon( Icon(
imageVector = Icons.Outlined.Settings, imageVector = Icons.Outlined.Settings,
contentDescription = stringResource(id = StringR.string.common_settings), contentDescription = stringResource(id = CommonStrings.common_settings),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.onSurface, tint = MaterialTheme.colorScheme.onSurface,
) )
@ -101,7 +101,7 @@ private fun RoomListModalBottomSheetContent(
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Text(
text = stringResource(id = StringR.string.action_leave_room), text = stringResource(id = CommonStrings.action_leave_room),
color = ElementTheme.colors.textActionCritical, color = ElementTheme.colors.textActionCritical,
) )
}, },
@ -109,7 +109,7 @@ private fun RoomListModalBottomSheetContent(
leadingContent = { leadingContent = {
Icon( Icon(
resourceId = VectorIcons.DoorOpen, resourceId = VectorIcons.DoorOpen,
contentDescription = stringResource(id = StringR.string.action_leave_room), contentDescription = stringResource(id = CommonStrings.action_leave_room),
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = ElementTheme.colors.textActionCritical, tint = ElementTheme.colors.textActionCritical,
) )

View file

@ -25,16 +25,16 @@ import io.element.android.libraries.designsystem.utils.SnackbarMessage
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.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import io.element.android.libraries.ui.strings.R as StringR
open class RoomListStateProvider : PreviewParameterProvider<RoomListState> { open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
override val values: Sequence<RoomListState> override val values: Sequence<RoomListState>
get() = sequenceOf( get() = sequenceOf(
aRoomListState(), aRoomListState(),
aRoomListState().copy(displayVerificationPrompt = true), aRoomListState().copy(displayVerificationPrompt = true),
aRoomListState().copy(snackbarMessage = SnackbarMessage(StringR.string.common_verification_complete)), aRoomListState().copy(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
aRoomListState().copy(hasNetworkConnection = false), aRoomListState().copy(hasNetworkConnection = false),
aRoomListState().copy(invitesState = InvitesState.SeenInvites), aRoomListState().copy(invitesState = InvitesState.SeenInvites),
aRoomListState().copy(invitesState = InvitesState.NewInvites), aRoomListState().copy(invitesState = InvitesState.NewInvites),

View file

@ -83,8 +83,8 @@ import io.element.android.libraries.designsystem.theme.roomListUnreadIndicator
import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState import io.element.android.libraries.designsystem.utils.rememberSnackbarHostState
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.designsystem.R as DrawableR
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun RoomListView( fun RoomListView(
@ -229,7 +229,7 @@ fun RoomListContent(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Text(
text = stringResource(StringR.string.action_invites_list), text = stringResource(CommonStrings.action_invites_list),
fontSize = 14.sp, fontSize = 14.sp,
style = noFontPadding, style = noFontPadding,
) )
@ -314,7 +314,7 @@ internal fun RequestVerificationHeader(
Icon( Icon(
modifier = Modifier.clickable(onClick = onDismissClicked), modifier = Modifier.clickable(onClick = onDismissClicked),
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(StringR.string.action_close) contentDescription = stringResource(CommonStrings.action_close)
) )
} }
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@ -325,7 +325,7 @@ internal fun RequestVerificationHeader(
contentPadding = PaddingValues(horizontal = 20.dp, vertical = 7.dp), contentPadding = PaddingValues(horizontal = 20.dp, vertical = 7.dp),
onClick = onVerifyClicked, onClick = onVerifyClicked,
) { ) {
Text(stringResource(StringR.string.action_continue), style = ElementTextStyles.Button) Text(stringResource(CommonStrings.action_continue), style = ElementTextStyles.Button)
} }
} }
} }

View file

@ -48,7 +48,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -109,7 +109,7 @@ private fun DefaultRoomListTopBar(
onClick = onOpenSettings onClick = onOpenSettings
) { ) {
val avatarData by remember { derivedStateOf { matrixUser.getAvatarData() } } val avatarData by remember { derivedStateOf { matrixUser.getAvatarData() } }
Avatar(avatarData, contentDescription = stringResource(StringR.string.common_settings)) Avatar(avatarData, contentDescription = stringResource(CommonStrings.common_settings))
} }
} }
}, },
@ -117,7 +117,7 @@ private fun DefaultRoomListTopBar(
IconButton( IconButton(
onClick = onSearchClicked, onClick = onSearchClicked,
) { ) {
Icon(Icons.Default.Search, contentDescription = stringResource(StringR.string.action_search)) Icon(Icons.Default.Search, contentDescription = stringResource(CommonStrings.action_search))
} }
}, },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,

View file

@ -64,7 +64,7 @@ import io.element.android.libraries.designsystem.theme.components.TextField
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.copy import io.element.android.libraries.designsystem.utils.copy
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
internal fun RoomListSearchResultView( internal fun RoomListSearchResultView(
@ -150,7 +150,7 @@ internal fun RoomListSearchResultContent(
}) { }) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.action_cancel) contentDescription = stringResource(CommonStrings.action_cancel)
) )
} }
} }

View file

@ -51,8 +51,8 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationEmoji
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep as FlowStep import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep as FlowStep
import io.element.android.libraries.ui.strings.R as StringR
@Composable @Composable
fun VerifySelfSessionView( fun VerifySelfSessionView(
@ -190,8 +190,8 @@ internal fun BottomMenu(screenState: VerifySelfSessionState, goBack: () -> Unit)
else -> null else -> null
} }
val negativeButtonTitle = when (verificationViewState) { val negativeButtonTitle = when (verificationViewState) {
FlowStep.Initial -> StringR.string.action_cancel FlowStep.Initial -> CommonStrings.action_cancel
FlowStep.Canceled -> StringR.string.action_cancel FlowStep.Canceled -> CommonStrings.action_cancel
is FlowStep.Verifying -> R.string.screen_session_verification_they_dont_match is FlowStep.Verifying -> R.string.screen_session_verification_they_dont_match
else -> null else -> null
} }

View file

@ -26,4 +26,8 @@ dependencies {
api(libs.dagger) api(libs.dagger)
api(libs.appyx.core) api(libs.appyx.core)
api(libs.androidx.lifecycle.runtime) api(libs.androidx.lifecycle.runtime)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.test.truth)
} }

View file

@ -18,51 +18,152 @@ package io.element.android.libraries.architecture
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* Sealed type that allows to model an asynchronous operation.
*/
@Stable @Stable
sealed interface Async<out T> { sealed interface Async<out T> {
/**
* Represents a failed operation.
*
* @param T the type of data returned by the operation.
* @property error the error that caused the operation to fail.
* @property prevData the data returned by a previous successful run of the operation if any.
*/
data class Failure<out T>(
val error: Throwable,
val prevData: T? = null,
) : Async<T>
/**
* Represents an operation that is currently ongoing.
*
* @param T the type of data returned by the operation.
* @property prevData the data returned by a previous successful run of the operation if any.
*/
data class Loading<out T>(
val prevData: T? = null,
) : Async<T>
/**
* Represents a successful operation.
*
* @param T the type of data returned by the operation.
* @property data the data returned by the operation.
*/
data class Success<out T>(
val data: T,
) : Async<T>
/**
* Represents an uninitialized operation (i.e. yet to be run).
*/
object Uninitialized : Async<Nothing> object Uninitialized : Async<Nothing>
data class Loading<out T>(val prevState: T? = null) : Async<T>
data class Failure<out T>(val error: Throwable, val prevState: T? = null) : Async<T>
data class Success<out T>(val state: T) : Async<T>
fun dataOrNull(): T? { /**
return when (this) { * Returns the data returned by the operation, or null otherwise.
is Failure -> prevState *
is Loading -> prevState * Please note this method may return stale data if the operation is not [Success].
is Success -> state */
Uninitialized -> null fun dataOrNull(): T? = when (this) {
is Failure -> prevData
is Loading -> prevData
is Success -> data
Uninitialized -> null
}
/**
* Returns the error that caused the operation to fail, or null otherwise.
*/
fun errorOrNull(): Throwable? = when (this) {
is Failure -> error
else -> null
}
fun isFailure(): Boolean = this is Failure<T>
fun isLoading(): Boolean = this is Loading<T>
fun isSuccess(): Boolean = this is Success<T>
fun isUninitialized(): Boolean = this == Uninitialized
}
suspend inline fun <T> MutableState<Async<T>>.runCatchingUpdatingState(
errorTransform: (Throwable) -> Throwable = { it },
block: () -> T,
): Result<T> = runUpdatingState(
state = this,
errorTransform = errorTransform,
resultBlock = {
runCatching {
block()
} }
} },
} )
suspend inline fun <T> (suspend () -> T).execute( suspend inline fun <T> (suspend () -> T).runCatchingUpdatingState(
state: MutableState<Async<T>>, state: MutableState<Async<T>>,
errorMapping: ((Throwable) -> Throwable) = { it }, errorTransform: (Throwable) -> Throwable = { it },
) { ): Result<T> = runUpdatingState(
try { state = state,
state.value = Async.Loading() errorTransform = errorTransform,
val result = this() resultBlock = {
state.value = Async.Success(result) runCatching {
} catch (error: Throwable) { this()
state.value = Async.Failure(errorMapping.invoke(error)) }
} },
} )
suspend inline fun <T> (suspend () -> Result<T>).executeResult(state: MutableState<Async<T>>) { suspend inline fun <T> MutableState<Async<T>>.runUpdatingState(
if (state.value !is Async.Success) { errorTransform: (Throwable) -> Throwable = { it },
state.value = Async.Loading() resultBlock: () -> Result<T>,
): Result<T> = runUpdatingState(
state = this,
errorTransform = errorTransform,
resultBlock = resultBlock,
)
/**
* Calls the specified [Result]-returning function [resultBlock]
* encapsulating its progress and return value into an [Async] while
* posting its updates to the MutableState [state].
*
* @param T the type of data returned by the operation.
* @param state the [MutableState] to post updates to.
* @param errorTransform a function to transform the error before posting it.
* @param resultBlock a suspending function that returns a [Result].
* @return the [Result] returned by [resultBlock].
*/
@OptIn(ExperimentalContracts::class)
@Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE")
suspend inline fun <T> runUpdatingState(
state: MutableState<Async<T>>,
errorTransform: (Throwable) -> Throwable = { it },
resultBlock: suspend () -> Result<T>,
): Result<T> {
contract {
callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE)
} }
this().fold( val prevData = state.value.dataOrNull()
state.value = Async.Loading(prevData = prevData)
return resultBlock().fold(
onSuccess = { onSuccess = {
state.value = Async.Success(it) state.value = Async.Success(it)
Result.success(it)
}, },
onFailure = { onFailure = {
state.value = Async.Failure(it) val error = errorTransform(it)
state.value = Async.Failure(
error = error,
prevData = prevData,
)
Result.failure(error)
} }
) )
} }
fun <T> Async<T>.isLoading(): Boolean {
return this is Async.Loading<T>
}

View file

@ -0,0 +1,113 @@
/*
* 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.architecture
import androidx.compose.runtime.MutableState
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.junit.Test
class AsyncKtTest {
@Test
fun `updates state when block returns success`() = runTest {
val state = TestableMutableState<Async<Int>>(Async.Uninitialized)
val result = runUpdatingState(state) {
delay(1)
Result.success(1)
}
assertThat(result.isSuccess).isTrue()
assertThat(result.getOrNull()).isEqualTo(1)
assertThat(state.popFirst()).isEqualTo(Async.Uninitialized)
assertThat(state.popFirst()).isEqualTo(Async.Loading(null))
assertThat(state.popFirst()).isEqualTo(Async.Success(1))
state.assertNoMoreValues()
}
@Test
fun `updates state when block returns failure`() = runTest {
val state = TestableMutableState<Async<Int>>(Async.Uninitialized)
val result = runUpdatingState(state) {
delay(1)
Result.failure(MyThrowable("hello"))
}
assertThat(result.isFailure).isTrue()
assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello"))
assertThat(state.popFirst()).isEqualTo(Async.Uninitialized)
assertThat(state.popFirst()).isEqualTo(Async.Loading(null))
assertThat(state.popFirst()).isEqualTo(Async.Failure<Int>(MyThrowable("hello")))
state.assertNoMoreValues()
}
@Test
fun `updates state when block returns failure transforming the error`() = runTest {
val state = TestableMutableState<Async<Int>>(Async.Uninitialized)
val result = runUpdatingState(state, { MyThrowable(it.message + " world") }) {
delay(1)
Result.failure(MyThrowable("hello"))
}
assertThat(result.isFailure).isTrue()
assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello world"))
assertThat(state.popFirst()).isEqualTo(Async.Uninitialized)
assertThat(state.popFirst()).isEqualTo(Async.Loading(null))
assertThat(state.popFirst()).isEqualTo(Async.Failure<Int>(MyThrowable("hello world")))
state.assertNoMoreValues()
}
}
/**
* A fake [MutableState] that allows to record all the states that were set.
*/
private class TestableMutableState<T>(
value: T
) : MutableState<T> {
private val _deque = ArrayDeque<T>(listOf(value))
override var value: T
get() = _deque.last()
set(value) {
_deque.addLast(value)
}
/**
* Returns the states that were set in the order they were set.
*/
fun popFirst(): T = _deque.removeFirst()
fun assertNoMoreValues() {
assertThat(_deque).isEmpty()
}
override operator fun component1(): T = value
override operator fun component2(): (T) -> Unit = { value = it }
}
/**
* An exception that is also a data class so we can compare it using equals.
*/
private data class MyThrowable(val myMessage: String) : Throwable(myMessage)

View file

@ -31,7 +31,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Button import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun AsyncFailure( fun AsyncFailure(
@ -45,11 +45,11 @@ fun AsyncFailure(
.padding(vertical = 32.dp), .padding(vertical = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text(text = throwable.message ?: stringResource(id = StringR.string.error_unknown)) Text(text = throwable.message ?: stringResource(id = CommonStrings.error_unknown))
if (onRetry != null) { if (onRetry != null) {
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Button(onClick = onRetry) { Button(onClick = onRetry) {
Text(text = stringResource(id = StringR.string.action_retry)) Text(text = stringResource(id = CommonStrings.action_retry))
} }
} }
} }

View file

@ -28,14 +28,14 @@ import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun BackButton( fun BackButton(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
imageVector: ImageVector = Icons.Default.ArrowBack, imageVector: ImageVector = Icons.Default.ArrowBack,
contentDescription: String = stringResource(StringR.string.action_back), contentDescription: String = stringResource(CommonStrings.action_back),
enabled: Boolean = true, enabled: Boolean = true,
) { ) {
IconButton( IconButton(

View file

@ -31,7 +31,7 @@ import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.BooleanProvider import io.element.android.libraries.designsystem.utils.BooleanProvider
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -41,8 +41,8 @@ fun ConfirmationDialog(
onDismiss: () -> Unit, onDismiss: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String? = null, title: String? = null,
submitText: String = stringResource(id = StringR.string.action_ok), submitText: String = stringResource(id = CommonStrings.action_ok),
cancelText: String = stringResource(id = StringR.string.action_cancel), cancelText: String = stringResource(id = CommonStrings.action_cancel),
thirdButtonText: String? = null, thirdButtonText: String? = null,
emphasizeSubmitButton: Boolean = false, emphasizeSubmitButton: Boolean = false,
onCancelClicked: () -> Unit = onDismiss, onCancelClicked: () -> Unit = onDismiss,

View file

@ -28,7 +28,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -91,8 +91,8 @@ private fun ErrorDialogContent(
} }
object ErrorDialogDefaults { object ErrorDialogDefaults {
val title: String @Composable get() = stringResource(id = StringR.string.dialog_title_error) val title: String @Composable get() = stringResource(id = CommonStrings.dialog_title_error)
val submitText: String @Composable get() = stringResource(id = StringR.string.action_ok) val submitText: String @Composable get() = stringResource(id = CommonStrings.action_ok)
} }
@Preview(group = PreviewGroup.Dialogs) @Preview(group = PreviewGroup.Dialogs)

View file

@ -29,7 +29,7 @@ import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun RetryDialog( fun RetryDialog(
@ -109,9 +109,9 @@ private fun RetryDialogContent(
} }
object RetryDialogDefaults { object RetryDialogDefaults {
val title: String @Composable get() = stringResource(id = StringR.string.dialog_title_error) val title: String @Composable get() = stringResource(id = CommonStrings.dialog_title_error)
val retryText: String @Composable get() = stringResource(id = StringR.string.action_retry) val retryText: String @Composable get() = stringResource(id = CommonStrings.action_retry)
val dismissText: String @Composable get() = stringResource(id = StringR.string.action_cancel) val dismissText: String @Composable get() = stringResource(id = CommonStrings.action_cancel)
} }
@Preview(group = PreviewGroup.Dialogs) @Preview(group = PreviewGroup.Dialogs)

View file

@ -47,7 +47,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementThemedPreview import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.LocalColors import io.element.android.libraries.designsystem.theme.LocalColors
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -100,7 +100,7 @@ fun <T> SearchBar(
IconButton(onClick = { onQueryChange("") }) { IconButton(onClick = { onQueryChange("") }) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.action_clear), contentDescription = stringResource(CommonStrings.action_clear),
) )
} }
} }
@ -110,7 +110,7 @@ fun <T> SearchBar(
{ {
Icon( Icon(
imageVector = Icons.Default.Search, imageVector = Icons.Default.Search,
contentDescription = stringResource(R.string.action_search), contentDescription = stringResource(CommonStrings.action_search),
tint = MaterialTheme.colorScheme.tertiary, tint = MaterialTheme.colorScheme.tertiary,
) )
} }
@ -135,7 +135,7 @@ fun <T> SearchBar(
Spacer(Modifier.size(80.dp)) Spacer(Modifier.size(80.dp))
Text( Text(
text = stringResource(R.string.common_no_results), text = stringResource(CommonStrings.common_no_results),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.tertiary, color = MaterialTheme.colorScheme.tertiary,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()

View file

@ -47,9 +47,9 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType import io.element.android.libraries.matrix.api.timeline.item.event.UnknownMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject import javax.inject.Inject
import io.element.android.libraries.ui.strings.R as StringR
@ContributesBinding(SessionScope::class) @ContributesBinding(SessionScope::class)
class DefaultRoomLastMessageFormatter @Inject constructor( class DefaultRoomLastMessageFormatter @Inject constructor(
@ -66,7 +66,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
return when (val content = event.content) { return when (val content = event.content) {
is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom) is MessageContent -> processMessageContents(content, senderDisplayName, isDmRoom)
RedactedContent -> { RedactedContent -> {
val message = sp.getString(StringR.string.common_message_removed) val message = sp.getString(CommonStrings.common_message_removed)
if (!isDmRoom) { if (!isDmRoom) {
prefix(message, senderDisplayName) prefix(message, senderDisplayName)
} else { } else {
@ -77,7 +77,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
content.body content.body
} }
is UnableToDecryptContent -> { is UnableToDecryptContent -> {
val message = sp.getString(StringR.string.common_decryption_error) val message = sp.getString(CommonStrings.common_decryption_error)
if (!isDmRoom) { if (!isDmRoom) {
prefix(message, senderDisplayName) prefix(message, senderDisplayName)
} else { } else {
@ -94,7 +94,7 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList) stateContentFormatter.format(content, senderDisplayName, isOutgoing, RenderingMode.RoomList)
} }
is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> { is FailedToParseMessageLikeContent, is FailedToParseStateContent, is UnknownContent -> {
prefixIfNeeded(sp.getString(StringR.string.common_unsupported_event), senderDisplayName, isDmRoom) prefixIfNeeded(sp.getString(CommonStrings.common_unsupported_event), senderDisplayName, isDmRoom)
} }
} }
} }
@ -111,19 +111,19 @@ class DefaultRoomLastMessageFormatter @Inject constructor(
messageType.body messageType.body
} }
is VideoMessageType -> { is VideoMessageType -> {
sp.getString(StringR.string.common_video) sp.getString(CommonStrings.common_video)
} }
is ImageMessageType -> { is ImageMessageType -> {
sp.getString(StringR.string.common_image) sp.getString(CommonStrings.common_image)
} }
is FileMessageType -> { is FileMessageType -> {
sp.getString(StringR.string.common_file) sp.getString(CommonStrings.common_file)
} }
is AudioMessageType -> { is AudioMessageType -> {
sp.getString(StringR.string.common_audio) sp.getString(CommonStrings.common_audio)
} }
UnknownMessageType -> { UnknownMessageType -> {
sp.getString(StringR.string.common_unsupported_event) sp.getString(CommonStrings.common_unsupported_event)
} }
is NoticeMessageType -> { is NoticeMessageType -> {
messageType.body messageType.body

View file

@ -34,7 +34,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.strings.StringProvider
import javax.inject.Inject import javax.inject.Inject
@ -71,7 +71,7 @@ class DefaultTimelineEventFormatter @Inject constructor(
if (buildMeta.isDebuggable) { if (buildMeta.isDebuggable) {
error("You should not use this formatter for this event: $event") error("You should not use this formatter for this event: $event")
} }
sp.getString(R.string.common_unsupported_event) sp.getString(CommonStrings.common_unsupported_event)
} }
} }
} }

View file

@ -19,10 +19,10 @@ package io.element.android.libraries.eventformatter.impl
import io.element.android.libraries.eventformatter.impl.mode.RenderingMode import io.element.android.libraries.eventformatter.impl.mode.RenderingMode
import io.element.android.libraries.matrix.api.timeline.item.event.OtherState import io.element.android.libraries.matrix.api.timeline.item.event.OtherState
import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.strings.StringProvider
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import io.element.android.libraries.ui.strings.R as StringR
class StateContentFormatter @Inject constructor( class StateContentFormatter @Inject constructor(
private val sp: StringProvider, private val sp: StringProvider,
@ -50,7 +50,7 @@ class StateContentFormatter @Inject constructor(
sp.getString(R.string.state_event_room_created, senderDisplayName) sp.getString(R.string.state_event_room_created, senderDisplayName)
} }
} }
is OtherState.RoomEncryption -> sp.getString(StringR.string.common_encryption_enabled) is OtherState.RoomEncryption -> sp.getString(CommonStrings.common_encryption_enabled)
is OtherState.RoomName -> { is OtherState.RoomName -> {
val hasRoomName = content.name != null val hasRoomName = content.name != null
when { when {

View file

@ -48,7 +48,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
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.RoomSummaryDetails import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun SelectedRoom( fun SelectedRoom(
@ -84,7 +84,7 @@ fun SelectedRoom(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(id = StringR.string.action_remove), contentDescription = stringResource(id = CommonStrings.action_remove),
tint = MaterialTheme.colorScheme.onPrimary, tint = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.padding(2.dp) modifier = Modifier.padding(2.dp)
) )

View file

@ -47,7 +47,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun SelectedUser( fun SelectedUser(
@ -83,7 +83,7 @@ fun SelectedUser(
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(id = StringR.string.action_remove), contentDescription = stringResource(id = CommonStrings.action_remove),
tint = MaterialTheme.colorScheme.onPrimary, tint = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.padding(2.dp) modifier = Modifier.padding(2.dp)
) )

View file

@ -48,7 +48,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.noFontPadding import io.element.android.libraries.designsystem.theme.noFontPadding
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun UnresolvedUserRow( fun UnresolvedUserRow(
@ -94,7 +94,7 @@ fun UnresolvedUserRow(
) )
Text( Text(
text = stringResource(R.string.common_invite_unknown_profile), text = stringResource(CommonStrings.common_invite_unknown_profile),
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
fontSize = 12.sp, fontSize = 12.sp,
lineHeight = 16.sp, lineHeight = 16.sp,

View file

@ -23,7 +23,7 @@ import androidx.compose.material.icons.outlined.PhotoCamera
import androidx.compose.material.icons.outlined.PhotoLibrary import androidx.compose.material.icons.outlined.PhotoLibrary
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import io.element.android.libraries.ui.strings.R import io.element.android.libraries.ui.strings.CommonStrings
@Immutable @Immutable
sealed class AvatarAction( sealed class AvatarAction(
@ -31,7 +31,7 @@ sealed class AvatarAction(
val icon: ImageVector, val icon: ImageVector,
val destructive: Boolean = false, val destructive: Boolean = false,
) { ) {
object TakePhoto : AvatarAction(titleResId = R.string.action_take_photo, icon = Icons.Outlined.PhotoCamera) object TakePhoto : AvatarAction(titleResId = CommonStrings.action_take_photo, icon = Icons.Outlined.PhotoCamera)
object ChoosePhoto : AvatarAction(titleResId = R.string.action_choose_photo, icon = Icons.Outlined.PhotoLibrary) object ChoosePhoto : AvatarAction(titleResId = CommonStrings.action_choose_photo, icon = Icons.Outlined.PhotoLibrary)
object Remove : AvatarAction(titleResId = R.string.action_remove, icon = Icons.Outlined.Delete, destructive = true) object Remove : AvatarAction(titleResId = CommonStrings.action_remove, icon = Icons.Outlined.Delete, destructive = true)
} }

View file

@ -79,7 +79,7 @@ import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail import io.element.android.libraries.matrix.ui.components.AttachmentThumbnail
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo
import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType
import io.element.android.libraries.ui.strings.R as StringR import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -146,7 +146,7 @@ fun TextComposer(
contentPadding = PaddingValues(top = 10.dp, bottom = 10.dp, start = 12.dp, end = 42.dp), contentPadding = PaddingValues(top = 10.dp, bottom = 10.dp, start = 12.dp, end = 42.dp),
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
placeholder = { placeholder = {
Text(stringResource(StringR.string.common_message), style = defaultTypography) Text(stringResource(CommonStrings.common_message), style = defaultTypography)
}, },
colors = TextFieldDefaults.colors( colors = TextFieldDefaults.colors(
unfocusedTextColor = MaterialTheme.colorScheme.secondary, unfocusedTextColor = MaterialTheme.colorScheme.secondary,
@ -225,7 +225,7 @@ private fun EditingModeView(
) )
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(StringR.string.action_close), contentDescription = stringResource(CommonStrings.action_close),
tint = MaterialTheme.colorScheme.secondary, tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier modifier = Modifier
.size(16.dp) .size(16.dp)
@ -283,7 +283,7 @@ private fun ReplyToModeView(
) )
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = stringResource(StringR.string.action_close), contentDescription = stringResource(CommonStrings.action_close),
tint = MaterialTheme.colorScheme.secondary, tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier modifier = Modifier
.size(16.dp) .size(16.dp)
@ -367,8 +367,8 @@ private fun BoxScope.SendButton(
else -> R.drawable.ic_send else -> R.drawable.ic_send
} }
val contentDescription = when (composerMode) { val contentDescription = when (composerMode) {
is MessageComposerMode.Edit -> stringResource(StringR.string.action_edit) is MessageComposerMode.Edit -> stringResource(CommonStrings.action_edit)
else -> stringResource(StringR.string.action_send) else -> stringResource(CommonStrings.action_send)
} }
Icon( Icon(
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),

View file

@ -0,0 +1,19 @@
/*
* 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.ui.strings
typealias CommonPlurals = R.plurals

View file

@ -0,0 +1,19 @@
/*
* 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.ui.strings
typealias CommonStrings = R.string

View file

@ -130,5 +130,5 @@ System\.currentTimeMillis\(\)===1
### Suspicious String template. Please check that the string template will behave as expected, i.e. the class field and not the whole object will be used. For instance `Timber.d("$event.type")` is not correct, you should write `Timber.d("${event.type}")`. In the former the whole event content will be logged, since it's a data class. If this is expected (i.e. to fix false positive), please add explicit curly braces (`{` and `}`) around the variable, for instance `"elementLogs.${i}.txt"` ### Suspicious String template. Please check that the string template will behave as expected, i.e. the class field and not the whole object will be used. For instance `Timber.d("$event.type")` is not correct, you should write `Timber.d("${event.type}")`. In the former the whole event content will be logged, since it's a data class. If this is expected (i.e. to fix false positive), please add explicit curly braces (`{` and `}`) around the variable, for instance `"elementLogs.${i}.txt"`
\$[a-zA-Z_]\w*\??\.[a-zA-Z_] \$[a-zA-Z_]\w*\??\.[a-zA-Z_]
### Use `import io.element.android.libraries.ui.strings.R as StringsR` then `StringR.string.<stringKey>` instead ### Use `import io.element.android.libraries.ui.strings.CommonStrings` then `CommonStrings.<stringKey>` instead
io\.element\.android\.libraries\.ui\.strings\.R\. import io\.element\.android\.libraries\.ui\.strings\.R

View file

@ -155,14 +155,6 @@ if (buildFilesWithMissingProcessor.length > 0) {
warn("You have made changes to a file containing a `@Preview` annotated function but its module doesn't include the showkase processor. Missing processor in: " + buildFilesWithMissingProcessor.join(", ")) warn("You have made changes to a file containing a `@Preview` annotated function but its module doesn't include the showkase processor. Missing processor in: " + buildFilesWithMissingProcessor.join(", "))
} }
// Check for screenshots on view changes
const hasChangedViews = filesWithPreviews.length > 0
if (hasChangedViews) {
if (!pr.body.includes("user-images")) {
warn("You seem to have made changes to views. Please consider adding screenshots.")
}
}
// Check for pngs on resources // Check for pngs on resources
const hasPngs = editedFiles.filter(file => { const hasPngs = editedFiles.filter(file => {
file.toLowerCase().endsWith(".png") && !file.includes("snapshots/images/") // Exclude screenshots file.toLowerCase().endsWith(".png") && !file.includes("snapshots/images/") // Exclude screenshots