Browse Source

Merge branch 'develop' into renovate/android.gradle.plugin

pull/962/head
Marco Romano 1 year ago committed by GitHub
parent
commit
c4cf6c8170
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitattributes
  2. 23
      .github/workflows/build.yml
  3. 6
      .github/workflows/danger.yml
  4. 5
      .github/workflows/gradle-wrapper-validation.yml
  5. 6
      .github/workflows/maestro.yml
  6. 9
      .github/workflows/quality.yml
  7. 51
      .github/workflows/sonar.yml
  8. 5
      .github/workflows/tests.yml
  9. 4
      .github/workflows/validate-lfs.yml
  10. 2
      .idea/kotlinc.xml
  11. 4
      .maestro/README.md
  12. 21
      CHANGES.md
  13. 32
      CONTRIBUTING.md
  14. 47
      README.md
  15. 6
      app/src/main/kotlin/io/element/android/x/ElementXApplication.kt
  16. 4
      app/src/main/kotlin/io/element/android/x/di/AppBindings.kt
  17. 4
      app/src/main/kotlin/io/element/android/x/icon/IconPreview.kt
  18. 36
      app/src/main/kotlin/io/element/android/x/initializer/MatrixInitializer.kt
  19. 57
      app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt
  20. 2
      appnav/build.gradle.kts
  21. 3
      appnav/src/main/kotlin/io/element/android/appnav/LoggedInEventProcessor.kt
  22. 44
      appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt
  23. 36
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt
  24. 4
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt
  25. 10
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt
  26. 4
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt
  27. 24
      appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt
  28. 28
      appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomNodeView.kt
  29. 3
      appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt
  30. 7
      appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt
  31. 28
      appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt
  32. 16
      appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt
  33. 24
      build.gradle.kts
  34. 1
      changelog.d/1064.wip
  35. 1
      changelog.d/769.feature
  36. 7
      docs/_developer_onboarding.md
  37. BIN
      docs/images-lfs/screen_1_dark.png
  38. BIN
      docs/images-lfs/screen_1_light.png
  39. BIN
      docs/images-lfs/screen_2_dark.png
  40. BIN
      docs/images-lfs/screen_2_light.png
  41. BIN
      docs/images-lfs/screen_3_dark.png
  42. BIN
      docs/images-lfs/screen_3_light.png
  43. BIN
      docs/images-lfs/screen_4_dark.png
  44. BIN
      docs/images-lfs/screen_4_light.png
  45. BIN
      docs/images/screen1.png
  46. BIN
      docs/images/screen2.png
  47. BIN
      docs/images/screen3.png
  48. BIN
      docs/images/screen4.png
  49. 4
      docs/nightly_build.md
  50. 10
      docs/screenshot_testing.md
  51. 2
      fastlane/metadata/android/en-US/changelogs/40001020.txt
  52. 4
      features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt
  53. 16
      features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt
  54. 8
      features/analytics/impl/src/main/res/values-de/translations.xml
  55. 10
      features/analytics/impl/src/main/res/values-ru/translations.xml
  56. 4
      features/analytics/impl/src/main/res/values-zh-rTW/translations.xml
  57. 13
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt
  58. 4
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt
  59. 8
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt
  60. 4
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchSingleUserResultItem.kt
  61. 6
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt
  62. 19
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt
  63. 2
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt
  64. 10
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt
  65. 2
      features/createroom/impl/src/main/res/values-fr/translations.xml
  66. 15
      features/createroom/impl/src/main/res/values-ru/translations.xml
  67. 7
      features/createroom/impl/src/main/res/values-zh-rTW/translations.xml
  68. 8
      features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeView.kt
  69. 9
      features/ftue/impl/src/main/res/values-ru/translations.xml
  70. 2
      features/ftue/impl/src/main/res/values-sk/translations.xml
  71. 4
      features/ftue/impl/src/main/res/values-zh-rTW/translations.xml
  72. 10
      features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt
  73. 5
      features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt
  74. 19
      features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/components/InviteSummaryRow.kt
  75. 4
      features/invitelist/impl/src/main/res/values-de/translations.xml
  76. 9
      features/invitelist/impl/src/main/res/values-ru/translations.xml
  77. 76
      features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt
  78. 2
      features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt
  79. 2
      features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt
  80. 2
      features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt
  81. 2
      features/login/impl/build.gradle.kts
  82. 8
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt
  83. 4
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt
  84. 1
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt
  85. 3
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/CustomTabHandler.kt
  86. 4
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt
  87. 4
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt
  88. 2
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt
  89. 14
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt
  90. 4
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt
  91. 4
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt
  92. 2
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt
  93. 38
      features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt
  94. 13
      features/login/impl/src/main/res/drawable/ic_homeserver.xml
  95. BIN
      features/login/impl/src/main/res/drawable/onboarding_icon_light.png
  96. 8
      features/login/impl/src/main/res/values-de/translations.xml
  97. 47
      features/login/impl/src/main/res/values-ru/translations.xml
  98. 16
      features/login/impl/src/main/res/values-zh-rTW/translations.xml
  99. 8
      features/logout/api/src/main/res/values-ru/translations.xml
  100. 8
      features/logout/api/src/main/res/values-zh-rTW/translations.xml
  101. Some files were not shown because too many files have changed in this diff Show More

1
.gitattributes vendored

