From 28ed753090cd5f55081706aa56de8ac921cf4b38 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 11:36:23 +0200 Subject: [PATCH 01/18] Add string key naming rules. --- tools/localazy/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index dcb45c5be7..af040d17f7 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -4,10 +4,22 @@ Localazy is used to host the source strings and their translations. ## 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 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`; +- `a11y_` pattern can be used for strings 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`. + ## CLI Installation To install the Localazy client, follow the instructions from [here](https://localazy.com/docs/cli/installation). From 965a8676a60d080bc2f077e2345dc5514dc671a7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 14:03:52 +0200 Subject: [PATCH 02/18] Update documentation about strings. --- CONTRIBUTING.md | 32 ++++++++++++++++++++++---------- tools/localazy/README.md | 10 ++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5cd8785b33..0c462036b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 @@ * [lint](#lint) * [Unit tests](#unit-tests) * [Tests](#tests) - * [Internationalisation](#internationalisation) * [Accessibility](#accessibility) * [Jetpack Compose](#jetpack-compose) * [Authors](#authors) @@ -40,11 +41,26 @@ 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 +## Strings -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/). +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 +151,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_`. diff --git a/tools/localazy/README.md b/tools/localazy/README.md index af040d17f7..e855b33992 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -2,6 +2,16 @@ Localazy is used to host the source strings and their translations. + + +* [Localazy project](#localazy-project) + * [Key naming rules](#key-naming-rules) +* [CLI Installation](#cli-installation) +* [Download translations](#download-translations) +* [Add translations to a specific module](#add-translations-to-a-specific-module) + + + ## 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). Please follow the key naming rules (see below). From adf9b1bed93d354bef18ae99dcd92ceb048f2358 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 14:04:17 +0200 Subject: [PATCH 03/18] Add a note about the configuration. --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c462036b9..82602e7e63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,8 @@ 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`. +Note: please make sure that the configuration is `app` and not `samples.minimal`. + ## Strings The strings of the project are managed externally using [https://localazy.com](https://localazy.com) and shared with ElementX iOS. From 7688cfb0310d71d72164ea539ab36e90fa931a14 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 14:05:50 +0200 Subject: [PATCH 04/18] Small clarification --- tools/localazy/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index e855b33992..6117cf17c0 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -25,7 +25,7 @@ For code clarity and in order to download strings to the correct module, here ar - 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 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`; -- `a11y_` pattern can be used for strings used for accessibility. Example: `a11y_hide_password`, `screen_roomlist_a11y_create_message`; +- `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`. From ae5a094fe42dbab5c36b38326edd3960b8477705 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 14:07:22 +0200 Subject: [PATCH 05/18] Add rule for string keys starting with `a11y`. --- tools/localazy/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index 6117cf17c0..10faf09ead 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -23,6 +23,7 @@ Never edit manually the files `localazy.xml` or `translations.xml`!. 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`; - `a11y_` pattern can be used for strings that are only used for accessibility. Example: `a11y_hide_password`, `screen_roomlist_a11y_create_message`; From bff138ae22d5e60f079e7a05fb48f96d23367137 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 14:09:46 +0200 Subject: [PATCH 06/18] typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82602e7e63..125c69adb6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ 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 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). From cbc7982cf5eb86e1a4ee0aa6a9811362783fe156 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 28 Mar 2023 15:16:03 +0200 Subject: [PATCH 07/18] Add rules for platform suffixes. --- tools/localazy/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index 10faf09ead..ed47efd2c3 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -6,6 +6,7 @@ Localazy is used to host the source strings and their translations. * [Localazy project](#localazy-project) * [Key naming rules](#key-naming-rules) + * [Special suffixes](#special-suffixes) * [CLI Installation](#cli-installation) * [Download translations](#download-translations) * [Add translations to a specific module](#add-translations-to-a-specific-module) @@ -31,6 +32,13 @@ For code clarity and in order to download strings to the correct module, here ar *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. + ## CLI Installation To install the Localazy client, follow the instructions from [here](https://localazy.com/docs/cli/installation). From 3950f3fda5adf22ee6fd3bbe0a5ad23df425088e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 23:11:38 +0000 Subject: [PATCH 08/18] Update dependency com.bumble.appyx:core to v1.1.2 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa7ed975b7..80f67bab6b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" From 7bf41d12e202033f7d01c88169f6376db16f185b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 10:58:18 +0200 Subject: [PATCH 09/18] Add ability to download only English string (for developer). This is default behavior. --- tools/localazy/README.md | 8 +++- tools/localazy/downloadStrings.sh | 20 ++++++++-- tools/localazy/generateLocalazyConfig.py | 48 +++++++++++++----------- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index dcb45c5be7..968d92047d 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -20,7 +20,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 diff --git a/tools/localazy/downloadStrings.sh b/tools/localazy/downloadStrings.sh index 163aab8e31..5892a87c38 100755 --- a/tools/localazy/downloadStrings.sh +++ b/tools/localazy/downloadStrings.sh @@ -18,12 +18,24 @@ set -e -echo "Generating the configuration file for localazy..." -./tools/localazy/generateLocalazyConfig.py +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 "Deleting all existing localazy.xml and translations.xml files..." +echo "Generating the configuration file for localazy..." +./tools/localazy/generateLocalazyConfig.py $allFiles + +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 diff --git a/tools/localazy/generateLocalazyConfig.py b/tools/localazy/generateLocalazyConfig.py index 829541462e..13b76e7d57 100755 --- a/tools/localazy/generateLocalazyConfig.py +++ b/tools/localazy/generateLocalazyConfig.py @@ -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" @@ -35,20 +37,21 @@ 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 = { + "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" + ] + } + allActions.append(actionTranslation) + allRegexToExcludeFromMainModule.extend(entry["includeRegex"]) # Append configuration for the main string module: default language mainAction = { @@ -62,16 +65,17 @@ 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 = { + "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) # Generate the configuration for localazy result = { From d0298392c4783fbe4f92a30021c80e43a7bd511a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 11:04:18 +0200 Subject: [PATCH 10/18] Add GitHub action to sync Localazy strings. --- .github/workflows/sync-localazy.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/sync-localazy.yml diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml new file mode 100644 index 0000000000..3efbb41f27 --- /dev/null +++ b/.github/workflows/sync-localazy.yml @@ -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@v4 + with: + commit-message: Sync Strings from Localazy + title: Sync Strings + body: | + - Update Strings from Localazy + branch: sync-localazy + base: develop From 45cea6cc3b79c9e91091d7d4c2bc8bddd19e5f82 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 11:11:47 +0200 Subject: [PATCH 11/18] Invoke `./tools/localazy/downloadStrings.sh --all` --- .../src/main/res/values-es/translations.xml | 6 + .../src/main/res/values-it/translations.xml | 6 + .../src/main/res/values-ro/translations.xml | 6 + .../src/main/res/values-es/translations.xml | 20 +++ .../src/main/res/values-it/translations.xml | 20 +++ .../src/main/res/values-ro/translations.xml | 20 +++ .../src/main/res/values-es/translations.xml | 8 + .../src/main/res/values-it/translations.xml | 8 + .../src/main/res/values-ro/translations.xml | 8 + .../src/main/res/values-es/translations.xml | 5 + .../src/main/res/values-it/translations.xml | 5 + .../src/main/res/values-ro/translations.xml | 5 + .../src/main/res/values-es/translations.xml | 5 + .../src/main/res/values-it/translations.xml | 5 + .../src/main/res/values-ro/translations.xml | 5 + .../src/main/res/values-es/translations.xml | 14 ++ .../src/main/res/values-it/translations.xml | 14 ++ .../src/main/res/values-ro/translations.xml | 14 ++ .../src/main/res/values-es/translations.xml | 25 +++ .../src/main/res/values-it/translations.xml | 21 +++ .../src/main/res/values-ro/translations.xml | 22 +++ .../impl/src/main/res/values/localazy.xml | 6 + .../src/main/res/values-es/translations.xml | 61 +++++++ .../src/main/res/values-it/translations.xml | 61 +++++++ .../src/main/res/values-ro/translations.xml | 61 +++++++ .../src/main/res/values-es/translations.xml | 19 +++ .../src/main/res/values-it/translations.xml | 19 +++ .../src/main/res/values-ro/translations.xml | 19 +++ .../src/main/res/values-es/translations.xml | 17 ++ .../src/main/res/values-it/translations.xml | 17 ++ .../src/main/res/values-ro/translations.xml | 17 ++ .../src/main/res/values-de/translations.xml | 1 + .../src/main/res/values-es/translations.xml | 156 ++++++++++++++++++ .../src/main/res/values-fr/translations.xml | 1 + .../src/main/res/values-it/translations.xml | 148 +++++++++++++++++ .../src/main/res/values-ro/translations.xml | 150 ++++++++++++++++- .../src/main/res/values/localazy.xml | 11 +- 37 files changed, 995 insertions(+), 11 deletions(-) create mode 100644 features/createroom/impl/src/main/res/values-es/translations.xml create mode 100644 features/createroom/impl/src/main/res/values-it/translations.xml create mode 100644 features/createroom/impl/src/main/res/values-ro/translations.xml create mode 100644 features/login/impl/src/main/res/values-es/translations.xml create mode 100644 features/login/impl/src/main/res/values-it/translations.xml create mode 100644 features/login/impl/src/main/res/values-ro/translations.xml create mode 100644 features/logout/api/src/main/res/values-es/translations.xml create mode 100644 features/logout/api/src/main/res/values-it/translations.xml create mode 100644 features/logout/api/src/main/res/values-ro/translations.xml create mode 100644 features/onboarding/impl/src/main/res/values-es/translations.xml create mode 100644 features/onboarding/impl/src/main/res/values-it/translations.xml create mode 100644 features/onboarding/impl/src/main/res/values-ro/translations.xml create mode 100644 features/rageshake/api/src/main/res/values-es/translations.xml create mode 100644 features/rageshake/api/src/main/res/values-it/translations.xml create mode 100644 features/rageshake/api/src/main/res/values-ro/translations.xml create mode 100644 features/rageshake/impl/src/main/res/values-es/translations.xml create mode 100644 features/rageshake/impl/src/main/res/values-it/translations.xml create mode 100644 features/rageshake/impl/src/main/res/values-ro/translations.xml create mode 100644 features/roomdetails/impl/src/main/res/values-es/translations.xml create mode 100644 features/roomdetails/impl/src/main/res/values-it/translations.xml create mode 100644 features/roomdetails/impl/src/main/res/values-ro/translations.xml create mode 100644 features/roomlist/impl/src/main/res/values-es/translations.xml create mode 100644 features/roomlist/impl/src/main/res/values-it/translations.xml create mode 100644 features/roomlist/impl/src/main/res/values-ro/translations.xml create mode 100644 features/verifysession/impl/src/main/res/values-es/translations.xml create mode 100644 features/verifysession/impl/src/main/res/values-it/translations.xml create mode 100644 features/verifysession/impl/src/main/res/values-ro/translations.xml create mode 100644 libraries/textcomposer/src/main/res/values-es/translations.xml create mode 100644 libraries/textcomposer/src/main/res/values-it/translations.xml create mode 100644 libraries/textcomposer/src/main/res/values-ro/translations.xml create mode 100644 libraries/ui-strings/src/main/res/values-es/translations.xml create mode 100644 libraries/ui-strings/src/main/res/values-it/translations.xml diff --git a/features/createroom/impl/src/main/res/values-es/translations.xml b/features/createroom/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..f6248df74e --- /dev/null +++ b/features/createroom/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,6 @@ + + + "Nueva sala" + "Invitar gente" + "Añadir personas" + \ No newline at end of file diff --git a/features/createroom/impl/src/main/res/values-it/translations.xml b/features/createroom/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..ea0c0b10e1 --- /dev/null +++ b/features/createroom/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,6 @@ + + + "Nuova stanza" + "Invita persone" + "Aggiungi persone" + \ No newline at end of file diff --git a/features/createroom/impl/src/main/res/values-ro/translations.xml b/features/createroom/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..98839a883e --- /dev/null +++ b/features/createroom/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,6 @@ + + + "Cameră nouă" + "Invitați persoane" + "Adaugați persoane" + \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-es/translations.xml b/features/login/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..a299083994 --- /dev/null +++ b/features/login/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,20 @@ + + + "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." + "Este servidor no soporta sliding sync." + "Dirección del homeserver" + "Solo puedes conectarte a un servidor que soporte sliding sync. El administrador de tu servidor tendrá que configurarlo. %1$s" + "Continuar" + "¿Cuál es la dirección de tu servidor?" + "Selecciona tu servidor" + "Esta cuenta ha sido desactivada." + "Usuario y/o contraseña incorrectos" + "Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'" + "El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver." + "Introduce tus datos" + "Contraseña" + "Donde viven tus conversaciones" + "Continuar" + "¡Hola de nuevo!" + "Usuario" + \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..429f156883 --- /dev/null +++ b/features/login/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,20 @@ + + + "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." + "Questo server attualmente non supporta la sincronizzazione scorrevole." + "URL dell\'homeserver" + "Puoi connetterti solo a un server esistente che supporta la sincronizzazione scorrevole. L\'amministratore del tuo server domestico dovrà configurarlo. %1$s" + "Continua" + "Qual è l\'indirizzo del tuo server?" + "Seleziona il tuo server" + "Questo profilo è stato disattivato." + "Nome utente e/o password errati" + "Questo non è un identificatore utente valido. Formato previsto: \'@user:homeserver.org\'" + "L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver." + "Inserisci i tuoi dati" + "Password" + "Dove vivono le tue conversazioni" + "Continua" + "Bentornato!" + "Nome utente" + \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..2b5cce6829 --- /dev/null +++ b/features/login/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,20 @@ + + + "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." + "Momentan acest server nu oferă suport pentru sliding sync." + "Adresa URL a homeserver-ului" + "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" + "Continuați" + "Care este adresa serverului dumneavoastră?" + "Selectați serverul" + "Acest cont a fost dezactivat." + "Utilizator și/sau parolă incorecte" + "Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”" + "Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver." + "Introduceți detaliile" + "Parolă" + "Locul unde trăiesc conversațiile tale" + "Continuați" + "Bine ați revenit!" + "Utilizator" + \ No newline at end of file diff --git a/features/logout/api/src/main/res/values-es/translations.xml b/features/logout/api/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..9072ab88a2 --- /dev/null +++ b/features/logout/api/src/main/res/values-es/translations.xml @@ -0,0 +1,8 @@ + + + "¿Estás seguro de que quieres cerrar sesión?" + "Cerrar sesión" + "Cerrar sesión" + "Cerrando sesión…" + "Cerrar sesión" + \ No newline at end of file diff --git a/features/logout/api/src/main/res/values-it/translations.xml b/features/logout/api/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..8b01a02780 --- /dev/null +++ b/features/logout/api/src/main/res/values-it/translations.xml @@ -0,0 +1,8 @@ + + + "Sei sicuro di voler uscire?" + "Esci" + "Esci" + "Uscita in corso..." + "Esci" + \ No newline at end of file diff --git a/features/logout/api/src/main/res/values-ro/translations.xml b/features/logout/api/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..8befb1b1dd --- /dev/null +++ b/features/logout/api/src/main/res/values-ro/translations.xml @@ -0,0 +1,8 @@ + + + "Sunteți sigur că vreți să vă deconectați?" + "Deconectați-vă" + "Deconectați-vă" + "Deconectare în curs…" + "Deconectați-vă" + \ No newline at end of file diff --git a/features/onboarding/impl/src/main/res/values-es/translations.xml b/features/onboarding/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..235fb4558a --- /dev/null +++ b/features/onboarding/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,5 @@ + + + "Bienvenido a la beta de %1$s. Vitaminado, para mayor rapidez y sencillez." + "Siéntente en tu Elemento" + \ No newline at end of file diff --git a/features/onboarding/impl/src/main/res/values-it/translations.xml b/features/onboarding/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..cd3c6a696c --- /dev/null +++ b/features/onboarding/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,5 @@ + + + "Benvenuto nella beta di %1$s. Potenziato in velocità e semplicità." + "Sii nel tuo elemento" + \ No newline at end of file diff --git a/features/onboarding/impl/src/main/res/values-ro/translations.xml b/features/onboarding/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..03d967ab75 --- /dev/null +++ b/features/onboarding/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,5 @@ + + + "Bun venit la versiunea beta a %1$s. Supraalimentat, pentru viteză și simplitate." + "Fii în Elementul tău" + \ No newline at end of file diff --git a/features/rageshake/api/src/main/res/values-es/translations.xml b/features/rageshake/api/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..26ff483b91 --- /dev/null +++ b/features/rageshake/api/src/main/res/values-es/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s se cerró inesperadamente la última vez que se lo usaste. ¿Quieres compartir un informe de error con nosotros?" + "Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?" + \ No newline at end of file diff --git a/features/rageshake/api/src/main/res/values-it/translations.xml b/features/rageshake/api/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..e6ef37d287 --- /dev/null +++ b/features/rageshake/api/src/main/res/values-it/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?" + "Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?" + \ No newline at end of file diff --git a/features/rageshake/api/src/main/res/values-ro/translations.xml b/features/rageshake/api/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..17180d5145 --- /dev/null +++ b/features/rageshake/api/src/main/res/values-ro/translations.xml @@ -0,0 +1,5 @@ + + + "%1$s s-a blocat ultima dată când a fost folosit. Doriți să ne trimiteți un raport?" + "Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?" + \ No newline at end of file diff --git a/features/rageshake/impl/src/main/res/values-es/translations.xml b/features/rageshake/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..527376f268 --- /dev/null +++ b/features/rageshake/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,14 @@ + + + "Adjuntar captura de pantalla" + "Podéis poneros en contacto conmigo para resolver dudas relacionadas" + "Editar captura de pantalla" + "Describe el problema. ¿Qué hiciste? ¿Qué esperabas que ocurriera? ¿Qué ocurrió en realidad? Por favor, detállalo todo lo que puedas." + "Describe el error..." + "Si es posible, escriba la descripción en inglés." + "Enviar registros de fallos" + "Enviar registros para ayudar" + "Enviar captura de pantalla" + "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." + "%1$s se cerró inesperadamente la última vez que se lo usaste. ¿Quieres compartir un informe de error con nosotros?" + \ No newline at end of file diff --git a/features/rageshake/impl/src/main/res/values-it/translations.xml b/features/rageshake/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..60397f8719 --- /dev/null +++ b/features/rageshake/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,14 @@ + + + "Allega istantanea schermo" + "Potete contattarmi per qualsiasi altra domanda" + "Modifica istantanea schermo" + "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." + "Descrivi il problema..." + "Se possibile, scrivere la descrizione in inglese." + "Invia i log degli arresti anomali" + "Invia i log per aiutarci" + "Invia istantanea schermo" + "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." + "%1$s si è chiuso inaspettatamente l\'ultima volta che è stato usato. Vuoi condividere con noi un rapporto sull\'arresto anomalo?" + \ No newline at end of file diff --git a/features/rageshake/impl/src/main/res/values-ro/translations.xml b/features/rageshake/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..6b66ea417e --- /dev/null +++ b/features/rageshake/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,14 @@ + + + "Atașați o captură de ecran" + "Puteți să mă contactați dacă aveți întrebări suplimentare" + "Editați captura de ecran" + "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ță." + "Descrieți eroarea…" + "Dacă posibil, vă rugăm să scrieți descrierea în engleză." + "Trimiteți log-uri" + "Trimiteți log-uri pentru a ajuta" + "Trimiteți captură de ecran" + "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." + "%1$s s-a blocat ultima dată când a fost folosit. Dorești să ne trimiti un raport?" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values-es/translations.xml b/features/roomdetails/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..817d3613ae --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,25 @@ + + + + "0 personas" + "Una persona" + + + + "%1$d personas" + + "Bloquear" + "Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puedes revertir esta acción en cualquier momento." + "Bloquear usuario" + "Desbloquear" + "Al desbloquear al usuario, podrás volver a ver todos sus mensajes." + "Desbloquear usuario" + "Los mensajes están protegidos con \"candados\". Sólo tú y los destinatarios tenéis las llaves únicas para abrirlos." + "Cifrado de mensajes activado" + "Invitar a otras personas" + "Salir de la sala" + "Personas" + "Seguridad" + "Compartir sala" + "Tema" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..9a980b79a9 --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,21 @@ + + + + "1 persona" + "%1$d persone" + + "Blocca" + "Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti i loro messaggi saranno nascosti. Potrai annullare questa azione in qualsiasi momento." + "Blocca utente" + "Sblocca" + "Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi." + "Sblocca utente" + "I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli." + "Crittografia messaggi abilitata" + "Invita persone" + "Esci dalla stanza" + "Persone" + "Sicurezza" + "Condividi stanza" + "Oggetto" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values-ro/translations.xml b/features/roomdetails/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..db6777fb7f --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,22 @@ + + + + "o persoană" + + "%1$d persoane" + + "Blocați" + "Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând." + "Blocați utilizatorul" + "Deblocați" + "La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta." + "Deblocați utilizatorul" + "Mesajele sunt securizate cu încuietori. Doar dumneavoastră și destinatarii aveți cheile unice pentru a le debloca." + "Criptarea mesajelor este activată" + "Invitați persoane" + "Părăsiți camera" + "Persoane" + "Securitate" + "Partajați camera" + "Subiect" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index 82c0c1c418..f63757a8e3 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -4,6 +4,12 @@ "1 person" "%1$d people" + "Block" + "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime." + "Block user" + "Unblock" + "On unblocking the user, you will be able to see all messages by them again." + "Unblock user" "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." "Message encryption enabled" "Invite people" diff --git a/features/roomlist/impl/src/main/res/values-es/translations.xml b/features/roomlist/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..079ffb2e72 --- /dev/null +++ b/features/roomlist/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,61 @@ + + + "Crear una nueva conversación o sala" + "Todos los chats" + "Parece que estás usando un nuevo dispositivo. Verifica que eres tú para acceder a tus mensajes cifrados." + "Accede a tu historial de mensajes" + "(el avatar también cambió)" + "%1$s cambió su avatar" + "Cambiaste tu avatar" + "%1$s cambió su nombre de %2$s a %3$s" + "Cambiaste tu nombre de %1$s a %2$s" + "%1$s eliminó su nombre (era %2$s)" + "Eliminaste tu nombre (era %1$s)" + "%1$s cambió su nombre a %2$s" + "Cambiaste tu nombre a %1$s" + "%1$s cambió el avatar de la sala" + "Cambiaste el avatar de la sala" + "%1$s eliminó el avatar de la sala" + "Eliminaste el avatar de la sala" + "%1$s expulsó permanentemente a %2$s" + "Expulsaste permanentemente a %1$s" + "%1$s creó la sala" + "Tú creaste la sala" + "%1$s invitó a %2$s" + "%1$s aceptó la invitación" + "Aceptaste la invitación" + "Invitaste a %1$s" + "%1$s te invitó." + "%1$s se unió a la sala" + "Te uniste a la sala" + "%1$s solicitó unirse" + "%1$s permitió que %2$s se uniera" + "%1$s te permitió unirte" + "Solicitaste unirte" + "%1$s rechazó la solicitud de %2$s para unirse" + "Rechazaste la solicitud de %1$s para unirte" + "%1$s rechazó su solicitud para unirte" + "%1$s ya no está interesado en unirse" + "Cancelaste tu solicitud de unirte" + "%1$s salió de la sala" + "Saliste de la sala" + "%1$s cambió el nombre de la sala a: %2$s" + "Cambiaste el nombre de la sala a: %1$s" + "%1$s eliminó el nombre de la sala" + "Eliminaste el nombre de la sala" + "%1$s rechazó la invitación" + "Rechazaste la invitación" + "%1$s echó a %2$s" + "Echaste a %1$s" + "%1$s envió una invitación a %2$s para unirse a la sala" + "Enviaste una invitación a %1$s para unirse a la sala" + "%1$s revocó la invitación a %2$s para unirse a la sala" + "Revocaste la invitación de %1$s para unirse a la sala" + "%1$s cambió el tema a: %2$s" + "Cambiaste el tema a: %1$s" + "%1$s eliminó el tema de la sala" + "Eliminaste el tema de la sala" + "%1$s readmitió a %2$s" + "Readmitiste a %1$s" + "%1$s realizó un cambio desconocido en su membresía" + \ No newline at end of file diff --git a/features/roomlist/impl/src/main/res/values-it/translations.xml b/features/roomlist/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..20bf487937 --- /dev/null +++ b/features/roomlist/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,61 @@ + + + "Crea una nuova conversazione o stanza" + "Tutte le conversazioni" + "Sembra che tu stia utilizzando un nuovo dispositivo. Verifica di essere tu per accedere ai tuoi messaggi crittografati." + "Accedi alla cronologia dei messaggi" + "(anche l\'avatar è stato cambiato)" + "%1$s ha cambiato il proprio avatar" + "Hai cambiato il tuo avatar" + "%1$s ha cambiato il proprio nome visualizzato da %2$s a %3$s" + "Hai cambiato il tuo nome visualizzato da %1$s a %2$s" + "%1$s ha rimosso il proprio nome visualizzato (era %2$s)" + "Hai rimosso il tuo nome visualizzato (era %1$s)" + "%1$s ha impostato il proprio nome visualizzato su %2$s" + "Hai impostato il tuo nome visualizzato su %1$s" + "%1$s ha cambiato l\'avatar della stanza" + "Hai cambiato l\'avatar della stanza" + "%1$s ha rimosso l\'avatar della stanza" + "Hai rimosso l\'avatar della stanza" + "%1$s ha rimosso %2$s" + "Hai rimosso %1$s" + "%1$s ha creato la stanza" + "Hai creato la stanza" + "%1$s ha invitato %2$s" + "%1$s ha accettato l\'invito" + "Hai accettato l\'invito" + "Hai invitato %1$s" + "%1$s ti ha invitato" + "%1$s si è unito alla stanza" + "Ti sei unito alla stanza" + "%1$s ha chiesto di unirsi" + "%1$s ha permesso a %2$s di unirsi" + "%1$s ti ha permesso di unirti" + "Hai richiesto di unirti" + "%1$s ha rifiutato la richiesta di unirsi di %2$s" + "Hai rifiutato la richiesta di unirsi di %1$s" + "%1$s ha rifiutato la tua richiesta di unirti" + "%1$s non è più interessato a partecipare" + "Hai annullato la tua richiesta di unirti" + "%1$s ha lasciato la stanza" + "Hai lasciato la stanza" + "%1$s ha cambiato il nome della stanza in: %2$s" + "Hai cambiato il nome della stanza in: %1$s" + "%1$s ha rimosso il nome della stanza" + "Hai rimosso il nome della stanza" + "%1$s ha rifiutato l\'invito" + "Hai rifiutato l\'invito" + "%1$s ha rimosso %2$s" + "Hai rimosso %1$s" + "%1$s ha inviato un invito a %2$s per unirsi alla stanza" + "Hai inviato un invito a %1$s per unirsi alla stanza" + "%1$s ha revocato l\'invito di %2$s ad unirsi alla stanza." + "Hai revocato l\'invito a %1$s a universi alla stanza" + "%1$s ha cambiato l\'oggetto in: %2$s" + "Hai cambiato l\'oggetto in: %1$s" + "%1$s ha rimosso l\'oggetto della stanza" + "Hai rimosso l\'oggetto della stanza" + "%1$s ha sbloccato %2$s" + "Hai sbloccato %1$s" + "%1$s ha apportato una modifica sconosciuta alla propria iscrizione" + \ No newline at end of file diff --git a/features/roomlist/impl/src/main/res/values-ro/translations.xml b/features/roomlist/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..89760b3497 --- /dev/null +++ b/features/roomlist/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,61 @@ + + + "Creați o conversație sau o cameră nouă" + "Toate conversatiile" + "Se pare că folosiți un dispozitiv nou. Verificați-vă identitatea pentru acces la mesajele dumneavoastră criptate." + "Accesați istoricul mesajelor" + "(s-a schimbat si avatarul)" + "%1$s și-a schimbat avatarul" + "V-ați schimbat avatarul" + "%1$s și-a schimbat numele din %2$s în %3$s" + "V-ați schimbat numele din %1$s în %2$s" + "%1$s și-a sters numele (era %2$s)" + "V-ați sters numele (era %1$s)" + "%1$s și-a schimbat numele %2$s" + "V-ați schimbat numele în %1$s" + "%1$s a schimbat avatarul camerei" + "Ați schimbat avatarul camerei" + "%1$s a șters avatarul camerei" + "Ați șters avatarul camerei" + "%1$s a adăugat o interdicție pentru %2$s" + "Ați adăugat o interdicție pentru %1$s" + "%1$s a creat camera" + "Ați creat camera" + "%1$s l-a invitat pe %2$s" + "%1$s a acceptat invitația" + "Ați acceptat invitația" + "L-ați invitat pe %1$s" + "%1$s v-a invitat" + "%1$s a intrat în cameră" + "Ați intrat în cameră" + "%1$s a solicitat să se alăture camerei" + "%1$s i-a permis lui %2$s să se alăture camerei" + "%1$s v-a permis să vă alăturați camerei" + "Ați solicitat să vă alăturați camerei" + "%1$s a respins solicitarea de alăturare a lui %2$s" + "Ați respins solicitarea de alăturare a lui %1$s" + "%1$s a respins cererea dumneavoastră de alăturare" + "%1$s nu mai este interesat să se alăture camerei" + "Ați anulat cererea de alăturare" + "%1$s a părăsit camera" + "Ați părăsit camera" + "%1$s a schimbat numele camerei în: %2$s" + "Ați schimbat numele camerei în: %1$s" + "%1$s a sters numele camerei" + "Ați șters numele camerei" + "%1$s a respins invitația" + "Ați respins invitația" + "%1$s l-a îndepărtat pe %2$s" + "L-ați îndepărtat pe %1$s" + "%1$s a trimis o invitație către %2$s pentru a se alătura camerei" + "Ați trimis o invitație către %1$s pentru a se alătura camerei" + "%1$s a revocat invitația pentru %2$s de a se alătura camerei" + "Ați revocat invitația pentru %1$s de a se alătura camerei" + "%1$s a schimbat subiectul în: %2$s" + "Ați schimbat subiectul în: %1$s" + "%1$s a șters subiectul camerei" + "Ați șters subiectul camerei" + "%1$s a anulat interdicția pentru %2$s" + "Ați anulat interdicția pentru %1$s" + "%1$s a făcut o modificare necunoscută asupra calității sale de membru" + \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values-es/translations.xml b/features/verifysession/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..839c945e24 --- /dev/null +++ b/features/verifysession/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,19 @@ + + + "Algo no fue bien. Se agotó el tiempo de espera de la solicitud o se rechazó." + "Verificación cancelada" + "Confirma que los emojis que aparecen a continuación coinciden con los que aparecen en tu otra sesión." + "Comparar emojis" + "Tu nueva sesión ya está verificada. Tienes acceso a tus mensajes cifrados y otros usuarios lo considerarán de confianza." + "Demuestra que eres tú para acceder a tu historial de mensajes cifrados." + "Abrir una sesión existente" + "Reintentar la verificación" + "Estoy listo" + "Comenzar" + "Esperando a que coincida" + "Compara los emoji, asegurándote de que aparecen en el mismo orden." + "No coinciden" + "Coinciden" + "Acepta la solicitud para iniciar el proceso de verificación en tu otra sesión para continuar." + "A la espera de aceptar la solicitud" + \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..3d8a46d581 --- /dev/null +++ b/features/verifysession/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,19 @@ + + + "C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata." + "Verifica annullata" + "Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione." + "Confronta le emoji" + "La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile." + "Dimostra la tua identità per accedere alla cronologia dei messaggi crittografati." + "Apri una sessione esistente" + "Riprova la verifica" + "Sono pronto" + "Inizia" + "In attesa di un riscontro" + "Confronta le emoji uniche, assicurandoti che appaiano nello stesso ordine." + "Non corrispondono" + "Corrispondono" + "Accetta la richiesta di avviare il processo di verifica nell\'altra sessione per continuare." + "In attesa di accettare la richiesta" + \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values-ro/translations.xml b/features/verifysession/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..f2bade56fc --- /dev/null +++ b/features/verifysession/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,19 @@ + + + "Ceva nu este în regulă. Fie cererea a expirat, fie a fost respinsă." + "Verificare anulată" + "Confirmați că emoticoanele de mai jos se potrivesc cu cele afișate în cealaltă sesiune." + "Comparați emoticoanele" + "Noua dumneavoastră sesiune este acum verificată. Are acces la mesajele dumneavoastră criptate, iar alți utilizatori vă vor vedea ca fiind de încredere." + "Demonstrați-vă identitatea pentru a accesa istoricul mesajelor criptate." + "Deschideți o sesiune existentă" + "Reîncercați verificarea" + "Sunt pregătit" + "Începeți" + "Se așteaptă confirmarea" + "Comparăți emoticoalene asigurându-vă că apar în aceeași ordine." + "Nu se potrivesc" + "Se potrivesc" + "Acceptați solicitarea de a începe procesul de verificare în cealaltă sesiune pentru a continua." + "Se așteptă acceptarea cererii" + \ No newline at end of file diff --git a/libraries/textcomposer/src/main/res/values-es/translations.xml b/libraries/textcomposer/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..7392bd2b31 --- /dev/null +++ b/libraries/textcomposer/src/main/res/values-es/translations.xml @@ -0,0 +1,17 @@ + + + "Lista de puntos" + "Bloque de código" + "Mensaje..." + "Aplicar formato negrita" + "Aplicar formato cursiva" + "Aplicar formato tachado" + "Aplicar formato de subrayado" + "Pantalla completa" + "Añadir sangría" + "Código" + "Enlazar" + "Lista numérica" + "Cita" + "Quitar sangría" + \ No newline at end of file diff --git a/libraries/textcomposer/src/main/res/values-it/translations.xml b/libraries/textcomposer/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..54ca270f28 --- /dev/null +++ b/libraries/textcomposer/src/main/res/values-it/translations.xml @@ -0,0 +1,17 @@ + + + "Attiva/disattiva l\'elenco puntato" + "Attiva/disattiva il blocco di codice" + "Messaggio…" + "Applica il formato in grassetto" + "Applicare il formato corsivo" + "Applica il formato barrato" + "Applicare il formato di sottolineatura" + "Attiva/disattiva la modalità a schermo intero" + "Rientro a destra" + "Applicare il formato del codice in linea" + "Imposta collegamento" + "Attiva/disattiva elenco numerato" + "Attiva/disattiva citazione" + "Rientro a sinistra" + \ No newline at end of file diff --git a/libraries/textcomposer/src/main/res/values-ro/translations.xml b/libraries/textcomposer/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..b053e0ecaa --- /dev/null +++ b/libraries/textcomposer/src/main/res/values-ro/translations.xml @@ -0,0 +1,17 @@ + + + "Comutați lista cu puncte" + "Comutați blocul de cod" + "Mesaj…" + "Aplicați formatul aldin" + "Aplicați formatul italic" + "Aplicați formatul barat" + "Aplică formatul de subliniere" + "Comutați modul ecran complet" + "Indentare" + "Aplicați formatul de cod inline" + "Setați linkul" + "Comutați lista numerotată" + "Aplicați citatul" + "Dez-identare" + \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 64684b7905..22c60db481 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -1,4 +1,5 @@ "Bestätigen" + "de" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..d05dc300d2 --- /dev/null +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -0,0 +1,156 @@ + + + "Ocultar contraseña" + "Enviar archivos" + "Mostrar contraseña" + "Menú de usuario" + "Atrás" + "Cancelar" + "Borrar" + "Cerrar" + "Completar verificación" + "Confirmar" + "Continuar" + "Copiar" + "Copiar enlace" + "Crear una sala" + "Desactivar" + "Hecho" + "Editar" + "Activar" + "Invitar" + "Invitar amigos a %1$s" + "Más información" + "Salir" + "Salir de la sala" + "Siguiente" + "No" + "Ahora no" + "OK" + "Respuesta rápida" + "Citar" + "Eliminar" + "Responder" + "Informar de un error" + "Reportar Contenido" + "Reintentar" + "Reintentar descifrado" + "Guardar" + "Buscar" + "Enviar" + "Compartir" + "Compartir enlace" + "Saltar" + "Comenzar" + "Iniciar chat" + "Iniciar la verificación" + "Ver Fuente" + "Sí" + "Acerca de" + "Sonido" + "Burbujas" + "Creando sala..." + "Saliste de la sala" + "Error de descifrado" + "Opciones de desarrollador" + "(editado)" + "Edición" + "Cifrado activado" + "Error" + "Archivo" + "GIF" + "Imagen" + "Enlace copiado al portapapeles" + "Cargando…" + "Mensaje" + "Diseño del mensaje" + "Mensaje eliminado" + "Moderno" + "No hay resultados" + "Sin conexión" + "Contraseña" + "Personas" + "Enlace permanente" + "Reacciones" + "Respondiendo a %1$s" + "Informar de un error" + "Informe enviado" + "Buscar a alguien" + "Seguridad" + "Selecciona tu servidor" + "Enviando…" + "Servidor no compatible" + "Dirección del servidor" + "Ajustes" + "Sticker" + "Terminado" + "Sugerencias" + "Tema" + "No se puede descifrar" + "Evento no compatible" + "Usuario" + "Verificación cancelada" + "Verificación completada" + "Vídeo" + "Esperando..." + "Confirmar" + "Error" + "Terminado" + "Atención" + "Actividades" + "Banderas" + "Comida y bebida" + "Animales y naturaleza" + "Objetos" + "Emojis y personas" + "Viajes y lugares" + "Símbolos" + "No se pudo crear el enlace permanente" + "Error al cargar mensajes" + "No se encontró ninguna aplicación compatible con esta acción." + "Algunos mensajes no se han enviado" + "Lo siento, se ha producido un error" + "Hola, puedes hablar conmigo en %1$s: %2$s" + "¿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ú." + "¿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." + "¿Seguro que quieres salir de la habitación?" + "%1$s Android" + + "No hay miembros" + "%1$d miembro" + + + + "%1$d miembros" + + + "No hay cambios en la sala" + "%1$d cambio en la sala" + + + + "%1$d cambios en la sala" + + "Agitar con fuerza para informar de un error" + "Parece que sacudes el teléfono con frustración. ¿Quieres abrir la pantalla de informe de errores?" + "Este mensaje se notificará al administrador de su homeserver. No podrán leer ningún mensaje cifrado." + "Motivo para denunciar este contenido" + "Este es el principio de %1$s." + "Este es el principio de esta conversación." + "Nuevos" + "Bloquear usuario" + "Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario" + "Bloquear" + "Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puede revertir esta acción en cualquier momento." + "Bloquear usuario" + "Desbloquear" + "Al desbloquear al usuario, podrás volver a ver todos sus mensajes." + "Desbloquear usuario" + "Se ha producido un error al intentar iniciar un chat" + "No podemos validar el ID de Matrix de este usuario. Es posible que no reciba la invitación." + "Agitar con fuerza" + "Umbral de detección" + "General" + "Versión: %1$s (%2$s)" + "es" + \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index b75da2740b..5bdce47d20 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -1,4 +1,5 @@ "Confirmer" + "fr" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..80fed2e6f8 --- /dev/null +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -0,0 +1,148 @@ + + + "Nascondi password" + "Invia file" + "Mostra password" + "Menu utente" + "Indietro" + "Annulla" + "Cancella" + "Chiudi" + "Completa verifica" + "Conferma" + "Continua" + "Copia" + "Copia collegamento" + "Crea una stanza" + "Disabilita" + "Fine" + "Modifica" + "Attiva" + "Invita" + "Invita amici a %1$s" + "Ulteriori informazioni" + "Esci" + "Esci dalla stanza" + "Avanti" + "No" + "Non ora" + "OK" + "Risposta rapida" + "Citazione" + "Rimuovi" + "Rispondi" + "Segnala un problema" + "Segnala Contenuto" + "Riprova" + "Riprova la decrittazione" + "Salva" + "Ricerca" + "Invia" + "Condividi" + "Condividi collegamento" + "Salta" + "Inizia" + "Avvia conversazione" + "Avvia la verifica" + "Vedi Sorgente" + "Sì" + "Informazioni" + "Audio" + "Fumetti" + "Creazione stanza..." + "Hai lasciato la stanza" + "Errore di decrittazione" + "Opzioni sviluppatore" + "(modificato)" + "Modifica in corso" + "Crittografia abilitata" + "Errore" + "File" + "GIF" + "Immagine" + "Collegamento copiato negli appunti" + "Caricamento…" + "Messaggio" + "Layout del messaggio" + "Messaggio rimosso" + "Moderno" + "Nessun risultato" + "Non in linea" + "Password" + "Persone" + "Collegamento permanente" + "Reazioni" + "Risposta a %1$s" + "Segnala un problema" + "Segnalazione inviata" + "Cerca qualcuno" + "Sicurezza" + "Seleziona il tuo server" + "Invio in corso…" + "Server non supportato" + "URL del server" + "Impostazioni" + "Adesivo" + "Operazione riuscita" + "Suggerimenti" + "Oggetto" + "Impossibile decrittografare" + "Evento non supportato" + "Nome utente" + "Verifica annullata" + "Verifica completata" + "Video" + "In attesa…" + "Conferma" + "Errore" + "Operazione riuscita" + "Attenzione" + "Attività" + "Bandiere" + "Cibi & Bevande" + "Animali & Natura" + "Oggetti" + "Faccine & Persone" + "Viaggi & Luoghi" + "Simboli" + "Impossibile creare il collegamento permanente" + "Caricamento dei messaggi non riuscito" + "Non è stata trovata alcuna app compatibile per gestire questa azione." + "Alcuni messaggi non sono stati inviati" + "Siamo spiacenti, si è verificato un errore" + "Ehi, parlami su %1$s: %2$s" + "Sei sicuro di voler lasciare questa stanza? Sei l\'unica persona presente. Se esci, nessuno potrà unirsi in futuro, te compreso." + "Sei sicuro di voler lasciare questa stanza? Questa stanza non è pubblica e non potrai rientrare senza un invito." + "Sei sicuro di voler lasciare la stanza?" + "%1$s Android" + + "%1$d membro" + "%1$d membri" + + + "%1$d modifica alla stanza" + "%1$d modifiche alla stanza" + + "Scuoti per segnalare un problema" + "Sembra che tu stia scuotendo il telefono per la frustrazione. Vuoi aprire la schermata di segnalazione dei problemi?" + "Questo messaggio verrà segnalato all\'amministratore dell\'homeserver. Questi non sarà in grado di leggere i messaggi criptati." + "Motivo della segnalazione di questo contenuto" + "Questo è l\'inizio di %1$s." + "Questo è l\'inizio della conversazione." + "Nuovo" + "Blocca utente" + "Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente" + "Blocca" + "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." + "Blocca utente" + "Sblocca" + "Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi." + "Sblocca utente" + "Si è verificato un errore durante il tentativo di avviare una chat" + "Non possiamo convalidare l\'ID Matrix di questo utente. L\'invito potrebbe non essere ricevuto." + "Rageshake" + "Soglia di rilevamento" + "Generali" + "Versione: %1$s (%2$s)" + "it" + \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index 6878d7aab4..1872bb057f 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -1,10 +1,150 @@ - "Confirmare" + "Ascundeți parola" + "Trimiteți fișiere" + "Afișați parola" + "Meniu utilizator" + "Înapoi" + "Anulați" + "Ștergeți" + "Închideți" + "Verificare completă" + "Confirmați" + "Continuați" + "Copiați" + "Copiați linkul" "Creați o cameră" - "Gata" + "Dezactivați" + "Efectuat" + "Editați" + "Activați" + "Invitați" + "Invitați prieteni în %1$s" + "Aflați mai multe" + "Părăsiți" + "Părăsiți camera" + "Următorul" + "Nu" + "Nu acum" "OK" - "Raportează conținutul" - "Începe discuția" - "Vezi sursa" + "Raspuns rapid" + "Citat" + "Ștergeți" + "Răspundeți" + "Raportați o eroare" + "Raportați conținutul" + "Reîncercați" + "Reîncercați decriptarea" + "Salvați" + "Căutați" + "Trimiteți" + "Partajați" + "Partajați linkul" + "Omiteți" + "Începeți" + "Începeți discuția" + "Începeți verificarea" + "Vedeți sursă" + "Da" + "Despre" + "Audio" + "Baloane" + "Se creează camera…" + "Ați parăsit camera" + "Eroare de decriptare" + "Opțiuni programator" + "(editat)" + "Editare" + "Criptare activată" + "Eroare" + "Fişier" + "GIF" + "Imagine" + "Linkul a fost copiat în clipboard" + "Se încarcă…" + "Mesaj" + "Aranjamentul mesajelor" + "Mesaj sters" + "Modern" + "Niciun rezultat" + "Deconectat" + "Parola" + "Persoane" + "Permalink" + "Reacții" + "Răspuns pentru %1$s" + "Raportați o eroare" + "Raport trimis" + "Căutați pe cineva" + "Securitate" + "Selectați serverul" + "Se trimite…" + "Serverul nu este compatibil" + "Adresa URL a serverului" + "Setări" + "Autocolant" + "Succes" + "Sugestii" + "Subiect" + "Nu s-a putut decripta" + "Eveniment neacceptat" + "Utilizator" + "Verificare anulată" + "Verificare completă" + "Video" + "Se aşteaptă…" + "Confirmare" + "Eroare" + "Succes" + "Avertisment" + "Activități" + "Steaguri" + "Mâncare & Băutură" + "Animale și Natură" + "Obiecte" + "Fețe zâmbitoare & Oameni" + "Călătorii & Locuri" + "Simboluri" + "Crearea permalink-ului a eșuat" + "Încărcarea mesajelor a eșuat" + "Nu a fost găsită nicio aplicație capabilă să gestioneze această acțiune." + "Unele mesaje nu au fost trimise" + "Ne pare rău, a apărut o eroare" + "Hei, vorbește cu mine pe %1$s: %2$s" + "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." + "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." + "Sunteți sigur că vreți să părăsiți camera?" + "%1$s Android" + + "%1$d membru" + + "%1$d membri" + + + "%1$d schimbare a camerii" + + "%1$d schimbări ale camerei" + + "Rageshake pentru a raporta erori" + "Se pare că scuturați telefonul de frustrare. Doriți să deschdeți ecranul de raportare a unei erori?" + "Acest mesaj va fi raportat administratorilor homeserver-ului tau. Ei nu vor putea citi niciun mesaj criptat." + "Motivul raportării acestui conținut" + "Acesta este începutul conversației %1$s." + "Acesta este începutul acestei conversații." + "Nou" + "Blocați utilizatorul" + "Confirmați că doriți să ascundeți toate mesajele curente și viitoare de la acest utilizator" + "Blocați" + "Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând." + "Blocați utilizatorul" + "Deblocați" + "La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta." + "Deblocați utilizatorul" + "A apărut o eroare la încercarea începerii conversației" + "Nu am putut valida ID-ul Matrix al acestui utilizator. Este posibil ca invitația să nu fi fost primită." + "Rageshake" + "Prag de detecție" + "General" + "Versiunea: %1$s (%2$s)" + "ro" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 7ae90b9b53..28cef98c60 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -38,6 +38,7 @@ "Save" "Search" "Send" + "Share" "Share link" "Skip" "Start" @@ -49,6 +50,7 @@ "Audio" "Bubbles" "Creating room…" + "Left room" "Decryption error" "Developer options" "(edited)" @@ -128,12 +130,6 @@ "This is the beginning of %1$s." "This is the beginning of this conversation." "New" - "Block" - "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime." - "Block user" - "Unblock" - "On unblocking the user, you will be able to see all messages by them again." - "Unblock user" "Block user" "Check if you want to hide all current and future messages from this user" "Block" @@ -143,8 +139,11 @@ "On unblocking the user, you will be able to see all messages by them again." "Unblock user" "An error occurred when trying to start a chat" + "We can’t validate this user’s Matrix ID. The invite might not be received." "Rageshake" "Detection threshold" "General" "Version: %1$s (%2$s)" + "en" + "en" \ No newline at end of file From 43c070b01b7001874be0324944c9aae33f990d34 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 11:22:53 +0200 Subject: [PATCH 12/18] Add key naming rules for dialogs. --- tools/localazy/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index ed47efd2c3..58bd137932 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -27,6 +27,7 @@ For code clarity and in order to download strings to the correct module, here ar - 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`. From b410fc6983e740b68c5cef33fafa1d9c83e453b0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 11:33:46 +0200 Subject: [PATCH 13/18] Add a section about placeholders. --- tools/localazy/README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/localazy/README.md b/tools/localazy/README.md index 58bd137932..395961f91b 100644 --- a/tools/localazy/README.md +++ b/tools/localazy/README.md @@ -7,6 +7,7 @@ Localazy is used to host the source strings and their translations. * [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) @@ -40,6 +41,10 @@ For code clarity and in order to download strings to the correct module, here ar 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). From 830946e0de01b05a4c5d2203b333d2486a9c6fb6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 11:44:55 +0200 Subject: [PATCH 14/18] Ensure ellipsis char is used (fix lint issue). --- .../logout/api/src/main/res/values-it/translations.xml | 2 +- .../impl/src/main/res/values-es/translations.xml | 2 +- .../impl/src/main/res/values-it/translations.xml | 2 +- .../textcomposer/src/main/res/values-es/translations.xml | 2 +- .../ui-strings/src/main/res/values-es/translations.xml | 4 ++-- .../ui-strings/src/main/res/values-it/translations.xml | 2 +- tools/localazy/generateLocalazyConfig.py | 9 +++++++++ 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/features/logout/api/src/main/res/values-it/translations.xml b/features/logout/api/src/main/res/values-it/translations.xml index 8b01a02780..7d1a3ae304 100644 --- a/features/logout/api/src/main/res/values-it/translations.xml +++ b/features/logout/api/src/main/res/values-it/translations.xml @@ -3,6 +3,6 @@ "Sei sicuro di voler uscire?" "Esci" "Esci" - "Uscita in corso..." + "Uscita in corso…" "Esci" \ No newline at end of file diff --git a/features/rageshake/impl/src/main/res/values-es/translations.xml b/features/rageshake/impl/src/main/res/values-es/translations.xml index 527376f268..0b1a374b97 100644 --- a/features/rageshake/impl/src/main/res/values-es/translations.xml +++ b/features/rageshake/impl/src/main/res/values-es/translations.xml @@ -4,7 +4,7 @@ "Podéis poneros en contacto conmigo para resolver dudas relacionadas" "Editar captura de pantalla" "Describe el problema. ¿Qué hiciste? ¿Qué esperabas que ocurriera? ¿Qué ocurrió en realidad? Por favor, detállalo todo lo que puedas." - "Describe el error..." + "Describe el error…" "Si es posible, escriba la descripción en inglés." "Enviar registros de fallos" "Enviar registros para ayudar" diff --git a/features/rageshake/impl/src/main/res/values-it/translations.xml b/features/rageshake/impl/src/main/res/values-it/translations.xml index 60397f8719..c8a15eeedf 100644 --- a/features/rageshake/impl/src/main/res/values-it/translations.xml +++ b/features/rageshake/impl/src/main/res/values-it/translations.xml @@ -4,7 +4,7 @@ "Potete contattarmi per qualsiasi altra domanda" "Modifica istantanea schermo" "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." - "Descrivi il problema..." + "Descrivi il problema…" "Se possibile, scrivere la descrizione in inglese." "Invia i log degli arresti anomali" "Invia i log per aiutarci" diff --git a/libraries/textcomposer/src/main/res/values-es/translations.xml b/libraries/textcomposer/src/main/res/values-es/translations.xml index 7392bd2b31..e302765a58 100644 --- a/libraries/textcomposer/src/main/res/values-es/translations.xml +++ b/libraries/textcomposer/src/main/res/values-es/translations.xml @@ -2,7 +2,7 @@ "Lista de puntos" "Bloque de código" - "Mensaje..." + "Mensaje…" "Aplicar formato negrita" "Aplicar formato cursiva" "Aplicar formato tachado" diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index d05dc300d2..dbbf33933d 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -49,7 +49,7 @@ "Acerca de" "Sonido" "Burbujas" - "Creando sala..." + "Creando sala…" "Saliste de la sala" "Error de descifrado" "Opciones de desarrollador" @@ -92,7 +92,7 @@ "Verificación cancelada" "Verificación completada" "Vídeo" - "Esperando..." + "Esperando…" "Confirmar" "Error" "Terminado" diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index 80fed2e6f8..96d0648d3b 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -49,7 +49,7 @@ "Informazioni" "Audio" "Fumetti" - "Creazione stanza..." + "Creazione stanza…" "Hai lasciato la stanza" "Errore di decrittazione" "Opzioni sviluppatore" diff --git a/tools/localazy/generateLocalazyConfig.py b/tools/localazy/generateLocalazyConfig.py index 13b76e7d57..c5f99aca2c 100755 --- a/tools/localazy/generateLocalazyConfig.py +++ b/tools/localazy/generateLocalazyConfig.py @@ -20,6 +20,11 @@ regexToAlwaysExclude = [ ".*_ios" ] +# 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 @@ -33,6 +38,7 @@ for entry in config["modules"]: "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)), + "replacements": replacements, "conditions": [ "equals: ${languageCode}, en" ] @@ -46,6 +52,7 @@ for entry in config["modules"]: "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)), + "replacements": replacements, "conditions": [ "!equals: ${languageCode}, en" ] @@ -58,6 +65,7 @@ mainAction = { "type": "android", "output": "libraries/ui-strings/src/main/res/values/localazy.xml", "excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)), + "replacements": replacements, "conditions": [ "equals: ${languageCode}, en" ] @@ -71,6 +79,7 @@ if allFiles: "type": "android", "output": "libraries/ui-strings/src/main/res/values-${langAndroidResNoScript}/translations.xml", "excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)), + "replacements": replacements, "conditions": [ "!equals: ${languageCode}, en" ] From e316fcef0404b714234995f2424ce959dcbb814c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 12:23:47 +0200 Subject: [PATCH 15/18] Create baseAction for shared values. --- tools/localazy/generateLocalazyConfig.py | 25 ++++++++++-------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tools/localazy/generateLocalazyConfig.py b/tools/localazy/generateLocalazyConfig.py index c5f99aca2c..e2bc310e1e 100755 --- a/tools/localazy/generateLocalazyConfig.py +++ b/tools/localazy/generateLocalazyConfig.py @@ -20,9 +20,12 @@ regexToAlwaysExclude = [ ".*_ios" ] -# Replacement done in all string values -replacements = { - "...": "…" +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 @@ -33,12 +36,10 @@ 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)), - "replacements": replacements, "conditions": [ "equals: ${languageCode}, en" ] @@ -47,12 +48,10 @@ for entry in config["modules"]: allActions.append(action) # Create action for the translations if allFiles: - actionTranslation = { - "type": "android", + 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)), - "replacements": replacements, "conditions": [ "!equals: ${languageCode}, en" ] @@ -61,11 +60,9 @@ for entry in config["modules"]: 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)), - "replacements": replacements, "conditions": [ "equals: ${languageCode}, en" ] @@ -75,11 +72,9 @@ allActions.append(mainAction) if allFiles: # Append configuration for the main string module: translations - mainActionTranslation = { - "type": "android", + mainActionTranslation = baseAction | { "output": "libraries/ui-strings/src/main/res/values-${langAndroidResNoScript}/translations.xml", "excludeKeys": list(map(lambda i: "REGEX:" + i, allRegexToExcludeFromMainModule + regexToAlwaysExclude)), - "replacements": replacements, "conditions": [ "!equals: ${languageCode}, en" ] From 79d8a51ba79139638d1732feee23e9c558ae37db Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 5 Apr 2023 12:25:17 +0200 Subject: [PATCH 16/18] Invoke `./tools/localazy/downloadStrings.sh --all` --- .../impl/src/main/res/values-es/translations.xml | 4 ---- .../ui-strings/src/main/res/values-es/translations.xml | 8 -------- 2 files changed, 12 deletions(-) diff --git a/features/roomdetails/impl/src/main/res/values-es/translations.xml b/features/roomdetails/impl/src/main/res/values-es/translations.xml index 817d3613ae..ba4327000b 100644 --- a/features/roomdetails/impl/src/main/res/values-es/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-es/translations.xml @@ -1,11 +1,7 @@ - "0 personas" "Una persona" - - - "%1$d personas" "Bloquear" diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index dbbf33933d..4b14f3a4a7 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -116,19 +116,11 @@ "¿Seguro que quieres salir de la habitación?" "%1$s Android" - "No hay miembros" "%1$d miembro" - - - "%1$d miembros" - "No hay cambios en la sala" "%1$d cambio en la sala" - - - "%1$d cambios en la sala" "Agitar con fuerza para informar de un error" From 9064481b4c8f56af7630922a5f69c887c3304813 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 5 Apr 2023 15:36:41 +0200 Subject: [PATCH 17/18] [Room Details] Leave room (#296) * Add leave room functionality to the Room Details screen * Add snackbar message throught `SnackbarDistpacher` --- .../io/element/android/x/di/AppModule.kt | 7 + appnav/build.gradle.kts | 1 + .../android/appnav/LoggedInEventProcessor.kt | 72 +++++++++ .../android/appnav/LoggedInFlowNode.kt | 13 ++ .../io/element/android/appnav/RoomFlowNode.kt | 18 ++- changelog.d/286.feature | 1 + .../roomdetails/api/RoomDetailsEntryPoint.kt | 4 +- .../impl/DefaultRoomDetailsEntryPoint.kt | 5 +- .../roomdetails/impl/RoomDetailsEvent.kt | 6 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 39 ++++- .../roomdetails/impl/RoomDetailsState.kt | 27 +++- .../impl/RoomDetailsStateProvider.kt | 4 +- .../roomdetails/impl/RoomDetailsView.kt | 46 +++++- .../roomdetails/RoomDetailsPresenterTests.kt | 138 +++++++++++++++++- .../features/roomlist/impl/RoomListEvents.kt | 1 - .../roomlist/impl/RoomListPresenter.kt | 19 ++- .../features/roomlist/impl/RoomListState.kt | 3 +- .../roomlist/impl/RoomListStateProvider.kt | 6 +- .../features/roomlist/impl/RoomListView.kt | 24 +-- .../roomlist/impl/RoomListPresenterTests.kt | 35 +---- .../impl/VerifySelfSessionPresenterTests.kt | 2 + .../components/dialogs/ConfirmationDialog.kt | 4 +- .../designsystem/utils/SnackbarDispatcher.kt | 72 +++++++++ .../libraries/matrix/api/MatrixClient.kt | 3 + .../libraries/matrix/api/room/MatrixRoom.kt | 4 + .../matrix/api/room/RoomMembershipObserver.kt | 40 +++++ .../libraries/matrix/impl/RustMatrixClient.kt | 8 +- .../matrix/impl/di/SessionMatrixModule.kt | 8 + .../matrix/impl/room/RustMatrixRoom.kt | 7 + .../libraries/matrix/test/FakeMatrixClient.kt | 5 + .../matrix/test/room/FakeMatrixRoom.kt | 9 ++ .../android/samples/minimal/RoomListScreen.kt | 4 +- 32 files changed, 564 insertions(+), 71 deletions(-) create mode 100644 appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt create mode 100644 changelog.d/286.feature create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcher.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index c553f3f7f0..4a9ee85fc8 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -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 { diffUpdateDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() ) } + + @Provides + @SingleIn(AppScope::class) + fun provideSnackbarDispatcher(): SnackbarDispatcher { + return SnackbarDispatcher() + } } diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index ea4c2d9a94..1cfcc40510 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -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) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt new file mode 100644 index 0000000000..f1d87330e1 --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt @@ -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)) + } +} diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 4b2d653d79..1028c6f16d 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -46,13 +46,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( @@ -63,6 +67,8 @@ class LoggedInFlowNode @AssistedInject constructor( private val createRoomEntryPoint: CreateRoomEntryPoint, private val appNavigationStateService: AppNavigationStateService, private val verifySessionEntryPoint: VerifySessionEntryPoint, + private val coroutineScope: CoroutineScope, + snackbarDispatcher: SnackbarDispatcher, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.RoomList, @@ -87,6 +93,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() @@ -99,6 +110,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().notLoggedInImageLoaderFactory() @@ -106,6 +118,7 @@ class LoggedInFlowNode @AssistedInject constructor( plugins().forEach { it.onFlowReleased(inputs.matrixClient) } appNavigationStateService.onLeavingSpace() appNavigationStateService.onLeavingSession() + loggedInFlowProcessor.stopObserving() } ) } diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt index 14b90b0064..69c02d500e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt @@ -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 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( private val messagesEntryPoint: MessagesEntryPoint, private val roomDetailsEntryPoint: RoomDetailsEntryPoint, private val appNavigationStateService: AppNavigationStateService, + roomMembershipObserver: RoomMembershipObserver, + coroutineScope: CoroutineScope, ) : BackstackNode( backstack = BackStack( initialElement = NavTarget.Messages, @@ -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( 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( }) } NavTarget.RoomDetails -> { - roomDetailsEntryPoint.createNode(this, buildContext) + roomDetailsEntryPoint.createNode(this, buildContext, emptyList()) } } } diff --git a/changelog.d/286.feature b/changelog.d/286.feature new file mode 100644 index 0000000000..0a193367fd --- /dev/null +++ b/changelog.d/286.feature @@ -0,0 +1 @@ +Add leave room functionality to the Room Details screen. diff --git a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt b/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt index e56cc7e705..560e9d5dd7 100644 --- a/features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt +++ b/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 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): Node } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt index a52575606c..eebdbea062 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/DefaultRoomDetailsEntryPoint.kt +++ b/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 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 @ContributesBinding(AppScope::class) class DefaultRoomDetailsEntryPoint @Inject constructor() : RoomDetailsEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext): Node { - return parentNode.createNode(buildContext) + override fun createNode(parentNode: Node, buildContext: BuildContext, plugins: List): Node { + return parentNode.createNode(buildContext, plugins) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt index ad6b4ea78f..3ef87d17e0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsEvent.kt @@ -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 +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 80ae4426d9..48dce4609c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -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 { @Composable override fun present(): RoomDetailsState { -// fun handleEvents(event: RoomDetailsEvent) {} - + val coroutineScope = rememberCoroutineScope() + var leaveRoomWarning by remember { + mutableStateOf(null) + } + var error by remember { + mutableStateOf(null) + } var memberCount: Async by remember { mutableStateOf(Async.Loading()) } LaunchedEffect(Unit) { withContext(Dispatchers.IO) { @@ -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( roomTopic = room.topic, memberCount = memberCount, isEncrypted = room.isEncrypted, -// eventSink = ::handleEvents + displayLeaveRoomWarning = leaveRoomWarning, + error = error, + eventSink = ::handleEvents ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index 78ee70529d..a9d3dc2fb1 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -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( val roomTopic: String?, val memberCount: Async, 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): LeaveRoomWarning { + return when { + !isPublic -> PrivateRoom + (memberCount as? Async.Success)?.state == 1 -> LastUserInRoom + else -> Generic + } + } + } +} + +sealed interface RoomDetailsError { + object AlertGeneric : RoomDetailsError +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index c91db4d3a8..2629d76aed 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -43,5 +43,7 @@ fun aRoomDetailsState() = RoomDetailsState( "|| MAI iki/Marketing...", memberCount = Async.Success(32), isEncrypted = true, -// eventSink = {} + displayLeaveRoomWarning = null, + error = null, + eventSink = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index e4d4ac5609..d5b2a4a9e4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/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 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 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( 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) { } @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) = diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 7679e1eb7d..41404b7354 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -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 { @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 { @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 { 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 { 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( avatarUrl: String? = "https://matrix.org/avatar.jpg", members: List = emptyList(), isEncrypted: Boolean = true, + isPublic: Boolean = true, ) = FakeMatrixRoom( roomId = roomId, name = name, @@ -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, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index 47c34d6a5c..299c670eb4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -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 } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 7d9a62f2d7..ac37dfe30d 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/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 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( private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, private val sessionVerificationService: SessionVerificationService, + private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { + private val roomMembershipObserver: RoomMembershipObserver = client.roomMembershipObserver() + @Composable override fun present(): RoomListState { val matrixUser: MutableState = remember { @@ -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( 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 ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 122c5e5506..a14ef74e94 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/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 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( val matrixUser: MatrixUser?, val roomList: ImmutableList, val filter: String, - val presentVerificationSuccessfulMessage: Boolean, val displayVerificationPrompt: Boolean, + val snackbarMessage: SnackbarMessage?, val eventSink: (RoomListEvents) -> Unit ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 68db631675..d1ca647d62 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/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 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 { override val values: Sequence 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( roomList = aRoomListRoomSummaryList(), filter = "filter", eventSink = {}, - presentVerificationSuccessfulMessage = false, + snackbarMessage = null, displayVerificationPrompt = false, ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 8d31b17a14..1f49d8db0f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -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 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( } 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, + ) + } } } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 1e8c15bf6d..3f3e43e2e7 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/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 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 { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -75,6 +76,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -95,6 +97,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -119,6 +122,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -148,6 +152,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -182,6 +187,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -230,6 +236,7 @@ class RoomListPresenterTests { givenIsReady(true) givenVerifiedStatus(SessionVerifiedStatus.NotVerified) }, + SnackbarDispatcher(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -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) diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index 6c79c1b762..4007f4c8cf 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/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 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 diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt index ff434c9748..07a85e0645 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/ConfirmationDialog.kt +++ b/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 @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( modifier = modifier, onDismissRequest = onDismiss, title = { - Text(text = title) + if (title != null) { Text(text = title) } }, text = { Text(content) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcher.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcher.kt new file mode 100644 index 0000000000..1131777398 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/SnackbarDispatcher.kt @@ -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(null) + val snackbarMessage: Flow = 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 = {}, +) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 9ce5502843..8a991771a5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -20,6 +20,7 @@ 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.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 @@ -43,4 +44,6 @@ interface MatrixClient : Closeable { ): Result fun onSlidingSyncUpdate() + + fun roomMembershipObserver(): RoomMembershipObserver } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index bd76d95b35..60ff09a15b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -32,8 +32,10 @@ interface MatrixRoom: Closeable { val topic: String? val avatarUrl: String? val isEncrypted: Boolean + val isPublic: Boolean suspend fun members() : List + suspend fun memberCount(): Int fun syncUpdateFlow(): Flow @@ -53,4 +55,6 @@ interface MatrixRoom: Closeable { suspend fun replyMessage(eventId: EventId, message: String): Result suspend fun redactEvent(eventId: EventId, reason: String? = null): Result + + fun leave(): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt new file mode 100644 index 0000000000..42b0996bb1 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMembershipObserver.kt @@ -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(replay = 1) + val updates = _updates.asSharedFlow() + + fun notifyUserLeftRoom(roomId: RoomId) { + _updates.tryEmit(RoomMembershipUpdate(roomId, false, MembershipChange.LEFT)) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 378fec56c7..2fe34bfa55 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/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 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 @@ -89,6 +90,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) @@ -128,6 +130,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() @@ -150,7 +154,7 @@ class RustMatrixClient constructor( slidingSyncRoom = slidingSyncRoom, innerRoom = fullRoom, coroutineScope = coroutineScope, - coroutineDispatchers = dispatchers + coroutineDispatchers = dispatchers, ) } @@ -243,6 +247,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(":", "_") diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt index 3079c75dc8..b49050cdc2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/di/SessionMatrixModule.kt @@ -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 { fun providesRustSessionVerificationService(matrixClient: MatrixClient): SessionVerificationService { return matrixClient.sessionVerificationService() } + + @Provides + @SingleIn(SessionScope::class) + fun provideRoomMembershipObserver(matrixClient: MatrixClient): RoomMembershipObserver { + return matrixClient.roomMembershipObserver() + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 291aa57392..fd5a63cb3b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -129,6 +129,9 @@ class RustMatrixRoom( override val alternativeAliases: List get() = innerRoom.alternativeAliases() + override val isPublic: Boolean + get() = innerRoom.isPublic() + override suspend fun fetchMembers(): Result = withContext(coroutineDispatchers.io) { runCatching { innerRoom.fetchMembers() @@ -179,4 +182,8 @@ class RustMatrixRoom( innerRoom.redact(eventId.value, reason, transactionId) } } + + override fun leave(): Result { + return runCatching { innerRoom.leave() } + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 9272e794b4..998c7cdea4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -21,6 +21,7 @@ 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.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 @@ -81,4 +82,8 @@ class FakeMatrixClient( override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService override fun onSlidingSyncUpdate() {} + + override fun roomMembershipObserver(): RoomMembershipObserver { + return RoomMembershipObserver(A_SESSION_ID) + } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index b8ea695f47..1c6d580a3c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -37,6 +37,7 @@ class FakeMatrixRoom( override val isEncrypted: Boolean = false, override val alias: String? = null, override val alternativeAliases: List = emptyList(), + override val isPublic: Boolean = true, private val members: List = emptyList(), private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), ) : MatrixRoom { @@ -46,6 +47,8 @@ class FakeMatrixRoom( var areMembersFetched: Boolean = false private set + private var leaveRoomError: Throwable? = null + override fun syncUpdateFlow(): Flow { return emptyFlow() } @@ -114,8 +117,14 @@ class FakeMatrixRoom( return Result.success(Unit) } + override fun leave(): Result = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit) + override fun close() = Unit + fun givenLeaveRoomError(throwable: Throwable?) { + this.leaveRoomError = throwable + } + fun givenFetchMemberResult(result: Result) { fetchMemberResult = result } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 6c95dee210..5dce2feafe 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -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( matrixClient, DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), DefaultRoomLastMessageFormatter(context, matrixClient), - sessionVerificationService + sessionVerificationService, + SnackbarDispatcher(), ) @Composable From 54a1729bd885df0574494ad4c6988f4f2747827a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 13:56:35 +0000 Subject: [PATCH 18/18] Update peter-evans/create-pull-request action to v5 --- .github/workflows/sync-localazy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-localazy.yml b/.github/workflows/sync-localazy.yml index 3efbb41f27..e3c745b8f6 100644 --- a/.github/workflows/sync-localazy.yml +++ b/.github/workflows/sync-localazy.yml @@ -18,7 +18,7 @@ jobs: - name: Run Localazy script run: ./tools/localazy/downloadStrings.sh --all - name: Create Pull Request for Strings - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v5 with: commit-message: Sync Strings from Localazy title: Sync Strings