Merge branch 'develop' into feature/bma/removeOldResources

This commit is contained in:
Benoit Marty
2023-06-27 16:09:30 +02:00
85 changed files with 562 additions and 321 deletions
+5
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`).
@@ -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)
} }
} }
@@ -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"
@@ -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))
} }
} }
} }
@@ -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,
@@ -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 = {},
@@ -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 {
@@ -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,
) )
@@ -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 {
@@ -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)
} }
} }
@@ -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,
) )
} }
@@ -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 {
@@ -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))
} }
) )
}, },
@@ -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),
@@ -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) }
) )
} }
@@ -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))
} }
} }
} }
@@ -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)
} }
} }
@@ -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),
) )
} }
@@ -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
} }
} }
@@ -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)
} }
} }
@@ -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()
} }
@@ -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)
@@ -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,
@@ -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
@@ -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,
@@ -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)
} }
} }
@@ -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))
} }
} }
@@ -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) }
} }
} }
@@ -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))
} }
} }
} }
@@ -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
} }
} }
@@ -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
@@ -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,
@@ -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
} }
} }
} }
@@ -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))
} }
} }
) )
@@ -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) }
} }
} }
@@ -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 = {
@@ -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,
) )
@@ -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
) )
@@ -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
) )
@@ -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
) )
@@ -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)
} }
} }
} }
@@ -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,
) )
@@ -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)
} }
} }
@@ -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") {
@@ -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
) )
@@ -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,
@@ -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,
@@ -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[ */,
@@ -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) {
@@ -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),
@@ -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> {
@@ -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)) },
) )
@@ -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),
) )
} }
@@ -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)
} }
} }
@@ -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 = {},
@@ -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,
) )
} }
@@ -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,
) )
@@ -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()
} }
} }
@@ -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,
) )
@@ -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),
@@ -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)
} }
} }
} }
@@ -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,
@@ -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)
) )
} }
} }
@@ -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
} }
+4
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)
} }
@@ -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> {
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) { * Represents a failed operation.
is Failure -> prevState *
is Loading -> prevState * @param T the type of data returned by the operation.
is Success -> state * @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>
/**
* Returns the data returned by the operation, or null otherwise.
*
* Please note this method may return stale data if the operation is not [Success].
*/
fun dataOrNull(): T? = when (this) {
is Failure -> prevData
is Loading -> prevData
is Success -> data
Uninitialized -> null Uninitialized -> null
} }
}
/**
* Returns the error that caused the operation to fail, or null otherwise.
*/
fun errorOrNull(): Throwable? = when (this) {
is Failure -> error
else -> null
} }
suspend inline fun <T> (suspend () -> T).execute( 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).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>
}
@@ -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)
@@ -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))
} }
} }
} }
@@ -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(
@@ -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,
@@ -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)
@@ -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)
@@ -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()
@@ -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
@@ -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)
} }
} }
} }
@@ -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 {
@@ -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)
) )
@@ -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)
) )
@@ -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,
@@ -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)
} }
@@ -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),
@@ -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
@@ -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
+2 -2
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
-8
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