@ -1 +1,2 @@ @@ -1 +1,2 @@
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
**/docs/images-lfs/*.png filter=lfs diff=lfs merge=lfs -text

23
.github/workflows/build.yml

@ -2,7 +2,8 @@ name: APK Build @@ -2,7 +2,8 @@ name: APK Build
on:
workflow_dispatch:
pull_request: { }
pull_request:
merge_group:
push:
branches: [ develop ]
@ -13,14 +14,17 @@ env: @@ -13,14 +14,17 @@ env:
jobs:
debug:
name: Build debug APKs
name: Build APKs
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
# Skip for `main` and the merge queue if the branch is up to date with `develop`
if: github.ref != 'refs/heads/main' && github.event.merge_group.base_ref != 'refs/heads/develop'
strategy:
matrix:
variant: [debug, release, nightly, samples]
fail-fast: false
# Allow all jobs on develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-{0}', github.sha) || format('build-debug-{0}', github.ref) }}
group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-{0}-{1}', matrix.variant, github.sha) || format('build-{0}-{1}', matrix.variant, github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
@ -38,12 +42,14 @@ jobs: @@ -38,12 +42,14 @@ jobs:
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble debug APK
if: ${{ matrix.variant == 'debug' }}
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
run: ./gradlew assembleDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Upload debug APKs
- name: Upload APK APKs
if: ${{ matrix.variant == 'debug' }}
uses: actions/upload-artifact@v3
with:
name: elementx-debug
@ -55,12 +61,12 @@ jobs: @@ -55,12 +61,12 @@ jobs:
continue-on-error: true
env:
token: ${{ secrets.DIAWI_TOKEN }}
if: ${{ github.event_name == 'pull_request' && env.token != '' }}
if: ${{ matrix.variant == 'debug' && github.event_name == 'pull_request' && env.token != '' }}
with:
token: ${{ env.token }}
file: app/build/outputs/apk/debug/app-arm64-v8a-debug.apk
- name: Add or update PR comment with QR Code to download APK.
if: ${{ github.event_name == 'pull_request' && steps.diawi.conclusion == 'success' }}
if: ${{ matrix.variant == 'debug' && github.event_name == 'pull_request' && steps.diawi.conclusion == 'success' }}
uses: NejcZdovc/comment-pr@v2
with:
message: |
@ -72,8 +78,11 @@ jobs: @@ -72,8 +78,11 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Compile release sources
if: ${{ matrix.variant == 'release' }}
run: ./gradlew compileReleaseSources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Compile nightly sources
if: ${{ matrix.variant == 'nightly' }}
run: ./gradlew compileNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Compile samples minimal
if: ${{ matrix.variant == 'samples' }}
run: ./gradlew :samples:minimal:assemble $CI_GRADLE_ARG_PROPERTIES

6
.github/workflows/danger.yml

@ -1,17 +1,19 @@ @@ -1,17 +1,19 @@
name: Danger CI
on: [pull_request]
on: [pull_request, merge_group]
jobs:
build:
runs-on: ubuntu-latest
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
name: Danger main check
steps:
- uses: actions/checkout@v3
- run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger
uses: danger/danger-js@11.2.6
uses: danger/danger-js@11.2.8
with:
args: "--dangerfile ./tools/danger/dangerfile.js"
env:

5
.github/workflows/gradle-wrapper-validation.yml

@ -1,12 +1,15 @@ @@ -1,12 +1,15 @@
name: "Validate Gradle Wrapper"
on:
pull_request: { }
pull_request:
merge_group:
push:
branches: [ main, develop ]
jobs:
validation:
name: "Validation"
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
runs-on: ubuntu-latest
# No concurrency required, this is a prerequisite to other actions and should run every time.
steps:

6
.github/workflows/maestro.yml

@ -40,12 +40,6 @@ jobs: @@ -40,12 +40,6 @@ jobs:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
- name: Upload debug APKs
uses: actions/upload-artifact@v3
with:
name: elementx-debug
path: |
app/build/outputs/apk/debug/*.apk
- uses: mobile-dev-inc/action-maestro-cloud@v1.4.1
with:
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}

9
.github/workflows/quality.yml

@ -2,7 +2,8 @@ name: Code Quality Checks @@ -2,7 +2,8 @@ name: Code Quality Checks
on:
workflow_dispatch:
pull_request: { }
pull_request:
merge_group:
push:
branches: [ main, develop ]
@ -15,6 +16,8 @@ jobs: @@ -15,6 +16,8 @@ jobs:
checkScript:
name: Search for forbidden patterns
runs-on: ubuntu-latest
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
steps:
- uses: actions/checkout@v3
- name: Run code quality check suite
@ -23,6 +26,8 @@ jobs: @@ -23,6 +26,8 @@ jobs:
check:
name: Project Check Suite
runs-on: ubuntu-latest
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
# Allow all jobs on main and develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('check-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-develop-{0}', github.sha) || format('check-{0}', github.ref) }}
@ -65,7 +70,7 @@ jobs: @@ -65,7 +70,7 @@ jobs:
yarn add danger-plugin-lint-report --dev
- name: Danger lint
if: always()
uses: danger/danger-js@11.2.6
uses: danger/danger-js@11.2.8
with:
args: "--dangerfile ./tools/danger/dangerfile-lint.js"
env:

51
.github/workflows/sonar.yml

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
name: Code Quality Checks
on:
workflow_dispatch:
pull_request:
merge_group:
push:
branches: [ main, develop ]
# Enrich gradle.properties for CI/CD
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options="-Xmx2g" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon --warn
jobs:
sonar:
name: Project Check Suite
runs-on: ubuntu-latest
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
# Allow all jobs on main and develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/main' && format('sonar-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('sonar-develop-{0}', github.sha) || format('sonar-{0}', github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }}
- name: Use JDK 17
uses: actions/setup-java@v3
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.7.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: 🔊 Publish results to Sonar
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }}
run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES
- name: Prepare Danger
if: always()
run: |
npm install --save-dev @babel/core
npm install --save-dev @babel/plugin-transform-flow-strip-types
yarn add danger-plugin-lint-report --dev

5
.github/workflows/tests.yml

@ -2,7 +2,8 @@ name: Test @@ -2,7 +2,8 @@ name: Test
on:
workflow_dispatch:
pull_request: { }
pull_request:
merge_group:
push:
branches: [ main, develop ]
@ -15,6 +16,8 @@ jobs: @@ -15,6 +16,8 @@ jobs:
tests:
name: Runs unit tests
runs-on: ubuntu-latest
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
# Allow all jobs on main and develop. Just one per PR.
concurrency:

4
.github/workflows/validate-lfs.yml

@ -1,10 +1,12 @@ @@ -1,10 +1,12 @@
name: Validate Git LFS
on: [pull_request]
on: [pull_request, merge_group]
jobs:
build:
runs-on: ubuntu-latest
# Don't run in the merge queue again if the branch is up to date with `develop`
if: github.event.merge_group.base_ref != 'refs/heads/develop'
name: Validate
steps:
- uses: nschloe/action-cached-lfs-checkout@v1.2.1

2
.idea/kotlinc.xml

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.8.22" />
<option name="version" value="1.9.0" />
</component>
</project>

4
.maestro/README.md

@ -18,7 +18,7 @@ To setup, please refer at [https://maestro.mobile.dev](https://maestro.mobile.de @@ -18,7 +18,7 @@ To setup, please refer at [https://maestro.mobile.dev](https://maestro.mobile.de
From root dir of the project
*Note: Since ElementX does not allow account creation nor room creation, we have to use an existing account with an existing room to run maestro test suite. So to run locally, please replace `user` and `123` with your test matrix.org account credentials, and `my room` with one of a room this account has join. Note that the test will send messages to this room.*
*Note: Since Element X does not allow account creation, we have to use an existing account to run maestro test suite. So to run locally, please replace `user` and `123` with your test matrix.org account credentials, and `my room` with one of a room this account has joined. Note that the test will send messages to this room.*
```shell
maestro test \
@ -39,7 +39,7 @@ Test result will be printed on the console, and screenshots will be generated at @@ -39,7 +39,7 @@ Test result will be printed on the console, and screenshots will be generated at
Tests are yaml files. Generally each yaml file should leave the app in the same screen than at the beginning.
Start the ElementX app and run this command to help writing test.
Start the Element X app and run this command to help writing test.
```shell
maestro studio

21
CHANGES.md

@ -1,3 +1,24 @@ @@ -1,3 +1,24 @@
Changes in Element X v0.1.2 (2023-08-16)
========================================
Bugfixes 🐛
----------
- Filter push notifications using push rules. ([#640](https://github.com/vector-im/element-x-android/issues/640))
- Use `for` instead of `forEach` in `DefaultDiffCacheInvalidator` to improve performance. ([#1035](https://github.com/vector-im/element-x-android/issues/1035))
In development 🚧
----------------
- [Poll] Render start event in the timeline ([#1031](https://github.com/vector-im/element-x-android/issues/1031))
Other changes
-------------
- Add Button component based on Compound designs ([#1021](https://github.com/vector-im/element-x-android/issues/1021))
- Compound: implement dialogs. ([#1043](https://github.com/vector-im/element-x-android/issues/1043))
- Compound: customise `IconButton` component. ([#1049](https://github.com/vector-im/element-x-android/issues/1049))
- Compound: implement `DropdownMenu` customisations. ([#1050](https://github.com/vector-im/element-x-android/issues/1050))
- Compound: implement Snackbar component. ([#1054](https://github.com/vector-im/element-x-android/issues/1054))
Changes in Element X v0.1.0 (2023-07-19)
========================================

32
CONTRIBUTING.md

@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
* [Kotlin](#kotlin)
* [Changelog](#changelog)
* [Code quality](#code-quality)
* [detekt](#detekt)
* [ktlint](#ktlint)
* [knit](#knit)
* [lint](#lint)
@ -50,7 +51,7 @@ Note: please make sure that the configuration is `app` and not `samples.minimal` @@ -50,7 +51,7 @@ 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.
The strings of the project are managed externally using [https://localazy.com](https://localazy.com) and shared with Element X iOS.
### I want to add new strings to the project
@ -60,14 +61,12 @@ Please follow the naming rules for the key. More details in [the dedicated secti @@ -60,14 +61,12 @@ Please follow the naming rules for the key. More details in [the dedicated secti
### 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 with an English string, please open an issue on the github project of Element X (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).
More information can be found [in this README.md](./tools/localazy/README.md).
## I want to submit a PR to fix an issue
@ -101,11 +100,17 @@ See https://github.com/twisted/towncrier#news-fragments if you need more details @@ -101,11 +100,17 @@ See https://github.com/twisted/towncrier#news-fragments if you need more details
Make sure the following commands execute without any error:
<pre>
./gradlew check
./tools/quality/check.sh
</pre>
Some separate commands can also be run, see below.
#### detekt
<pre>
./gradlew detekt
</pre>
#### ktlint
<pre>
@ -153,7 +158,7 @@ Make sure the following commands execute without any error: @@ -153,7 +158,7 @@ Make sure the following commands execute without any error:
### Tests
Element X is currently supported on Android Lollipop (API 21+): please test your change on an Android device (or Android emulator) running with API 21. Many issues can happen (including crashes) on older devices.
Element X is currently supported on Android Marshmallow (API 23+): please test your change on an Android device (or Android emulator) running with API 23. Many issues can happen (including crashes) on older devices.
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
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.
@ -166,7 +171,18 @@ For instance, when updating the image `src` of an ImageView, please also conside @@ -166,7 +171,18 @@ For instance, when updating the image `src` of an ImageView, please also conside
### Jetpack Compose
When adding or editing `@Composable`, make sure that you create a `@Preview` function, with suffix `Preview`. This will also create a UI test automatically.
When adding or editing `@Composable`, make sure that you create an internal function annotated with `@DayNightPreviews`, with a name suffixed by `Preview`, and having `ElementPreview` as the root composable.
Example:
```kotlin
@DayNightPreviews
@Composable
internal fun PinIconPreview() = ElementPreview {
PinIcon()
}
```
This will allow to preview the composable in both light and dark mode in Android Studio. This will also automatically add UI tests. The GitHub action [Record screenshots](https://github.com/vector-im/element-x-android/actions/workflows/recordScreenshots.yml) has to be run to record the new screenshots. The PR reviewer can trigger this for you if you're not part of the core team.
### Authors

47
README.md

@ -3,14 +3,18 @@ @@ -3,14 +3,18 @@
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-x-android&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-x-android&metric=bugs)](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
[![codecov](https://codecov.io/github/vector-im/element-x-android/branch/develop/graph/badge.svg?token=ecwvia7amV)](https://codecov.io/github/vector-im/element-x-android)
[![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org)
[![Weblate](https://translate.element.io/widgets/element-android/-/svg-badge.svg)](https://translate.element.io/engage/element-android/?utm_source=widget)
[![Element X_Android Matrix room #element-x-android:matrix.org](https://img.shields.io/matrix/element-x-android:matrix.org.svg?label=%23element-x-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-x-android:matrix.org)
[![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element)
# element-x-android
# Element X Android
ElementX Android is a [Matrix](https://matrix.org/) Android Client provided by [Element](https://element.io/). This app is currently in a pre-alpha release stage with only basic functionality.
Element X Android is a [Matrix](https://matrix.org/) Android Client provided by [element.io](https://element.io/). This app is currently in a pre-alpha release stage with only basic functionalities.
The application is a total rewrite of [Element-Android](https://github.com/vector-im/element-android) using the [Matrix Rust SDK](https://github.com/matrix-org/matrix-rust-sdk) underneath and targeting devices running Android 6+. The UI layer is written using Jetpack compose.
The application is a total rewrite of [Element-Android](https://github.com/vector-im/element-android) using the [Matrix Rust SDK](https://github.com/matrix-org/matrix-rust-sdk) underneath and targeting devices running Android 6+. The UI layer is written using [Jetpack Compose](https://developer.android.com/jetpack/compose), and the navigation is managed using [Appyx](https://github.com/bumble-tech/appyx).
Learn more about why we are building Element X in our blog post: [https://element.io/blog/element-x-experience-the-future-of-element/](https://element.io/blog/element-x-experience-the-future-of-element/).
## Table of contents
<!--- TOC -->
@ -28,24 +32,41 @@ The application is a total rewrite of [Element-Android](https://github.com/vecto @@ -28,24 +32,41 @@ The application is a total rewrite of [Element-Android](https://github.com/vecto
Here are some early screenshots of the application:
|<img src=./docs/images/screen1.png width=280 />|<img src=./docs/images/screen2.png width=280 />|<img src=./docs/images/screen3.png width=280 />|<img src=./docs/images/screen4.png width=280 />|
<!--
Commands run before taking the screenshots:
adb shell settings put system time_12_24 24
adb shell am broadcast -a com.android.systemui.demo -e command enter
adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1337
adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e level 4
adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show -e level 4
adb shell am broadcast -a com.android.systemui.demo -e command notifications -e visible false
adb shell am broadcast -a com.android.systemui.demo -e command battery -e plugged false -e level 100
And to exit demo mode:
adb shell am broadcast -a com.android.systemui.demo -e command exit
-->
|<img src=./docs/images-lfs/screen_1_light.png width=280 />|<img src=./docs/images-lfs/screen_2_light.png width=280 />|<img src=./docs/images-lfs/screen_3_light.png width=280 />|<img src=./docs/images-lfs/screen_4_light.png width=280 />|
|-|-|-|-|
|<img src=./docs/images-lfs/screen_1_dark.png width=280 />|<img src=./docs/images-lfs/screen_2_dark.png width=280 />|<img src=./docs/images-lfs/screen_3_dark.png width=280 />|<img src=./docs/images-lfs/screen_4_dark.png width=280 />|
## Rust SDK
ElementX leverages the [Matrix Rust SDK](https://github.com/matrix-org/matrix-rust-sdk) through an FFI layer that the final client can directly import and use.
Element X leverages the [Matrix Rust SDK](https://github.com/matrix-org/matrix-rust-sdk) through an FFI layer that the final client can directly import and use.
We're doing this as a way to share code between platforms and while we've seen promising results it's still in the experimental stage and bound to change.
## Status
This project is in work in progress. The app does not cover yet all functionalities we expect.
This project is in work in progress. The app does not cover yet all functionalities we expect. The list of supported features can be found in [this issue](https://github.com/vector-im/element-x-android/issues/911).
## Contributing
Please see our [contribution guide](CONTRIBUTING.md).
Want to get actively involved in the project? You're more than welcome! A good way to start is to check the issues that are labelled with the [good first issue](https://github.com/vector-im/element-x-android/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label. Let us know by commenting the issue that you're starting working on it.
But first make sure to read our [contribution guide](CONTRIBUTING.md) first.
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#element-android:matrix.org).
You can also come chat with the community in the Matrix [room](https://matrix.to/#/#element-x-android:matrix.org) dedicated to the project.
## Build instructions
@ -54,9 +75,9 @@ Makes sure to select the `app` configuration when building (as we also have samp @@ -54,9 +75,9 @@ Makes sure to select the `app` configuration when building (as we also have samp
## Support
When you are experiencing an issue on ElementX Android, please first search in [GitHub issues](https://github.com/vector-im/element-x-android/issues)
and then in [#element-android:matrix.org](https://matrix.to/#/#element-android:matrix.org).
If after your research you still have a question, ask at [#element-android:matrix.org](https://matrix.to/#/#element-android:matrix.org). Otherwise feel free to create a GitHub issue if you encounter a bug or a crash, by explaining clearly in detail what happened. You can also perform bug reporting (Rageshake) from the Element application by shaking your phone or going to the application settings. This is especially recommended when you encounter a crash.
When you are experiencing an issue on Element X Android, please first search in [GitHub issues](https://github.com/vector-im/element-x-android/issues)
and then in [#element-x-android:matrix.org](https://matrix.to/#/#element-x-android:matrix.org).
If after your research you still have a question, ask at [#element-x-android:matrix.org](https://matrix.to/#/#element-x-android:matrix.org). Otherwise feel free to create a GitHub issue if you encounter a bug or a crash, by explaining clearly in detail what happened. You can also perform bug reporting from the application settings. This is especially recommended when you encounter a crash.
## Copyright & License

6
app/src/main/kotlin/io/element/android/x/ElementXApplication.kt

@ -24,8 +24,7 @@ import io.element.android.x.di.DaggerAppComponent @@ -24,8 +24,7 @@ import io.element.android.x.di.DaggerAppComponent
import io.element.android.x.info.logApplicationInfo
import io.element.android.x.initializer.CrashInitializer
import io.element.android.x.initializer.EmojiInitializer
import io.element.android.x.initializer.MatrixInitializer
import io.element.android.x.initializer.TimberInitializer
import io.element.android.x.initializer.TracingInitializer
class ElementXApplication : Application(), DaggerComponentOwner {
@ -39,8 +38,7 @@ class ElementXApplication : Application(), DaggerComponentOwner { @@ -39,8 +38,7 @@ class ElementXApplication : Application(), DaggerComponentOwner {
appComponent = DaggerAppComponent.factory().create(applicationContext)
AppInitializer.getInstance(this).apply {
initializeComponent(CrashInitializer::class.java)
initializeComponent(TimberInitializer::class.java)
initializeComponent(MatrixInitializer::class.java)
initializeComponent(TracingInitializer::class.java)
initializeComponent(EmojiInitializer::class.java)
}
logApplicationInfo()

4
app/src/main/kotlin/io/element/android/x/di/AppBindings.kt

@ -17,11 +17,15 @@ @@ -17,11 +17,15 @@
package io.element.android.x.di
import com.squareup.anvil.annotations.ContributesTo
import io.element.android.features.rageshake.api.reporter.BugReporter
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.tracing.TracingService
@ContributesTo(AppScope::class)
interface AppBindings {
fun mainDaggerComponentOwner(): MainDaggerComponentsOwner
fun snackbarDispatcher(): SnackbarDispatcher
fun tracingService(): TracingService
fun bugReporter(): BugReporter
}

4
app/src/main/kotlin/io/element/android/x/icon/IconPreview.kt

@ -28,7 +28,7 @@ import io.element.android.x.R @@ -28,7 +28,7 @@ import io.element.android.x.R
@Preview
@Composable
fun IconPreview(
internal fun IconPreview(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
@ -39,7 +39,7 @@ fun IconPreview( @@ -39,7 +39,7 @@ fun IconPreview(
@Preview
@Composable
fun RoundIconPreview(
internal fun RoundIconPreview(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.clip(shape = CircleShape)) {

36
app/src/main/kotlin/io/element/android/x/initializer/MatrixInitializer.kt

@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
/*
* Copyright (c) 2022 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.x.initializer
import android.content.Context
import androidx.startup.Initializer
import io.element.android.libraries.matrix.impl.tracing.setupTracing
import io.element.android.libraries.matrix.api.tracing.TracingConfigurations
import io.element.android.x.BuildConfig
class MatrixInitializer : Initializer<Unit> {
override fun create(context: Context) {
if (BuildConfig.DEBUG) {
setupTracing(TracingConfigurations.debug)
} else {
setupTracing(TracingConfigurations.release)
}
}
override fun dependencies(): List<Class<out Initializer<*>>> = listOf(TimberInitializer::class.java)
}

57
app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* Copyright (c) 2022 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.x.initializer
import android.content.Context
import androidx.startup.Initializer
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.matrix.api.tracing.TracingConfiguration
import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations
import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration
import io.element.android.x.BuildConfig
import io.element.android.x.di.AppBindings
import timber.log.Timber
class TracingInitializer : Initializer<Unit> {
override fun create(context: Context) {
val appBindings = context.bindings<AppBindings>()
val tracingService = appBindings.tracingService()
val bugReporter = appBindings.bugReporter()
Timber.plant(tracingService.createTimberTree())
val tracingConfiguration = if (BuildConfig.DEBUG) {
TracingConfiguration(
filterConfiguration = TracingFilterConfigurations.debug,
writesToLogcat = true,
writesToFilesConfiguration = WriteToFilesConfiguration.Disabled
)
} else {
TracingConfiguration(
filterConfiguration = TracingFilterConfigurations.release,
writesToLogcat = false,
writesToFilesConfiguration = WriteToFilesConfiguration.Enabled(
directory = bugReporter.logDirectory().absolutePath,
filenamePrefix = "logs"
)
)
}
bugReporter.cleanLogDirectoryIfNeeded()
tracingService.setupTracing(tracingConfiguration)
}
override fun dependencies(): List<Class<out Initializer<*>>> = mutableListOf()
}

2
appnav/build.gradle.kts

@ -65,6 +65,8 @@ dependencies { @@ -65,6 +65,8 @@ dependencies {
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.tests.testutils)
testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.rageshake.impl)
testImplementation(projects.services.appnavstate.test)

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

@ -58,7 +58,8 @@ class LoggedInEventProcessor @Inject constructor( @@ -58,7 +58,8 @@ class LoggedInEventProcessor @Inject constructor(
.filter { it }
.onEach {
displayMessage(CommonStrings.common_verification_complete)
}.launchIn(this)
}
.launchIn(this)
}
}

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

@ -44,14 +44,14 @@ import io.element.android.appnav.loggedin.LoggedInNode @@ -44,14 +44,14 @@ import io.element.android.appnav.loggedin.LoggedInNode
import io.element.android.appnav.room.RoomFlowNode
import io.element.android.appnav.room.RoomLoadedFlowNode
import io.element.android.features.createroom.api.CreateRoomEntryPoint
import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.invitelist.api.InviteListEntryPoint
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.preferences.api.PreferencesEntryPoint
import io.element.android.features.roomlist.api.RoomListEntryPoint
import io.element.android.features.verifysession.api.VerifySessionEntryPoint
import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.api.state.FtueState
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
@ -69,10 +69,12 @@ import io.element.android.libraries.matrix.ui.di.MatrixUIBindings @@ -69,10 +69,12 @@ import io.element.android.libraries.matrix.ui.di.MatrixUIBindings
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.services.appnavstate.api.AppNavigationStateService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import timber.log.Timber
@ContributesNode(AppScope::class)
class LoggedInFlowNode @AssistedInject constructor(
@ -100,13 +102,13 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -100,13 +102,13 @@ class LoggedInFlowNode @AssistedInject constructor(
) {
interface Callback : Plugin {
fun onOpenBugReport() = Unit
fun onOpenBugReport()
}
interface LifecycleCallback : NodeLifecycleCallback {
fun onFlowCreated(identifier: String, client: MatrixClient) = Unit
fun onFlowCreated(identifier: String, client: MatrixClient)
fun onFlowReleased(identifier: String, client: MatrixClient) = Unit
fun onFlowReleased(identifier: String, client: MatrixClient)
}
data class Inputs(
@ -123,7 +125,6 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -123,7 +125,6 @@ class LoggedInFlowNode @AssistedInject constructor(
override fun onBuilt() {
super.onBuilt()
lifecycle.subscribe(
onCreate = {
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.matrixClient) }
@ -138,14 +139,12 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -138,14 +139,12 @@ class LoggedInFlowNode @AssistedInject constructor(
backstack.push(NavTarget.Ftue)
}
},
onResume = {
lifecycleScope.launch {
syncService.startSync()
onStop = {
//Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
coroutineScope.launch {
syncService.stopSync()
}
},
onPause = {
syncService.stopSync()
},
onDestroy = {
plugins<LifecycleCallback>().forEach { it.onFlowReleased(id, inputs.matrixClient) }
appNavigationStateService.onLeavingSpace(id)
@ -153,22 +152,23 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -153,22 +152,23 @@ class LoggedInFlowNode @AssistedInject constructor(
loggedInFlowProcessor.stopObserving()
}
)
observeSyncStateAndNetworkStatus()
}
@OptIn(FlowPreview::class)
private fun observeSyncStateAndNetworkStatus() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
combine(
syncService.syncState,
// small debounce to avoid spamming startSync when the state is changing quickly in case of error.
syncService.syncState.debounce(100),
networkMonitor.connectivity
) { syncState, networkStatus ->
syncState == SyncState.Error && networkStatus == NetworkStatus.Online
Pair(syncState, networkStatus)
}
.distinctUntilChanged()
.collect { restartSync ->
if (restartSync) {
.collect { (syncState, networkStatus) ->
Timber.d("Sync state: $syncState, network status: $networkStatus")
if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) {
syncService.startSync()
}
}
@ -305,7 +305,8 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -305,7 +305,8 @@ class LoggedInFlowNode @AssistedInject constructor(
override fun onFtueFlowFinished() {
backstack.pop()
}
}).build()
})
.build()
}
}
}
@ -350,3 +351,4 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -350,3 +351,4 @@ class LoggedInFlowNode @AssistedInject constructor(
backstack.push(NavTarget.InviteList)
}
}

36
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt

@ -21,16 +21,27 @@ import android.os.Build @@ -21,16 +21,27 @@ import android.os.Build
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
import io.element.android.libraries.push.api.PushService
import kotlinx.coroutines.delay
import javax.inject.Inject
private const val DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS = 1500L
class LoggedInPresenter @Inject constructor(
private val matrixClient: MatrixClient,
private val permissionsPresenterFactory: PermissionsPresenter.Factory,
private val networkMonitor: NetworkMonitor,
private val pushService: PushService,
) : Presenter<LoggedInState> {
@ -53,18 +64,25 @@ class LoggedInPresenter @Inject constructor( @@ -53,18 +64,25 @@ class LoggedInPresenter @Inject constructor(
pushService.registerWith(matrixClient, pushProvider, distributor)
}
val syncState = matrixClient.syncService().syncState.collectAsState()
val roomListState by matrixClient.roomListService.state.collectAsState()
val networkStatus by networkMonitor.connectivity.collectAsState()
val permissionsState = postNotificationPermissionsPresenter.present()
// fun handleEvents(event: LoggedInEvents) {
// when (event) {
// }
// }
var showSyncSpinner by remember {
mutableStateOf(false)
}
LaunchedEffect(roomListState, networkStatus) {
showSyncSpinner = when {
networkStatus == NetworkStatus.Offline -> false
roomListState == RoomListService.State.Running -> false
else -> {
delay(DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS)
true
}
}
}
return LoggedInState(
syncState = syncState.value,
showSyncSpinner = showSyncSpinner,
permissionsState = permissionsState,
// eventSink = ::handleEvents
)
}
}

4
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt

@ -16,11 +16,9 @@ @@ -16,11 +16,9 @@
package io.element.android.appnav.loggedin
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.permissions.api.PermissionsState
data class LoggedInState(
val syncState: SyncState,
val showSyncSpinner: Boolean,
val permissionsState: PermissionsState,
// val eventSink: (LoggedInEvents) -> Unit
)

10
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt

@ -17,22 +17,20 @@ @@ -17,22 +17,20 @@
package io.element.android.appnav.loggedin
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
override val values: Sequence<LoggedInState>
get() = sequenceOf(
aLoggedInState(),
aLoggedInState(syncState = SyncState.Idle),
aLoggedInState(false),
aLoggedInState(true),
// Add other state here
)
}
fun aLoggedInState(
syncState: SyncState = SyncState.Running,
showSyncSpinner: Boolean = true,
) = LoggedInState(
syncState = syncState,
showSyncSpinner = showSyncSpinner,
permissionsState = createDummyPostNotificationPermissionsState(),
// eventSink = {}
)

4
appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt

@ -47,7 +47,7 @@ fun LoggedInView( @@ -47,7 +47,7 @@ fun LoggedInView(
modifier = Modifier
.padding(top = 8.dp)
.align(Alignment.TopCenter),
syncState = state.syncState,
isVisible = state.showSyncSpinner,
)
PermissionsView(
state = state.permissionsState,
@ -58,7 +58,7 @@ fun LoggedInView( @@ -58,7 +58,7 @@ fun LoggedInView(
@DayNightPreviews
@Composable
fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview {
internal fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview {
LoggedInView(
state = state
)

24
appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt

@ -38,19 +38,18 @@ import io.element.android.libraries.designsystem.preview.ElementPreview @@ -38,19 +38,18 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun SyncStateView(
syncState: SyncState,
isVisible: Boolean,
modifier: Modifier = Modifier
) {
val animationSpec = spring<Float>(stiffness = 500F)
AnimatedVisibility(
modifier = modifier,
visible = syncState.mustBeVisible(),
visible = isVisible,
enter = fadeIn(animationSpec = animationSpec),
exit = fadeOut(animationSpec = animationSpec),
) {
@ -60,15 +59,15 @@ fun SyncStateView( @@ -60,15 +59,15 @@ fun SyncStateView(
) {
Row(
modifier = Modifier
.background(color = ElementTheme.colors.bgSubtleSecondary)
.padding(horizontal = 24.dp, vertical = 10.dp),
.background(color = ElementTheme.colors.bgSubtleSecondary)
.padding(horizontal = 24.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
CircularProgressIndicator(
modifier = Modifier
.progressSemantics()
.size(12.dp),
.progressSemantics()
.size(12.dp),
color = ElementTheme.colors.textPrimary,
strokeWidth = 1.5.dp,
)
@ -82,20 +81,13 @@ fun SyncStateView( @@ -82,20 +81,13 @@ fun SyncStateView(
}
}
private fun SyncState.mustBeVisible() = when (this) {
SyncState.Idle -> true /* Cold start of the app */
SyncState.Running -> false
SyncState.Error -> false /* In this case, the network error banner can be displayed */
SyncState.Terminated -> true /* The app is resumed and the sync is started again */
}
@DayNightPreviews
@Composable
fun SyncStateViewPreview() = ElementPreview {
internal fun SyncStateViewPreview() = ElementPreview {
// Add a box to see the shadow
Box(modifier = Modifier.padding(24.dp)) {
SyncStateView(
syncState = SyncState.Idle
isVisible = true
)
}
}

28
appnav/src/main/kotlin/io/element/android/appnav/room/LoadingRoomNodeView.kt

@ -16,19 +16,13 @@ @@ -16,19 +16,13 @@
package io.element.android.appnav.room
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -38,7 +32,7 @@ import androidx.compose.ui.tooling.preview.Preview @@ -38,7 +32,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -47,7 +41,6 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre @@ -47,7 +41,6 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.theme.placeholderBackground
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@ -102,20 +95,7 @@ private fun LoadingRoomTopBar( @@ -102,20 +95,7 @@ private fun LoadingRoomTopBar(
BackButton(onClick = onBackClicked)
},
title = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(AvatarSize.TimelineRoom.dp)
.align(Alignment.CenterVertically)
.background(color = ElementTheme.colors.placeholderBackground, shape = CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
PlaceholderAtom(width = 20.dp, height = 7.dp)
Spacer(modifier = Modifier.width(7.dp))
PlaceholderAtom(width = 45.dp, height = 7.dp)
}
IconTitlePlaceholdersRowMolecule(iconSize = AvatarSize.TimelineRoom.dp)
},
windowInsets = WindowInsets(0.dp),
)
@ -123,12 +103,12 @@ private fun LoadingRoomTopBar( @@ -123,12 +103,12 @@ private fun LoadingRoomTopBar(
@Preview
@Composable
fun LoadingRoomNodeViewLightPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) =
internal fun LoadingRoomNodeViewLightPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun LoadingRoomNodeViewDarkPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) =
internal fun LoadingRoomNodeViewDarkPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

