Browse Source

Merge remote-tracking branch 'origin/develop' into feature/fre/start_chat_with_matrix_id

test/jme/compound-poc
Florian Renaud 1 year ago
parent
commit
8132dc15e7
  1. 28
      .github/workflows/sync-localazy.yml
  2. 34
      CONTRIBUTING.md
  3. 7
      app/src/main/kotlin/io/element/android/x/di/AppModule.kt
  4. 1
      appnav/build.gradle.kts
  5. 72
      appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt
  6. 13
      appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt
  7. 18
      appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt
  8. 1
      changelog.d/286.feature
  9. 6
      features/createroom/impl/src/main/res/values-es/translations.xml
  10. 6
      features/createroom/impl/src/main/res/values-it/translations.xml
  11. 6
      features/createroom/impl/src/main/res/values-ro/translations.xml
  12. 20
      features/login/impl/src/main/res/values-es/translations.xml
  13. 20
      features/login/impl/src/main/res/values-it/translations.xml
  14. 20
      features/login/impl/src/main/res/values-ro/translations.xml
  15. 8
      features/logout/api/src/main/res/values-es/translations.xml
  16. 8
      features/logout/api/src/main/res/values-it/translations.xml
  17. 8
      features/logout/api/src/main/res/values-ro/translations.xml
  18. 5
      features/onboarding/impl/src/main/res/values-es/translations.xml
  19. 5
      features/onboarding/impl/src/main/res/values-it/translations.xml
  20. 5
      features/onboarding/impl/src/main/res/values-ro/translations.xml
  21. 5
      features/rageshake/api/src/main/res/values-es/translations.xml
  22. 5
      features/rageshake/api/src/main/res/values-it/translations.xml
  23. 5
      features/rageshake/api/src/main/res/values-ro/translations.xml
  24. 14
      features/rageshake/impl/src/main/res/values-es/translations.xml
  25. 14
      features/rageshake/impl/src/main/res/values-it/translations.xml
  26. 14
      features/rageshake/impl/src/main/res/values-ro/translations.xml
  27. 4
      features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt
  28. 5
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt
  29. 6
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt
  30. 39
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt
  31. 27
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt
  32. 4
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt
  33. 46
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt
  34. 21
      features/roomdetails/impl/src/main/res/values-es/translations.xml
  35. 21
      features/roomdetails/impl/src/main/res/values-it/translations.xml
  36. 22
      features/roomdetails/impl/src/main/res/values-ro/translations.xml
  37. 6
      features/roomdetails/impl/src/main/res/values/localazy.xml
  38. 138
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt
  39. 1
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt
  40. 19
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  41. 3
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
  42. 6
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
  43. 24
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt
  44. 61
      features/roomlist/impl/src/main/res/values-es/translations.xml
  45. 61
      features/roomlist/impl/src/main/res/values-it/translations.xml
  46. 61
      features/roomlist/impl/src/main/res/values-ro/translations.xml
  47. 35
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
  48. 19
      features/verifysession/impl/src/main/res/values-es/translations.xml
  49. 19
      features/verifysession/impl/src/main/res/values-it/translations.xml
  50. 19
      features/verifysession/impl/src/main/res/values-ro/translations.xml
  51. 2
      features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt
  52. 2
      gradle/libs.versions.toml
  53. 4
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt
  54. 72
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcher.kt
  55. 3
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt
  56. 4
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt
  57. 40
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt
  58. 8
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt
  59. 8
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt
  60. 7
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt
  61. 5
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt
  62. 9
      libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt
  63. 17
      libraries/textcomposer/src/main/res/values-es/translations.xml
  64. 17
      libraries/textcomposer/src/main/res/values-it/translations.xml
  65. 17
      libraries/textcomposer/src/main/res/values-ro/translations.xml
  66. 1
      libraries/ui-strings/src/main/res/values-de/translations.xml
  67. 148
      libraries/ui-strings/src/main/res/values-es/translations.xml
  68. 1
      libraries/ui-strings/src/main/res/values-fr/translations.xml
  69. 148
      libraries/ui-strings/src/main/res/values-it/translations.xml
  70. 150
      libraries/ui-strings/src/main/res/values-ro/translations.xml
  71. 11
      libraries/ui-strings/src/main/res/values/localazy.xml
  72. 4
      samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
  73. 47
      tools/localazy/README.md
  74. 18
      tools/localazy/downloadStrings.sh
  75. 60
      tools/localazy/generateLocalazyConfig.py

28
.github/workflows/sync-localazy.yml

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
name: Sync Localazy
on:
schedule:
# At 00:00 on every Monday UTC
- cron: '0 0 * * 1'
jobs:
sync-localazy:
runs-on: ubuntu-latest
# Skip in forks
if: github.repository == 'vector-im/element-x-android'
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Run Localazy script
run: ./tools/localazy/downloadStrings.sh --all
- name: Create Pull Request for Strings
uses: peter-evans/create-pull-request@v5
with:
commit-message: Sync Strings from Localazy
title: Sync Strings
body: |
- Update Strings from Localazy
branch: sync-localazy
base: develop

34
CONTRIBUTING.md

