Merge branch 'develop' into renovate/android.gradle.plugin
This commit is contained in:
commit
c4cf6c8170
1254 changed files with 9562 additions and 4228 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -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
vendored
23
.github/workflows/build.yml
vendored
|
|
@ -2,7 +2,8 @@ name: APK Build
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request: { }
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [ develop ]
|
||||
|
||||
|
|
@ -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:
|
|||
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:
|
|||
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:
|
|||
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
vendored
6
.github/workflows/danger.yml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
vendored
6
.github/workflows/maestro.yml
vendored
|
|
@ -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
vendored
9
.github/workflows/quality.yml
vendored
|
|
@ -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:
|
|||
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:
|
|||
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:
|
|||
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
vendored
Normal file
51
.github/workflows/sonar.yml
vendored
Normal file
|
|
@ -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
vendored
5
.github/workflows/tests.yml
vendored
|
|
@ -2,7 +2,8 @@ name: Test
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request: { }
|
||||
pull_request:
|
||||
merge_group:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
|
||||
|
|
@ -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
vendored
4
.github/workflows/validate-lfs.yml
vendored
|
|
@ -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
generated
2
.idea/kotlinc.xml
generated
|
|
@ -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>
|
||||
|
|
@ -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
|
|||
|
||||
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
21
CHANGES.md
|
|
@ -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)
|
||||
========================================
|
||||
|
||||
|
|
|
|||
|
|
@ -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`
|
|||
|
||||
## 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
|
|||
|
||||
### 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
|
|||
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:
|
|||
|
||||
### 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
|
|||
|
||||
### 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
47
README.md
|
|
@ -3,14 +3,18 @@
|
|||
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||
[](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
|
||||
[](https://codecov.io/github/vector-im/element-x-android)
|
||||
[](https://matrix.to/#/#element-android:matrix.org)
|
||||
[](https://translate.element.io/engage/element-android/?utm_source=widget)
|
||||
[](https://matrix.to/#/#element-x-android:matrix.org)
|
||||
[](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
|
|||
|
||||
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.
|
||||
|
||||
Come chat with the community in the dedicated Matrix [room](https://matrix.to/#/#element-android:matrix.org).
|
||||
But first make sure to read our [contribution guide](CONTRIBUTING.md) first.
|
||||
|
||||
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
|
|||
|
||||
## 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
|
||||
@Preview
|
||||
@Composable
|
||||
fun RoundIconPreview(
|
||||
internal fun RoundIconPreview(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier.clip(shape = CircleShape)) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,8 @@ class LoggedInEventProcessor @Inject constructor(
|
|||
.filter { it }
|
||||
.onEach {
|
||||
displayMessage(CommonStrings.common_verification_complete)
|
||||
}.launchIn(this)
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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(
|
|||
) {
|
||||
|
||||
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(
|
|||
|
||||
override fun onBuilt() {
|
||||
super.onBuilt()
|
||||
|
||||
lifecycle.subscribe(
|
||||
onCreate = {
|
||||
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.matrixClient) }
|
||||
|
|
@ -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(
|
|||
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(
|
|||
override fun onFtueFlowFinished() {
|
||||
backstack.pop()
|
||||
}
|
||||
}).build()
|
||||
})
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -350,3 +351,4 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
backstack.push(NavTarget.InviteList)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview {
|
||||
internal fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview {
|
||||
LoggedInView(
|
||||
state = state
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
) {
|
||||
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(
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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
|
|||
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(
|
|||
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(
|
|||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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}")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
@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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
// 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 {
|
|||
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
Normal file
1
changelog.d/1064.wip
Normal file
|
|
@ -0,0 +1 @@
|
|||
[Poll] Add feature flag in developer options
|
||||
1
changelog.d/769.feature
Normal file
1
changelog.d/769.feature
Normal file
|
|
@ -0,0 +1 @@
|
|||
Allow cancelling media upload
|
||||
|
|
@ -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
|
|||
|
||||
### 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
|
|||
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.
|
||||
|
|
|
|||
3
docs/images-lfs/screen_1_dark.png
Normal file
3
docs/images-lfs/screen_1_dark.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4515f7589c422197a82672cdc3e64814ccca9a9a022b806facee44dd67d51ff2
|
||||
size 1116864
|
||||
3
docs/images-lfs/screen_1_light.png
Normal file
3
docs/images-lfs/screen_1_light.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2afb667a8b679f4395c28407b014f074e8b74745f6ecdd6ad699a6650cee2bc7
|
||||
size 771160
|
||||
3
docs/images-lfs/screen_2_dark.png
Normal file
3
docs/images-lfs/screen_2_dark.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:910f9ab58a197a16b1295cd6b65d406ebfff1298c9c128a326edf1ec834d7fa7
|
||||
size 332936
|
||||
3
docs/images-lfs/screen_2_light.png
Normal file
3
docs/images-lfs/screen_2_light.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f7b80b9124c5c04c9db3824b68ab35883d41d05918abd415f7565f87d2713cfa
|
||||
size 338455
|
||||
3
docs/images-lfs/screen_3_dark.png
Normal file
3
docs/images-lfs/screen_3_dark.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dadff79ae955ab1da5c0a0567960fc567526ad0d8d2ea418d8adacf3a7a400cc
|
||||
size 243201
|
||||
3
docs/images-lfs/screen_3_light.png
Normal file
3
docs/images-lfs/screen_3_light.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e2f328b3e8bf2ebe4abc4c130e28f6fc2351f5ac339be0819e5fddb657f4a560
|
||||
size 246731
|
||||
3
docs/images-lfs/screen_4_dark.png
Normal file
3
docs/images-lfs/screen_4_dark.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:519fb54f0070833b2a4671e1f0fc7b2ec60930d69b592b8a760f52a73dc4fe38
|
||||
size 132247
|
||||
3
docs/images-lfs/screen_4_light.png
Normal file
3
docs/images-lfs/screen_4_light.png
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f683a228d7168c75d6f42c353c1a0e169cb22cb8fa6696e9e3ffeb80854b2441
|
||||
size 131610
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 125 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 318 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 205 KiB |
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
|||
|
||||
## 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
Normal file
2
fastlane/metadata/android/en-US/changelogs/40001020.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
First release of Element X 🚀!
|
||||
Full changelog: https://github.com/vector-im/element-x-android/releases
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|||
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(
|
|||
},
|
||||
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,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
private fun ContentToPreview() {
|
||||
Column {
|
||||
SearchSingleUserResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = false))
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
SearchSingleUserResultItem(searchResult = UserSearchResult(aMatrixUser(), isUnresolved = true))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
}
|
||||
)
|
||||
if (index < users.lastIndex) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -128,7 +128,7 @@ fun SearchUserBar(
|
|||
onClick = { onUserSelected(searchResult.matrixUser) }
|
||||
)
|
||||
if (index < users.lastIndex) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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
|
|||
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(
|
|||
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(
|
|||
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 =
|
|||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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(
|
|||
) {
|
||||
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(
|
|||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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))
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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(
|
|||
@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(
|
|||
client.getRoom(roomId)?.use {
|
||||
it.leave().getOrThrow()
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId)
|
||||
}
|
||||
Unit
|
||||
}.let { }
|
||||
}.runCatchingUpdatingState(declinedAction)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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(
|
|||
)
|
||||
|
||||
if (index != state.inviteList.lastIndex) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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(
|
|||
// 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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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
|
|||
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 {
|
|||
|
||||
@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 {
|
|||
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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
|
||||
@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 {
|
|||
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 {
|
|||
|
||||
@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 {
|
|||
}.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 {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun FakeRoomSummaryDataSource.withRoomInvitation(): FakeRoomSummaryDataSource {
|
||||
private suspend fun FakeRoomListService.withRoomInvitation(): FakeRoomListService {
|
||||
postInviteRooms(
|
||||
listOf(
|
||||
RoomSummary.Filled(
|
||||
|
|
@ -446,7 +446,7 @@ class InviteListPresenterTests {
|
|||
return this
|
||||
}
|
||||
|
||||
private suspend fun FakeRoomSummaryDataSource.withDirectChatInvitation(): FakeRoomSummaryDataSource {
|
||||
private suspend fun FakeRoomListService.withDirectChatInvitation(): FakeRoomListService {
|
||||
postInviteRooms(
|
||||
listOf(
|
||||
RoomSummary.Filled(
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ fun StaticMapView(
|
|||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun StaticMapViewPreview() = ElementPreview {
|
||||
internal fun StaticMapViewPreview() = ElementPreview {
|
||||
StaticMapView(
|
||||
lat = 0.0,
|
||||
lon = 0.0,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ internal fun StaticMapPlaceholder(
|
|||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun StaticMapPlaceholderPreview(
|
||||
internal fun StaticMapPlaceholderPreview(
|
||||
@PreviewParameter(BooleanParameterProvider::class) values: Boolean
|
||||
) = ElementPreview {
|
||||
StaticMapPlaceholder(
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ fun SendLocationView(
|
|||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun SendLocationViewPreview(
|
||||
internal fun SendLocationViewPreview(
|
||||
@PreviewParameter(SendLocationStateProvider::class) state: SendLocationState
|
||||
) = ElementPreview {
|
||||
SendLocationView(
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
Column(modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { onClick() }) {
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
},
|
||||
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(
|
|||
.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(
|
|||
|
||||
@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
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
// 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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|||
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(
|
|||
.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(
|
|||
}
|
||||
}
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
|
|
@ -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 @@
|
|||
<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
Normal file
47
features/login/impl/src/main/res/values-ru/translations.xml
Normal file
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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…
Add table
Add a link
Reference in a new issue