3
appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt

@ -96,7 +96,8 @@ class RoomFlowNode @AssistedInject constructor( @@ -96,7 +96,8 @@ class RoomFlowNode @AssistedInject constructor(
} else {
backstack.newRoot(NavTarget.Loading)
}
}.launchIn(lifecycleScope)
}
.launchIn(lifecycleScope)
}
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {

7
appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt

@ -75,8 +75,8 @@ class RoomLoadedFlowNode @AssistedInject constructor( @@ -75,8 +75,8 @@ class RoomLoadedFlowNode @AssistedInject constructor(
}
interface LifecycleCallback : NodeLifecycleCallback {
fun onFlowCreated(identifier: String, room: MatrixRoom) = Unit
fun onFlowReleased(identifier: String, room: MatrixRoom) = Unit
fun onFlowCreated(identifier: String, room: MatrixRoom)
fun onFlowReleased(identifier: String, room: MatrixRoom)
}
data class Inputs(
@ -115,7 +115,8 @@ class RoomLoadedFlowNode @AssistedInject constructor( @@ -115,7 +115,8 @@ class RoomLoadedFlowNode @AssistedInject constructor(
room.updateMembers()
.onFailure {
Timber.e(it, "Fail to fetch members for room ${room.roomId}")
}.onSuccess {
}
.onSuccess {
Timber.v("Success fetching members for room ${room.roomId}")
}
}

28
appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt

@ -20,13 +20,18 @@ import app.cash.molecule.RecompositionMode @@ -20,13 +20,18 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -42,14 +47,33 @@ class LoggedInPresenterTest { @@ -42,14 +47,33 @@ class LoggedInPresenterTest {
}
}
private fun createPresenter(): LoggedInPresenter {
@Test
fun `present - show sync spinner`() = runTest {
val roomListService = FakeRoomListService()
val presenter = createPresenter(roomListService, NetworkStatus.Online)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.showSyncSpinner).isFalse()
consumeItemsUntilPredicate { it.showSyncSpinner }
roomListService.postState(RoomListService.State.Running)
consumeItemsUntilPredicate { !it.showSyncSpinner }
}
}
private fun createPresenter(
roomListService: RoomListService = FakeRoomListService(),
networkStatus: NetworkStatus = NetworkStatus.Offline
): LoggedInPresenter {
return LoggedInPresenter(
matrixClient = FakeMatrixClient(),
matrixClient = FakeMatrixClient(roomListService = roomListService),
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permission: String): PermissionsPresenter {
return NoopPermissionsPresenter()
}
},
networkMonitor = FakeNetworkMonitor(networkStatus),
pushService = object : PushService {
override fun notificationStyleChanged() {
}

16
appnav/src/test/kotlin/io/element/android/appnav/room/LoadingRoomStateFlowFactoryTest.kt

@ -18,12 +18,12 @@ package io.element.android.appnav.room @@ -18,12 +18,12 @@ package io.element.android.appnav.room
import app.cash.turbine.test
import com.google.common.truth.Truth
import io.element.android.libraries.matrix.api.room.RoomSummaryDataSource
import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import kotlinx.coroutines.test.runTest
import org.junit.Test
@ -47,29 +47,29 @@ class LoadingRoomStateFlowFactoryTest { @@ -47,29 +47,29 @@ class LoadingRoomStateFlowFactoryTest {
@Test
fun `flow should emit Loading and then Loaded when there is a room in cache after SS is loaded`() = runTest {
val room = FakeMatrixRoom(sessionId= A_SESSION_ID, roomId = A_ROOM_ID)
val roomSummaryDataSource = FakeRoomSummaryDataSource()
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomSummaryDataSource = roomSummaryDataSource)
val roomListService = FakeRoomListService()
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
flowFactory
.create(this, A_ROOM_ID)
.test {
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
matrixClient.givenGetRoomResult(A_ROOM_ID, room)
roomSummaryDataSource.postLoadingState(RoomSummaryDataSource.LoadingState.Loaded(1))
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loaded(room))
}
}
@Test
fun `flow should emit Loading and then Error when there is no room in cache after SS is loaded`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource()
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomSummaryDataSource = roomSummaryDataSource)
val roomListService = FakeRoomListService()
val matrixClient = FakeMatrixClient(A_SESSION_ID, roomListService = roomListService)
val flowFactory = LoadingRoomStateFlowFactory(matrixClient)
flowFactory
.create(this, A_ROOM_ID)
.test {
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Loading)
roomSummaryDataSource.postLoadingState(RoomSummaryDataSource.LoadingState.Loaded(1))
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
Truth.assertThat(awaitItem()).isEqualTo(LoadingRoomState.Error)
}
}

24
build.gradle.kts

@ -1,9 +1,11 @@ @@ -1,9 +1,11 @@
import com.google.devtools.ksp.gradle.KspTask
import kotlinx.kover.api.KoverTaskExtension
import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp
import org.jetbrains.kotlin.cli.common.toBooleanLenient
buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.22")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0")
classpath("com.google.gms:google-services:4.3.15")
}
}
@ -56,7 +58,7 @@ allprojects { @@ -56,7 +58,7 @@ allprojects {
// activate all available (even unstable) rules.
allRules = true
// point to your custom config defining rules to run, overwriting default behavior
config = files("$rootDir/tools/detekt/detekt.yml")
config.from(files("$rootDir/tools/detekt/detekt.yml"))
}
dependencies {
detektPlugins("io.nlopez.compose.rules:detekt:0.1.12")
@ -343,3 +345,21 @@ subprojects { @@ -343,3 +345,21 @@ subprojects {
tasks.findByName("recordPaparazziDebug")?.dependsOn(removeOldScreenshotsTask)
tasks.findByName("recordPaparazziRelease")?.dependsOn(removeOldScreenshotsTask)
}
// Workaround for https://github.com/airbnb/Showkase/issues/335
subprojects {
tasks.withType<KspTask>() {
doLast {
fileTree(buildDir).apply { include("**/*ShowkaseExtension*.kt") }.files.forEach { file ->
ReplaceRegExp().apply {
setMatch("public fun Showkase.getMetadata")
setReplace("@Suppress(\"DEPRECATION\") public fun Showkase.getMetadata")
setFlags("g")
setByLine(true)
setFile(file)
execute()
}
}
}
}
}

1
changelog.d/1064.wip

@ -0,0 +1 @@ @@ -0,0 +1 @@
[Poll] Add feature flag in developer options

1
changelog.d/769.feature

@ -0,0 +1 @@ @@ -0,0 +1 @@
Allow cancelling media upload

7
docs/_developer_onboarding.md

@ -145,7 +145,7 @@ Then you can launch the build script from the matrix-rust-components-kotlin repo @@ -145,7 +145,7 @@ Then you can launch the build script from the matrix-rust-components-kotlin repo
- `-m` Option to select the gradle module to build. Default is sdk.
- `-t` Option to to select an android target to build against. Default will build for all targets.
So for example to build the sdk against aarch64-linux-android target and copy the generated aar to ElementX project:
So for example to build the sdk against aarch64-linux-android target and copy the generated aar to Element X project:
```shell
./scripts/build.sh -p [YOUR MATRIX RUST SDK PATH] -t aarch64-linux-android -o [YOUR element-x-android PATH]/libraries/rustsdk/matrix-rust-sdk.aar
@ -313,7 +313,7 @@ suffix `Presenter`,states MUST have a suffix `State`, etc. Also we want to have @@ -313,7 +313,7 @@ suffix `Presenter`,states MUST have a suffix `State`, etc. Also we want to have
### Push
**Note** Firebase Push is not yet implemented on the project.
**Note** Firebase is implemented, but Unified Push is not yet fully implemented on the project, so this is not possible to choose this push provider in the app at the moment.
Please see the dedicated [documentation](notifications.md) for more details.
@ -342,8 +342,7 @@ We have 3 tests frameworks in place, and this should be sufficient to guarantee @@ -342,8 +342,7 @@ We have 3 tests frameworks in place, and this should be sufficient to guarantee
file [TemplateView.kt](../features/template/src/main/kotlin/io/element/android/features/template/TemplateView.kt). We create PreviewProvider to provide
different states. See for instance the
file [TemplateStateProvider.kt](../features/template/src/main/kotlin/io/element/android/features/template/TemplateStateProvider.kt)
- Tests on presenter with [Molecule](https://github.com/cashapp/molecule) and [Turbine](https://github.com/cashapp/turbine). See in the template the
class [TemplatePresenterTests](../features/template/src/test/kotlin/io/element/android/features/template/TemplatePresenterTests.kt).
- Tests on presenter with [Molecule](https://github.com/cashapp/molecule) and [Turbine](https://github.com/cashapp/turbine). See in the template the class [TemplatePresenterTests](../features/template/src/test/kotlin/io/element/android/features/template/TemplatePresenterTests.kt).
**Note** For now we want to avoid using class mocking (with library such as *mockk*), because this should be not necessary. We prefer to create Fake
implementation of our interfaces. Mocking can be used to mock Android framework classes though, such as `Bitmap` for instance.

BIN
docs/images-lfs/screen_1_dark.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_1_light.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_2_dark.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_2_light.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_3_dark.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_3_light.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_4_dark.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images-lfs/screen_4_light.png (Stored with Git LFS)

Binary file not shown.

BIN
docs/images/screen1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

BIN
docs/images/screen2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/images/screen3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 KiB

BIN
docs/images/screen4.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

4
docs/nightly_build.md

@ -10,11 +10,11 @@ @@ -10,11 +10,11 @@
## Configuration
The nightly build will contain what's on develop, in release mode, for the main variant. It is signed using a dedicated signature, and has a dedicated appId (`io.element.android.x.nightly`), so it can be installed along with the production version of ElementX Android. The only other difference compared to ElementX Android is a different app name. We do not want to change the app name since it will also affect some strings in the app, and we do want to do that. (TODO today, the app name is changed.)
The nightly build will contain what's on develop, in release mode, for the main variant. It is signed using a dedicated signature, and has a dedicated appId (`io.element.android.x.nightly`), so it can be installed along with the production version of Element X Android. The only other difference compared to ElementX Android is a different app name. We do not want to change the app name since it will also affect some strings in the app, and we do want to do that. (TODO today, the app name is changed.)
Nightly builds are built and released to Firebase every days, and automatically.
This is recommended to exclusively use this app, with your main account, instead of ElementX Android, and fallback to ElementX Android just in case of regression, to discover as soon as possible any regression, and report it to the team. To avoid double notification, you may want to disable the notification from the Element Android production version. Just open Element Android, navigate to `Settings/Notifications` and uncheck `Enable notifications for this session` (TODO Not supported yet).
This is recommended to exclusively use this app, with your main account, instead of Element X Android, and fallback to ElementX Android just in case of regression, to discover as soon as possible any regression, and report it to the team. To avoid double notification, you may want to disable the notification from the Element Android production version. Just open Element Android, navigate to `Settings/Notifications` and uncheck `Enable notifications for this session` (TODO Not supported yet).
*Note:* Due to a limitation of Firebase, the nightly build is the universal build, which means that the size of the APK is a bit bigger, but this should not have any other side effect.

10
docs/screenshot_testing.md

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
## Overview
- Screenshot tests are tests which record the content of a rendered screen and verify subsequent runs to check if the screen renders differently.
- ElementX uses [Paparazzi](https://github.com/cashapp/paparazzi) to render, record and verify Composable. All Composable Preview will be use to make screenshot test, thanks to the usage of [Showkase](https://github.com/airbnb/Showkase).
- Element X uses [Paparazzi](https://github.com/cashapp/paparazzi) to render, record and verify Composable. All Composable Preview will be use to make screenshot test, thanks to the usage of [Showkase](https://github.com/airbnb/Showkase).
- The screenshot verification occurs on every pull request as part of the `tests.yml` workflow.
## Setup
@ -30,6 +30,14 @@ If installed correctly, `git push` and `git pull` will now include LFS content. @@ -30,6 +30,14 @@ If installed correctly, `git push` and `git pull` will now include LFS content.
## Recording
Recording of screenshots is done by triggering the GitHub action [Record screenshots](https://github.com/vector-im/element-x-android/actions/workflows/recordScreenshots.yml), to avoid differences of generated binary files (png images) depending on developers' environment.
So basically, you will create a branch, do some commits with your work on it, then push your branch, trigger the GitHub action to record the screenshots (only if you think preview may have changed), and finally create a pull request. The GitHub action will record the screenshots and commit the changes to the branch.
You can still record the screenshots locally, but please do not commit the changes.
To record the screenshot locally, run the following command:
```shell
./gradlew recordPaparazziDebug
```

2
fastlane/metadata/android/en-US/changelogs/40001020.txt

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
First release of Element X 🚀!
Full changelog: https://github.com/vector-im/element-x-android/releases

4
features/analytics/api/src/main/kotlin/io/element/android/features/analytics/api/preferences/AnalyticsPreferencesView.kt

@ -81,12 +81,12 @@ fun buildAnnotatedStringWithColoredPart( @@ -81,12 +81,12 @@ fun buildAnnotatedStringWithColoredPart(
@Preview
@Composable
fun AnalyticsPreferencesViewLightPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) =
internal fun AnalyticsPreferencesViewLightPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun AnalyticsPreferencesViewDarkPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) =
internal fun AnalyticsPreferencesViewDarkPreview(@PreviewParameter(AnalyticsPreferencesStateProvider::class) state: AnalyticsPreferencesState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

16
features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInView.kt

@ -54,6 +54,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark @@ -54,6 +54,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
@ -188,29 +189,28 @@ private fun AnalyticsOptInFooter( @@ -188,29 +189,28 @@ private fun AnalyticsOptInFooter(
modifier = modifier,
) {
Button(
text = stringResource(id = CommonStrings.action_ok),
onClick = onTermsAccepted,
modifier = Modifier.fillMaxWidth(),
) {
Text(text = stringResource(id = CommonStrings.action_ok))
}
)
TextButton(
text = stringResource(id = CommonStrings.action_not_now),
size = ButtonSize.Medium,
onClick = onTermsDeclined,
modifier = Modifier.fillMaxWidth(),
) {
Text(text = stringResource(id = CommonStrings.action_not_now))
}
)
}
}
@Preview
@Composable
fun AnalyticsOptInViewLightPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewLight {
internal fun AnalyticsOptInViewLightPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewLight {
ContentToPreview(state)
}
@Preview
@Composable
fun AnalyticsOptInViewDarkPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewDark {
internal fun AnalyticsOptInViewDarkPreview(@PreviewParameter(AnalyticsOptInStateProvider::class) state: AnalyticsOptInState) = ElementPreviewDark {
ContentToPreview(state)
}

8
features/analytics/impl/src/main/res/values-de/translations.xml

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"Wir erfassen und analysieren "<b>"keine"</b>" Account-Daten"</string>
<string name="screen_analytics_prompt_data_usage">"Wir werden keine personenbezogenen Daten aufzeichnen oder auswerten"</string>
<string name="screen_analytics_prompt_help_us_improve">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
<string name="screen_analytics_prompt_read_terms">"Sie können alle unsere Nutzerbedingungen %1$s lesen."</string>
<string name="screen_analytics_prompt_read_terms">"Du kannst alle unsere Nutzerbedingungen %1$s lesen."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
<string name="screen_analytics_prompt_settings">"Sie können die Analyse jederzeit in den Einstellungen deaktivieren"</string>
<string name="screen_analytics_prompt_settings">"Du kannst dies jederzeit deaktivieren"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben "<b>"keine"</b>" Informationen an Dritte weiter"</string>
<string name="screen_analytics_prompt_title">"Helfen Sie %1$s zu verbessern"</string>
<string name="screen_analytics_prompt_title">"Hilf uns, %1$s zu verbessern"</string>
</resources>

10
features/analytics/impl/src/main/res/values-ru/translations.xml

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"Мы не будем записывать или профилировать какие-либо персональные данные"</string>
<string name="screen_analytics_prompt_help_us_improve">"Предоставлять анонимные данные об использовании, чтобы помочь нам выявить проблемы."</string>
<string name="screen_analytics_prompt_read_terms">"Вы можете ознакомиться со всеми нашими условиями %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"здесь"</string>
<string name="screen_analytics_prompt_settings">"Вы можете отключить эту функцию в любое время"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Мы не будем передавать ваши данные третьим лицам"</string>
<string name="screen_analytics_prompt_title">"Помогите улучшить %1$s"</string>
</resources>

4
features/analytics/impl/src/main/res/values-zh-rTW/translations.xml

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
</resources>

13
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt

@ -28,7 +28,6 @@ import androidx.compose.ui.Modifier @@ -28,7 +28,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.createroom.impl.R
import io.element.android.features.createroom.impl.components.UserListView
import io.element.android.features.createroom.impl.userlist.UserListEvents
@ -36,7 +35,6 @@ import io.element.android.features.createroom.impl.userlist.UserListState @@ -36,7 +35,6 @@ import io.element.android.features.createroom.impl.userlist.UserListState
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.aliasButtonText
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
@ -103,16 +101,11 @@ fun AddPeopleViewTopBar( @@ -103,16 +101,11 @@ fun AddPeopleViewTopBar(
},
navigationIcon = { BackButton(onClick = onBackPressed) },
actions = {
val textActionResId = if (hasSelectedUsers) CommonStrings.action_next else CommonStrings.action_skip
TextButton(
modifier = Modifier.padding(horizontal = 8.dp),
text = stringResource(id = textActionResId),
onClick = onNextPressed,
) {
val textActionResId = if (hasSelectedUsers) CommonStrings.action_next else CommonStrings.action_skip
Text(
text = stringResource(id = textActionResId),
style = ElementTheme.typography.aliasButtonText,
)
}
)
}
)
}

4
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/RoomPrivacyOption.kt

@ -93,11 +93,11 @@ fun RoomPrivacyOption( @@ -93,11 +93,11 @@ fun RoomPrivacyOption(
@Preview
@Composable
fun RoomPrivacyOptionLightPreview() = ElementPreviewLight { ContentToPreview() }
internal fun RoomPrivacyOptionLightPreview() = ElementPreviewLight { ContentToPreview() }
@Preview
@Composable
fun RoomPrivacyOptionDarkPreview() = ElementPreviewDark { ContentToPreview() }
internal fun RoomPrivacyOptionDarkPreview() = ElementPreviewDark { ContentToPreview() }
@Composable
private fun ContentToPreview() {

8
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchMultipleUsersResultItem.kt

@ -22,7 +22,7 @@ import androidx.compose.ui.Modifier @@ -22,7 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.matrix.ui.components.CheckableMatrixUserRow
import io.element.android.libraries.matrix.ui.components.CheckableUnresolvedUserRow
import io.element.android.libraries.matrix.ui.components.aMatrixUser
@ -63,11 +63,11 @@ internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview { Con @@ -63,11 +63,11 @@ internal fun SearchMultipleUsersResultItemPreview() = ElementThemedPreview { Con
private fun ContentToPreview() {
Column {
SearchMultipleUsersResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = false), isUserSelected = false)
Divider()
HorizontalDivider()
SearchMultipleUsersResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = false), isUserSelected = true)
Divider()
HorizontalDivider()
SearchMultipleUsersResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = true), isUserSelected = false)
Divider()
HorizontalDivider()
SearchMultipleUsersResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = true), isUserSelected = true)
}
}

4
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchSingleUserResultItem.kt

@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier @@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
import io.element.android.libraries.matrix.ui.components.UnresolvedUserRow
import io.element.android.libraries.matrix.ui.components.aMatrixUser
@ -59,7 +59,7 @@ internal fun SearchSingleUserResultItemPreview() = ElementThemedPreview { Conten @@ -59,7 +59,7 @@ internal fun SearchSingleUserResultItemPreview() = ElementThemedPreview { Conten
private fun ContentToPreview() {
Column {
SearchSingleUserResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = false))
Divider()
HorizontalDivider()
SearchSingleUserResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = true))
}
}

6
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt

@ -35,7 +35,7 @@ import androidx.compose.runtime.remember @@ -35,7 +35,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.SearchBar
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
import io.element.android.libraries.matrix.api.user.MatrixUser
@ -117,7 +117,7 @@ fun SearchUserBar( @@ -117,7 +117,7 @@ fun SearchUserBar(
}
)
if (index < users.lastIndex) {
Divider()
HorizontalDivider()
}
}
} else {
@ -128,7 +128,7 @@ fun SearchUserBar( @@ -128,7 +128,7 @@ fun SearchUserBar(
onClick = { onUserSelected(searchResult.matrixUser) }
)
if (index < users.lastIndex) {
Divider()
HorizontalDivider()
}
}
}

19
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt

@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.imePadding @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue
@ -43,6 +44,7 @@ import androidx.compose.ui.focus.FocusManager @@ -43,6 +44,7 @@ import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@ -55,7 +57,6 @@ import io.element.android.libraries.designsystem.components.button.BackButton @@ -55,7 +57,6 @@ import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.aliasButtonText
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
@ -193,15 +194,10 @@ fun ConfigureRoomToolbar( @@ -193,15 +194,10 @@ fun ConfigureRoomToolbar(
navigationIcon = { BackButton(onClick = onBackPressed) },
actions = {
TextButton(
modifier = Modifier.padding(horizontal = 8.dp),
text = stringResource(CommonStrings.action_create),
enabled = isNextActionEnabled,
onClick = onNextPressed,
) {
Text(
text = stringResource(CommonStrings.action_create),
style = ElementTheme.typography.aliasButtonText,
)
}
)
}
)
}
@ -247,6 +243,9 @@ fun RoomTopic( @@ -247,6 +243,9 @@ fun RoomTopic(
placeholder = stringResource(CommonStrings.common_topic_placeholder),
onValueChange = onTopicChanged,
maxLines = 3,
keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.Sentences,
),
)
}
@ -277,12 +276,12 @@ private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier = @@ -277,12 +276,12 @@ private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
@Preview
@Composable
fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
internal fun ConfigureRoomViewLightPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun ConfigureRoomViewDarkPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
internal fun ConfigureRoomViewDarkPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

2
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt

@ -41,7 +41,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot @@ -41,7 +41,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider<CreateRoomRoot
}
),
aCreateRoomRootState().copy(
startDmAction = Async.Failure(Throwable()),
startDmAction = Async.Failure(Throwable("error")),
userListState = aMatrixUser().let {
aUserListState().copy(
searchQuery = it.userId.value,

10
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt

@ -42,6 +42,7 @@ import androidx.compose.ui.unit.dp @@ -42,6 +42,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.features.createroom.impl.R
import io.element.android.features.createroom.impl.components.UserListView
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.VectorIcons
import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -55,7 +56,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar @@ -55,7 +56,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.designsystem.R as DrawableR
@OptIn(ExperimentalLayoutApi::class)
@Composable
@ -162,12 +162,12 @@ fun CreateRoomActionButtonsList( @@ -162,12 +162,12 @@ fun CreateRoomActionButtonsList(
) {
Column(modifier = modifier) {
CreateRoomActionButton(
iconRes = DrawableR.drawable.ic_groups,
iconRes = VectorIcons.Groups,
text = stringResource(id = R.string.screen_create_room_action_create_room),
onClick = onNewRoomClicked,
)
CreateRoomActionButton(
iconRes = DrawableR.drawable.ic_share,
iconRes = VectorIcons.Share,
text = stringResource(id = CommonStrings.action_invite_friends_to_app, state.applicationName),
onClick = onInvitePeopleClicked,
)
@ -205,12 +205,12 @@ fun CreateRoomActionButton( @@ -205,12 +205,12 @@ fun CreateRoomActionButton(
@Preview
@Composable
fun CreateRoomRootViewLightPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
internal fun CreateRoomRootViewLightPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun CreateRoomRootViewDarkPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
internal fun CreateRoomRootViewDarkPreview(@PreviewParameter(CreateRoomRootStateProvider::class) state: CreateRoomRootState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

2
features/createroom/impl/src/main/res/values-fr/translations.xml

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
<string name="screen_create_room_action_invite_people">"Inviter des amis sur Element"</string>
<string name="screen_create_room_add_people_title">"Inviter des personnes"</string>
<string name="screen_create_room_error_creating_room">"Une erreur s\'est produite lors de la création du salon"</string>
<string name="screen_create_room_private_option_description">"Les messages dans ce salon sont chiffrés. Une fopis activé, le chiffrement ne peut pas être désactivé."</string>
<string name="screen_create_room_private_option_description">"Les messages dans ce salon sont chiffrés. Une fois activé, le chiffrement ne peut pas être désactivé."</string>
<string name="screen_create_room_private_option_title">"Salon privé (sur invitation uniquement)"</string>
<string name="screen_create_room_public_option_description">"Les messages ne sont pas chiffrés et n\'importe qui peut les lire. Vous pouvez activer le chiffrement ultérieurement."</string>
<string name="screen_create_room_public_option_title">"Salon public (n’importe qui)"</string>

15
features/createroom/impl/src/main/res/values-ru/translations.xml

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Новая комната"</string>
<string name="screen_create_room_action_invite_people">"Пригласите друзей в Element"</string>
<string name="screen_create_room_add_people_title">"Пригласить людей"</string>
<string name="screen_create_room_error_creating_room">"Произошла ошибка при создании комнаты"</string>
<string name="screen_create_room_private_option_description">"Сообщения в этой комнате зашифрованы. Отключить шифрование впоследствии невозможно."</string>
<string name="screen_create_room_private_option_title">"Приватная комната (только по приглашению)"</string>
<string name="screen_create_room_public_option_description">"Сообщения не зашифрованы, и каждый может их прочитать. Вы можете включить шифрование позже."</string>
<string name="screen_create_room_public_option_title">"Публичная комната (любой)"</string>
<string name="screen_create_room_room_name_label">"Название комнаты"</string>
<string name="screen_create_room_topic_label">"Тема (необязательно)"</string>
<string name="screen_start_chat_error_starting_chat">"Произошла ошибка при попытке открытия комнаты"</string>
<string name="screen_create_room_title">"Создать комнату"</string>
</resources>

7
features/createroom/impl/src/main/res/values-zh-rTW/translations.xml

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_invite_people">"邀請朋友使用 Element"</string>
<string name="screen_create_room_room_name_label">"聊天室名稱"</string>
<string name="screen_create_room_topic_label">"主題(非必填)"</string>
<string name="screen_create_room_title">"建立聊天室"</string>
</resources>

8
features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeView.kt

@ -97,9 +97,11 @@ fun WelcomeView( @@ -97,9 +97,11 @@ fun WelcomeView(
}
},
footer = {
Button(modifier = Modifier.fillMaxWidth(), onClick = onContinueClicked) {
Text(text = stringResource(CommonStrings.action_continue))
}
Button(
text = stringResource(CommonStrings.action_continue),
modifier = Modifier.fillMaxWidth(),
onClick = onContinueClicked
)
Spacer(modifier = Modifier.height(32.dp))
}
)

9
features/ftue/impl/src/main/res/values-ru/translations.xml

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_welcome_bullet_1">"Звонки, опросы, поиск и многое другое будут добавлены позже в этом году."</string>
<string name="screen_welcome_bullet_2">"История сообщений для зашифрованных комнат в этом обновлении будет недоступна."</string>
<string name="screen_welcome_bullet_3">"Мы будем рады услышать ваше мнение, сообщите нам об этом через страницу настроек."</string>
<string name="screen_welcome_button">"Поехали!"</string>
<string name="screen_welcome_subtitle">"Вот что вам необходимо знать:"</string>
<string name="screen_welcome_title">"Добро пожаловать в %1$s!"</string>
</resources>

2
features/ftue/impl/src/main/res/values-sk/translations.xml

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_welcome_bullet_1">"Hovory, zdieľanie polohy, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku."</string>
<string name="screen_welcome_bullet_1">"Hovory, ankety, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku."</string>
<string name="screen_welcome_bullet_2">"História správ pre zašifrované miestnosti nebude v tejto aktualizácii k dispozícii."</string>
<string name="screen_welcome_bullet_3">"Radi by sme od vás počuli, dajte nám vedieť, čo si myslíte, prostredníctvom stránky nastavení."</string>
<string name="screen_welcome_button">"Poďme na to!"</string>

4
features/ftue/impl/src/main/res/values-zh-rTW/translations.xml

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_welcome_button">"開始吧!"</string>
</resources>

10
features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt

@ -36,7 +36,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -36,7 +36,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.api.extensions.toAnalyticsJoinedRoom
@ -56,8 +56,9 @@ class InviteListPresenter @Inject constructor( @@ -56,8 +56,9 @@ class InviteListPresenter @Inject constructor(
@Composable
override fun present(): InviteListState {
val invites by client
.roomSummaryDataSource
.inviteRooms()
.roomListService
.invites()
.summaries
.collectAsState()
var seenInvites by remember { mutableStateOf<Set<RoomId>>(emptySet()) }
@ -152,8 +153,7 @@ class InviteListPresenter @Inject constructor( @@ -152,8 +153,7 @@ class InviteListPresenter @Inject constructor(
client.getRoom(roomId)?.use {
it.leave().getOrThrow()
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId)
}
Unit
}.let { }
}.runCatchingUpdatingState(declinedAction)
}

5
features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListView.kt

@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog @@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
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
@ -86,7 +86,6 @@ fun InviteListView( @@ -86,7 +86,6 @@ fun InviteListView(
title = stringResource(titleResource),
submitText = stringResource(CommonStrings.action_decline),
cancelText = stringResource(CommonStrings.action_cancel),
emphasizeSubmitButton = true,
onSubmitClicked = { state.eventSink(InviteListEvents.ConfirmDeclineInvite) },
onDismiss = { state.eventSink(InviteListEvents.CancelDeclineInvite) }
)
@ -162,7 +161,7 @@ fun InviteListContent( @@ -162,7 +161,7 @@ fun InviteListContent(
)
if (index != state.inviteList.lastIndex) {
Divider()
HorizontalDivider()
}
}
}

19
features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/components/InviteSummaryRow.kt

@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Arrangement @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -49,8 +48,8 @@ import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAto @@ -49,8 +48,8 @@ import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAto
import io.element.android.libraries.designsystem.components.avatar.Avatar
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.aliasButtonText
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
@ -133,23 +132,19 @@ internal fun DefaultInviteSummaryRow( @@ -133,23 +132,19 @@ internal fun DefaultInviteSummaryRow(
// CTAs
Row(Modifier.padding(top = 12.dp)) {
OutlinedButton(
content = { Text(stringResource(CommonStrings.action_decline), style = ElementTheme.typography.aliasButtonText) },
text = stringResource(CommonStrings.action_decline),
onClick = onDeclineClicked,
modifier = Modifier
.weight(1f)
.heightIn(max = 36.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp),
modifier = Modifier.weight(1f),
size = ButtonSize.Medium,
)
Spacer(modifier = Modifier.width(12.dp))
Button(
content = { Text(stringResource(CommonStrings.action_accept), style = ElementTheme.typography.aliasButtonText) },
text = stringResource(CommonStrings.action_accept),
onClick = onAcceptClicked,
modifier = Modifier
.weight(1f)
.heightIn(max = 36.dp),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 0.dp),
modifier = Modifier.weight(1f),
size = ButtonSize.Medium,
)
}
}

4
features/invitelist/impl/src/main/res/values-de/translations.xml

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invites_decline_chat_message">"Möchten Sie den Beitritt zu %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du den Beitritt zu %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
<string name="screen_invites_decline_direct_chat_message">"Möchten Sie den Chat mit %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_direct_chat_message">"Möchtest du den privaten Chat mit %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_direct_chat_title">"Chat ablehnen"</string>
<string name="screen_invites_empty_list">"Keine Einladungen"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) hat dich eingeladen"</string>

9
features/invitelist/impl/src/main/res/values-ru/translations.xml

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invites_decline_chat_message">"Вы уверены, что хотите отклонить приглашение в %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Отклонить приглашение"</string>
<string name="screen_invites_decline_direct_chat_message">"Вы уверены, что хотите отказаться от приватного общения с %1$s?"</string>
<string name="screen_invites_decline_direct_chat_title">"Отклонить чат"</string>
<string name="screen_invites_empty_list">"Нет приглашений"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) пригласил вас"</string>
</resources>

76
features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt

@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.api.MatrixClient @@ -30,8 +30,8 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.room.RoomSummaryDetails
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
@ -40,7 +40,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID @@ -40,7 +40,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager
import io.element.android.services.analytics.api.AnalyticsService
@ -51,9 +51,9 @@ class InviteListPresenterTests { @@ -51,9 +51,9 @@ class InviteListPresenterTests {
@Test
fun `present - starts empty, adds invites when received`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource()
val roomListService = FakeRoomListService()
val presenter = createPresenter(
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
FakeMatrixClient(roomListService = roomListService)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -61,7 +61,7 @@ class InviteListPresenterTests { @@ -61,7 +61,7 @@ class InviteListPresenterTests {
val initialState = awaitItem()
Truth.assertThat(initialState.inviteList).isEmpty()
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary()))
roomListService.postInviteRooms(listOf(aRoomSummary()))
val withInviteState = awaitItem()
Truth.assertThat(withInviteState.inviteList.size).isEqualTo(1)
@ -72,9 +72,9 @@ class InviteListPresenterTests { @@ -72,9 +72,9 @@ class InviteListPresenterTests {
@Test
fun `present - uses user ID and avatar for direct invites`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
val roomListService = FakeRoomListService().withDirectChatInvitation()
val presenter = createPresenter(
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
FakeMatrixClient(roomListService = roomListService)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -98,9 +98,9 @@ class InviteListPresenterTests { @@ -98,9 +98,9 @@ class InviteListPresenterTests {
@Test
fun `present - includes sender details for room invites`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val presenter = createPresenter(
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
FakeMatrixClient(roomListService = roomListService)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -122,10 +122,10 @@ class InviteListPresenterTests { @@ -122,10 +122,10 @@ class InviteListPresenterTests {
@Test
fun `present - shows confirm dialog for declining direct chat invites`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withDirectChatInvitation()
val roomListService = FakeRoomListService().withDirectChatInvitation()
val presenter = InviteListPresenter(
FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
),
FakeSeenInvitesStore(),
FakeAnalyticsService(),
@ -148,9 +148,9 @@ class InviteListPresenterTests { @@ -148,9 +148,9 @@ class InviteListPresenterTests {
@Test
fun `present - shows confirm dialog for declining room invites`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val presenter = createPresenter(
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
FakeMatrixClient(roomListService = roomListService)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -169,9 +169,9 @@ class InviteListPresenterTests { @@ -169,9 +169,9 @@ class InviteListPresenterTests {
@Test
fun `present - hides confirm dialog when cancelling`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val presenter = createPresenter(
FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
FakeMatrixClient(roomListService = roomListService)
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@ -190,10 +190,10 @@ class InviteListPresenterTests { @@ -190,10 +190,10 @@ class InviteListPresenterTests {
@Test
fun `present - declines invite after confirming`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val fakeNotificationDrawerManager = FakeNotificationDrawerManager()
val client = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
)
val room = FakeMatrixRoom()
val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager)
@ -217,9 +217,9 @@ class InviteListPresenterTests { @@ -217,9 +217,9 @@ class InviteListPresenterTests {
@Test
fun `present - declines invite after confirming and sets state on error`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val client = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
)
val room = FakeMatrixRoom()
val presenter = createPresenter(client)
@ -247,9 +247,9 @@ class InviteListPresenterTests { @@ -247,9 +247,9 @@ class InviteListPresenterTests {
@Test
fun `present - dismisses declining error state`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val client = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
)
val room = FakeMatrixRoom()
val presenter = createPresenter(client)
@ -279,10 +279,10 @@ class InviteListPresenterTests { @@ -279,10 +279,10 @@ class InviteListPresenterTests {
@Test
fun `present - accepts invites and sets state on success`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val fakeNotificationDrawerManager = FakeNotificationDrawerManager()
val client = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
)
val room = FakeMatrixRoom()
val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager)
@ -303,9 +303,9 @@ class InviteListPresenterTests { @@ -303,9 +303,9 @@ class InviteListPresenterTests {
@Test
fun `present - accepts invites and sets state on error`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val client = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
)
val room = FakeMatrixRoom()
val presenter = createPresenter(client)
@ -325,9 +325,9 @@ class InviteListPresenterTests { @@ -325,9 +325,9 @@ class InviteListPresenterTests {
@Test
fun `present - dismisses accepting error state`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource().withRoomInvitation()
val roomListService = FakeRoomListService().withRoomInvitation()
val client = FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
)
val room = FakeMatrixRoom()
val presenter = createPresenter(client)
@ -352,11 +352,11 @@ class InviteListPresenterTests { @@ -352,11 +352,11 @@ class InviteListPresenterTests {
@Test
fun `present - stores seen invites when received`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource()
val roomListService = FakeRoomListService()
val store = FakeSeenInvitesStore()
val presenter = InviteListPresenter(
FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
),
store,
FakeAnalyticsService(),
@ -368,19 +368,19 @@ class InviteListPresenterTests { @@ -368,19 +368,19 @@ class InviteListPresenterTests {
awaitItem()
// When one invite is received, that ID is saved
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary()))
roomListService.postInviteRooms(listOf(aRoomSummary()))
awaitItem()
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID))
// When a second is added, both are saved
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
awaitItem()
Truth.assertThat(store.getProvidedRoomIds()).isEqualTo(setOf(A_ROOM_ID, A_ROOM_ID_2))
// When they're both dismissed, an empty set is saved
roomSummaryDataSource.postInviteRooms(listOf())
roomListService.postInviteRooms(listOf())
awaitItem()
Truth.assertThat(store.getProvidedRoomIds()).isEmpty()
@ -389,12 +389,12 @@ class InviteListPresenterTests { @@ -389,12 +389,12 @@ class InviteListPresenterTests {
@Test
fun `present - marks invite as new if they're unseen`() = runTest {
val roomSummaryDataSource = FakeRoomSummaryDataSource()
val roomListService = FakeRoomListService()
val store = FakeSeenInvitesStore()
store.publishRoomIds(setOf(A_ROOM_ID))
val presenter = InviteListPresenter(
FakeMatrixClient(
roomSummaryDataSource = roomSummaryDataSource,
roomListService = roomListService,
),
store,
FakeAnalyticsService(),
@ -405,7 +405,7 @@ class InviteListPresenterTests { @@ -405,7 +405,7 @@ class InviteListPresenterTests {
}.test {
awaitItem()
roomSummaryDataSource.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
roomListService.postInviteRooms(listOf(aRoomSummary(), aRoomSummary(A_ROOM_ID_2)))
skipItems(1)
val withInviteState = awaitItem()
@ -417,7 +417,7 @@ class InviteListPresenterTests { @@ -417,7 +417,7 @@ class InviteListPresenterTests {
}
}
private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource {
private suspend fun FakeRoomListService.withRoomInvitation(): FakeRoomListService {
postInviteRooms(
listOf(
RoomSummary.Filled(
@ -446,7 +446,7 @@ class InviteListPresenterTests { @@ -446,7 +446,7 @@ class InviteListPresenterTests {
return this
}
private suspend fun FakeRoomSummaryDataSource.withDirectChatInvitation(): FakeRoomSummaryDataSource {
private suspend fun FakeRoomListService.withDirectChatInvitation(): FakeRoomListService {
postInviteRooms(
listOf(
RoomSummary.Filled(

2
features/location/api/src/main/kotlin/io/element/android/features/location/api/StaticMapView.kt

@ -120,7 +120,7 @@ fun StaticMapView( @@ -120,7 +120,7 @@ fun StaticMapView(
@DayNightPreviews
@Composable
fun StaticMapViewPreview() = ElementPreview {
internal fun StaticMapViewPreview() = ElementPreview {
StaticMapView(
lat = 0.0,
lon = 0.0,

2
features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt

@ -79,7 +79,7 @@ internal fun StaticMapPlaceholder( @@ -79,7 +79,7 @@ internal fun StaticMapPlaceholder(
@DayNightPreviews
@Composable
fun StaticMapPlaceholderPreview(
internal fun StaticMapPlaceholderPreview(
@PreviewParameter(BooleanParameterProvider::class) values: Boolean
) = ElementPreview {
StaticMapPlaceholder(

2
features/location/impl/src/main/kotlin/io/element/android/features/location/impl/send/SendLocationView.kt

@ -224,7 +224,7 @@ fun SendLocationView( @@ -224,7 +224,7 @@ fun SendLocationView(
@DayNightPreviews
@Composable
fun SendLocationViewPreview(
internal fun SendLocationViewPreview(
@PreviewParameter(SendLocationStateProvider::class) state: SendLocationState
) = ElementPreview {
SendLocationView(

2
features/login/impl/build.gradle.kts

@ -19,7 +19,7 @@ plugins { @@ -19,7 +19,7 @@ plugins {
alias(libs.plugins.anvil)
alias(libs.plugins.ksp)
id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.8.22"
kotlin("plugin.serialization") version "1.9.0"
}
android {

8
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt

@ -38,7 +38,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom @@ -38,7 +38,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
@ -55,7 +55,7 @@ fun AccountProviderView( @@ -55,7 +55,7 @@ fun AccountProviderView(
Column(modifier = modifier
.fillMaxWidth()
.clickable { onClick() }) {
Divider()
HorizontalDivider()
Column(
modifier = Modifier
.fillMaxWidth()
@ -114,12 +114,12 @@ fun AccountProviderView( @@ -114,12 +114,12 @@ fun AccountProviderView(
@Preview
@Composable
fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
internal fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
ElementPreviewLight { ContentToPreview(item) }
@Preview
@Composable
fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
internal fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
ElementPreviewDark { ContentToPreview(item) }
@Composable

4
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerView.kt

@ -71,12 +71,12 @@ fun ChangeServerView( @@ -71,12 +71,12 @@ fun ChangeServerView(
@Preview
@Composable
fun ChangeServerViewLightPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) =
internal fun ChangeServerViewLightPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun ChangeServerViewDarkPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) =
internal fun ChangeServerViewDarkPreview(@PreviewParameter(ChangeServerStateProvider::class) state: ChangeServerState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

1
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/dialogs/SlidingSyncNotSupportedDialog.kt

@ -35,7 +35,6 @@ internal fun SlidingSyncNotSupportedDialog( @@ -35,7 +35,6 @@ internal fun SlidingSyncNotSupportedDialog(
submitText = stringResource(CommonStrings.action_learn_more),
onSubmitClicked = onLearnMoreClicked,
onCancelClicked = onDismiss,
emphasizeSubmitButton = true,
title = stringResource(CommonStrings.dialog_title_error),
content = stringResource(R.string.screen_change_server_error_no_sliding_sync_message),
)

3
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/customtab/CustomTabHandler.kt

@ -41,8 +41,7 @@ class CustomTabHandler @Inject constructor( @@ -41,8 +41,7 @@ class CustomTabHandler @Inject constructor(
if (packageName != null) {
customTabsServiceConnection = object : CustomTabsServiceConnection() {
override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) {
customTabsClient = client
.also { it.warmup(0L) }
customTabsClient = client.apply { warmup(0L) }
prefetchUrl(url)
}

4
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/oidc/webview/OidcView.kt

@ -100,12 +100,12 @@ fun OidcView( @@ -100,12 +100,12 @@ fun OidcView(
@Preview
@Composable
fun OidcViewLightPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) =
internal fun OidcViewLightPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun OidcViewDarkPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) =
internal fun OidcViewDarkPreview(@PreviewParameter(OidcStateProvider::class) state: OidcState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

4
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/changeaccountprovider/ChangeAccountProviderView.kt

@ -127,12 +127,12 @@ fun ChangeAccountProviderView( @@ -127,12 +127,12 @@ fun ChangeAccountProviderView(
@Preview
@Composable
fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) =
internal fun ChangeAccountProviderViewLightPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) =
internal fun ChangeAccountProviderViewDarkPreview(@PreviewParameter(ChangeAccountProviderStateProvider::class) state: ChangeAccountProviderState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

2
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt

@ -92,7 +92,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( @@ -92,7 +92,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
} else if (matrixHomeServerDetails.supportsPasswordLogin) {
LoginFlow.PasswordLogin
} else {
throw IllegalStateException("Unsupported login flow")
error("Unsupported login flow")
}
}.getOrThrow()
}.runCatchingUpdatingState(loginFlowAction, errorTransform = ChangeServerError::from)

14
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt

@ -36,11 +36,10 @@ import io.element.android.libraries.architecture.Async @@ -36,11 +36,10 @@ import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.button.ButtonWithProgress
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.testtags.TestTags
@ -87,7 +86,7 @@ fun ConfirmAccountProviderView( @@ -87,7 +86,7 @@ fun ConfirmAccountProviderView(
},
footer = {
ButtonColumnMolecule {
ButtonWithProgress(
Button(
text = stringResource(id = R.string.screen_account_provider_continue),
showProgress = isLoading,
onClick = { eventSink.invoke(ConfirmAccountProviderEvents.Continue) },
@ -97,14 +96,13 @@ fun ConfirmAccountProviderView( @@ -97,14 +96,13 @@ fun ConfirmAccountProviderView(
.testTag(TestTags.loginContinue)
)
TextButton(
text = stringResource(id = R.string.screen_account_provider_change),
onClick = onChange,
enabled = true,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginChangeServer)
) {
Text(text = stringResource(id = R.string.screen_account_provider_change))
}
)
}
}
) {
@ -143,12 +141,12 @@ fun ConfirmAccountProviderView( @@ -143,12 +141,12 @@ fun ConfirmAccountProviderView(
@Preview
@Composable
fun ConfirmAccountProviderViewLightPreview(@PreviewParameter(ConfirmAccountProviderStateProvider::class) state: ConfirmAccountProviderState) =
internal fun ConfirmAccountProviderViewLightPreview(@PreviewParameter(ConfirmAccountProviderStateProvider::class) state: ConfirmAccountProviderState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun ConfirmAccountProviderViewDarkPreview(@PreviewParameter(ConfirmAccountProviderStateProvider::class) state: ConfirmAccountProviderState) =
internal fun ConfirmAccountProviderViewDarkPreview(@PreviewParameter(ConfirmAccountProviderStateProvider::class) state: ConfirmAccountProviderState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

4
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordView.kt

@ -60,11 +60,11 @@ import io.element.android.features.login.impl.error.loginError @@ -60,11 +60,11 @@ import io.element.android.features.login.impl.error.loginError
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.button.ButtonWithProgress
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.OutlinedTextField
@ -141,7 +141,7 @@ fun LoginPasswordView( @@ -141,7 +141,7 @@ fun LoginPasswordView(
// Flexible spacing to keep the submit button at the bottom
Spacer(modifier = Modifier.weight(1f))
// Submit
ButtonWithProgress(
Button(
text = stringResource(R.string.screen_login_submit),
showProgress = isLoading,
onClick = ::submit,

4
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/searchaccountprovider/SearchAccountProviderView.kt

@ -211,12 +211,12 @@ private fun HomeserverData.toAccountProvider(): AccountProvider { @@ -211,12 +211,12 @@ private fun HomeserverData.toAccountProvider(): AccountProvider {
@Preview
@Composable
fun SearchAccountProviderViewLightPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) =
internal fun SearchAccountProviderViewLightPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun SearchAccountProviderViewDarkPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) =
internal fun SearchAccountProviderViewDarkPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable

2
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListStateProvider.kt

@ -25,7 +25,7 @@ open class WaitListStateProvider : PreviewParameterProvider<WaitListState> { @@ -25,7 +25,7 @@ open class WaitListStateProvider : PreviewParameterProvider<WaitListState> {
get() = sequenceOf(
aWaitListState(loginAction = Async.Uninitialized),
aWaitListState(loginAction = Async.Loading()),
aWaitListState(loginAction = Async.Failure(Throwable())),
aWaitListState(loginAction = Async.Failure(Throwable("error"))),
aWaitListState(loginAction = Async.Failure(Throwable(message = "IO_ELEMENT_X_WAIT_LIST"))),
aWaitListState(loginAction = Async.Success(SessionId("@alice:element.io"))),
// Add other state here

38
features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/waitlistscreen/WaitListView.kt

@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.padding @@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAbsoluteAlignment
@ -52,7 +51,6 @@ import io.element.android.libraries.architecture.Async @@ -52,7 +51,6 @@ import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.aliasButtonText
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Text
@ -141,18 +139,10 @@ private fun WaitListContent( @@ -141,18 +139,10 @@ private fun WaitListContent(
.padding(horizontal = 16.dp, vertical = 16.dp)
) {
if (state.loginAction !is Async.Success) {
TextButton(
onClick = onCancelClicked,
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.Black,
disabledContainerColor = Color.White,
disabledContentColor = Color.Black,
),
) {
Text(
ElementTheme(darkTheme = true) {
TextButton(
text = stringResource(CommonStrings.action_cancel),
style = ElementTheme.typography.aliasButtonText,
onClick = onCancelClicked,
)
}
}
@ -208,22 +198,14 @@ private fun WaitListContent( @@ -208,22 +198,14 @@ private fun WaitListContent(
}
}
if (state.loginAction is Async.Success) {
Button(
onClick = { state.eventSink.invoke(WaitListEvents.Continue) },
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color.Black,
disabledContainerColor = Color.White,
disabledContentColor = Color.Black,
),
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(bottom = 8.dp)
) {
Text(
ElementTheme(darkTheme = true) {
Button(
text = stringResource(id = CommonStrings.action_continue),
style = ElementTheme.typography.aliasButtonText,
onClick = { state.eventSink.invoke(WaitListEvents.Continue) },
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.padding(bottom = 8.dp),
)
}
}

13
features/login/impl/src/main/res/drawable/ic_homeserver.xml

@ -1,13 +0,0 @@ @@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="42dp"
android:height="42dp"
android:viewportWidth="42"
android:viewportHeight="42">
<group>
<clip-path
android:pathData="M0,0h42v42h-42z"/>
<path
android:pathData="M33.25,22.75H8.75C6.825,22.75 5.25,24.325 5.25,26.25V33.25C5.25,35.175 6.825,36.75 8.75,36.75H33.25C35.175,36.75 36.75,35.175 36.75,33.25V26.25C36.75,24.325 35.175,22.75 33.25,22.75ZM12.25,33.25C10.325,33.25 8.75,31.675 8.75,29.75C8.75,27.825 10.325,26.25 12.25,26.25C14.175,26.25 15.75,27.825 15.75,29.75C15.75,31.675 14.175,33.25 12.25,33.25ZM33.25,5.25H8.75C6.825,5.25 5.25,6.825 5.25,8.75V15.75C5.25,17.675 6.825,19.25 8.75,19.25H33.25C35.175,19.25 36.75,17.675 36.75,15.75V8.75C36.75,6.825 35.175,5.25 33.25,5.25ZM12.25,15.75C10.325,15.75 8.75,14.175 8.75,12.25C8.75,10.325 10.325,8.75 12.25,8.75C14.175,8.75 15.75,10.325 15.75,12.25C15.75,14.175 14.175,15.75 12.25,15.75Z"
android:fillColor="#737D8C"/>
</group>
</vector>

BIN
features/login/impl/src/main/res/drawable/onboarding_icon_light.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

8
features/login/impl/src/main/res/values-de/translations.xml

@ -3,13 +3,13 @@ @@ -3,13 +3,13 @@
<string name="screen_account_provider_change">"Kontoanbieter wechseln"</string>
<string name="screen_account_provider_continue">"Weiter"</string>
<string name="screen_account_provider_form_hint">"Adresse des Homeservers"</string>
<string name="screen_account_provider_form_notice">"Geben Sie einen Suchbegriff oder eine Domainadresse ein."</string>
<string name="screen_account_provider_form_notice">"Gib einen Suchbegriff oder eine Domainadresse ein."</string>
<string name="screen_account_provider_form_subtitle">"Suche nach einem Unternehmen, einer Community oder einem privaten Server."</string>
<string name="screen_account_provider_form_title">"Finde einen Accountanbieter"</string>
<string name="screen_account_provider_form_title">"Finde einen Kontoanbieter"</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Konversationen stattfinden — genauso wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren."</string>
<string name="screen_account_provider_signin_title">"Du bist dabei dich bei %s anzumelden"</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Konversationen stattfinden — genauso wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren."</string>
<string name="screen_account_provider_signup_title">"Du bist dabei einen Account auf %s zu erstellen"</string>
<string name="screen_account_provider_signup_title">"Du bist dabei ein Konto auf %s zu erstellen"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org ist ein offenes Netzwerk für sichere, dezentralisierte Kommunikation."</string>
<string name="screen_change_account_provider_other">"Andere"</string>
<string name="screen_change_account_provider_subtitle">"Verwende einen anderen Kontoanbieter, z. B. deinen eigenen privaten Server oder ein Arbeitskonto."</string>
@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix ist ein offenes Netzwerk für sichere, dezentrale Kommunikation"</string>
<string name="screen_server_confirmation_message_register">"Hier werden deine Konversationen stattfinden — genau so wie du einen E-Mail-Anbieter verwenden würdest, um deine E-Mails aufzubewahren."</string>
<string name="screen_server_confirmation_title_login">"Du bist dabei dich bei %1$s anzumelden"</string>
<string name="screen_server_confirmation_title_register">"Du bist dabei einen Account auf %1$s zu erstellen"</string>
<string name="screen_server_confirmation_title_register">"Du bist dabei ein Konto auf %1$s zu erstellen"</string>
<string name="screen_waitlist_message">"Im Moment besteht eine hohe Nachfrage nach %1$s auf %2$s. Besuche die App in ein paar Tagen wieder und versuche es erneut.
Vielen Dank für deine Geduld!"</string>

47
features/login/impl/src/main/res/values-ru/translations.xml

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_account_provider_change">"Переключить аккаунт"</string>
<string name="screen_account_provider_continue">"Продолжить"</string>
<string name="screen_account_provider_form_hint">"Адрес домашнего сервера"</string>
<string name="screen_account_provider_form_notice">"Введите поисковый запрос или адрес домена."</string>
<string name="screen_account_provider_form_subtitle">"Поиск компании, сообщества или частного сервера."</string>
<string name="screen_account_provider_form_title">"Поиск сервера учетной записи"</string>
<string name="screen_account_provider_signin_subtitle">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_account_provider_signin_title">"Вы собираетесь войти в %s"</string>
<string name="screen_account_provider_signup_subtitle">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_account_provider_signup_title">"Вы собираетесь создать учетную запись на %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org — это открытая сеть для безопасной децентрализованной связи."</string>
<string name="screen_change_account_provider_other">"Другое"</string>
<string name="screen_change_account_provider_subtitle">"Используйте другого поставщика учетных записей, например, собственный частный сервер или рабочую учетную запись."</string>
<string name="screen_change_account_provider_title">"Сменить поставщика учетной записи"</string>
<string name="screen_change_server_error_invalid_homeserver">"Нам не удалось связаться с этим домашним сервером. Убедитесь, что вы правильно ввели URL-адрес домашнего сервера. Если URL-адрес указан правильно, обратитесь к администратору домашнего сервера за дополнительной помощью."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"В настоящее время этот сервер не поддерживает скользящую синхронизацию."</string>
<string name="screen_change_server_form_header">"URL-адрес домашнего сервера"</string>
<string name="screen_change_server_form_notice">"Вы можете подключиться только к существующему серверу, поддерживающему скользящую синхронизацию. Администратору домашнего сервера потребуется настроить его. %1$s"</string>
<string name="screen_change_server_subtitle">"Какой адрес у вашего сервера?"</string>
<string name="screen_login_error_deactivated_account">"Данная учетная запись была деактивирована."</string>
<string name="screen_login_error_invalid_credentials">"Неверное имя пользователя и/или пароль"</string>
<string name="screen_login_error_invalid_user_id">"Это не корректный идентификатор пользователя. Ожидаемый формат: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_unsupported_authentication">"Выбранный домашний сервер не поддерживает пароль или логин OIDC. Пожалуйста, свяжитесь с администратором или выберите другой домашний сервер."</string>
<string name="screen_login_form_header">"Введите сведения о себе"</string>
<string name="screen_login_title">"Рады видеть вас снова!"</string>
<string name="screen_login_title_with_homeserver">"Войти в %1$s"</string>
<string name="screen_server_confirmation_change_server">"Сменить учетную запись"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Частный сервер для сотрудников Element."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
<string name="screen_server_confirmation_message_register">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_server_confirmation_title_login">"Вы собираетесь войти в %1$s"</string>
<string name="screen_server_confirmation_title_register">"Вы собираетесь создать учетную запись на %1$s"</string>
<string name="screen_waitlist_message">"В настоящее время существует высокий спрос на %1$s на %2$s. Вернитесь в приложение через несколько дней и попробуйте снова.
Спасибо за терпение!"</string>
<string name="screen_waitlist_message_success">"Добро пожаловать в %1$s!"</string>
<string name="screen_waitlist_title">"Почти готово!"</string>
<string name="screen_waitlist_title_success">"Вы зарегистрированы!"</string>
<string name="screen_change_server_submit">"Продолжить"</string>
<string name="screen_change_server_title">"Выберите свой сервер"</string>
<string name="screen_login_password_hint">"Пароль"</string>
<string name="screen_login_submit">"Продолжить"</string>
<string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
<string name="screen_login_username_hint">"Имя пользователя"</string>
</resources>

16
features/login/impl/src/main/res/values-zh-rTW/translations.xml

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_account_provider_continue">"繼續"</string>
<string name="screen_account_provider_signin_title">"您即將登入%s"</string>
<string name="screen_account_provider_signup_title">"您即將在 %s 建立帳號"</string>
<string name="screen_change_account_provider_other">"其他"</string>
<string name="screen_login_title">"歡迎回來!"</string>
<string name="screen_server_confirmation_title_login">"您即將登入 %1$s"</string>
<string name="screen_server_confirmation_title_register">"您即將在 %1$s 建立帳號"</string>
<string name="screen_waitlist_message_success">"歡迎使用 %1$s!"</string>
<string name="screen_change_server_submit">"繼續"</string>
<string name="screen_change_server_title">"選擇您的伺服器"</string>
<string name="screen_login_password_hint">"密碼"</string>
<string name="screen_login_submit">"繼續"</string>
<string name="screen_login_username_hint">"使用者名稱"</string>
</resources>

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

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Вы уверены, что вы хотите выйти?"</string>
<string name="screen_signout_confirmation_dialog_title">"Выйти"</string>
<string name="screen_signout_in_progress_dialog_content">"Выполняется выход…"</string>
<string name="screen_signout_confirmation_dialog_submit">"Выйти"</string>
<string name="screen_signout_preference_item">"Выйти"</string>
</resources>

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

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"您確定要登出嗎?"</string>
<string name="screen_signout_confirmation_dialog_title">"登出"</string>
<string name="screen_signout_in_progress_dialog_content">"正在登出…"</string>
<string name="screen_signout_confirmation_dialog_submit">"登出"</string>
<string name="screen_signout_preference_item">"登出"</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save