@ -5,7 +5,9 @@ @@ -5,7 +5,9 @@
* [Contributing code to Matrix](#contributing-code-to-matrix)
* [Android Studio settings](#android-studio-settings)
* [Compilation](#compilation)
* [I want to help translating Element](#i-want-to-help-translating-element)
* [Strings](#strings)
* [I want to add new strings to the project](#i-want-to-add-new-strings-to-the-project)
* [I want to help translating Element](#i-want-to-help-translating-element)
* [I want to submit a PR to fix an issue](#i-want-to-submit-a-pr-to-fix-an-issue)
* [Kotlin](#kotlin)
* [Changelog](#changelog)
@ -15,7 +17,6 @@ @@ -15,7 +17,6 @@
* [lint](#lint)
* [Unit tests](#unit-tests)
* [Tests](#tests)
* [Internationalisation](#internationalisation)
* [Accessibility](#accessibility)
* [Jetpack Compose](#jetpack-compose)
* [Authors](#authors)
@ -40,11 +41,28 @@ Please ensure that you're using the project formatting rules (which are in the p @@ -40,11 +41,28 @@ Please ensure that you're using the project formatting rules (which are in the p
This project should compile without any special action. Just clone it and open it with Android Studio, or compile from command line using `gradlew`.
## I want to help translating Element
Note: please make sure that the configuration is `app` and not `samples.minimal`.
For now strings are coming from Element Android project, so:
- If you want to fix an issue with an English string, please submit a PR on Element Android.
- If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.element.io/projects/element-android/).
## Strings
The strings of the project are managed externally using [https://localazy.com](https://localazy.com) and shared with ElementX iOS.
### I want to add new strings to the project
Only the core team can modify or add English strings to Localazy. As an external contributor, if you want to add new strings, feel free to add an Android resource file to the project (for instance a file named `temporary.xml`), with a note in the description of the PR for the reviewer to integrate the String into `Localazy`. If accepted, the reviewer will add the String(s) for you, and then you can download them on your branch (following these [instructions](./tools/localazy/README.md#download-translations)) and remove the temporary file.
Please follow the naming rules for the key. More details in [the dedicated section in this README.md](./tools/localazy/README.md#key-naming-rules)
### I want to help translating Element
Please note that the Localazy project is not open yet for external contributions.
To help translating, please go to [https://localazy.com/p/element](https://localazy.com/p/element).
- If you want to fix an issue with an English string, please open an issue on the github project of ElementX (Android or iOS). Only the core team can modify or add English strings.
- If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please go to [https://localazy.com/p/element](https://localazy.com/p/element).
More informations can be found [in this README.md](./tools/localazy/README.md).
## I want to submit a PR to fix an issue
@ -135,10 +153,6 @@ Also, if possible, please test your change on a real device. Testing on Android @@ -135,10 +153,6 @@ Also, if possible, please test your change on a real device. Testing on Android
You should consider adding Unit tests with your PR, and also integration tests (AndroidTest). Please refer to [this document](./docs/integration_tests.md) to install and run the integration test environment.
### Internationalisation
For now strings are coming from Element Android project, so please read [the documentation](https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#internationalisation) from there.
### Accessibility
Please consider accessibility as an important point. As a minimum requirement, in layout XML files please use attributes such as `android:contentDescription` and `android:importantForAccessibility`, and test with a screen reader if it's working well. You can add new string resources, dedicated to accessibility, in this case, please prefix theirs id with `a11y_`.

7
app/src/main/kotlin/io/element/android/x/di/AppModule.kt

@ -22,6 +22,7 @@ import dagger.Module @@ -22,6 +22,7 @@ import dagger.Module
import dagger.Provides
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn
@ -78,4 +79,10 @@ object AppModule { @@ -78,4 +79,10 @@ object AppModule {
diffUpdateDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
)
}
@Provides
@SingleIn(AppScope::class)
fun provideSnackbarDispatcher(): SnackbarDispatcher {
return SnackbarDispatcher()
}
}

1
appnav/build.gradle.kts

@ -45,6 +45,7 @@ dependencies { @@ -45,6 +45,7 @@ dependencies {
implementation(projects.libraries.matrix.api)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.matrixui)
implementation(projects.libraries.uiStrings)
implementation(projects.features.verifysession.api)
implementation(projects.features.roomdetails.api)
implementation(projects.tests.uitests)

72
appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
/*
* 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.appnav
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.SnackbarMessage
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.VerificationFlowState
import io.element.android.libraries.ui.strings.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
class LoggedInEventProcessor @Inject constructor(
private val snackbarDispatcher: SnackbarDispatcher,
roomMembershipObserver: RoomMembershipObserver,
sessionVerificationService: SessionVerificationService,
) {
private var observingJob: Job? = null
private val displayLeftRoomMessage = roomMembershipObserver.updates
.map { !it.isUserInRoom }
private val displayVerificationSuccessfulMessage = sessionVerificationService.verificationFlowState
.map { it == VerificationFlowState.Finished }
fun observeEvents(coroutineScope: CoroutineScope) {
observingJob = coroutineScope.launch {
displayLeftRoomMessage.onEach {
displayMessage(R.string.common_current_user_left_room)
}.launchIn(this)
displayVerificationSuccessfulMessage
.drop(1)
.onEach {
displayMessage(R.string.common_verification_complete)
}.launchIn(this)
}
}
fun stopObserving() {
observingJob?.cancel()
observingJob = null
}
private suspend fun displayMessage(message: Int) {
snackbarDispatcher.post(SnackbarMessage(message))
}
}

13
appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

@ -47,13 +47,17 @@ import io.element.android.libraries.architecture.bindings @@ -47,13 +47,17 @@ import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.parcelize.Parcelize
import kotlin.coroutines.coroutineContext
@ContributesNode(AppScope::class)
class LoggedInFlowNode @AssistedInject constructor(
@ -64,6 +68,8 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -64,6 +68,8 @@ class LoggedInFlowNode @AssistedInject constructor(
private val createRoomEntryPoint: CreateRoomEntryPoint,
private val appNavigationStateService: AppNavigationStateService,
private val verifySessionEntryPoint: VerifySessionEntryPoint,
private val coroutineScope: CoroutineScope,
snackbarDispatcher: SnackbarDispatcher,
) : BackstackNode<LoggedInFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.RoomList,
@ -88,6 +94,11 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -88,6 +94,11 @@ class LoggedInFlowNode @AssistedInject constructor(
) : NodeInputs
private val inputs: Inputs = inputs()
private val loggedInFlowProcessor = LoggedInEventProcessor(
snackbarDispatcher,
inputs.matrixClient.roomMembershipObserver(),
inputs.matrixClient.sessionVerificationService(),
)
override fun onBuilt() {
super.onBuilt()
@ -100,6 +111,7 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -100,6 +111,7 @@ class LoggedInFlowNode @AssistedInject constructor(
appNavigationStateService.onNavigateToSession(inputs.matrixClient.sessionId)
// TODO We do not support Space yet, so directly navigate to main space
appNavigationStateService.onNavigateToSpace(MAIN_SPACE)
loggedInFlowProcessor.observeEvents(coroutineScope)
},
onDestroy = {
val imageLoaderFactory = bindings<MatrixUIBindings>().notLoggedInImageLoaderFactory()
@ -107,6 +119,7 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -107,6 +119,7 @@ class LoggedInFlowNode @AssistedInject constructor(
plugins<LifecycleCallback>().forEach { it.onFlowReleased(inputs.matrixClient) }
appNavigationStateService.onLeavingSpace()
appNavigationStateService.onLeavingSession()
loggedInFlowProcessor.stopObserving()
}
)
}

18
appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt

@ -18,6 +18,7 @@ package io.element.android.appnav @@ -18,6 +18,7 @@ package io.element.android.appnav
import android.os.Parcelable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.lifecycle.subscribe
@ -38,7 +39,12 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi @@ -38,7 +39,12 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import timber.log.Timber
@ -49,6 +55,8 @@ class RoomFlowNode @AssistedInject constructor( @@ -49,6 +55,8 @@ class RoomFlowNode @AssistedInject constructor(
private val messagesEntryPoint: MessagesEntryPoint,
private val roomDetailsEntryPoint: RoomDetailsEntryPoint,
private val appNavigationStateService: AppNavigationStateService,
roomMembershipObserver: RoomMembershipObserver,
coroutineScope: CoroutineScope,
) : BackstackNode<RoomFlowNode.NavTarget>(
backstack = BackStack(
initialElement = NavTarget.Messages,
@ -68,6 +76,7 @@ class RoomFlowNode @AssistedInject constructor( @@ -68,6 +76,7 @@ class RoomFlowNode @AssistedInject constructor(
) : NodeInputs
private val inputs: Inputs = inputs()
private val timeline = inputs.room.timeline()
private val roomFlowPresenter = RoomFlowPresenter(inputs.room)
@ -85,6 +94,13 @@ class RoomFlowNode @AssistedInject constructor( @@ -85,6 +94,13 @@ class RoomFlowNode @AssistedInject constructor(
appNavigationStateService.onLeavingRoom()
}
)
roomMembershipObserver.updates
.filter { update -> update.roomId == inputs.room.roomId && !update.isUserInRoom }
.onEach {
navigateUp()
}
.launchIn(coroutineScope)
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
@ -97,7 +113,7 @@ class RoomFlowNode @AssistedInject constructor( @@ -97,7 +113,7 @@ class RoomFlowNode @AssistedInject constructor(
})
}
NavTarget.RoomDetails -> {
roomDetailsEntryPoint.createNode(this, buildContext)
roomDetailsEntryPoint.createNode(this, buildContext, emptyList())
}
}
}

1
changelog.d/286.feature

@ -0,0 +1 @@ @@ -0,0 +1 @@
Add leave room functionality to the Room Details screen.

6
features/createroom/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nueva sala"</string>
<string name="screen_create_room_action_invite_people">"Invitar gente"</string>
<string name="screen_create_room_add_people_title">"Añadir personas"</string>
</resources>

6
features/createroom/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Nuova stanza"</string>
<string name="screen_create_room_action_invite_people">"Invita persone"</string>
<string name="screen_create_room_add_people_title">"Aggiungi persone"</string>
</resources>

6
features/createroom/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Cameră nouă"</string>
<string name="screen_create_room_action_invite_people">"Invitați persoane"</string>
<string name="screen_create_room_add_people_title">"Adaugați persoane"</string>
</resources>

20
features/login/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_change_server_error_invalid_homeserver">"No hemos podido acceder a este servidor. Comprueba que has introducido correctamente la dirección del servidor. Si la dirección es correcta, ponte en contacto con el administrador del servidor para obtener más ayuda."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Este servidor no soporta sliding sync."</string>
<string name="screen_change_server_form_header">"Dirección del homeserver"</string>
<string name="screen_change_server_form_notice">"Solo puedes conectarte a un servidor que soporte sliding sync. El administrador de tu servidor tendrá que configurarlo. %1$s"</string>
<string name="screen_change_server_submit">"Continuar"</string>
<string name="screen_change_server_subtitle">"¿Cuál es la dirección de tu servidor?"</string>
<string name="screen_change_server_title">"Selecciona tu servidor"</string>
<string name="screen_login_error_deactivated_account">"Esta cuenta ha sido desactivada."</string>
<string name="screen_login_error_invalid_credentials">"Usuario y/o contraseña incorrectos"</string>
<string name="screen_login_error_invalid_user_id">"Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_unsupported_authentication">"El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver."</string>
<string name="screen_login_form_header">"Introduce tus datos"</string>
<string name="screen_login_password_hint">"Contraseña"</string>
<string name="screen_login_server_header">"Donde viven tus conversaciones"</string>
<string name="screen_login_submit">"Continuar"</string>
<string name="screen_login_title">"¡Hola de nuevo!"</string>
<string name="screen_login_username_hint">"Usuario"</string>
</resources>

20
features/login/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_change_server_error_invalid_homeserver">"Non siamo riusciti a raggiungere questo homserver. Verifica di aver inserito correttamente l\'URL del server domestico. Se l\'URL è corretto, contatta l\'amministratore del tuo server domestico per ulteriore assistenza."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Questo server attualmente non supporta la sincronizzazione scorrevole."</string>
<string name="screen_change_server_form_header">"URL dell\'homeserver"</string>
<string name="screen_change_server_form_notice">"Puoi connetterti solo a un server esistente che supporta la sincronizzazione scorrevole. L\'amministratore del tuo server domestico dovrà configurarlo. %1$s"</string>
<string name="screen_change_server_submit">"Continua"</string>
<string name="screen_change_server_subtitle">"Qual è l\'indirizzo del tuo server?"</string>
<string name="screen_change_server_title">"Seleziona il tuo server"</string>
<string name="screen_login_error_deactivated_account">"Questo profilo è stato disattivato."</string>
<string name="screen_login_error_invalid_credentials">"Nome utente e/o password errati"</string>
<string name="screen_login_error_invalid_user_id">"Questo non è un identificatore utente valido. Formato previsto: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_unsupported_authentication">"L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver."</string>
<string name="screen_login_form_header">"Inserisci i tuoi dati"</string>
<string name="screen_login_password_hint">"Password"</string>
<string name="screen_login_server_header">"Dove vivono le tue conversazioni"</string>
<string name="screen_login_submit">"Continua"</string>
<string name="screen_login_title">"Bentornato!"</string>
<string name="screen_login_username_hint">"Nome utente"</string>
</resources>

20
features/login/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_change_server_error_invalid_homeserver">"Nu am putut accesa acest homeserver. Te rugăm să verifici că ai introdus corect adresa URL a homeserver-ului. Dacă adresa URL este corectă, contactează administratorul homeserver-ului pentru ajutor suplimentar."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Momentan acest server nu oferă suport pentru sliding sync."</string>
<string name="screen_change_server_form_header">"Adresa URL a homeserver-ului"</string>
<string name="screen_change_server_form_notice">"Vă putețo conecta numai la un server existent care oferă suport pentru sliding sync. Administratorul homeserver-ului dumneavoastră va trebui să îl configureze. %1$s"</string>
<string name="screen_change_server_submit">"Continuați"</string>
<string name="screen_change_server_subtitle">"Care este adresa serverului dumneavoastră?"</string>
<string name="screen_change_server_title">"Selectați serverul"</string>
<string name="screen_login_error_deactivated_account">"Acest cont a fost dezactivat."</string>
<string name="screen_login_error_invalid_credentials">"Utilizator și/sau parolă incorecte"</string>
<string name="screen_login_error_invalid_user_id">"Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”"</string>
<string name="screen_login_error_unsupported_authentication">"Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver."</string>
<string name="screen_login_form_header">"Introduceți detaliile"</string>
<string name="screen_login_password_hint">"Parolă"</string>
<string name="screen_login_server_header">"Locul unde trăiesc conversațiile tale"</string>
<string name="screen_login_submit">"Continuați"</string>
<string name="screen_login_title">"Bine ați revenit!"</string>
<string name="screen_login_username_hint">"Utilizator"</string>
</resources>

8
features/logout/api/src/main/res/values-es/translations.xml

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"¿Estás seguro de que quieres cerrar sesión?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Cerrar sesión"</string>
<string name="screen_signout_confirmation_dialog_title">"Cerrar sesión"</string>
<string name="screen_signout_in_progress_dialog_content">"Cerrando sesión…"</string>
<string name="screen_signout_preference_item">"Cerrar sesión"</string>
</resources>

8
features/logout/api/src/main/res/values-it/translations.xml

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Sei sicuro di voler uscire?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Esci"</string>
<string name="screen_signout_confirmation_dialog_title">"Esci"</string>
<string name="screen_signout_in_progress_dialog_content">"Uscita in corso…"</string>
<string name="screen_signout_preference_item">"Esci"</string>
</resources>

8
features/logout/api/src/main/res/values-ro/translations.xml

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Sunteți sigur că vreți să vă deconectați?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Deconectați-vă"</string>
<string name="screen_signout_confirmation_dialog_title">"Deconectați-vă"</string>
<string name="screen_signout_in_progress_dialog_content">"Deconectare în curs…"</string>
<string name="screen_signout_preference_item">"Deconectați-vă"</string>
</resources>

5
features/onboarding/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_onboarding_welcome_subtitle">"Bienvenido a la beta de %1$s. Vitaminado, para mayor rapidez y sencillez."</string>
<string name="screen_onboarding_welcome_title">"Siéntente en tu Elemento"</string>
</resources>

5
features/onboarding/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_onboarding_welcome_subtitle">"Benvenuto nella beta di %1$s. Potenziato in velocità e semplicità."</string>
<string name="screen_onboarding_welcome_title">"Sii nel tuo elemento"</string>
</resources>

5
features/onboarding/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_onboarding_welcome_subtitle">"Bun venit la versiunea beta a %1$s. Supraalimentat, pentru viteză și simplitate."</string>
<string name="screen_onboarding_welcome_title">"Fii în Elementul tău"</string>
</resources>

5
features/rageshake/api/src/main/res/values-es/translations.xml

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"%1$s se cerró inesperadamente la última vez que se lo usaste. ¿Quieres compartir un informe de error con nosotros?"</string>
<string name="rageshake_detection_dialog_content">"Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?"</string>
</resources>

5
features/rageshake/api/src/main/res/values-it/translations.xml

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?"</string>
<string name="rageshake_detection_dialog_content">"Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?"</string>
</resources>

5
features/rageshake/api/src/main/res/values-ro/translations.xml

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"%1$s s-a blocat ultima dată când a fost folosit. Doriți să ne trimiteți un raport?"</string>
<string name="rageshake_detection_dialog_content">"Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?"</string>
</resources>

14
features/rageshake/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_bug_report_attach_screenshot">"Adjuntar captura de pantalla"</string>
<string name="screen_bug_report_contact_me">"Podéis poneros en contacto conmigo para resolver dudas relacionadas"</string>
<string name="screen_bug_report_edit_screenshot">"Editar captura de pantalla"</string>
<string name="screen_bug_report_editor_description">"Describe el problema. ¿Qué hiciste? ¿Qué esperabas que ocurriera? ¿Qué ocurrió en realidad? Por favor, detállalo todo lo que puedas."</string>
<string name="screen_bug_report_editor_placeholder">"Describe el error…"</string>
<string name="screen_bug_report_editor_supporting">"Si es posible, escriba la descripción en inglés."</string>
<string name="screen_bug_report_include_crash_logs">"Enviar registros de fallos"</string>
<string name="screen_bug_report_include_logs">"Enviar registros para ayudar"</string>
<string name="screen_bug_report_include_screenshot">"Enviar captura de pantalla"</string>
<string name="screen_bug_report_logs_description">"Para comprobar que todo funciona correctamente, se enviarán registros de fallos con su mensaje. Serán privados. Para enviar sólo tu mensaje, desactiva esta opción."</string>
<string name="screen_bug_report_rash_logs_alert_title">"%1$s se cerró inesperadamente la última vez que se lo usaste. ¿Quieres compartir un informe de error con nosotros?"</string>
</resources>

14
features/rageshake/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_bug_report_attach_screenshot">"Allega istantanea schermo"</string>
<string name="screen_bug_report_contact_me">"Potete contattarmi per qualsiasi altra domanda"</string>
<string name="screen_bug_report_edit_screenshot">"Modifica istantanea schermo"</string>
<string name="screen_bug_report_editor_description">"Descrivi il bug. Che cosa hai fatto? Cosa ti aspettavi che accadesse? Cosa è effettivamente accaduto. Si prega di inserire il maggior numero di dettagli possibile."</string>
<string name="screen_bug_report_editor_placeholder">"Descrivi il problema…"</string>
<string name="screen_bug_report_editor_supporting">"Se possibile, scrivere la descrizione in inglese."</string>
<string name="screen_bug_report_include_crash_logs">"Invia i log degli arresti anomali"</string>
<string name="screen_bug_report_include_logs">"Invia i log per aiutarci"</string>
<string name="screen_bug_report_include_screenshot">"Invia istantanea schermo"</string>
<string name="screen_bug_report_logs_description">"Per verificare che le cose funzionino come previsto, i log verranno inviati con il tuo messaggio. Questi saranno privati. Per inviare solo il tuo messaggio, disattiva questa impostazione."</string>
<string name="screen_bug_report_rash_logs_alert_title">"%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?"</string>
</resources>

14
features/rageshake/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_bug_report_attach_screenshot">"Atașați o captură de ecran"</string>
<string name="screen_bug_report_contact_me">"Puteți să mă contactați dacă aveți întrebări suplimentare"</string>
<string name="screen_bug_report_edit_screenshot">"Editați captura de ecran"</string>
<string name="screen_bug_report_editor_description">"Vă rugăm să descrieți eroarea. Ce ați făcut? Ce vă aşteptați să se întâmple? Ce s-a întâmplat de fapt. Vă rugam să intrați în cât mai multe detalii cu putință."</string>
<string name="screen_bug_report_editor_placeholder">"Descrieți eroarea…"</string>
<string name="screen_bug_report_editor_supporting">"Dacă posibil, vă rugăm să scrieți descrierea în engleză."</string>
<string name="screen_bug_report_include_crash_logs">"Trimiteți log-uri"</string>
<string name="screen_bug_report_include_logs">"Trimiteți log-uri pentru a ajuta"</string>
<string name="screen_bug_report_include_screenshot">"Trimiteți captură de ecran"</string>
<string name="screen_bug_report_logs_description">"Pentru a verifica că lucrurile funcționează conform așteptărilor, log-uri vor fi trimise împreună cu mesajul. Acestea vor fi private. Pentru a trimite doar mesajul, dezactivați această setare."</string>
<string name="screen_bug_report_rash_logs_alert_title">"%1$s s-a blocat ultima dată când a fost folosit. Dorești să ne trimiti un raport?"</string>
</resources>

4
features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt

@ -18,9 +18,9 @@ package io.element.android.features.roomdetails.api @@ -18,9 +18,9 @@ package io.element.android.features.roomdetails.api
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint
interface RoomDetailsEntryPoint : FeatureEntryPoint {
fun createNode(parentNode: Node, buildContext: BuildContext): Node
fun createNode(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node
}

5
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt

@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.impl @@ -18,6 +18,7 @@ package io.element.android.features.roomdetails.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint
import io.element.android.libraries.architecture.createNode
@ -26,7 +27,7 @@ import javax.inject.Inject @@ -26,7 +27,7 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
return parentNode.createNode<RoomDetailsFlowNode>(buildContext)
override fun createNode(parentNode: Node, buildContext: BuildContext, plugins: List<Plugin>): Node {
return parentNode.createNode<RoomDetailsFlowNode>(buildContext, plugins)
}
}

6
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt

@ -16,4 +16,8 @@ @@ -16,4 +16,8 @@
package io.element.android.features.roomdetails.impl
sealed interface RoomDetailsEvent
sealed interface RoomDetailsEvent {
data class LeaveRoom(val needsConfirmation: Boolean) : RoomDetailsEvent
object ClearLeaveRoomWarning : RoomDetailsEvent
object ClearError : RoomDetailsEvent
}

39
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt

@ -21,22 +21,31 @@ import androidx.compose.runtime.LaunchedEffect @@ -21,22 +21,31 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
class RoomDetailsPresenter @Inject constructor(
private val room: MatrixRoom,
private val roomMembershipObserver: RoomMembershipObserver,
) : Presenter<RoomDetailsState> {
@Composable
override fun present(): RoomDetailsState {
// fun handleEvents(event: RoomDetailsEvent) {}
val coroutineScope = rememberCoroutineScope()
var leaveRoomWarning by remember {
mutableStateOf<LeaveRoomWarning?>(null)
}
var error by remember {
mutableStateOf<RoomDetailsError?>(null)
}
var memberCount: Async<Int> by remember { mutableStateOf(Async.Loading()) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
@ -47,6 +56,28 @@ class RoomDetailsPresenter @Inject constructor( @@ -47,6 +56,28 @@ class RoomDetailsPresenter @Inject constructor(
)
}
}
fun handleEvents(event: RoomDetailsEvent) {
when (event) {
is RoomDetailsEvent.LeaveRoom -> {
if (event.needsConfirmation) {
leaveRoomWarning = LeaveRoomWarning.computeLeaveRoomWarning(room.isPublic, memberCount)
} else {
coroutineScope.launch(Dispatchers.IO) {
room.leave()
.onSuccess {
roomMembershipObserver.notifyUserLeftRoom(room.roomId)
}.onFailure {
error = RoomDetailsError.AlertGeneric
}
leaveRoomWarning = null
}
}
}
is RoomDetailsEvent.ClearLeaveRoomWarning -> leaveRoomWarning = null
RoomDetailsEvent.ClearError -> error = null
}
}
return RoomDetailsState(
roomId = room.roomId.value,
@ -56,7 +87,9 @@ class RoomDetailsPresenter @Inject constructor( @@ -56,7 +87,9 @@ class RoomDetailsPresenter @Inject constructor(
roomTopic = room.topic,
memberCount = memberCount,
isEncrypted = room.isEncrypted,
// eventSink = ::handleEvents
displayLeaveRoomWarning = leaveRoomWarning,
error = error,
eventSink = ::handleEvents
)
}
}

27
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt

@ -17,6 +17,9 @@ @@ -17,6 +17,9 @@
package io.element.android.features.roomdetails.impl
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.isLoading
import io.element.android.libraries.matrix.api.room.MatrixRoom
data class RoomDetailsState(
val roomId: String,
@ -26,5 +29,27 @@ data class RoomDetailsState( @@ -26,5 +29,27 @@ data class RoomDetailsState(
val roomTopic: String?,
val memberCount: Async<Int>,
val isEncrypted: Boolean,
// val eventSink: (RoomDetailsEvent) -> Unit
val displayLeaveRoomWarning: LeaveRoomWarning?,
val error: RoomDetailsError?,
val eventSink: (RoomDetailsEvent) -> Unit
)
sealed class LeaveRoomWarning {
object Generic : LeaveRoomWarning()
object PrivateRoom : LeaveRoomWarning()
object LastUserInRoom : LeaveRoomWarning()
companion object {
fun computeLeaveRoomWarning(isPublic: Boolean, memberCount: Async<Int>): LeaveRoomWarning {
return when {
!isPublic -> PrivateRoom
(memberCount as? Async.Success<Int>)?.state == 1 -> LastUserInRoom
else -> Generic
}
}
}
}
sealed interface RoomDetailsError {
object AlertGeneric : RoomDetailsError
}

4
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt

@ -43,5 +43,7 @@ fun aRoomDetailsState() = RoomDetailsState( @@ -43,5 +43,7 @@ fun aRoomDetailsState() = RoomDetailsState(
"|| MAI iki/Marketing...",
memberCount = Async.Success(32),
isEncrypted = true,
// eventSink = {}
displayLeaveRoomWarning = null,
error = null,
eventSink = {}
)

46
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt

@ -49,6 +49,8 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar @@ -49,6 +49,8 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceText
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -57,6 +59,7 @@ import io.element.android.libraries.designsystem.theme.LocalColors @@ -57,6 +59,7 @@ import io.element.android.libraries.designsystem.theme.LocalColors
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.ui.strings.R as StringR
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -101,7 +104,24 @@ fun RoomDetailsView( @@ -101,7 +104,24 @@ fun RoomDetailsView(
SecuritySection()
}
OtherActionsSection()
OtherActionsSection(onLeaveRoom = {
state.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = true))
})
if (state.displayLeaveRoomWarning != null) {
ConfirmLeaveRoomDialog(
leaveRoomWarning = state.displayLeaveRoomWarning,
onConfirmLeave = { state.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = false)) },
onDismiss = { state.eventSink(RoomDetailsEvent.ClearLeaveRoomWarning) }
)
}
if (state.error != null) {
ErrorDialog(
content = stringResource(StringR.string.error_unknown),
onDismiss = { state.eventSink(RoomDetailsEvent.ClearError) }
)
}
}
}
}
@ -189,16 +209,38 @@ internal fun SecuritySection(modifier: Modifier = Modifier) { @@ -189,16 +209,38 @@ internal fun SecuritySection(modifier: Modifier = Modifier) {
}
@Composable
internal fun OtherActionsSection(modifier: Modifier = Modifier) {
internal fun OtherActionsSection(onLeaveRoom: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(showDivider = false, modifier = modifier) {
PreferenceText(
title = stringResource(R.string.screen_room_details_leave_room_title),
icon = ImageVector.vectorResource(R.drawable.ic_door_open),
tintColor = LocalColors.current.textActionCritical,
onClick = onLeaveRoom,
)
}
}
@Composable
internal fun ConfirmLeaveRoomDialog(
leaveRoomWarning: LeaveRoomWarning,
onConfirmLeave: () -> Unit,
onDismiss: () -> Unit
) {
val content = stringResource(
when (leaveRoomWarning) {
LeaveRoomWarning.PrivateRoom -> StringR.string.leave_room_alert_private_subtitle
LeaveRoomWarning.LastUserInRoom -> StringR.string.leave_room_alert_empty_subtitle
LeaveRoomWarning.Generic -> StringR.string.leave_room_alert_subtitle
}
)
ConfirmationDialog(
content = content,
submitText = stringResource(StringR.string.action_leave),
onSubmitClicked = onConfirmLeave,
onDismiss = onDismiss,
)
}
@Preview
@Composable
fun RoomDetailsLightPreview(@PreviewParameter(RoomDetailsStateProvider::class) state: RoomDetailsState) =

21
features/roomdetails/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_room_member_list_header_title">
<item quantity="one">"Una persona"</item>
<item quantity="other">"%1$d personas"</item>
</plurals>
<string name="screen_dm_details_block_alert_action">"Bloquear"</string>
<string name="screen_dm_details_block_alert_description">"Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puedes revertir esta acción en cualquier momento."</string>
<string name="screen_dm_details_block_user">"Bloquear usuario"</string>
<string name="screen_dm_details_unblock_alert_action">"Desbloquear"</string>
<string name="screen_dm_details_unblock_alert_description">"Al desbloquear al usuario, podrás volver a ver todos sus mensajes."</string>
<string name="screen_dm_details_unblock_user">"Desbloquear usuario"</string>
<string name="screen_room_details_encryption_enabled_subtitle">"Los mensajes están protegidos con \"candados\". Sólo tú y los destinatarios tenéis las llaves únicas para abrirlos."</string>
<string name="screen_room_details_encryption_enabled_title">"Cifrado de mensajes activado"</string>
<string name="screen_room_details_invite_people_title">"Invitar a otras personas"</string>
<string name="screen_room_details_leave_room_title">"Salir de la sala"</string>
<string name="screen_room_details_people_title">"Personas"</string>
<string name="screen_room_details_security_title">"Seguridad"</string>
<string name="screen_room_details_share_room_title">"Compartir sala"</string>
<string name="screen_room_details_topic_title">"Tema"</string>
</resources>

21
features/roomdetails/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_room_member_list_header_title">
<item quantity="one">"1 persona"</item>
<item quantity="other">"%1$d persone"</item>
</plurals>
<string name="screen_dm_details_block_alert_action">"Blocca"</string>
<string name="screen_dm_details_block_alert_description">"Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti i loro messaggi saranno nascosti. Potrai annullare questa azione in qualsiasi momento."</string>
<string name="screen_dm_details_block_user">"Blocca utente"</string>
<string name="screen_dm_details_unblock_alert_action">"Sblocca"</string>
<string name="screen_dm_details_unblock_alert_description">"Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."</string>
<string name="screen_dm_details_unblock_user">"Sblocca utente"</string>
<string name="screen_room_details_encryption_enabled_subtitle">"I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli."</string>
<string name="screen_room_details_encryption_enabled_title">"Crittografia messaggi abilitata"</string>
<string name="screen_room_details_invite_people_title">"Invita persone"</string>
<string name="screen_room_details_leave_room_title">"Esci dalla stanza"</string>
<string name="screen_room_details_people_title">"Persone"</string>
<string name="screen_room_details_security_title">"Sicurezza"</string>
<string name="screen_room_details_share_room_title">"Condividi stanza"</string>
<string name="screen_room_details_topic_title">"Oggetto"</string>
</resources>

22
features/roomdetails/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_room_member_list_header_title">
<item quantity="one">"o persoană"</item>
<item quantity="few"></item>
<item quantity="other">"%1$d persoane"</item>
</plurals>
<string name="screen_dm_details_block_alert_action">"Blocați"</string>
<string name="screen_dm_details_block_alert_description">"Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând."</string>
<string name="screen_dm_details_block_user">"Blocați utilizatorul"</string>
<string name="screen_dm_details_unblock_alert_action">"Deblocați"</string>
<string name="screen_dm_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
<string name="screen_dm_details_unblock_user">"Deblocați utilizatorul"</string>
<string name="screen_room_details_encryption_enabled_subtitle">"Mesajele sunt securizate cu încuietori. Doar dumneavoastră și destinatarii aveți cheile unice pentru a le debloca."</string>
<string name="screen_room_details_encryption_enabled_title">"Criptarea mesajelor este activată"</string>
<string name="screen_room_details_invite_people_title">"Invitați persoane"</string>
<string name="screen_room_details_leave_room_title">"Părăsiți camera"</string>
<string name="screen_room_details_people_title">"Persoane"</string>
<string name="screen_room_details_security_title">"Securitate"</string>
<string name="screen_room_details_share_room_title">"Partajați camera"</string>
<string name="screen_room_details_topic_title">"Subiect"</string>
</resources>

6
features/roomdetails/impl/src/main/res/values/localazy.xml

@ -4,6 +4,12 @@ @@ -4,6 +4,12 @@
<item quantity="one">"1 person"</item>
<item quantity="other">"%1$d people"</item>
</plurals>
<string name="screen_dm_details_block_alert_action">"Block"</string>
<string name="screen_dm_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
<string name="screen_dm_details_block_user">"Block user"</string>
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
<string name="screen_dm_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</string>
<string name="screen_room_details_encryption_enabled_subtitle">"Messages are secured with locks. Only you and the recipients have the unique keys to unlock them."</string>
<string name="screen_room_details_encryption_enabled_title">"Message encryption enabled"</string>
<string name="screen_room_details_invite_people_title">"Invite people"</string>

138
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt

@ -20,21 +20,37 @@ import app.cash.molecule.RecompositionClock @@ -20,21 +20,37 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import io.element.android.features.roomdetails.impl.LeaveRoomWarning
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
import io.element.android.libraries.architecture.Async
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.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ExperimentalCoroutinesApi
class RoomDetailsPresenterTests {
private val roomMembershipObserver = RoomMembershipObserver(A_SESSION_ID)
@Test
fun `present - initial state is created from room info`() = runTest {
val room = aMatrixRoom()
val presenter = RoomDetailsPresenter(room)
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@ -53,7 +69,7 @@ class RoomDetailsPresenterTests { @@ -53,7 +69,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - room member count is calculated asynchronously`() = runTest {
val room = aMatrixRoom()
val presenter = RoomDetailsPresenter(room)
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@ -68,7 +84,7 @@ class RoomDetailsPresenterTests { @@ -68,7 +84,7 @@ class RoomDetailsPresenterTests {
@Test
fun `present - initial state with no room name`() = runTest {
val room = aMatrixRoom(name = null)
val presenter = RoomDetailsPresenter(room)
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@ -84,7 +100,7 @@ class RoomDetailsPresenterTests { @@ -84,7 +100,7 @@ class RoomDetailsPresenterTests {
val room = aMatrixRoom(name = null).apply {
givenFetchMemberResult(Result.failure(Throwable()))
}
val presenter = RoomDetailsPresenter(room)
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
@ -94,6 +110,100 @@ class RoomDetailsPresenterTests { @@ -94,6 +110,100 @@ class RoomDetailsPresenterTests {
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - Leave with confirmation on private room shows a specific warning`() = runTest {
val room = aMatrixRoom(isPublic = false)
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
// Allow room member count to load
skipItems(1)
initialState.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = true))
val confirmationState = awaitItem()
Truth.assertThat(confirmationState.displayLeaveRoomWarning).isEqualTo(LeaveRoomWarning.PrivateRoom)
}
}
@Test
fun `present - Leave with confirmation on empty room shows a specific warning`() = runTest {
val room = aMatrixRoom(members = listOf(aRoomMember()))
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
// Allow room member count to load
skipItems(1)
initialState.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = true))
val confirmationState = awaitItem()
Truth.assertThat(confirmationState.displayLeaveRoomWarning).isEqualTo(LeaveRoomWarning.LastUserInRoom)
}
}
@Test
fun `present - Leave with confirmation shows a generic warning`() = runTest {
val room = aMatrixRoom()
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
// Allow room member count to load
skipItems(1)
initialState.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = true))
val confirmationState = awaitItem()
Truth.assertThat(confirmationState.displayLeaveRoomWarning).isEqualTo(LeaveRoomWarning.Generic)
}
}
@Test
fun `present - Leave without confirmation leaves the room`() = runTest {
val room = aMatrixRoom()
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
// Allow room member count to load
skipItems(1)
initialState.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = false))
cancelAndIgnoreRemainingEvents()
}
// Membership observer should receive a 'left room' change
roomMembershipObserver.updates.take(1)
.onEach { update -> Truth.assertThat(update.change).isEqualTo(MembershipChange.LEFT) }
.collect()
}
@Test
fun `present - ClearError removes any error present`() = runTest {
val room = aMatrixRoom().apply {
givenLeaveRoomError(Throwable())
}
val presenter = RoomDetailsPresenter(room, roomMembershipObserver)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
// Allow room member count to load
skipItems(1)
initialState.eventSink(RoomDetailsEvent.LeaveRoom(needsConfirmation = false))
val errorState = awaitItem()
Truth.assertThat(errorState.error).isNotNull()
errorState.eventSink(RoomDetailsEvent.ClearError)
Truth.assertThat(awaitItem().error).isNull()
}
}
}
fun aMatrixRoom(
@ -104,6 +214,7 @@ fun aMatrixRoom( @@ -104,6 +214,7 @@ fun aMatrixRoom(
avatarUrl: String? = "https://matrix.org/avatar.jpg",
members: List<RoomMember> = emptyList(),
isEncrypted: Boolean = true,
isPublic: Boolean = true,
) = FakeMatrixRoom(
roomId = roomId,
name = name,
@ -112,4 +223,23 @@ fun aMatrixRoom( @@ -112,4 +223,23 @@ fun aMatrixRoom(
avatarUrl = avatarUrl,
members = members,
isEncrypted = isEncrypted,
isPublic = isPublic,
)
fun aRoomMember(
userId: UserId = A_USER_ID,
displayName: String? = null,
avatarUrl: String? = null,
membership: RoomMembershipState = RoomMembershipState.JOIN,
isNameAmbiguous: Boolean = false,
powerLevel: Long = 0L,
normalizedPowerLevel: Long = 0L
) = RoomMember(
userId = userId.value,
displayName = displayName,
avatarUrl = avatarUrl,
membership = membership,
isNameAmbiguous = isNameAmbiguous,
powerLevel = powerLevel,
normalizedPowerLevel = normalizedPowerLevel,
)

1
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt

@ -20,5 +20,4 @@ sealed interface RoomListEvents { @@ -20,5 +20,4 @@ sealed interface RoomListEvents {
data class UpdateFilter(val newFilter: String) : RoomListEvents
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents
object DismissRequestVerificationPrompt : RoomListEvents
object ClearSuccessfulVerificationMessage : RoomListEvents
}

19
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt

@ -34,12 +34,14 @@ import io.element.android.libraries.core.extensions.orEmpty @@ -34,12 +34,14 @@ import io.element.android.libraries.core.extensions.orEmpty
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.handleSnackbarMessage
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -56,8 +58,11 @@ class RoomListPresenter @Inject constructor( @@ -56,8 +58,11 @@ class RoomListPresenter @Inject constructor(
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val roomLastMessageFormatter: RoomLastMessageFormatter,
private val sessionVerificationService: SessionVerificationService,
private val snackbarDispatcher: SnackbarDispatcher,
) : Presenter<RoomListState> {
private val roomMembershipObserver: RoomMembershipObserver = client.roomMembershipObserver()
@Composable
override fun present(): RoomListState {
val matrixUser: MutableState<MatrixUser?> = remember {
@ -86,19 +91,11 @@ class RoomListPresenter @Inject constructor( @@ -86,19 +91,11 @@ class RoomListPresenter @Inject constructor(
derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified && !verificationPromptDismissed }
}
// Current verification flow status, if any (initial, requesting, accepted, etc.)
val currentVerificationFlowStatus by sessionVerificationService.verificationFlowState.collectAsState()
// We only care about the 'Finished' state to display the 'verification success' message
val presentVerificationSuccessfulMessage = remember {
derivedStateOf { currentVerificationFlowStatus == VerificationFlowState.Finished }
}
fun handleEvents(event: RoomListEvents) {
when (event) {
is RoomListEvents.UpdateFilter -> filter = event.newFilter
is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range)
RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true
RoomListEvents.ClearSuccessfulVerificationMessage -> sessionVerificationService.reset()
}
}
@ -106,12 +103,14 @@ class RoomListPresenter @Inject constructor( @@ -106,12 +103,14 @@ class RoomListPresenter @Inject constructor(
filteredRoomSummaries.value = updateFilteredRoomSummaries(roomSummaries, filter)
}
val snackbarMessage = handleSnackbarMessage(snackbarDispatcher)
return RoomListState(
matrixUser = matrixUser.value,
roomList = filteredRoomSummaries.value,
filter = filter,
presentVerificationSuccessfulMessage = presentVerificationSuccessfulMessage.value,
displayVerificationPrompt = displayVerificationPrompt,
snackbarMessage = snackbarMessage,
eventSink = ::handleEvents
)
}

3
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt

@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl @@ -18,6 +18,7 @@ package io.element.android.features.roomlist.impl
import androidx.compose.runtime.Immutable
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
@ -26,7 +27,7 @@ data class RoomListState( @@ -26,7 +27,7 @@ data class RoomListState(
val matrixUser: MatrixUser?,
val roomList: ImmutableList<RoomListRoomSummary>,
val filter: String,
val presentVerificationSuccessfulMessage: Boolean,
val displayVerificationPrompt: Boolean,
val snackbarMessage: SnackbarMessage?,
val eventSink: (RoomListEvents) -> Unit
)

6
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt

@ -20,17 +20,19 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider @@ -20,17 +20,19 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.utils.SnackbarMessage
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.MatrixUser
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import io.element.android.libraries.ui.strings.R as StringR
open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
override val values: Sequence<RoomListState>
get() = sequenceOf(
aRoomListState(),
aRoomListState().copy(displayVerificationPrompt = true),
aRoomListState().copy(presentVerificationSuccessfulMessage = true),
aRoomListState().copy(snackbarMessage = SnackbarMessage(StringR.string.common_verification_complete)),
)
}
@ -39,7 +41,7 @@ internal fun aRoomListState() = RoomListState( @@ -39,7 +41,7 @@ internal fun aRoomListState() = RoomListState(
roomList = aRoomListRoomSummaryList(),
filter = "filter",
eventSink = {},
presentVerificationSuccessfulMessage = false,
snackbarMessage = null,
displayVerificationPrompt = false,
)

24
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt

@ -40,10 +40,11 @@ import androidx.compose.material3.SnackbarHostState @@ -40,10 +40,11 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -67,6 +68,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface @@ -67,6 +68,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.launch
import io.element.android.libraries.designsystem.R as DrawableR
import io.element.android.libraries.ui.strings.R as StringR
@ -130,14 +132,18 @@ fun RoomListContent( @@ -130,14 +132,18 @@ fun RoomListContent(
}
val snackbarHostState = remember { SnackbarHostState() }
val verificationCompleteMessage = stringResource(StringR.string.common_verification_complete)
LaunchedEffect(state.presentVerificationSuccessfulMessage) {
if (state.presentVerificationSuccessfulMessage) {
snackbarHostState.showSnackbar(
message = verificationCompleteMessage,
duration = SnackbarDuration.Short,
)
state.eventSink(RoomListEvents.ClearSuccessfulVerificationMessage)
val snackbarMessageText = if (state.snackbarMessage != null ) {
stringResource(state.snackbarMessage.messageResId)
} else null
val coroutineScope = rememberCoroutineScope()
if (snackbarMessageText != null) {
SideEffect {
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = snackbarMessageText,
duration = SnackbarDuration.Short,
)
}
}
}

61
features/roomlist/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_roomlist_a11y_create_message">"Crear una nueva conversación o sala"</string>
<string name="screen_roomlist_main_space_title">"Todos los chats"</string>
<string name="session_verification_banner_message">"Parece que estás usando un nuevo dispositivo. Verifica que eres tú para acceder a tus mensajes cifrados."</string>
<string name="session_verification_banner_title">"Accede a tu historial de mensajes"</string>
<string name="state_event_avatar_changed_too">"(el avatar también cambió)"</string>
<string name="state_event_avatar_url_changed">"%1$s cambió su avatar"</string>
<string name="state_event_avatar_url_changed_by_you">"Cambiaste tu avatar"</string>
<string name="state_event_display_name_changed_from">"%1$s cambió su nombre de %2$s a %3$s"</string>
<string name="state_event_display_name_changed_from_by_you">"Cambiaste tu nombre de %1$s a %2$s"</string>
<string name="state_event_display_name_removed">"%1$s eliminó su nombre (era %2$s)"</string>
<string name="state_event_display_name_removed_by_you">"Eliminaste tu nombre (era %1$s)"</string>
<string name="state_event_display_name_set">"%1$s cambió su nombre a %2$s"</string>
<string name="state_event_display_name_set_by_you">"Cambiaste tu nombre a %1$s"</string>
<string name="state_event_room_avatar_changed">"%1$s cambió el avatar de la sala"</string>
<string name="state_event_room_avatar_changed_by_you">"Cambiaste el avatar de la sala"</string>
<string name="state_event_room_avatar_removed">"%1$s eliminó el avatar de la sala"</string>
<string name="state_event_room_avatar_removed_by_you">"Eliminaste el avatar de la sala"</string>
<string name="state_event_room_ban">"%1$s expulsó permanentemente a %2$s"</string>
<string name="state_event_room_ban_by_you">"Expulsaste permanentemente a %1$s"</string>
<string name="state_event_room_created">"%1$s creó la sala"</string>
<string name="state_event_room_created_by_you">"Tú creaste la sala"</string>
<string name="state_event_room_invite">"%1$s invitó a %2$s"</string>
<string name="state_event_room_invite_accepted">"%1$s aceptó la invitación"</string>
<string name="state_event_room_invite_accepted_by_you">"Aceptaste la invitación"</string>
<string name="state_event_room_invite_by_you">"Invitaste a %1$s"</string>
<string name="state_event_room_invite_you">"%1$s te invitó."</string>
<string name="state_event_room_join">"%1$s se unió a la sala"</string>
<string name="state_event_room_join_by_you">"Te uniste a la sala"</string>
<string name="state_event_room_knock">"%1$s solicitó unirse"</string>
<string name="state_event_room_knock_accepted">"%1$s permitió que %2$s se uniera"</string>
<string name="state_event_room_knock_accepted_by_you">"%1$s te permitió unirte"</string>
<string name="state_event_room_knock_by_you">"Solicitaste unirte"</string>
<string name="state_event_room_knock_denied">"%1$s rechazó la solicitud de %2$s para unirse"</string>
<string name="state_event_room_knock_denied_by_you">"Rechazaste la solicitud de %1$s para unirte"</string>
<string name="state_event_room_knock_denied_you">"%1$s rechazó su solicitud para unirte"</string>
<string name="state_event_room_knock_retracted">"%1$s ya no está interesado en unirse"</string>
<string name="state_event_room_knock_retracted_by_you">"Cancelaste tu solicitud de unirte"</string>
<string name="state_event_room_leave">"%1$s salió de la sala"</string>
<string name="state_event_room_leave_by_you">"Saliste de la sala"</string>
<string name="state_event_room_name_changed">"%1$s cambió el nombre de la sala a: %2$s"</string>
<string name="state_event_room_name_changed_by_you">"Cambiaste el nombre de la sala a: %1$s"</string>
<string name="state_event_room_name_removed">"%1$s eliminó el nombre de la sala"</string>
<string name="state_event_room_name_removed_by_you">"Eliminaste el nombre de la sala"</string>
<string name="state_event_room_reject">"%1$s rechazó la invitación"</string>
<string name="state_event_room_reject_by_you">"Rechazaste la invitación"</string>
<string name="state_event_room_remove">"%1$s echó a %2$s"</string>
<string name="state_event_room_remove_by_you">"Echaste a %1$s"</string>
<string name="state_event_room_third_party_invite">"%1$s envió una invitación a %2$s para unirse a la sala"</string>
<string name="state_event_room_third_party_invite_by_you">"Enviaste una invitación a %1$s para unirse a la sala"</string>
<string name="state_event_room_third_party_revoked_invite">"%1$s revocó la invitación a %2$s para unirse a la sala"</string>
<string name="state_event_room_third_party_revoked_invite_by_you">"Revocaste la invitación de %1$s para unirse a la sala"</string>
<string name="state_event_room_topic_changed">"%1$s cambió el tema a: %2$s"</string>
<string name="state_event_room_topic_changed_by_you">"Cambiaste el tema a: %1$s"</string>
<string name="state_event_room_topic_removed">"%1$s eliminó el tema de la sala"</string>
<string name="state_event_room_topic_removed_by_you">"Eliminaste el tema de la sala"</string>
<string name="state_event_room_unban">"%1$s readmitió a %2$s"</string>
<string name="state_event_room_unban_by_you">"Readmitiste a %1$s"</string>
<string name="state_event_room_unknown_membership_change">"%1$s realizó un cambio desconocido en su membresía"</string>
</resources>

61
features/roomlist/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_roomlist_a11y_create_message">"Crea una nuova conversazione o stanza"</string>
<string name="screen_roomlist_main_space_title">"Tutte le conversazioni"</string>
<string name="session_verification_banner_message">"Sembra che tu stia utilizzando un nuovo dispositivo. Verifica di essere tu per accedere ai tuoi messaggi crittografati."</string>
<string name="session_verification_banner_title">"Accedi alla cronologia dei messaggi"</string>
<string name="state_event_avatar_changed_too">"(anche l\'avatar è stato cambiato)"</string>
<string name="state_event_avatar_url_changed">"%1$s ha cambiato il proprio avatar"</string>
<string name="state_event_avatar_url_changed_by_you">"Hai cambiato il tuo avatar"</string>
<string name="state_event_display_name_changed_from">"%1$s ha cambiato il proprio nome visualizzato da %2$s a %3$s"</string>
<string name="state_event_display_name_changed_from_by_you">"Hai cambiato il tuo nome visualizzato da %1$s a %2$s"</string>
<string name="state_event_display_name_removed">"%1$s ha rimosso il proprio nome visualizzato (era %2$s)"</string>
<string name="state_event_display_name_removed_by_you">"Hai rimosso il tuo nome visualizzato (era %1$s)"</string>
<string name="state_event_display_name_set">"%1$s ha impostato il proprio nome visualizzato su %2$s"</string>
<string name="state_event_display_name_set_by_you">"Hai impostato il tuo nome visualizzato su %1$s"</string>
<string name="state_event_room_avatar_changed">"%1$s ha cambiato l\'avatar della stanza"</string>
<string name="state_event_room_avatar_changed_by_you">"Hai cambiato l\'avatar della stanza"</string>
<string name="state_event_room_avatar_removed">"%1$s ha rimosso l\'avatar della stanza"</string>
<string name="state_event_room_avatar_removed_by_you">"Hai rimosso l\'avatar della stanza"</string>
<string name="state_event_room_ban">"%1$s ha rimosso %2$s"</string>
<string name="state_event_room_ban_by_you">"Hai rimosso %1$s"</string>
<string name="state_event_room_created">"%1$s ha creato la stanza"</string>
<string name="state_event_room_created_by_you">"Hai creato la stanza"</string>
<string name="state_event_room_invite">"%1$s ha invitato %2$s"</string>
<string name="state_event_room_invite_accepted">"%1$s ha accettato l\'invito"</string>
<string name="state_event_room_invite_accepted_by_you">"Hai accettato l\'invito"</string>
<string name="state_event_room_invite_by_you">"Hai invitato %1$s"</string>
<string name="state_event_room_invite_you">"%1$s ti ha invitato"</string>
<string name="state_event_room_join">"%1$s si è unito alla stanza"</string>
<string name="state_event_room_join_by_you">"Ti sei unito alla stanza"</string>
<string name="state_event_room_knock">"%1$s ha chiesto di unirsi"</string>
<string name="state_event_room_knock_accepted">"%1$s ha permesso a %2$s di unirsi"</string>
<string name="state_event_room_knock_accepted_by_you">"%1$s ti ha permesso di unirti"</string>
<string name="state_event_room_knock_by_you">"Hai richiesto di unirti"</string>
<string name="state_event_room_knock_denied">"%1$s ha rifiutato la richiesta di unirsi di %2$s"</string>
<string name="state_event_room_knock_denied_by_you">"Hai rifiutato la richiesta di unirsi di %1$s"</string>
<string name="state_event_room_knock_denied_you">"%1$s ha rifiutato la tua richiesta di unirti"</string>
<string name="state_event_room_knock_retracted">"%1$s non è più interessato a partecipare"</string>
<string name="state_event_room_knock_retracted_by_you">"Hai annullato la tua richiesta di unirti"</string>
<string name="state_event_room_leave">"%1$s ha lasciato la stanza"</string>
<string name="state_event_room_leave_by_you">"Hai lasciato la stanza"</string>
<string name="state_event_room_name_changed">"%1$s ha cambiato il nome della stanza in: %2$s"</string>
<string name="state_event_room_name_changed_by_you">"Hai cambiato il nome della stanza in: %1$s"</string>
<string name="state_event_room_name_removed">"%1$s ha rimosso il nome della stanza"</string>
<string name="state_event_room_name_removed_by_you">"Hai rimosso il nome della stanza"</string>
<string name="state_event_room_reject">"%1$s ha rifiutato l\'invito"</string>
<string name="state_event_room_reject_by_you">"Hai rifiutato l\'invito"</string>
<string name="state_event_room_remove">"%1$s ha rimosso %2$s"</string>
<string name="state_event_room_remove_by_you">"Hai rimosso %1$s"</string>
<string name="state_event_room_third_party_invite">"%1$s ha inviato un invito a %2$s per unirsi alla stanza"</string>
<string name="state_event_room_third_party_invite_by_you">"Hai inviato un invito a %1$s per unirsi alla stanza"</string>
<string name="state_event_room_third_party_revoked_invite">"%1$s ha revocato l\'invito di %2$s ad unirsi alla stanza."</string>
<string name="state_event_room_third_party_revoked_invite_by_you">"Hai revocato l\'invito a %1$s a universi alla stanza"</string>
<string name="state_event_room_topic_changed">"%1$s ha cambiato l\'oggetto in: %2$s"</string>
<string name="state_event_room_topic_changed_by_you">"Hai cambiato l\'oggetto in: %1$s"</string>
<string name="state_event_room_topic_removed">"%1$s ha rimosso l\'oggetto della stanza"</string>
<string name="state_event_room_topic_removed_by_you">"Hai rimosso l\'oggetto della stanza"</string>
<string name="state_event_room_unban">"%1$s ha sbloccato %2$s"</string>
<string name="state_event_room_unban_by_you">"Hai sbloccato %1$s"</string>
<string name="state_event_room_unknown_membership_change">"%1$s ha apportato una modifica sconosciuta alla propria iscrizione"</string>
</resources>

61
features/roomlist/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_roomlist_a11y_create_message">"Creați o conversație sau o cameră nouă"</string>
<string name="screen_roomlist_main_space_title">"Toate conversatiile"</string>
<string name="session_verification_banner_message">"Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea pentru acces la mesajele dumneavoastră criptate."</string>
<string name="session_verification_banner_title">"Accesați istoricul mesajelor"</string>
<string name="state_event_avatar_changed_too">"(s-a schimbat si avatarul)"</string>
<string name="state_event_avatar_url_changed">"%1$s și-a schimbat avatarul"</string>
<string name="state_event_avatar_url_changed_by_you">"V-ați schimbat avatarul"</string>
<string name="state_event_display_name_changed_from">"%1$s și-a schimbat numele din %2$s în %3$s"</string>
<string name="state_event_display_name_changed_from_by_you">"V-ați schimbat numele din %1$s în %2$s"</string>
<string name="state_event_display_name_removed">"%1$s și-a sters numele (era %2$s)"</string>
<string name="state_event_display_name_removed_by_you">"V-ați sters numele (era %1$s)"</string>
<string name="state_event_display_name_set">"%1$s și-a schimbat numele %2$s"</string>
<string name="state_event_display_name_set_by_you">"V-ați schimbat numele în %1$s"</string>
<string name="state_event_room_avatar_changed">"%1$s a schimbat avatarul camerei"</string>
<string name="state_event_room_avatar_changed_by_you">"Ați schimbat avatarul camerei"</string>
<string name="state_event_room_avatar_removed">"%1$s a șters avatarul camerei"</string>
<string name="state_event_room_avatar_removed_by_you">"Ați șters avatarul camerei"</string>
<string name="state_event_room_ban">"%1$s a adăugat o interdicție pentru %2$s"</string>
<string name="state_event_room_ban_by_you">"Ați adăugat o interdicție pentru %1$s"</string>
<string name="state_event_room_created">"%1$s a creat camera"</string>
<string name="state_event_room_created_by_you">"Ați creat camera"</string>
<string name="state_event_room_invite">"%1$s l-a invitat pe %2$s"</string>
<string name="state_event_room_invite_accepted">"%1$s a acceptat invitația"</string>
<string name="state_event_room_invite_accepted_by_you">"Ați acceptat invitația"</string>
<string name="state_event_room_invite_by_you">"L-ați invitat pe %1$s"</string>
<string name="state_event_room_invite_you">"%1$s v-a invitat"</string>
<string name="state_event_room_join">"%1$s a intrat în cameră"</string>
<string name="state_event_room_join_by_you">"Ați intrat în cameră"</string>
<string name="state_event_room_knock">"%1$s a solicitat să se alăture camerei"</string>
<string name="state_event_room_knock_accepted">"%1$s i-a permis lui %2$s să se alăture camerei"</string>
<string name="state_event_room_knock_accepted_by_you">"%1$s v-a permis să vă alăturați camerei"</string>
<string name="state_event_room_knock_by_you">"Ați solicitat să vă alăturați camerei"</string>
<string name="state_event_room_knock_denied">"%1$s a respins solicitarea de alăturare a lui %2$s"</string>
<string name="state_event_room_knock_denied_by_you">"Ați respins solicitarea de alăturare a lui %1$s"</string>
<string name="state_event_room_knock_denied_you">"%1$s a respins cererea dumneavoastră de alăturare"</string>
<string name="state_event_room_knock_retracted">"%1$s nu mai este interesat să se alăture camerei"</string>
<string name="state_event_room_knock_retracted_by_you">"Ați anulat cererea de alăturare"</string>
<string name="state_event_room_leave">"%1$s a părăsit camera"</string>
<string name="state_event_room_leave_by_you">"Ați părăsit camera"</string>
<string name="state_event_room_name_changed">"%1$s a schimbat numele camerei în: %2$s"</string>
<string name="state_event_room_name_changed_by_you">"Ați schimbat numele camerei în: %1$s"</string>
<string name="state_event_room_name_removed">"%1$s a sters numele camerei"</string>
<string name="state_event_room_name_removed_by_you">"Ați șters numele camerei"</string>
<string name="state_event_room_reject">"%1$s a respins invitația"</string>
<string name="state_event_room_reject_by_you">"Ați respins invitația"</string>
<string name="state_event_room_remove">"%1$s l-a îndepărtat pe %2$s"</string>
<string name="state_event_room_remove_by_you">"L-ați îndepărtat pe %1$s"</string>
<string name="state_event_room_third_party_invite">"%1$s a trimis o invitație către %2$s pentru a se alătura camerei"</string>
<string name="state_event_room_third_party_invite_by_you">"Ați trimis o invitație către %1$s pentru a se alătura camerei"</string>
<string name="state_event_room_third_party_revoked_invite">"%1$s a revocat invitația pentru %2$s de a se alătura camerei"</string>
<string name="state_event_room_third_party_revoked_invite_by_you">"Ați revocat invitația pentru %1$s de a se alătura camerei"</string>
<string name="state_event_room_topic_changed">"%1$s a schimbat subiectul în: %2$s"</string>
<string name="state_event_room_topic_changed_by_you">"Ați schimbat subiectul în: %1$s"</string>
<string name="state_event_room_topic_removed">"%1$s a șters subiectul camerei"</string>
<string name="state_event_room_topic_removed_by_you">"Ați șters subiectul camerei"</string>
<string name="state_event_room_unban">"%1$s a anulat interdicția pentru %2$s"</string>
<string name="state_event_room_unban_by_you">"Ați anulat interdicția pentru %1$s"</string>
<string name="state_event_room_unknown_membership_change">"%1$s a făcut o modificare necunoscută asupra calității sale de membru"</string>
</resources>

35
features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt

@ -24,8 +24,8 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary @@ -24,8 +24,8 @@ import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_ROOM_ID
@ -49,6 +49,7 @@ class RoomListPresenterTests { @@ -49,6 +49,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -75,6 +76,7 @@ class RoomListPresenterTests { @@ -75,6 +76,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -95,6 +97,7 @@ class RoomListPresenterTests { @@ -95,6 +97,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -119,6 +122,7 @@ class RoomListPresenterTests { @@ -119,6 +122,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -148,6 +152,7 @@ class RoomListPresenterTests { @@ -148,6 +152,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -182,6 +187,7 @@ class RoomListPresenterTests { @@ -182,6 +187,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -230,6 +236,7 @@ class RoomListPresenterTests { @@ -230,6 +236,7 @@ class RoomListPresenterTests {
givenIsReady(true)
givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
},
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
@ -242,32 +249,6 @@ class RoomListPresenterTests { @@ -242,32 +249,6 @@ class RoomListPresenterTests {
}
}
@Test
fun `present - presentVerificationSuccessfulMessage & ClearVerificationSuccesfulMessage`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource()
val presenter = RoomListPresenter(
FakeMatrixClient(
sessionId = A_SESSION_ID,
roomSummaryDataSource = roomSummaryDataSource
),
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService().apply {
givenIsReady(true)
givenVerificationFlowState(VerificationFlowState.Finished)
},
)
moleculeFlow(RecompositionClock.Immediate) {
presenter.present()
}.test {
skipItems(1)
val displayMessageItem = awaitItem()
Truth.assertThat(displayMessageItem.presentVerificationSuccessfulMessage).isTrue()
displayMessageItem.eventSink(RoomListEvents.ClearSuccessfulVerificationMessage)
Truth.assertThat(awaitItem().presentVerificationSuccessfulMessage).isFalse()
}
}
private fun createDateFormatter(): LastMessageTimestampFormatter {
return FakeLastMessageTimestampFormatter().apply {
givenFormat(A_FORMATTED_DATE)

19
features/verifysession/impl/src/main/res/values-es/translations.xml

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_session_verification_cancelled_subtitle">"Algo no fue bien. Se agotó el tiempo de espera de la solicitud o se rechazó."</string>
<string name="screen_session_verification_cancelled_title">"Verificación cancelada"</string>
<string name="screen_session_verification_compare_emojis_subtitle">"Confirma que los emojis que aparecen a continuación coinciden con los que aparecen en tu otra sesión."</string>
<string name="screen_session_verification_compare_emojis_title">"Comparar emojis"</string>
<string name="screen_session_verification_complete_subtitle">"Tu nueva sesión ya está verificada. Tienes acceso a tus mensajes cifrados y otros usuarios lo considerarán de confianza."</string>
<string name="screen_session_verification_open_existing_session_subtitle">"Demuestra que eres tú para acceder a tu historial de mensajes cifrados."</string>
<string name="screen_session_verification_open_existing_session_title">"Abrir una sesión existente"</string>
<string name="screen_session_verification_positive_button_canceled">"Reintentar la verificación"</string>
<string name="screen_session_verification_positive_button_initial">"Estoy listo"</string>
<string name="screen_session_verification_positive_button_ready">"Comenzar"</string>
<string name="screen_session_verification_positive_button_verifying_ongoing">"Esperando a que coincida"</string>
<string name="screen_session_verification_request_accepted_subtitle">"Compara los emoji, asegurándote de que aparecen en el mismo orden."</string>
<string name="screen_session_verification_they_dont_match">"No coinciden"</string>
<string name="screen_session_verification_they_match">"Coinciden"</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"Acepta la solicitud para iniciar el proceso de verificación en tu otra sesión para continuar."</string>
<string name="screen_session_verification_waiting_to_accept_title">"A la espera de aceptar la solicitud"</string>
</resources>

19
features/verifysession/impl/src/main/res/values-it/translations.xml

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_session_verification_cancelled_subtitle">"C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata."</string>
<string name="screen_session_verification_cancelled_title">"Verifica annullata"</string>
<string name="screen_session_verification_compare_emojis_subtitle">"Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione."</string>
<string name="screen_session_verification_compare_emojis_title">"Confronta le emoji"</string>
<string name="screen_session_verification_complete_subtitle">"La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile."</string>
<string name="screen_session_verification_open_existing_session_subtitle">"Dimostra la tua identità per accedere alla cronologia dei messaggi crittografati."</string>
<string name="screen_session_verification_open_existing_session_title">"Apri una sessione esistente"</string>
<string name="screen_session_verification_positive_button_canceled">"Riprova la verifica"</string>
<string name="screen_session_verification_positive_button_initial">"Sono pronto"</string>
<string name="screen_session_verification_positive_button_ready">"Inizia"</string>
<string name="screen_session_verification_positive_button_verifying_ongoing">"In attesa di un riscontro"</string>
<string name="screen_session_verification_request_accepted_subtitle">"Confronta le emoji uniche, assicurandoti che appaiano nello stesso ordine."</string>
<string name="screen_session_verification_they_dont_match">"Non corrispondono"</string>
<string name="screen_session_verification_they_match">"Corrispondono"</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"Accetta la richiesta di avviare il processo di verifica nell\'altra sessione per continuare."</string>
<string name="screen_session_verification_waiting_to_accept_title">"In attesa di accettare la richiesta"</string>
</resources>

19
features/verifysession/impl/src/main/res/values-ro/translations.xml

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_session_verification_cancelled_subtitle">"Ceva nu este în regulă. Fie cererea a expirat, fie a fost respinsă."</string>
<string name="screen_session_verification_cancelled_title">"Verificare anulată"</string>
<string name="screen_session_verification_compare_emojis_subtitle">"Confirmați că emoticoanele de mai jos se potrivesc cu cele afișate în cealaltă sesiune."</string>
<string name="screen_session_verification_compare_emojis_title">"Comparați emoticoanele"</string>
<string name="screen_session_verification_complete_subtitle">"Noua dumneavoastră sesiune este acum verificată. Are acces la mesajele dumneavoastră criptate, iar alți utilizatori vă vor vedea ca fiind de încredere."</string>
<string name="screen_session_verification_open_existing_session_subtitle">"Demonstrați-vă identitatea pentru a accesa istoricul mesajelor criptate."</string>
<string name="screen_session_verification_open_existing_session_title">"Deschideți o sesiune existentă"</string>
<string name="screen_session_verification_positive_button_canceled">"Reîncercați verificarea"</string>
<string name="screen_session_verification_positive_button_initial">"Sunt pregătit"</string>
<string name="screen_session_verification_positive_button_ready">"Începeți"</string>
<string name="screen_session_verification_positive_button_verifying_ongoing">"Se așteaptă confirmarea"</string>
<string name="screen_session_verification_request_accepted_subtitle">"Comparăți emoticoalene asigurându-vă că apar în aceeași ordine."</string>
<string name="screen_session_verification_they_dont_match">"Nu se potrivesc"</string>
<string name="screen_session_verification_they_match">"Se potrivesc"</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"Acceptați solicitarea de a începe procesul de verificare în cealaltă sesiune pentru a continua."</string>
<string name="screen_session_verification_waiting_to_accept_title">"Se așteptă acceptarea cererii"</string>
</resources>

2
features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt

@ -27,9 +27,11 @@ import io.element.android.libraries.architecture.Async @@ -27,9 +27,11 @@ import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ExperimentalCoroutinesApi
class VerifySelfSessionPresenterTests {
@Test

2
gradle/libs.versions.toml

@ -38,7 +38,7 @@ datetime = "0.4.0" @@ -38,7 +38,7 @@ datetime = "0.4.0"
serialization_json = "1.5.0"
showkase = "1.0.0-beta17"
jsoup = "1.15.4"
appyx = "1.1.1"
appyx = "1.1.2"
dependencycheck = "8.2.1"
stem = "2.3.0"
sqldelight = "1.5.5"

4
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt

@ -37,11 +37,11 @@ import io.element.android.libraries.ui.strings.R as StringR @@ -37,11 +37,11 @@ import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun ConfirmationDialog(
title: String,
content: String,
onSubmitClicked: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
title: String? = null,
submitText: String = stringResource(id = StringR.string.action_ok),
cancelText: String = stringResource(id = StringR.string.action_cancel),
thirdButtonText: String? = null,
@ -60,7 +60,7 @@ fun ConfirmationDialog( @@ -60,7 +60,7 @@ fun ConfirmationDialog(
modifier = modifier,
onDismissRequest = onDismiss,
title = {
Text(text = title)
if (title != null) { Text(text = title) }
},
text = {
Text(content)

72
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcher.kt

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.utils
import androidx.annotation.StringRes
import androidx.compose.material3.SnackbarDuration
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class SnackbarDispatcher {
private val mutex = Mutex()
private val snackbarState = MutableStateFlow<SnackbarMessage?>(null)
val snackbarMessage: Flow<SnackbarMessage?> = snackbarState
suspend fun post(message: SnackbarMessage) {
mutex.withLock {
snackbarState.update { message }
}
}
suspend fun clear() {
mutex.withLock {
snackbarState.update { null }
}
}
}
@Composable
fun handleSnackbarMessage(
snackbarDispatcher: SnackbarDispatcher
): SnackbarMessage? {
val snackbarMessage by snackbarDispatcher.snackbarMessage.collectAsState(initial = null)
LaunchedEffect(snackbarMessage) {
if (snackbarMessage != null) {
launch(Dispatchers.Main) {
snackbarDispatcher.clear()
}
}
}
return snackbarMessage
}
data class SnackbarMessage(
@StringRes val messageResId: Int,
val duration: SnackbarDuration = SnackbarDuration.Short,
@StringRes val actionResId: Int? = null,
val action: () -> Unit = {},
)

3
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt

@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.SessionId @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import java.io.Closeable
@ -46,4 +47,6 @@ interface MatrixClient : Closeable { @@ -46,4 +47,6 @@ interface MatrixClient : Closeable {
): Result<ByteArray>
fun onSlidingSyncUpdate()
fun roomMembershipObserver(): RoomMembershipObserver
}

4
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt

@ -32,8 +32,10 @@ interface MatrixRoom: Closeable { @@ -32,8 +32,10 @@ interface MatrixRoom: Closeable {
val topic: String?
val avatarUrl: String?
val isEncrypted: Boolean
val isPublic: Boolean
suspend fun members() : List<RoomMember>
suspend fun memberCount(): Int
fun syncUpdateFlow(): Flow<Long>
@ -53,4 +55,6 @@ interface MatrixRoom: Closeable { @@ -53,4 +55,6 @@ interface MatrixRoom: Closeable {
suspend fun replyMessage(eventId: EventId, message: String): Result<Unit>
suspend fun redactEvent(eventId: EventId, reason: String? = null): Result<Unit>
fun leave(): Result<Unit>
}

40
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
/*
* 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.matrix.api.room
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class RoomMembershipObserver(
private val sessionId: SessionId,
) {
data class RoomMembershipUpdate(
val roomId: RoomId,
val isUserInRoom: Boolean,
val change: MembershipChange,
)
private val _updates = MutableSharedFlow<RoomMembershipUpdate>(replay = 1)
val updates = _updates.asSharedFlow()
fun notifyUserLeftRoom(roomId: RoomId) {
_updates.tryEmit(RoomMembershipUpdate(roomId, false, MembershipChange.LEFT))
}
}

8
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt

@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.impl.media.RustMediaResolver
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.impl.room.RustMatrixRoom
import io.element.android.libraries.matrix.impl.room.RustRoomSummaryDataSource
import io.element.android.libraries.matrix.impl.sync.SlidingSyncObserverProxy
@ -92,6 +93,7 @@ class RustMatrixClient constructor( @@ -92,6 +93,7 @@ class RustMatrixClient constructor(
requiredState = listOf(
RequiredState(key = "m.room.avatar", value = ""),
RequiredState(key = "m.room.encryption", value = ""),
RequiredState(key = "m.room.join_rules", value = ""),
)
)
.filters(slidingSyncFilters)
@ -131,6 +133,8 @@ class RustMatrixClient constructor( @@ -131,6 +133,8 @@ class RustMatrixClient constructor(
private val mediaResolver = RustMediaResolver(this)
private val isSyncing = AtomicBoolean(false)
private val roomMembershipObserver = RoomMembershipObserver(sessionId)
init {
client.setDelegate(clientDelegate)
rustRoomSummaryDataSource.init()
@ -153,7 +157,7 @@ class RustMatrixClient constructor( @@ -153,7 +157,7 @@ class RustMatrixClient constructor(
slidingSyncRoom = slidingSyncRoom,
innerRoom = fullRoom,
coroutineScope = coroutineScope,
coroutineDispatchers = dispatchers
coroutineDispatchers = dispatchers,
)
}
@ -270,6 +274,8 @@ class RustMatrixClient constructor( @@ -270,6 +274,8 @@ class RustMatrixClient constructor(
}
}
override fun roomMembershipObserver(): RoomMembershipObserver = roomMembershipObserver
private fun File.deleteSessionDirectory(userID: String): Boolean {
// Rust sanitises the user ID replacing invalid characters with an _
val sanitisedUserID = userID.replace(":", "_")

8
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt

@ -22,7 +22,9 @@ import dagger.Provides @@ -22,7 +22,9 @@ import dagger.Provides
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
@Module
@ContributesTo(SessionScope::class)
@ -32,4 +34,10 @@ object SessionMatrixModule { @@ -32,4 +34,10 @@ object SessionMatrixModule {
fun providesRustSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService {
return matrixClient.sessionVerificationService()
}
@Provides
@SingleIn(SessionScope::class)
fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver {
return matrixClient.roomMembershipObserver()
}
}

7
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt

@ -129,6 +129,9 @@ class RustMatrixRoom( @@ -129,6 +129,9 @@ class RustMatrixRoom(
override val alternativeAliases: List<String>
get() = innerRoom.alternativeAliases()
override val isPublic: Boolean
get() = innerRoom.isPublic()
override suspend fun fetchMembers(): Result<Unit> = withContext(coroutineDispatchers.io) {
runCatching {
innerRoom.fetchMembers()
@ -179,4 +182,8 @@ class RustMatrixRoom( @@ -179,4 +182,8 @@ class RustMatrixRoom(
innerRoom.redact(eventId.value, reason, transactionId)
}
}
override fun leave(): Result<Unit> {
return runCatching { innerRoom.leave() }
}
}

5
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt

@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.SessionId @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaResolver
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.test.media.FakeMediaResolver
@ -92,6 +93,10 @@ class FakeMatrixClient( @@ -92,6 +93,10 @@ class FakeMatrixClient(
override fun onSlidingSyncUpdate() {}
override fun roomMembershipObserver(): RoomMembershipObserver {
return RoomMembershipObserver(A_SESSION_ID)
}
// Mocks
fun givenLogoutError(failure: Throwable?) {

9
libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt

@ -37,6 +37,7 @@ class FakeMatrixRoom( @@ -37,6 +37,7 @@ class FakeMatrixRoom(
override val isEncrypted: Boolean = false,
override val alias: String? = null,
override val alternativeAliases: List<String> = emptyList(),
override val isPublic: Boolean = true,
private val members: List<RoomMember> = emptyList(),
private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(),
) : MatrixRoom {
@ -46,6 +47,8 @@ class FakeMatrixRoom( @@ -46,6 +47,8 @@ class FakeMatrixRoom(
var areMembersFetched: Boolean = false
private set
private var leaveRoomError: Throwable? = null
override fun syncUpdateFlow(): Flow<Long> {
return emptyFlow()
}
@ -114,8 +117,14 @@ class FakeMatrixRoom( @@ -114,8 +117,14 @@ class FakeMatrixRoom(
return Result.success(Unit)
}
override fun leave(): Result<Unit> = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit)
override fun close() = Unit
fun givenLeaveRoomError(throwable: Throwable?) {
this.leaveRoomError = throwable
}
fun givenFetchMemberResult(result: Result<Unit>) {
fetchMemberResult = result
}

17
libraries/textcomposer/src/main/res/values-es/translations.xml

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_bullet_list">"Lista de puntos"</string>
<string name="rich_text_editor_code_block">"Bloque de código"</string>
<string name="rich_text_editor_composer_placeholder">"Mensaje…"</string>
<string name="rich_text_editor_format_bold">"Aplicar formato negrita"</string>
<string name="rich_text_editor_format_italic">"Aplicar formato cursiva"</string>
<string name="rich_text_editor_format_strikethrough">"Aplicar formato tachado"</string>
<string name="rich_text_editor_format_underline">"Aplicar formato de subrayado"</string>
<string name="rich_text_editor_full_screen_toggle">"Pantalla completa"</string>
<string name="rich_text_editor_indent">"Añadir sangría"</string>
<string name="rich_text_editor_inline_code">"Código"</string>
<string name="rich_text_editor_link">"Enlazar"</string>
<string name="rich_text_editor_numbered_list">"Lista numérica"</string>
<string name="rich_text_editor_quote">"Cita"</string>
<string name="rich_text_editor_unindent">"Quitar sangría"</string>
</resources>

17
libraries/textcomposer/src/main/res/values-it/translations.xml

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_bullet_list">"Attiva/disattiva l\'elenco puntato"</string>
<string name="rich_text_editor_code_block">"Attiva/disattiva il blocco di codice"</string>
<string name="rich_text_editor_composer_placeholder">"Messaggio…"</string>
<string name="rich_text_editor_format_bold">"Applica il formato in grassetto"</string>
<string name="rich_text_editor_format_italic">"Applicare il formato corsivo"</string>
<string name="rich_text_editor_format_strikethrough">"Applica il formato barrato"</string>
<string name="rich_text_editor_format_underline">"Applicare il formato di sottolineatura"</string>
<string name="rich_text_editor_full_screen_toggle">"Attiva/disattiva la modalità a schermo intero"</string>
<string name="rich_text_editor_indent">"Rientro a destra"</string>
<string name="rich_text_editor_inline_code">"Applicare il formato del codice in linea"</string>
<string name="rich_text_editor_link">"Imposta collegamento"</string>
<string name="rich_text_editor_numbered_list">"Attiva/disattiva elenco numerato"</string>
<string name="rich_text_editor_quote">"Attiva/disattiva citazione"</string>
<string name="rich_text_editor_unindent">"Rientro a sinistra"</string>
</resources>

17
libraries/textcomposer/src/main/res/values-ro/translations.xml

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="rich_text_editor_bullet_list">"Comutați lista cu puncte"</string>
<string name="rich_text_editor_code_block">"Comutați blocul de cod"</string>
<string name="rich_text_editor_composer_placeholder">"Mesaj…"</string>
<string name="rich_text_editor_format_bold">"Aplicați formatul aldin"</string>
<string name="rich_text_editor_format_italic">"Aplicați formatul italic"</string>
<string name="rich_text_editor_format_strikethrough">"Aplicați formatul barat"</string>
<string name="rich_text_editor_format_underline">"Aplică formatul de subliniere"</string>
<string name="rich_text_editor_full_screen_toggle">"Comutați modul ecran complet"</string>
<string name="rich_text_editor_indent">"Indentare"</string>
<string name="rich_text_editor_inline_code">"Aplicați formatul de cod inline"</string>
<string name="rich_text_editor_link">"Setați linkul"</string>
<string name="rich_text_editor_numbered_list">"Comutați lista numerotată"</string>
<string name="rich_text_editor_quote">"Aplicați citatul"</string>
<string name="rich_text_editor_unindent">"Dez-identare"</string>
</resources>

1
libraries/ui-strings/src/main/res/values-de/translations.xml

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_confirm">"Bestätigen"</string>
<string name="test_language_identifier">"de"</string>
</resources>

148
libraries/ui-strings/src/main/res/values-es/translations.xml

@ -0,0 +1,148 @@ @@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_hide_password">"Ocultar contraseña"</string>
<string name="a11y_send_files">"Enviar archivos"</string>
<string name="a11y_show_password">"Mostrar contraseña"</string>
<string name="a11y_user_menu">"Menú de usuario"</string>
<string name="action_back">"Atrás"</string>
<string name="action_cancel">"Cancelar"</string>
<string name="action_clear">"Borrar"</string>
<string name="action_close">"Cerrar"</string>
<string name="action_complete_verification">"Completar verificación"</string>
<string name="action_confirm">"Confirmar"</string>
<string name="action_continue">"Continuar"</string>
<string name="action_copy">"Copiar"</string>
<string name="action_copy_link">"Copiar enlace"</string>
<string name="action_create_a_room">"Crear una sala"</string>
<string name="action_disable">"Desactivar"</string>
<string name="action_done">"Hecho"</string>
<string name="action_edit">"Editar"</string>
<string name="action_enable">"Activar"</string>
<string name="action_invite">"Invitar"</string>
<string name="action_invite_friends_to_app">"Invitar amigos a %1$s"</string>
<string name="action_learn_more">"Más información"</string>
<string name="action_leave">"Salir"</string>
<string name="action_leave_room">"Salir de la sala"</string>
<string name="action_next">"Siguiente"</string>
<string name="action_no">"No"</string>
<string name="action_not_now">"Ahora no"</string>
<string name="action_ok">"OK"</string>
<string name="action_quick_reply">"Respuesta rápida"</string>
<string name="action_quote">"Citar"</string>
<string name="action_remove">"Eliminar"</string>
<string name="action_reply">"Responder"</string>
<string name="action_report_bug">"Informar de un error"</string>
<string name="action_report_content">"Reportar Contenido"</string>
<string name="action_retry">"Reintentar"</string>
<string name="action_retry_decryption">"Reintentar descifrado"</string>
<string name="action_save">"Guardar"</string>
<string name="action_search">"Buscar"</string>
<string name="action_send">"Enviar"</string>
<string name="action_share">"Compartir"</string>
<string name="action_share_link">"Compartir enlace"</string>
<string name="action_skip">"Saltar"</string>
<string name="action_start">"Comenzar"</string>
<string name="action_start_chat">"Iniciar chat"</string>
<string name="action_start_verification">"Iniciar la verificación"</string>
<string name="action_view_source">"Ver Fuente"</string>
<string name="action_yes">"Sí"</string>
<string name="common_about">"Acerca de"</string>
<string name="common_audio">"Sonido"</string>
<string name="common_bubbles">"Burbujas"</string>
<string name="common_creating_room">"Creando sala…"</string>
<string name="common_current_user_left_room">"Saliste de la sala"</string>
<string name="common_decryption_error">"Error de descifrado"</string>
<string name="common_developer_options">"Opciones de desarrollador"</string>
<string name="common_edited_suffix">"(editado)"</string>
<string name="common_editing">"Edición"</string>
<string name="common_encryption_enabled">"Cifrado activado"</string>
<string name="common_error">"Error"</string>
<string name="common_file">"Archivo"</string>
<string name="common_gif">"GIF"</string>
<string name="common_image">"Imagen"</string>
<string name="common_link_copied_to_clipboard">"Enlace copiado al portapapeles"</string>
<string name="common_loading">"Cargando…"</string>
<string name="common_message">"Mensaje"</string>
<string name="common_message_layout">"Diseño del mensaje"</string>
<string name="common_message_removed">"Mensaje eliminado"</string>
<string name="common_modern">"Moderno"</string>
<string name="common_no_results">"No hay resultados"</string>
<string name="common_offline">"Sin conexión"</string>
<string name="common_password">"Contraseña"</string>
<string name="common_people">"Personas"</string>
<string name="common_permalink">"Enlace permanente"</string>
<string name="common_reactions">"Reacciones"</string>
<string name="common_replying_to">"Respondiendo a %1$s"</string>
<string name="common_report_a_bug">"Informar de un error"</string>
<string name="common_report_submitted">"Informe enviado"</string>
<string name="common_search_for_someone">"Buscar a alguien"</string>
<string name="common_security">"Seguridad"</string>
<string name="common_select_your_server">"Selecciona tu servidor"</string>
<string name="common_sending">"Enviando…"</string>
<string name="common_server_not_supported">"Servidor no compatible"</string>
<string name="common_server_url">"Dirección del servidor"</string>
<string name="common_settings">"Ajustes"</string>
<string name="common_sticker">"Sticker"</string>
<string name="common_success">"Terminado"</string>
<string name="common_suggestions">"Sugerencias"</string>
<string name="common_topic">"Tema"</string>
<string name="common_unable_to_decrypt">"No se puede descifrar"</string>
<string name="common_unsupported_event">"Evento no compatible"</string>
<string name="common_username">"Usuario"</string>
<string name="common_verification_cancelled">"Verificación cancelada"</string>
<string name="common_verification_complete">"Verificación completada"</string>
<string name="common_video">"Vídeo"</string>
<string name="common_waiting">"Esperando…"</string>
<string name="dialog_title_confirmation">"Confirmar"</string>
<string name="dialog_title_error">"Error"</string>
<string name="dialog_title_success">"Terminado"</string>
<string name="dialog_title_warning">"Atención"</string>
<string name="emoji_picker_category_activity">"Actividades"</string>
<string name="emoji_picker_category_flags">"Banderas"</string>
<string name="emoji_picker_category_foods">"Comida y bebida"</string>
<string name="emoji_picker_category_nature">"Animales y naturaleza"</string>
<string name="emoji_picker_category_objects">"Objetos"</string>
<string name="emoji_picker_category_people">"Emojis y personas"</string>
<string name="emoji_picker_category_places">"Viajes y lugares"</string>
<string name="emoji_picker_category_symbols">"Símbolos"</string>
<string name="error_failed_creating_the_permalink">"No se pudo crear el enlace permanente"</string>
<string name="error_failed_loading_messages">"Error al cargar mensajes"</string>
<string name="error_no_compatible_app_found">"No se encontró ninguna aplicación compatible con esta acción."</string>
<string name="error_some_messages_have_not_been_sent">"Algunos mensajes no se han enviado"</string>
<string name="error_unknown">"Lo siento, se ha producido un error"</string>
<string name="invite_friends_text">"Hola, puedes hablar conmigo en %1$s: %2$s"</string>
<string name="leave_room_alert_empty_subtitle">"¿Estás seguro de que quieres salir de esta sala? Eres la única persona aquí. Si te vas, nadie podrá unirse en el futuro, ni siquiera tú."</string>
<string name="leave_room_alert_private_subtitle">"¿Estás seguro de que quieres abandonar esta sala? Esta sala no es pública y no podrás volver a entrar sin una invitación."</string>
<string name="leave_room_alert_subtitle">"¿Seguro que quieres salir de la habitación?"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="common_member_count">
<item quantity="one">"%1$d miembro"</item>
<item quantity="other">"%1$d miembros"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d cambio en la sala"</item>
<item quantity="other">"%1$d cambios en la sala"</item>
</plurals>
<string name="preference_rageshake">"Agitar con fuerza para informar de un error"</string>
<string name="rageshake_dialog_content">"Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?"</string>
<string name="report_content_explanation">"Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado."</string>
<string name="report_content_hint">"Motivo para denunciar este contenido"</string>
<string name="room_timeline_beginning_of_room">"Este es el principio de %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Este es el principio de esta conversación."</string>
<string name="room_timeline_read_marker_title">"Nuevos"</string>
<string name="screen_report_content_block_user">"Bloquear usuario"</string>
<string name="screen_report_content_block_user_hint">"Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario"</string>
<string name="screen_room_member_details_block_alert_action">"Bloquear"</string>
<string name="screen_room_member_details_block_alert_description">"Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puede revertir esta acción en cualquier momento."</string>
<string name="screen_room_member_details_block_user">"Bloquear usuario"</string>
<string name="screen_room_member_details_unblock_alert_action">"Desbloquear"</string>
<string name="screen_room_member_details_unblock_alert_description">"Al desbloquear al usuario, podrás volver a ver todos sus mensajes."</string>
<string name="screen_room_member_details_unblock_user">"Desbloquear usuario"</string>
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
<string name="screen_start_chat_unknown_profile">"No podemos validar el ID de Matrix de este usuario. Es posible que no reciba la invitación."</string>
<string name="settings_rageshake">"Agitar con fuerza"</string>
<string name="settings_rageshake_detection_threshold">"Umbral de detección"</string>
<string name="settings_title_general">"General"</string>
<string name="settings_version_number">"Versión: %1$s (%2$s)"</string>
<string name="test_language_identifier">"es"</string>
</resources>

1
libraries/ui-strings/src/main/res/values-fr/translations.xml

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_confirm">"Confirmer"</string>
<string name="test_language_identifier">"fr"</string>
</resources>

148
libraries/ui-strings/src/main/res/values-it/translations.xml

@ -0,0 +1,148 @@ @@ -0,0 +1,148 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="a11y_hide_password">"Nascondi password"</string>
<string name="a11y_send_files">"Invia file"</string>
<string name="a11y_show_password">"Mostra password"</string>
<string name="a11y_user_menu">"Menu utente"</string>
<string name="action_back">"Indietro"</string>
<string name="action_cancel">"Annulla"</string>
<string name="action_clear">"Cancella"</string>
<string name="action_close">"Chiudi"</string>
<string name="action_complete_verification">"Completa verifica"</string>
<string name="action_confirm">"Conferma"</string>
<string name="action_continue">"Continua"</string>
<string name="action_copy">"Copia"</string>
<string name="action_copy_link">"Copia collegamento"</string>
<string name="action_create_a_room">"Crea una stanza"</string>
<string name="action_disable">"Disabilita"</string>
<string name="action_done">"Fine"</string>
<string name="action_edit">"Modifica"</string>
<string name="action_enable">"Attiva"</string>
<string name="action_invite">"Invita"</string>
<string name="action_invite_friends_to_app">"Invita amici a %1$s"</string>
<string name="action_learn_more">"Ulteriori informazioni"</string>
<string name="action_leave">"Esci"</string>
<string name="action_leave_room">"Esci dalla stanza"</string>
<string name="action_next">"Avanti"</string>
<string name="action_no">"No"</string>
<string name="action_not_now">"Non ora"</string>
<string name="action_ok">"OK"</string>
<string name="action_quick_reply">"Risposta rapida"</string>
<string name="action_quote">"Citazione"</string>
<string name="action_remove">"Rimuovi"</string>
<string name="action_reply">"Rispondi"</string>
<string name="action_report_bug">"Segnala un problema"</string>
<string name="action_report_content">"Segnala Contenuto"</string>
<string name="action_retry">"Riprova"</string>
<string name="action_retry_decryption">"Riprova la decrittazione"</string>
<string name="action_save">"Salva"</string>
<string name="action_search">"Ricerca"</string>
<string name="action_send">"Invia"</string>
<string name="action_share">"Condividi"</string>
<string name="action_share_link">"Condividi collegamento"</string>
<string name="action_skip">"Salta"</string>
<string name="action_start">"Inizia"</string>
<string name="action_start_chat">"Avvia conversazione"</string>
<string name="action_start_verification">"Avvia la verifica"</string>
<string name="action_view_source">"Vedi Sorgente"</string>
<string name="action_yes">"Sì"</string>
<string name="common_about">"Informazioni"</string>
<string name="common_audio">"Audio"</string>
<string name="common_bubbles">"Fumetti"</string>
<string name="common_creating_room">"Creazione stanza…"</string>
<string name="common_current_user_left_room">"Hai lasciato la stanza"</string>
<string name="common_decryption_error">"Errore di decrittazione"</string>
<string name="common_developer_options">"Opzioni sviluppatore"</string>
<string name="common_edited_suffix">"(modificato)"</string>
<string name="common_editing">"Modifica in corso"</string>
<string name="common_encryption_enabled">"Crittografia abilitata"</string>
<string name="common_error">"Errore"</string>
<string name="common_file">"File"</string>
<string name="common_gif">"GIF"</string>
<string name="common_image">"Immagine"</string>
<string name="common_link_copied_to_clipboard">"Collegamento copiato negli appunti"</string>
<string name="common_loading">"Caricamento…"</string>
<string name="common_message">"Messaggio"</string>
<string name="common_message_layout">"Layout del messaggio"</string>
<string name="common_message_removed">"Messaggio rimosso"</string>
<string name="common_modern">"Moderno"</string>
<string name="common_no_results">"Nessun risultato"</string>
<string name="common_offline">"Non in linea"</string>
<string name="common_password">"Password"</string>
<string name="common_people">"Persone"</string>
<string name="common_permalink">"Collegamento permanente"</string>
<string name="common_reactions">"Reazioni"</string>
<string name="common_replying_to">"Risposta a %1$s"</string>
<string name="common_report_a_bug">"Segnala un problema"</string>
<string name="common_report_submitted">"Segnalazione inviata"</string>
<string name="common_search_for_someone">"Cerca qualcuno"</string>
<string name="common_security">"Sicurezza"</string>
<string name="common_select_your_server">"Seleziona il tuo server"</string>
<string name="common_sending">"Invio in corso…"</string>
<string name="common_server_not_supported">"Server non supportato"</string>
<string name="common_server_url">"URL del server"</string>
<string name="common_settings">"Impostazioni"</string>
<string name="common_sticker">"Adesivo"</string>
<string name="common_success">"Operazione riuscita"</string>
<string name="common_suggestions">"Suggerimenti"</string>
<string name="common_topic">"Oggetto"</string>
<string name="common_unable_to_decrypt">"Impossibile decrittografare"</string>
<string name="common_unsupported_event">"Evento non supportato"</string>
<string name="common_username">"Nome utente"</string>
<string name="common_verification_cancelled">"Verifica annullata"</string>
<string name="common_verification_complete">"Verifica completata"</string>
<string name="common_video">"Video"</string>
<string name="common_waiting">"In attesa…"</string>
<string name="dialog_title_confirmation">"Conferma"</string>
<string name="dialog_title_error">"Errore"</string>
<string name="dialog_title_success">"Operazione riuscita"</string>
<string name="dialog_title_warning">"Attenzione"</string>
<string name="emoji_picker_category_activity">"Attività"</string>
<string name="emoji_picker_category_flags">"Bandiere"</string>
<string name="emoji_picker_category_foods">"Cibi &amp; Bevande"</string>
<string name="emoji_picker_category_nature">"Animali &amp; Natura"</string>
<string name="emoji_picker_category_objects">"Oggetti"</string>
<string name="emoji_picker_category_people">"Faccine &amp; Persone"</string>
<string name="emoji_picker_category_places">"Viaggi &amp; Luoghi"</string>
<string name="emoji_picker_category_symbols">"Simboli"</string>
<string name="error_failed_creating_the_permalink">"Impossibile creare il collegamento permanente"</string>
<string name="error_failed_loading_messages">"Caricamento dei messaggi non riuscito"</string>
<string name="error_no_compatible_app_found">"Non è stata trovata alcuna app compatibile per gestire questa azione."</string>
<string name="error_some_messages_have_not_been_sent">"Alcuni messaggi non sono stati inviati"</string>
<string name="error_unknown">"Siamo spiacenti, si è verificato un errore"</string>
<string name="invite_friends_text">"Ehi, parlami su %1$s: %2$s"</string>
<string name="leave_room_alert_empty_subtitle">"Sei sicuro di voler lasciare questa stanza? Sei l\'unica persona presente. Se esci, nessuno potrà unirsi in futuro, te compreso."</string>
<string name="leave_room_alert_private_subtitle">"Sei sicuro di voler lasciare questa stanza? Questa stanza non è pubblica e non potrai rientrare senza un invito."</string>
<string name="leave_room_alert_subtitle">"Sei sicuro di voler lasciare la stanza?"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="common_member_count">
<item quantity="one">"%1$d membro"</item>
<item quantity="other">"%1$d membri"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d modifica alla stanza"</item>
<item quantity="other">"%1$d modifiche alla stanza"</item>
</plurals>
<string name="preference_rageshake">"Scuoti per segnalare un problema"</string>
<string name="rageshake_dialog_content">"Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?"</string>
<string name="report_content_explanation">"Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati."</string>
<string name="report_content_hint">"Motivo della segnalazione di questo contenuto"</string>
<string name="room_timeline_beginning_of_room">"Questo è l\'inizio di %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Questo è l\'inizio della conversazione."</string>
<string name="room_timeline_read_marker_title">"Nuovo"</string>
<string name="screen_report_content_block_user">"Blocca utente"</string>
<string name="screen_report_content_block_user_hint">"Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente"</string>
<string name="screen_room_member_details_block_alert_action">"Blocca"</string>
<string name="screen_room_member_details_block_alert_description">"Gli utenti bloccati non saranno in grado di inviarti nuovi messaggi e tutti quelli già esistenti saranno nascosti. Potrai annullare questa azione in qualsiasi momento."</string>
<string name="screen_room_member_details_block_user">"Blocca utente"</string>
<string name="screen_room_member_details_unblock_alert_action">"Sblocca"</string>
<string name="screen_room_member_details_unblock_alert_description">"Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi."</string>
<string name="screen_room_member_details_unblock_user">"Sblocca utente"</string>
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
<string name="screen_start_chat_unknown_profile">"Non possiamo convalidare l\'ID Matrix di questo utente. L\'invito potrebbe non essere ricevuto."</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Soglia di rilevamento"</string>
<string name="settings_title_general">"Generali"</string>
<string name="settings_version_number">"Versione: %1$s (%2$s)"</string>
<string name="test_language_identifier">"it"</string>
</resources>

150
libraries/ui-strings/src/main/res/values-ro/translations.xml

@ -1,10 +1,150 @@ @@ -1,10 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="action_confirm">"Confirmare"</string>
<string name="a11y_hide_password">"Ascundeți parola"</string>
<string name="a11y_send_files">"Trimiteți fișiere"</string>
<string name="a11y_show_password">"Afișați parola"</string>
<string name="a11y_user_menu">"Meniu utilizator"</string>
<string name="action_back">"Înapoi"</string>
<string name="action_cancel">"Anulați"</string>
<string name="action_clear">"Ștergeți"</string>
<string name="action_close">"Închideți"</string>
<string name="action_complete_verification">"Verificare completă"</string>
<string name="action_confirm">"Confirmați"</string>
<string name="action_continue">"Continuați"</string>
<string name="action_copy">"Copiați"</string>
<string name="action_copy_link">"Copiați linkul"</string>
<string name="action_create_a_room">"Creați o cameră"</string>
<string name="action_done">"Gata"</string>
<string name="action_disable">"Dezactivați"</string>
<string name="action_done">"Efectuat"</string>
<string name="action_edit">"Editați"</string>
<string name="action_enable">"Activați"</string>
<string name="action_invite">"Invitați"</string>
<string name="action_invite_friends_to_app">"Invitați prieteni în %1$s"</string>
<string name="action_learn_more">"Aflați mai multe"</string>
<string name="action_leave">"Părăsiți"</string>
<string name="action_leave_room">"Părăsiți camera"</string>
<string name="action_next">"Următorul"</string>
<string name="action_no">"Nu"</string>
<string name="action_not_now">"Nu acum"</string>
<string name="action_ok">"OK"</string>
<string name="action_report_content">"Raportează conținutul"</string>
<string name="action_start_chat">"Începe discuția"</string>
<string name="action_view_source">"Vezi sursa"</string>
<string name="action_quick_reply">"Raspuns rapid"</string>
<string name="action_quote">"Citat"</string>
<string name="action_remove">"Ștergeți"</string>
<string name="action_reply">"Răspundeți"</string>
<string name="action_report_bug">"Raportați o eroare"</string>
<string name="action_report_content">"Raportați conținutul"</string>
<string name="action_retry">"Reîncercați"</string>
<string name="action_retry_decryption">"Reîncercați decriptarea"</string>
<string name="action_save">"Salvați"</string>
<string name="action_search">"Căutați"</string>
<string name="action_send">"Trimiteți"</string>
<string name="action_share">"Partajați"</string>
<string name="action_share_link">"Partajați linkul"</string>
<string name="action_skip">"Omiteți"</string>
<string name="action_start">"Începeți"</string>
<string name="action_start_chat">"Începeți discuția"</string>
<string name="action_start_verification">"Începeți verificarea"</string>
<string name="action_view_source">"Vedeți sursă"</string>
<string name="action_yes">"Da"</string>
<string name="common_about">"Despre"</string>
<string name="common_audio">"Audio"</string>
<string name="common_bubbles">"Baloane"</string>
<string name="common_creating_room">"Se creează camera…"</string>
<string name="common_current_user_left_room">"Ați parăsit camera"</string>
<string name="common_decryption_error">"Eroare de decriptare"</string>
<string name="common_developer_options">"Opțiuni programator"</string>
<string name="common_edited_suffix">"(editat)"</string>
<string name="common_editing">"Editare"</string>
<string name="common_encryption_enabled">"Criptare activată"</string>
<string name="common_error">"Eroare"</string>
<string name="common_file">"Fişier"</string>
<string name="common_gif">"GIF"</string>
<string name="common_image">"Imagine"</string>
<string name="common_link_copied_to_clipboard">"Linkul a fost copiat în clipboard"</string>
<string name="common_loading">"Se încarcă…"</string>
<string name="common_message">"Mesaj"</string>
<string name="common_message_layout">"Aranjamentul mesajelor"</string>
<string name="common_message_removed">"Mesaj sters"</string>
<string name="common_modern">"Modern"</string>
<string name="common_no_results">"Niciun rezultat"</string>
<string name="common_offline">"Deconectat"</string>
<string name="common_password">"Parola"</string>
<string name="common_people">"Persoane"</string>
<string name="common_permalink">"Permalink"</string>
<string name="common_reactions">"Reacții"</string>
<string name="common_replying_to">"Răspuns pentru %1$s"</string>
<string name="common_report_a_bug">"Raportați o eroare"</string>
<string name="common_report_submitted">"Raport trimis"</string>
<string name="common_search_for_someone">"Căutați pe cineva"</string>
<string name="common_security">"Securitate"</string>
<string name="common_select_your_server">"Selectați serverul"</string>
<string name="common_sending">"Se trimite…"</string>
<string name="common_server_not_supported">"Serverul nu este compatibil"</string>
<string name="common_server_url">"Adresa URL a serverului"</string>
<string name="common_settings">"Setări"</string>
<string name="common_sticker">"Autocolant"</string>
<string name="common_success">"Succes"</string>
<string name="common_suggestions">"Sugestii"</string>
<string name="common_topic">"Subiect"</string>
<string name="common_unable_to_decrypt">"Nu s-a putut decripta"</string>
<string name="common_unsupported_event">"Eveniment neacceptat"</string>
<string name="common_username">"Utilizator"</string>
<string name="common_verification_cancelled">"Verificare anulată"</string>
<string name="common_verification_complete">"Verificare completă"</string>
<string name="common_video">"Video"</string>
<string name="common_waiting">"Se aşteaptă…"</string>
<string name="dialog_title_confirmation">"Confirmare"</string>
<string name="dialog_title_error">"Eroare"</string>
<string name="dialog_title_success">"Succes"</string>
<string name="dialog_title_warning">"Avertisment"</string>
<string name="emoji_picker_category_activity">"Activități"</string>
<string name="emoji_picker_category_flags">"Steaguri"</string>
<string name="emoji_picker_category_foods">"Mâncare &amp; Băutură"</string>
<string name="emoji_picker_category_nature">"Animale și Natură"</string>
<string name="emoji_picker_category_objects">"Obiecte"</string>
<string name="emoji_picker_category_people">"Fețe zâmbitoare &amp; Oameni"</string>
<string name="emoji_picker_category_places">"Călătorii &amp; Locuri"</string>
<string name="emoji_picker_category_symbols">"Simboluri"</string>
<string name="error_failed_creating_the_permalink">"Crearea permalink-ului a eșuat"</string>
<string name="error_failed_loading_messages">"Încărcarea mesajelor a eșuat"</string>
<string name="error_no_compatible_app_found">"Nu a fost găsită nicio aplicație capabilă să gestioneze această acțiune."</string>
<string name="error_some_messages_have_not_been_sent">"Unele mesaje nu au fost trimise"</string>
<string name="error_unknown">"Ne pare rău, a apărut o eroare"</string>
<string name="invite_friends_text">"Hei, vorbește cu mine pe %1$s: %2$s"</string>
<string name="leave_room_alert_empty_subtitle">"Sunteți sigur că vreți să părăsiți această cameră? Sunteți singura persoană de aici. Dacă o părasiți, nimeni nu se va mai putea alătura în viitor, inclusiv dumneavoastra."</string>
<string name="leave_room_alert_private_subtitle">"Sunteți sigur că vrei să părăsiți această cameră? Această cameră nu este publică și nu va veti putea alătura din nou fără o invitație."</string>
<string name="leave_room_alert_subtitle">"Sunteți sigur că vreți să părăsiți camera?"</string>
<string name="login_initial_device_name_android">"%1$s Android"</string>
<plurals name="common_member_count">
<item quantity="one">"%1$d membru"</item>
<item quantity="few"></item>
<item quantity="other">"%1$d membri"</item>
</plurals>
<plurals name="room_timeline_state_changes">
<item quantity="one">"%1$d schimbare a camerii"</item>
<item quantity="few"></item>
<item quantity="other">"%1$d schimbări ale camerei"</item>
</plurals>
<string name="preference_rageshake">"Rageshake pentru a raporta erori"</string>
<string name="rageshake_dialog_content">"Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?"</string>
<string name="report_content_explanation">"Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat."</string>
<string name="report_content_hint">"Motivul raportării acestui conținut"</string>
<string name="room_timeline_beginning_of_room">"Acesta este începutul conversației %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Acesta este începutul acestei conversații."</string>
<string name="room_timeline_read_marker_title">"Nou"</string>
<string name="screen_report_content_block_user">"Blocați utilizatorul"</string>
<string name="screen_report_content_block_user_hint">"Confirmați că doriți să ascundeți toate mesajele curente și viitoare de la acest utilizator"</string>
<string name="screen_room_member_details_block_alert_action">"Blocați"</string>
<string name="screen_room_member_details_block_alert_description">"Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând."</string>
<string name="screen_room_member_details_block_user">"Blocați utilizatorul"</string>
<string name="screen_room_member_details_unblock_alert_action">"Deblocați"</string>
<string name="screen_room_member_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
<string name="screen_room_member_details_unblock_user">"Deblocați utilizatorul"</string>
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
<string name="screen_start_chat_unknown_profile">"Nu am putut valida ID-ul Matrix al acestui utilizator. Este posibil ca invitația să nu fi fost primită."</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Prag de detecție"</string>
<string name="settings_title_general">"General"</string>
<string name="settings_version_number">"Versiunea: %1$s (%2$s)"</string>
<string name="test_language_identifier">"ro"</string>
</resources>

11
libraries/ui-strings/src/main/res/values/localazy.xml

@ -38,6 +38,7 @@ @@ -38,6 +38,7 @@
<string name="action_save">"Save"</string>
<string name="action_search">"Search"</string>
<string name="action_send">"Send"</string>
<string name="action_share">"Share"</string>
<string name="action_share_link">"Share link"</string>
<string name="action_skip">"Skip"</string>
<string name="action_start">"Start"</string>
@ -49,6 +50,7 @@ @@ -49,6 +50,7 @@
<string name="common_audio">"Audio"</string>
<string name="common_bubbles">"Bubbles"</string>
<string name="common_creating_room">"Creating room…"</string>
<string name="common_current_user_left_room">"Left room"</string>
<string name="common_decryption_error">"Decryption error"</string>
<string name="common_developer_options">"Developer options"</string>
<string name="common_edited_suffix">"(edited)"</string>
@ -128,12 +130,6 @@ @@ -128,12 +130,6 @@
<string name="room_timeline_beginning_of_room">"This is the beginning of %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"This is the beginning of this conversation."</string>
<string name="room_timeline_read_marker_title">"New"</string>
<string name="screen_dm_details_block_alert_action">"Block"</string>
<string name="screen_dm_details_block_alert_description">"Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime."</string>
<string name="screen_dm_details_block_user">"Block user"</string>
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
<string name="screen_dm_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
<string name="screen_dm_details_unblock_user">"Unblock user"</string>
<string name="screen_report_content_block_user">"Block user"</string>
<string name="screen_report_content_block_user_hint">"Check if you want to hide all current and future messages from this user"</string>
<string name="screen_room_member_details_block_alert_action">"Block"</string>
@ -143,8 +139,11 @@ @@ -143,8 +139,11 @@
<string name="screen_room_member_details_unblock_alert_description">"On unblocking the user, you will be able to see all messages by them again."</string>
<string name="screen_room_member_details_unblock_user">"Unblock user"</string>
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
<string name="screen_start_chat_unknown_profile">"We can’t validate this user’s Matrix ID. The invite might not be received."</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Detection threshold"</string>
<string name="settings_title_general">"General"</string>
<string name="settings_version_number">"Version: %1$s (%2$s)"</string>
<string name="test_language_identifier">"en"</string>
<string name="test_untranslated_default_language_identifier">"en"</string>
</resources>

4
samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt

@ -26,6 +26,7 @@ import io.element.android.features.roomlist.impl.RoomListView @@ -26,6 +26,7 @@ import io.element.android.features.roomlist.impl.RoomListView
import io.element.android.libraries.dateformatter.impl.DateFormatters
import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter
import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.launch
@ -47,7 +48,8 @@ class RoomListScreen( @@ -47,7 +48,8 @@ class RoomListScreen(
matrixClient,
DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
DefaultRoomLastMessageFormatter(context, matrixClient),
sessionVerificationService
sessionVerificationService,
SnackbarDispatcher(),
)
@Composable

47
tools/localazy/README.md

@ -2,12 +2,49 @@ @@ -2,12 +2,49 @@
Localazy is used to host the source strings and their translations.
<!--- TOC -->
* [Localazy project](#localazy-project)
* [Key naming rules](#key-naming-rules)
* [Special suffixes](#special-suffixes)
* [Placeholders](#placeholders)
* [CLI Installation](#cli-installation)
* [Download translations](#download-translations)
* [Add translations to a specific module](#add-translations-to-a-specific-module)
<!--- END -->
## Localazy project
To add new strings, or to translate existing strings, go the the Localazy project: [https://localazy.com/p/element](https://localazy.com/p/element).
To add new strings, or to translate existing strings, go the the Localazy project: [https://localazy.com/p/element](https://localazy.com/p/element). Please follow the key naming rules (see below).
Never edit manually the files `localazy.xml` or `translations.xml`!.
### Key naming rules
For code clarity and in order to download strings to the correct module, here are some naming rules to follow as much as possible:
- Keys for common strings, i.e. strings that can be used at multiple places must start by `action_` if this is a verb, or `common_` if not;
- Keys for common accessibility strings must start by `a11y_`. Example: `a11y_hide_password`;
- Keys for strings used in a single screen must start with `screen_` followed by the screen name, followed by a free name. Example: `screen_onboarding_welcome_title`;
- Keys can have `_title` or `_subtitle` suffixes. Example: `screen_onboarding_welcome_title`, `screen_change_server_subtitle`;
- For dialogs, keys can have `_dialog_title`, `_dialog_content`, and `_dialog_submit` suffixes. Example: `screen_signout_confirmation_dialog_title`, `screen_signout_confirmation_dialog_content`, `screen_signout_confirmation_dialog_submit`;
- `a11y_` pattern can be used for strings that are only used for accessibility. Example: `a11y_hide_password`, `screen_roomlist_a11y_create_message`;
- Strings for error message can start by `error_`, or contain `_error_` if used in a specific screen only. Example: `error_some_messages_have_not_been_sent`, `screen_change_server_error_invalid_homeserver`.
*Note*: those rules applies for `strings` and for `plurals`.
#### Special suffixes
- if a key is suffixed by `_ios`, it will not be imported in the Android project;
- if a key is suffixed by `_android`, it will not be imported in the iOS project.
So feel free to use those suffixes when necessary for instance when the string content is referring to something related to Android only, or iOS only.
#### Placeholders
Placeholders should have the form `%1$s`, `%1$d`, etc.. Please use numbered placeholders. Note that Localazy will take care of converting the placeholder to Android (-> `%1$s`) and iOS specific format (-> `%1$@`). Ideally add a comment on Localazy to explain with what the placeholder(s) will be replaced at runtime.
## CLI Installation
To install the Localazy client, follow the instructions from [here](https://localazy.com/docs/cli/installation).
@ -20,7 +57,13 @@ In the root folder of the project, run: @@ -20,7 +57,13 @@ In the root folder of the project, run:
./tools/localazy/downloadStrings.sh
```
It will update all the `localazy.xml` and `translations.xml` resource files. In case of merge conflicts, just erase the files and download again using the script.
It will update all the `localazy.xml` resource files. In case of merge conflicts, just erase the files and download again using the script.
To also include the translations, i.e. the `translations.xml` files, add `--all` argument:
```shell
./tools/localazy/downloadStrings.sh --all
```
## Add translations to a specific module

18
tools/localazy/downloadStrings.sh

@ -18,12 +18,24 @@ @@ -18,12 +18,24 @@
set -e
if [[ $1 == "--all" ]]; then
echo "Note: I will update all the files."
allFiles=1
else
echo "Note: I will update only the English files."
allFiles=0
fi
echo "Generating the configuration file for localazy..."
./tools/localazy/generateLocalazyConfig.py
./tools/localazy/generateLocalazyConfig.py $allFiles
echo "Deleting all existing localazy.xml and translations.xml files..."
echo "Deleting all existing localazy.xml files..."
find . -name 'localazy.xml' -delete
find . -name 'translations.xml' -delete
if [[ $allFiles == 1 ]]; then
echo "Deleting all existing translations.xml files..."
find . -name 'translations.xml' -delete
fi
echo "Importing the strings..."
localazy download --config ./tools/localazy/localazy.json

60
tools/localazy/generateLocalazyConfig.py

@ -1,11 +1,13 @@ @@ -1,11 +1,13 @@
#!/usr/bin/env python3
import json
import sys
# Read the config.json file
with open('./tools/localazy/config.json', 'r') as f:
config = json.load(f)
allFiles = sys.argv[1] == "1"
# Convert a module name to a path
# Ex: ":features:verifysession:impl" => "features/verifysession/impl"
@ -18,6 +20,14 @@ regexToAlwaysExclude = [ @@ -18,6 +20,14 @@ regexToAlwaysExclude = [
".*_ios"
]
baseAction = {
"type": "android",
# Replacement done in all string values
"replacements": {
"...": ""
}
}
# Store all regex specific to module, to eclude the corresponding keyx from the common string module
allRegexToExcludeFromMainModule = []
# All actions that will be serialized in the localazy config
@ -26,8 +36,7 @@ allActions = [] @@ -26,8 +36,7 @@ allActions = []
# Iterating on the config
for entry in config["modules"]:
# Create action for the default language
action = {
"type": "android",
action = baseAction | {
"output": convertModuleToPath(entry["name"]) + "/src/main/res/values/localazy.xml",
"includeKeys": list(map(lambda i: "REGEX:" + i, entry["includeRegex"])),
"excludeKeys": list(map(lambda i: "REGEX:" + i, regexToAlwaysExclude)),
@ -35,24 +44,23 @@ for entry in config["modules"]: @@ -35,24 +44,23 @@ for entry in config["modules"]:
"equals: ${languageCode}, en"
]
}
# Create action for the translations
actionTranslation = {
"type": "android",
"output": convertModuleToPath(entry["name"]) + "/src/main/res/values-${langAndroidResNoScript}/translations.xml",
"includeKeys": list(map(lambda i: "REGEX:" + i, entry["includeRegex"])),
"excludeKeys": list(map(lambda i: "REGEX:" + i, regexToAlwaysExclude)),
"conditions": [
"!equals: ${languageCode}, en"
]
}
# print(action)
allRegexToExcludeFromMainModule.extend(entry["includeRegex"])
allActions.append(action)
allActions.append(actionTranslation)
# Create action for the translations
if allFiles:
actionTranslation = baseAction | {
"output": convertModuleToPath(entry["name"]) + "/src/main/res/values-${langAndroidResNoScript}/translations.xml",
"includeKeys": list(map(lambda i: "REGEX:" + i, entry["includeRegex"])),
"excludeKeys": list(map(lambda i: "REGEX:" + i, regexToAlwaysExclude)),
"conditions": [
"!equals: ${languageCode}, en"
]
}
allActions.append(actionTranslation)
allRegexToExcludeFromMainModule.extend(entry["includeRegex"])
# Append configuration for the main string module: default language
mainAction = {
"type": "android",
mainAction = baseAction | {
"output": "libraries/ui-strings/src/main/res/values/localazy.xml",
"excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)),
"conditions": [
@ -62,16 +70,16 @@ mainAction = { @@ -62,16 +70,16 @@ mainAction = {
# print(mainAction)
allActions.append(mainAction)
# Append configuration for the main string module: translations
mainActionTranslation = {
"type": "android",
"output": "libraries/ui-strings/src/main/res/values-${langAndroidResNoScript}/translations.xml",
"excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)),
"conditions": [
"!equals: ${languageCode}, en"
]
}
allActions.append(mainActionTranslation)
if allFiles:
# Append configuration for the main string module: translations
mainActionTranslation = baseAction | {
"output": "libraries/ui-strings/src/main/res/values-${langAndroidResNoScript}/translations.xml",
"excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)),
"conditions": [
"!equals: ${languageCode}, en"
]
}
allActions.append(mainActionTranslation)
# Generate the configuration for localazy
result = {

Loading…
Cancel
Save