Merge branch 'develop' into feature/bma/scroll-to-unread-messages

This commit is contained in:
Benoit Marty
2026-06-16 14:28:44 +02:00
committed by GitHub
382 changed files with 2538 additions and 1947 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
# Skip in forks, it doesn't work even with the fallback token # Skip in forks, it doesn't work even with the fallback token
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Add SSH private keys for submodule repositories - name: Add SSH private keys for submodule repositories
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
# Skip in forks # Skip in forks
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+2 -2
View File
@@ -38,7 +38,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -78,7 +78,7 @@ jobs:
concurrency: concurrency:
group: maestro-test group: maestro-test
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch' if: (github.event_name == 'pull_request' && github.event.pull_request.fork == null) || github.event_name == 'workflow_dispatch'
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Use JDK 21 - name: Use JDK 21
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
name: Dependency analysis name: Dependency analysis
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Use JDK 21 - name: Use JDK 21
+12 -12
View File
@@ -33,7 +33,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Add SSH private keys for submodule repositories - name: Add SSH private keys for submodule repositories
@@ -51,7 +51,7 @@ jobs:
name: Search for invalid screenshot files name: Search for invalid screenshot files
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Set up Python 3.12 - name: Set up Python 3.12
@@ -65,7 +65,7 @@ jobs:
name: Search for invalid dependencies name: Search for invalid dependencies
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Use JDK 21 - name: Use JDK 21
@@ -93,7 +93,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }} group: ${{ github.ref == 'refs/heads/main' && format('check-konsist-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-konsist-develop-{0}', github.sha) || format('check-konsist-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -134,7 +134,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }} group: ${{ github.ref == 'refs/heads/main' && format('check-compose-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-compose-develop-{0}', github.sha) || format('check-compose-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -168,7 +168,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }} group: ${{ github.ref == 'refs/heads/main' && format('check-lint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-lint-develop-{0}', github.sha) || format('check-lint-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -213,7 +213,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }} group: ${{ github.ref == 'refs/heads/main' && format('check-detekt-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-detekt-develop-{0}', github.sha) || format('check-detekt-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -254,7 +254,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }} group: ${{ github.ref == 'refs/heads/main' && format('check-ktlint-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-ktlint-develop-{0}', github.sha) || format('check-ktlint-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -295,7 +295,7 @@ jobs:
group: ${{ github.ref == 'refs/heads/main' && format('check-docs-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-docs-develop-{0}', github.sha) || format('check-docs-{0}', github.ref) }} group: ${{ github.ref == 'refs/heads/main' && format('check-docs-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-docs-develop-{0}', github.sha) || format('check-docs-{0}', github.ref) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
@@ -319,7 +319,7 @@ jobs:
name: Check shell scripts name: Check shell scripts
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Run shellcheck - name: Run shellcheck
@@ -333,7 +333,7 @@ jobs:
permissions: permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files. security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 - uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
@@ -344,7 +344,7 @@ jobs:
needs: [konsist, lint, ktlint, detekt] needs: [konsist, lint, ktlint, detekt]
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
+3 -3
View File
@@ -34,7 +34,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Use JDK 21 - name: Use JDK 21
@@ -71,7 +71,7 @@ jobs:
group: ${{ format('build-release-main-enterprise-{0}', github.sha) }} group: ${{ format('build-release-main-enterprise-{0}', github.sha) }}
cancel-in-progress: true cancel-in-progress: true
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Add SSH private keys for submodule repositories - name: Add SSH private keys for submodule repositories
@@ -122,7 +122,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Use JDK 21 - name: Use JDK 21
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
docker-images: true docker-images: true
swap-storage: false swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
# Ensure we are building the branch and not the branch after being merged on develop # Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881 # https://github.com/actions/checkout/issues/881
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
# Skip in forks # Skip in forks
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Use JDK 21 - name: Use JDK 21
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }} if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
# No concurrency required, runs every time on a schedule. # No concurrency required, runs every time on a schedule.
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with: with:
persist-credentials: false persist-credentials: false
- name: Set up Python 3.12 - name: Set up Python 3.12
+1 -1
View File
@@ -108,7 +108,7 @@ jobs:
# https://github.com/codecov/codecov-action # https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov - name: ☂️ Upload coverage reports to codecov
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with: with:
fail_ci_if_error: true fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
+1 -1
View File
@@ -2,6 +2,6 @@
<project version="4"> <project version="4">
<component name="KotlinJpsPluginSettings"> <component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" /> <option name="externalSystemId" value="Gradle" />
<option name="version" value="2.3.21" /> <option name="version" value="2.4.0" />
</component> </component>
</project> </project>
+7
View File
@@ -1,3 +1,10 @@
Changes in Element X v26.06.2
=============================
Security fixes 🔐
-----------------
- Fixed an issue with OIDC callback URLs that could cause the app to crash [(GHSA-r4f9-46vw-v7g3)](https://github.com/element-hq/element-x-android/security/advisories/GHSA-r4f9-46vw-v7g3).
Changes in Element X v26.06.1 Changes in Element X v26.06.1
============================= =============================
+1 -1
View File
@@ -28,7 +28,6 @@ import java.util.Locale
plugins { plugins {
id("io.element.android-compose-application") id("io.element.android-compose-application")
alias(libs.plugins.kotlin.android)
// When using precompiled plugins, we need to apply the firebase plugin like this // When using precompiled plugins, we need to apply the firebase plugin like this
id(libs.plugins.firebaseAppDistribution.get().pluginId) id(libs.plugins.firebaseAppDistribution.get().pluginId)
id("kotlin-parcelize") id("kotlin-parcelize")
@@ -189,6 +188,7 @@ android {
buildFeatures { buildFeatures {
buildConfig = true buildConfig = true
resValues = true
} }
flavorDimensions += "store" flavorDimensions += "store"
productFlavors { productFlavors {
-2
View File
@@ -106,8 +106,6 @@ allprojects {
// Fix compilation warning for annotations // Fix compilation warning for annotations
// See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details // See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details
freeCompilerArgs.add("-Xannotation-default-target=first-only") freeCompilerArgs.add("-Xannotation-default-target=first-only")
// Opt-in to context receivers
freeCompilerArgs.add("-Xcontext-parameters")
} }
} }
} }
-1
View File
@@ -84,7 +84,6 @@ dependencies {
implementation(projects.services.toolbox.api) implementation(projects.services.toolbox.api)
implementation(libs.androidx.webkit) implementation(libs.androidx.webkit)
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.network.retrofit)
implementation(libs.serialization.json) implementation(libs.serialization.json)
implementation(libs.element.call.embedded) implementation(libs.element.call.embedded)
api(projects.features.call.api) api(projects.features.call.api)
@@ -13,6 +13,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -36,6 +39,13 @@ fun AddPeopleView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
invitePeopleView: @Composable () -> Unit, invitePeopleView: @Composable () -> Unit,
) { ) {
val currentOnFinish by rememberUpdatedState(onFinish)
LaunchedEffect(state.sendInvitesAction, state.createRoomFromDmAction) {
if (state.sendInvitesAction.isSuccess() || state.createRoomFromDmAction.isSuccess()) {
currentOnFinish()
}
}
HeaderFooterPage( HeaderFooterPage(
modifier = modifier, modifier = modifier,
contentPadding = PaddingValues(0.dp), contentPadding = PaddingValues(0.dp),
@@ -47,7 +57,6 @@ fun AddPeopleView(
text = stringResource(CommonStrings.action_finish), text = stringResource(CommonStrings.action_finish),
onClick = { onClick = {
state.eventSink(InvitePeopleEvents.SendInvites) state.eventSink(InvitePeopleEvents.SendInvites)
onFinish()
}, },
enabled = state.canInvite, enabled = state.canInvite,
modifier = Modifier modifier = Modifier
@@ -3,9 +3,15 @@
<string name="screen_create_room_action_create_room">"Новы пакой"</string> <string name="screen_create_room_action_create_room">"Новы пакой"</string>
<string name="screen_create_room_add_people_title">"Запрасіць карыстальнікаў"</string> <string name="screen_create_room_add_people_title">"Запрасіць карыстальнікаў"</string>
<string name="screen_create_room_error_creating_room">"Пры стварэнні пакоя адбылася памылка"</string> <string name="screen_create_room_error_creating_room">"Пры стварэнні пакоя адбылася памылка"</string>
<string name="screen_create_room_name_placeholder">"Дадаць імя…"</string>
<string name="screen_create_room_new_room_title">"Новы пакой"</string>
<string name="screen_create_room_new_space_title">"Новая прастора"</string>
<string name="screen_create_room_private_option_description">"Толькі запрошаныя людзі могуць атрымаць доступ да гэтага пакоя. Усе паведамленні абаронены end-to-end шыфраваннем."</string> <string name="screen_create_room_private_option_description">"Толькі запрошаныя людзі могуць атрымаць доступ да гэтага пакоя. Усе паведамленні абаронены end-to-end шыфраваннем."</string>
<string name="screen_create_room_public_option_description">"Любы можа знайсці гэты пакой. <string name="screen_create_room_public_option_description">"Любы можа знайсці гэты пакой.
Вы можаце змяніць гэта ў любы час у наладах пакоя."</string> Вы можаце змяніць гэта ў любы час у наладах пакоя."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Папрасіце далучыцца"</string> <string name="screen_create_room_room_access_section_knocking_option_title">"Папрасіце далучыцца"</string>
<string name="screen_create_room_room_address_section_title">"Адрас"</string>
<string name="screen_create_room_space_selection_sheet_title">"Дадаць у прастору"</string>
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string> <string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
<string name="screen_create_room_topic_placeholder">"Дадаць апісанне…"</string>
</resources> </resources>
@@ -9,6 +9,6 @@
<string name="screen_deactivate_account_list_item_1_bold_part">"Отключить навсегда"</string> <string name="screen_deactivate_account_list_item_1_bold_part">"Отключить навсегда"</string>
<string name="screen_deactivate_account_list_item_2">"Вы будете удалены из всех чатов."</string> <string name="screen_deactivate_account_list_item_2">"Вы будете удалены из всех чатов."</string>
<string name="screen_deactivate_account_list_item_3">"Данные Вашего аккаунта будут удалены с нашего сервера идентификации."</string> <string name="screen_deactivate_account_list_item_3">"Данные Вашего аккаунта будут удалены с нашего сервера идентификации."</string>
<string name="screen_deactivate_account_list_item_4">"Ваши сообщения по-прежнему будут видны зарегистрированным пользователям, но не будут доступны новым или незарегистрированным пользователям, если вы решите удалить их."</string> <string name="screen_deactivate_account_list_item_4">"Ваши сообщения по-прежнему будут видны зарегистрированным пользователям, но не будут доступны новым или незарегистрированным пользователям, если Вы решите удалить их."</string>
<string name="screen_deactivate_account_title">"Удалить аккаунт"</string> <string name="screen_deactivate_account_title">"Удалить аккаунт"</string>
</resources> </resources>
@@ -6,7 +6,7 @@
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string> <string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string> <string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string> <string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string>
<string name="screen_identity_confirmed_subtitle">"Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству."</string> <string name="screen_identity_confirmed_subtitle">"Теперь Вы можете безопасно читать и отправлять сообщения, и все, с кем Вы общаетесь в чате, также могут доверять этому устройству."</string>
<string name="screen_identity_confirmed_title">"Устройство проверено"</string> <string name="screen_identity_confirmed_title">"Устройство проверено"</string>
<string name="screen_identity_use_another_device">"Использовать другое устройство"</string> <string name="screen_identity_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_waiting_on_other_device">"Ожидание другого устройства…"</string> <string name="screen_identity_waiting_on_other_device">"Ожидание другого устройства…"</string>
@@ -21,7 +21,7 @@
<string name="screen_invites_decline_direct_chat_message">"Вы уверены, что хотите отказаться от личного общения с %1$s?"</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_decline_direct_chat_title">"Отклонить чат"</string>
<string name="screen_invites_empty_list">"Нет приглашений"</string> <string name="screen_invites_empty_list">"Нет приглашений"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) вас"</string> <string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) Вас"</string>
<string name="screen_migration_message">"Это единоразовая процедура, спасибо, что подождали."</string> <string name="screen_migration_message">"Это единоразовая процедура, спасибо, что подождали."</string>
<string name="screen_migration_title">"Настройка Вашего аккаунта."</string> <string name="screen_migration_title">"Настройка Вашего аккаунта."</string>
<string name="screen_roomlist_a11y_create_message">"Создать новую беседу или комнату"</string> <string name="screen_roomlist_a11y_create_message">"Создать новую беседу или комнату"</string>
@@ -31,20 +31,20 @@
<string name="screen_roomlist_filter_favourites">"Избранные"</string> <string name="screen_roomlist_filter_favourites">"Избранные"</string>
<string name="screen_roomlist_filter_favourites_empty_state_subtitle">"Добавить чат в избранное можно в настройках чата. <string name="screen_roomlist_filter_favourites_empty_state_subtitle">"Добавить чат в избранное можно в настройках чата.
На данный момент вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string> На данный момент вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string>
<string name="screen_roomlist_filter_favourites_empty_state_title">"У вас пока нет избранных чатов"</string> <string name="screen_roomlist_filter_favourites_empty_state_title">"У Вас пока нет избранных чатов"</string>
<string name="screen_roomlist_filter_invites">"Приглашения"</string> <string name="screen_roomlist_filter_invites">"Приглашения"</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"У вас нет приглашений."</string> <string name="screen_roomlist_filter_invites_empty_state_title">"У Вас нет приглашений."</string>
<string name="screen_roomlist_filter_low_priority">"Низкий приоритет"</string> <string name="screen_roomlist_filter_low_priority">"Низкий приоритет"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"У вас пока нет чатов с низким приоритетом."</string> <string name="screen_roomlist_filter_low_priority_empty_state_title">"У Вас пока нет чатов с низким приоритетом."</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string> <string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"У вас нет чатов, соответствующих фильтрам"</string> <string name="screen_roomlist_filter_mixed_empty_state_title">"У Вас нет чатов, соответствующих фильтрам"</string>
<string name="screen_roomlist_filter_people">"Пользователи"</string> <string name="screen_roomlist_filter_people">"Пользователи"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"У вас пока нет личных сообщений"</string> <string name="screen_roomlist_filter_people_empty_state_title">"У Вас пока нет личных сообщений"</string>
<string name="screen_roomlist_filter_rooms">"Комнаты"</string> <string name="screen_roomlist_filter_rooms">"Комнаты"</string>
<string name="screen_roomlist_filter_rooms_empty_state_title">"Вас пока нет ни в одной комнате"</string> <string name="screen_roomlist_filter_rooms_empty_state_title">"Вас пока нет ни в одной комнате"</string>
<string name="screen_roomlist_filter_unreads">"Новые"</string> <string name="screen_roomlist_filter_unreads">"Новые"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Поздравляем! <string name="screen_roomlist_filter_unreads_empty_state_title">"Поздравляем!
У вас нет непрочитанных сообщений!"</string> У Вас нет непрочитанных сообщений!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Запрос на присоединение отправлен"</string> <string name="screen_roomlist_knock_event_sent_description">"Запрос на присоединение отправлен"</string>
<string name="screen_roomlist_main_space_title">"Чаты"</string> <string name="screen_roomlist_main_space_title">"Чаты"</string>
<string name="screen_roomlist_mark_as_read">"Пометить как прочитанное"</string> <string name="screen_roomlist_mark_as_read">"Пометить как прочитанное"</string>
@@ -10,7 +10,7 @@
<string name="screen_invites_decline_direct_chat_message">"Вы уверены, что хотите отказаться от личного общения с %1$s?"</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_decline_direct_chat_title">"Отклонить чат"</string>
<string name="screen_invites_empty_list">"Нет приглашений"</string> <string name="screen_invites_empty_list">"Нет приглашений"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) вас"</string> <string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) Вас"</string>
<string name="screen_join_room_decline_and_block_alert_confirmation">"Да, отклонить и заблокировать"</string> <string name="screen_join_room_decline_and_block_alert_confirmation">"Да, отклонить и заблокировать"</string>
<string name="screen_join_room_decline_and_block_alert_message">"Вы действительно хотите отклонить приглашение в комнату? Также %1$s больше не сможет связаться с вами или приглашать в комнаты."</string> <string name="screen_join_room_decline_and_block_alert_message">"Вы действительно хотите отклонить приглашение в комнату? Также %1$s больше не сможет связаться с вами или приглашать в комнаты."</string>
<string name="screen_join_room_decline_and_block_alert_title">"Отклонить приглашение и заблокировать"</string> <string name="screen_join_room_decline_and_block_alert_title">"Отклонить приглашение и заблокировать"</string>
@@ -3,7 +3,7 @@
<string name="screen_invite_users_already_a_member">"Уже участник"</string> <string name="screen_invite_users_already_a_member">"Уже участник"</string>
<string name="screen_invite_users_already_invited">"Уже приглашен(а)"</string> <string name="screen_invite_users_already_invited">"Уже приглашен(а)"</string>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"У тебя пока нет чатов с этими контактами. Подтверди приглашение в эту комнату, прежде чем продолжить."</string> <string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"У тебя пока нет чатов с этими контактами. Подтверди приглашение в эту комнату, прежде чем продолжить."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"У вас пока нет чатов с этим контактом. Подтверди приглашение в эту комнату, прежде чем продолжить."</string> <string name="screen_invite_users_confirm_dialog_subtitle_one_user">"У Вас пока нет чатов с этим контактом. Подтвердите, что это действительно тот контакт, которого Вы хотите пригласить, прежде чем продолжить."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Пригласить новых участников в эту комнату?"</string> <string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Пригласить новых участников в эту комнату?"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"Пригласить нового участника в эту комнату?"</string> <string name="screen_invite_users_confirm_dialog_title_one_user">"Пригласить нового участника в эту комнату?"</string>
</resources> </resources>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_cancel_knock_alert_confirmation">"Так, адмяніць"</string>
<string name="screen_join_room_decline_and_block_button_title">"Адхіліць і заблакіраваць"</string> <string name="screen_join_room_decline_and_block_button_title">"Адхіліць і заблакіраваць"</string>
<string name="screen_join_room_join_action">"Далучыцца"</string> <string name="screen_join_room_join_action">"Далучыцца"</string>
<string name="screen_join_room_knock_action">"Націсніце, каб далучыцца"</string> <string name="screen_join_room_knock_action">"Націсніце, каб далучыцца"</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_ban_by_message">"%1$s заблокировал(а) вас в комнате."</string> <string name="screen_join_room_ban_by_message">"%1$s заблокировал(а) Вас в комнате."</string>
<string name="screen_join_room_ban_message">"Вы были заблокированы в комнате"</string> <string name="screen_join_room_ban_message">"Вы были заблокированы в комнате"</string>
<string name="screen_join_room_ban_reason">"Причина: %1$s."</string> <string name="screen_join_room_ban_reason">"Причина: %1$s."</string>
<string name="screen_join_room_cancel_knock_action">"Отменить запрос"</string> <string name="screen_join_room_cancel_knock_action">"Отменить запрос"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"Не удалось показать предпросмотр комнаты"</string> <string name="screen_join_room_loading_alert_title">"Не удалось показать предпросмотр комнаты"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s еще не поддерживает пространства. Вы можете получить к ним доступ в веб-версии."</string> <string name="screen_join_room_space_not_supported_description">"%1$s еще не поддерживает пространства. Вы можете получить к ним доступ в веб-версии."</string>
<string name="screen_join_room_space_not_supported_title">"Пространства пока не поддерживаются"</string> <string name="screen_join_room_space_not_supported_title">"Пространства пока не поддерживаются"</string>
<string name="screen_join_room_subtitle_knock">"Нажмите кнопку ниже и администратор комнаты получит уведомление. После одобрения вы сможете присоединиться к обсуждению."</string> <string name="screen_join_room_subtitle_knock">"Нажмите кнопку ниже и администратор комнаты получит уведомление. После одобрения Вы сможете присоединиться к беседе."</string>
<string name="screen_join_room_subtitle_no_preview">"Вы должны быть участником этой комнаты, чтобы просмотреть историю сообщений."</string> <string name="screen_join_room_subtitle_no_preview">"Вы должны быть участником этой комнаты, чтобы просмотреть историю сообщений."</string>
<string name="screen_join_room_title_knock">"Хотите присоединиться к этой комнате?"</string> <string name="screen_join_room_title_knock">"Хотите присоединиться к этой комнате?"</string>
<string name="screen_join_room_title_no_preview">"Предпросмотр недоступен"</string> <string name="screen_join_room_title_no_preview">"Предпросмотр недоступен"</string>
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_decline_alert_title">"Адхіліць доступ"</string>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Паглядзець усе"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Прыняць"</string> <string name="screen_room_single_knock_request_accept_button_title">"Прыняць"</string>
</resources> </resources>
@@ -4,10 +4,10 @@
<string name="screen_knock_requests_list_accept_all_alert_description">"Вы действительно хотите принять все заявки на присоединение?"</string> <string name="screen_knock_requests_list_accept_all_alert_description">"Вы действительно хотите принять все заявки на присоединение?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Принять все запросы"</string> <string name="screen_knock_requests_list_accept_all_alert_title">"Принять все запросы"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Принять все"</string> <string name="screen_knock_requests_list_accept_all_button_title">"Принять все"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Мы не смогли принять все запросы. Попробовать еще раз?"</string> <string name="screen_knock_requests_list_accept_all_failed_alert_description">"Мы не смогли принять все запросы. Попробовать ещё раз?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не удалось принять все запросы"</string> <string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не удалось принять все запросы"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Принять все заявки на присоединение"</string> <string name="screen_knock_requests_list_accept_all_loading_title">"Принять все заявки на присоединение"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Мы не смогли принять этот запрос. Хотите попробовать еще раз?"</string> <string name="screen_knock_requests_list_accept_failed_alert_description">"Мы не смогли принять этот запрос. Хотите попробовать ещё раз?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Не удалось принять запрос"</string> <string name="screen_knock_requests_list_accept_failed_alert_title">"Не удалось принять запрос"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Принятие заявки на присоединение"</string> <string name="screen_knock_requests_list_accept_loading_title">"Принятие заявки на присоединение"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Да, отклонить и запретить"</string> <string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Да, отклонить и запретить"</string>
@@ -18,7 +18,7 @@
<string name="screen_knock_requests_list_decline_alert_description">"Вы уверены, что хотите отклонить запрос %1$s на присоединение к этой комнате?"</string> <string name="screen_knock_requests_list_decline_alert_description">"Вы уверены, что хотите отклонить запрос %1$s на присоединение к этой комнате?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Отклонить доступ"</string> <string name="screen_knock_requests_list_decline_alert_title">"Отклонить доступ"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Отклонить и запретить"</string> <string name="screen_knock_requests_list_decline_and_ban_action_title">"Отклонить и запретить"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Мы не смогли отклонить этот запрос. Попробовать еще раз?"</string> <string name="screen_knock_requests_list_decline_failed_alert_description">"Мы не смогли отклонить этот запрос. Попробовать ещё раз?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Не удалось отклонить запрос"</string> <string name="screen_knock_requests_list_decline_failed_alert_title">"Не удалось отклонить запрос"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Отклонение заявки на присоединение"</string> <string name="screen_knock_requests_list_decline_loading_title">"Отклонение заявки на присоединение"</string>
<string name="screen_knock_requests_list_empty_state_description">"Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате."</string> <string name="screen_knock_requests_list_empty_state_description">"Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате."</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Вы уверены, что хотите покинуть беседу? Эта беседа не является общедоступной, и вы не сможете присоединиться к ней без приглашения."</string> <string name="leave_conversation_alert_subtitle">"Вы уверены, что хотите покинуть беседу? Эта беседа не является общедоступной, и Вы не сможете снова присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_empty_subtitle">"Вы уверены, что хотите покинуть эту комнату? Здесь есть только вы. Если вы покинете комнату, никто не сможет присоединиться к ней в будущем, включая вас."</string> <string name="leave_room_alert_empty_subtitle">"Вы уверены, что хотите покинуть эту комнату? Здесь есть только Вы. Если Вы покинете комнату, никто не сможет присоединиться к ней в будущем, включая Вас."</string>
<string name="leave_room_alert_private_subtitle">"Вы уверены, что хотите покинуть эту комнату? Эта комната не является общедоступной, и Вы не сможете присоединиться к ней без приглашения."</string> <string name="leave_room_alert_private_subtitle">"Вы уверены, что хотите покинуть эту комнату? Эта комната не является общедоступной, и Вы не сможете присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_select_new_owner_action">"Назначить владельцев"</string> <string name="leave_room_alert_select_new_owner_action">"Назначить владельцев"</string>
<string name="leave_room_alert_select_new_owner_subtitle">"Вы единственный владелец этой комнаты. Перед тем, как её покинуть, необходимо передать владение кому-нибудь другому."</string> <string name="leave_room_alert_select_new_owner_subtitle">"Вы единственный владелец этой комнаты. Перед тем, как её покинуть, необходимо передать владение кому-нибудь другому."</string>
@@ -15,7 +15,7 @@
<string name="screen_link_new_device_error_not_supported_title">"QR-код не поддерживается"</string> <string name="screen_link_new_device_error_not_supported_title">"QR-код не поддерживается"</string>
<string name="screen_link_new_device_error_request_cancelled_subtitle">"Вход на другом устройстве был отменен."</string> <string name="screen_link_new_device_error_request_cancelled_subtitle">"Вход на другом устройстве был отменен."</string>
<string name="screen_link_new_device_error_request_cancelled_title">"Запрос на вход отменен"</string> <string name="screen_link_new_device_error_request_cancelled_title">"Запрос на вход отменен"</string>
<string name="screen_link_new_device_error_request_timeout_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте еще раз."</string> <string name="screen_link_new_device_error_request_timeout_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_link_new_device_error_request_timeout_title">"Вход в систему не был выполнен вовремя"</string> <string name="screen_link_new_device_error_request_timeout_title">"Вход в систему не был выполнен вовремя"</string>
<string name="screen_link_new_device_mobile_step1">"Откройте %1$s на другом устройстве"</string> <string name="screen_link_new_device_mobile_step1">"Откройте %1$s на другом устройстве"</string>
<string name="screen_link_new_device_mobile_step2">"Выберите %1$s"</string> <string name="screen_link_new_device_mobile_step2">"Выберите %1$s"</string>
@@ -26,7 +26,7 @@
<string name="screen_link_new_device_root_loading_qr_code">"Загрузка QR-кода…"</string> <string name="screen_link_new_device_root_loading_qr_code">"Загрузка QR-кода…"</string>
<string name="screen_link_new_device_root_mobile_device">"Смартфон"</string> <string name="screen_link_new_device_root_mobile_device">"Смартфон"</string>
<string name="screen_link_new_device_root_title">"Какое устройство вы хотите привязать?"</string> <string name="screen_link_new_device_root_title">"Какое устройство вы хотите привязать?"</string>
<string name="screen_link_new_device_wrong_number_subtitle">"Пожалуйста, попробуйте еще раз и убедитесь, что вы правильно ввели 2-значный код. Если цифры по-прежнему не совпадают, обратитесь к администратору сервера."</string> <string name="screen_link_new_device_wrong_number_subtitle">"Пожалуйста, попробуйте ещё раз и убедитесь, что Вы правильно ввели 2-значный код. Если цифры по-прежнему не совпадают, обратитесь к администратору сервера."</string>
<string name="screen_link_new_device_wrong_number_title">"Цифры не совпадают"</string> <string name="screen_link_new_device_wrong_number_title">"Цифры не совпадают"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Не беспокойтесь, существующие устройства в безопасности."</string> <string name="screen_qr_code_login_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Не беспокойтесь, существующие устройства в безопасности."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string> <string name="screen_qr_code_login_connection_note_secure_state_list_header">"Что теперь?"</string>
@@ -40,9 +40,9 @@
<string name="screen_qr_code_login_error_cancelled_title">"Запрос на вход отменен"</string> <string name="screen_qr_code_login_error_cancelled_title">"Запрос на вход отменен"</string>
<string name="screen_qr_code_login_error_declined_subtitle">"Вход в систему был отклонен на другом устройстве."</string> <string name="screen_qr_code_login_error_declined_subtitle">"Вход в систему был отклонен на другом устройстве."</string>
<string name="screen_qr_code_login_error_declined_title">"Вход отклонен"</string> <string name="screen_qr_code_login_error_declined_title">"Вход отклонен"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"На этом все."</string> <string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"На этом всё."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Вход уже выполнен на другом устройстве"</string> <string name="screen_qr_code_login_error_device_already_signed_in_title">"Вход уже выполнен на другом устройстве"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте еще раз."</string> <string name="screen_qr_code_login_error_expired_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_qr_code_login_error_expired_title">"Вход в систему не был выполнен вовремя"</string> <string name="screen_qr_code_login_error_expired_title">"Вход в систему не был выполнен вовремя"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Другое устройство не поддерживает вход в %s с помощью QR-кода. <string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Другое устройство не поддерживает вход в %s с помощью QR-кода.
@@ -55,6 +55,6 @@
<string name="screen_qr_code_login_invalid_scan_state_subtitle">"Неверный QR-код"</string> <string name="screen_qr_code_login_invalid_scan_state_subtitle">"Неверный QR-код"</string>
<string name="screen_qr_code_login_no_camera_permission_state_description">"Чтобы продолжить, вам необходимо разрешить %1$s использовать камеру вашего устройства."</string> <string name="screen_qr_code_login_no_camera_permission_state_description">"Чтобы продолжить, вам необходимо разрешить %1$s использовать камеру вашего устройства."</string>
<string name="screen_qr_code_login_no_camera_permission_state_title">"Разрешите доступ к камере для сканирования QR-кода"</string> <string name="screen_qr_code_login_no_camera_permission_state_title">"Разрешите доступ к камере для сканирования QR-кода"</string>
<string name="screen_qr_code_login_unknown_error_description">"Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз."</string> <string name="screen_qr_code_login_unknown_error_description">"Произошла непредвиденная ошибка. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_qr_code_login_verify_code_loading">"Ожидание другого устройства"</string> <string name="screen_qr_code_login_verify_code_loading">"Ожидание другого устройства"</string>
</resources> </resources>
@@ -8,6 +8,7 @@
package io.element.android.features.location.api package io.element.android.features.location.api
import android.graphics.Bitmap
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
@@ -24,13 +25,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.createBitmap
import coil3.Extras import coil3.Extras
import coil3.asImage
import coil3.compose.AsyncImagePainter import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter import coil3.compose.rememberAsyncImagePainter
import coil3.request.ImageRequest import coil3.request.ImageRequest
import coil3.request.SuccessResult
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.location.api.internal.StaticMapPlaceholder import io.element.android.features.location.api.internal.StaticMapPlaceholder
import io.element.android.features.location.api.internal.StaticMapUrlBuilder import io.element.android.features.location.api.internal.StaticMapUrlBuilder
@@ -39,6 +44,7 @@ import io.element.android.libraries.designsystem.components.LocationPin
import io.element.android.libraries.designsystem.components.PinVariant import io.element.android.libraries.designsystem.components.PinVariant
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.utils.CommonDrawables
/** /**
* Shows a static map image downloaded via a third party service's static maps API. * Shows a static map image downloaded via a third party service's static maps API.
@@ -128,32 +134,46 @@ private fun BoxWithConstraintsScope.LoadableMapContent(
var retryHash by remember { mutableIntStateOf(0) } var retryHash by remember { mutableIntStateOf(0) }
val builder = remember { StaticMapUrlBuilder() } val builder = remember { StaticMapUrlBuilder() }
val painter = rememberAsyncImagePainter( val (painter, state, contentScale) = if (LocalInspectionMode.current) {
model = if (constraints.isZero) { val painter = painterResource(CommonDrawables.sample_map)
// Avoid building a URL if any of the size constraints is zero val state = AsyncImagePainter.State.Success(
null painter = painter,
} else { result = SuccessResult(
ImageRequest.Builder(context) image = createBitmap(1, 1, Bitmap.Config.ALPHA_8).asImage(),
.data( request = ImageRequest.Builder(context).build()
builder.build( )
lat = location.lat, )
lon = location.lon, Triple(painter, state, ContentScale.Crop)
zoom = zoom, } else {
darkMode = darkMode, val painter = rememberAsyncImagePainter(
width = constraints.maxWidth, model = if (constraints.isZero) {
height = constraints.maxHeight, // Avoid building a URL if any of the size constraints is zero
density = LocalDensity.current.density, null
} else {
ImageRequest.Builder(context)
.data(
builder.build(
lat = location.lat,
lon = location.lon,
zoom = zoom,
darkMode = darkMode,
width = constraints.maxWidth,
height = constraints.maxHeight,
density = LocalDensity.current.density,
)
) )
) .size(width = constraints.maxWidth, height = constraints.maxHeight)
.size(width = constraints.maxWidth, height = constraints.maxHeight) .apply {
.apply { extras.set(Extras.Key("retry_hash"), retryHash).build()
extras.set(Extras.Key("retry_hash"), retryHash).build() }
} .build()
.build() }
} )
)
val state by painter.state.collectAsState()
Triple(painter, state, ContentScale.Fit)
}
val state by painter.state.collectAsState()
when (state) { when (state) {
is AsyncImagePainter.State.Success -> { is AsyncImagePainter.State.Success -> {
Image( Image(
@@ -162,7 +182,7 @@ private fun BoxWithConstraintsScope.LoadableMapContent(
modifier = Modifier.size(width = maxWidth, height = maxHeight), modifier = Modifier.size(width = maxWidth, height = maxHeight),
// The returned image can be smaller than the requested size due to the static maps API having // The returned image can be smaller than the requested size due to the static maps API having
// a max width and height of 2048 px. We apply ContentScale.Fit to handle this. // a max width and height of 2048 px. We apply ContentScale.Fit to handle this.
contentScale = ContentScale.Fit, contentScale = contentScale,
) )
LocationPin(variant = pinVariant, modifier = Modifier.centerBottomEdge(this)) LocationPin(variant = pinVariant, modifier = Modifier.centerBottomEdge(this))
} }
@@ -14,6 +14,7 @@ import io.element.android.features.location.impl.common.ui.LocationConstraintsDi
sealed interface LocationConstraintsCheck { sealed interface LocationConstraintsCheck {
data object Success : LocationConstraintsCheck data object Success : LocationConstraintsCheck
data object PermissionRationale : LocationConstraintsCheck data object PermissionRationale : LocationConstraintsCheck
data object PermissionShouldBeRequested : LocationConstraintsCheck
data object PermissionDenied : LocationConstraintsCheck data object PermissionDenied : LocationConstraintsCheck
data object LocationServiceDisabled : LocationConstraintsCheck data object LocationServiceDisabled : LocationConstraintsCheck
data object NotEnoughPowerLevel : LocationConstraintsCheck data object NotEnoughPowerLevel : LocationConstraintsCheck
@@ -34,6 +35,7 @@ fun checkLocationConstraints(
} }
} }
permissionsState.shouldShowRationale -> LocationConstraintsCheck.PermissionRationale permissionsState.shouldShowRationale -> LocationConstraintsCheck.PermissionRationale
!permissionsState.permissionsAlreadyRequested -> LocationConstraintsCheck.PermissionShouldBeRequested
else -> LocationConstraintsCheck.PermissionDenied else -> LocationConstraintsCheck.PermissionDenied
} }
} }
@@ -41,6 +43,7 @@ fun checkLocationConstraints(
fun LocationConstraintsCheck.toDialogState(): LocationConstraintsDialogState { fun LocationConstraintsCheck.toDialogState(): LocationConstraintsDialogState {
return when (this) { return when (this) {
LocationConstraintsCheck.Success -> LocationConstraintsDialogState.None LocationConstraintsCheck.Success -> LocationConstraintsDialogState.None
LocationConstraintsCheck.PermissionShouldBeRequested -> LocationConstraintsDialogState.None
LocationConstraintsCheck.PermissionRationale -> LocationConstraintsDialogState.PermissionRationale LocationConstraintsCheck.PermissionRationale -> LocationConstraintsDialogState.PermissionRationale
LocationConstraintsCheck.PermissionDenied -> LocationConstraintsDialogState.PermissionDenied LocationConstraintsCheck.PermissionDenied -> LocationConstraintsDialogState.PermissionDenied
LocationConstraintsCheck.LocationServiceDisabled -> LocationConstraintsDialogState.LocationServiceDisabled LocationConstraintsCheck.LocationServiceDisabled -> LocationConstraintsDialogState.LocationServiceDisabled
@@ -9,6 +9,10 @@
package io.element.android.features.location.impl.common.permissions package io.element.android.features.location.impl.common.permissions
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
@@ -32,11 +36,16 @@ class DefaultPermissionsPresenter(
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
override fun present(): PermissionsState { override fun present(): PermissionsState {
val multiplePermissionsState = rememberMultiplePermissionsState(permissions = permissions) var permissionsRequested by remember { mutableStateOf(false) }
val multiplePermissionsState = rememberMultiplePermissionsState(permissions = permissions) {
permissionsRequested = true
}
fun handleEvent(event: PermissionsEvents) { fun handleEvent(event: PermissionsEvents) {
when (event) { when (event) {
PermissionsEvents.RequestPermissions -> multiplePermissionsState.launchMultiplePermissionRequest() PermissionsEvents.RequestPermissions -> {
multiplePermissionsState.launchMultiplePermissionRequest()
}
} }
} }
@@ -47,6 +56,7 @@ class DefaultPermissionsPresenter(
else -> PermissionsState.Permissions.NoneGranted else -> PermissionsState.Permissions.NoneGranted
}, },
shouldShowRationale = multiplePermissionsState.shouldShowRationale, shouldShowRationale = multiplePermissionsState.shouldShowRationale,
permissionsAlreadyRequested = permissionsRequested,
eventSink = ::handleEvent, eventSink = ::handleEvent,
) )
} }
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.common.permissions
data class PermissionsState( data class PermissionsState(
val permissions: Permissions, val permissions: Permissions,
val shouldShowRationale: Boolean, val shouldShowRationale: Boolean,
val permissionsAlreadyRequested: Boolean,
val eventSink: (PermissionsEvents) -> Unit, val eventSink: (PermissionsEvents) -> Unit,
) { ) {
sealed interface Permissions { sealed interface Permissions {
@@ -88,6 +88,8 @@ class ShareLocationPresenter(
var dialogState: ShareLocationState.Dialog by remember { var dialogState: ShareLocationState.Dialog by remember {
mutableStateOf(ShareLocationState.Dialog.None) mutableStateOf(ShareLocationState.Dialog.None)
} }
// true when trying to initiate the live location share
var pendingLiveLocationShare by remember { mutableStateOf(false) }
val startLiveLocationAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) } val startLiveLocationAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
val currentUser by client.userProfile.collectAsState() val currentUser by client.userProfile.collectAsState()
val customMapStyleUrl by produceState(AsyncData.Loading()) { val customMapStyleUrl by produceState(AsyncData.Loading()) {
@@ -100,34 +102,55 @@ class ShareLocationPresenter(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
fun checkLocationConstraints() { fun checkLocationConstraints() {
// No need to check SendLiveLocationPermissions here val locationConstraints = checkLocationConstraints(
val locationConstraints = checkLocationConstraints(permissionsState, locationActions, SendLiveLocationPermissions.GRANTED) permissionsState = permissionsState,
locationActions = locationActions,
// No need to check SendLiveLocationPermissions here
sendLiveLocationPermissions = SendLiveLocationPermissions.GRANTED
)
if (locationConstraints is LocationConstraintsCheck.PermissionShouldBeRequested) {
permissionsState.eventSink(PermissionsEvents.RequestPermissions)
}
trackUserPosition = locationConstraints is LocationConstraintsCheck.Success trackUserPosition = locationConstraints is LocationConstraintsCheck.Success
dialogState = ShareLocationState.Dialog.Constraints(locationConstraints.toDialogState()) dialogState = ShareLocationState.Dialog.Constraints(locationConstraints.toDialogState())
} }
suspend fun computeLiveLocationDialogState(): ShareLocationState.Dialog { suspend fun checkLiveLocationConstraints() {
val hasAcceptedDisclaimer = liveLocationStore.hasAcceptedLiveLocationDisclaimer() val locationConstraints = checkLocationConstraints(
val constraintsResult = checkLocationConstraints(permissionsState, locationActions, sendLiveLocationPermissions) permissionsState = permissionsState,
return when { locationActions = locationActions,
!hasAcceptedDisclaimer -> { sendLiveLocationPermissions = sendLiveLocationPermissions,
ShareLocationState.Dialog.LiveLocationDisclaimer )
} when (locationConstraints) {
constraintsResult is LocationConstraintsCheck.Success -> { LocationConstraintsCheck.Success -> {
val durations = LIVE_LOCATION_DURATIONS.map { val hasAcceptedDisclaimer = liveLocationStore.hasAcceptedLiveLocationDisclaimer()
LiveLocationDuration(duration = it, formatted = durationFormatter.format(it)) dialogState = if (!hasAcceptedDisclaimer) {
ShareLocationState.Dialog.LiveLocationDisclaimer
} else {
val durations = LIVE_LOCATION_DURATIONS.map {
LiveLocationDuration(duration = it, formatted = durationFormatter.format(it))
}
ShareLocationState.Dialog.LiveLocationDurations(durations.toImmutableList())
} }
ShareLocationState.Dialog.LiveLocationDurations(durations.toImmutableList())
} }
else -> { else -> {
ShareLocationState.Dialog.Constraints(constraintsResult.toDialogState()) if (locationConstraints is LocationConstraintsCheck.PermissionShouldBeRequested) {
permissionsState.eventSink(PermissionsEvents.RequestPermissions)
}
dialogState = ShareLocationState.Dialog.Constraints(locationConstraints.toDialogState())
} }
} }
} }
val userLocationState = userLocationStateFactory.create(permissionsState.isAnyGranted) val userLocationState = userLocationStateFactory.create(permissionsState.isAnyGranted)
LaunchedEffect(permissionsState.permissions) { checkLocationConstraints() } LaunchedEffect(permissionsState) {
if (pendingLiveLocationShare) {
checkLiveLocationConstraints()
} else {
checkLocationConstraints()
}
}
fun handleEvent(event: ShareLocationEvent) { fun handleEvent(event: ShareLocationEvent) {
when (event) { when (event) {
@@ -136,7 +159,10 @@ class ShareLocationPresenter(
} }
ShareLocationEvent.StartTrackingUserLocation -> checkLocationConstraints() ShareLocationEvent.StartTrackingUserLocation -> checkLocationConstraints()
ShareLocationEvent.StopTrackingUserLocation -> trackUserPosition = false ShareLocationEvent.StopTrackingUserLocation -> trackUserPosition = false
ShareLocationEvent.DismissDialog -> dialogState = ShareLocationState.Dialog.None ShareLocationEvent.DismissDialog -> {
pendingLiveLocationShare = false
dialogState = ShareLocationState.Dialog.None
}
ShareLocationEvent.OpenAppSettings -> { ShareLocationEvent.OpenAppSettings -> {
locationActions.openAppSettings() locationActions.openAppSettings()
dialogState = ShareLocationState.Dialog.None dialogState = ShareLocationState.Dialog.None
@@ -146,15 +172,17 @@ class ShareLocationPresenter(
dialogState = ShareLocationState.Dialog.None dialogState = ShareLocationState.Dialog.None
} }
ShareLocationEvent.InitiateLiveLocationShare -> scope.launch { ShareLocationEvent.InitiateLiveLocationShare -> scope.launch {
dialogState = computeLiveLocationDialogState() pendingLiveLocationShare = true
checkLiveLocationConstraints()
} }
ShareLocationEvent.AcceptLiveLocationDisclaimer -> scope.launch { ShareLocationEvent.AcceptLiveLocationDisclaimer -> scope.launch {
liveLocationStore.setAcceptedLiveLocationDisclaimer() liveLocationStore.setAcceptedLiveLocationDisclaimer()
.onSuccess { .onSuccess {
dialogState = computeLiveLocationDialogState() checkLiveLocationConstraints()
} }
} }
is ShareLocationEvent.StartLiveLocationShare -> scope.launch { is ShareLocationEvent.StartLiveLocationShare -> scope.launch {
pendingLiveLocationShare = false
dialogState = ShareLocationState.Dialog.None dialogState = ShareLocationState.Dialog.None
startLiveLocationAction.runUpdatingState { startLiveLocationAction.runUpdatingState {
liveLocationShareManager.startShare( liveLocationShareManager.startShare(
@@ -90,10 +90,22 @@ import kotlinx.coroutines.launch
value = AsyncData.Success(client.getMapStyleUrl().getOrNull()) value = AsyncData.Success(client.getMapStyleUrl().getOrNull())
} }
LaunchedEffect(permissionsState.permissions) { fun checkLocationConstraints() {
if (permissionsState.isAnyGranted) { val locationConstraints = checkLocationConstraints(
dialogState = LocationConstraintsDialogState.None permissionsState = permissionsState,
locationActions = locationActions,
// No need to check SendLiveLocationPermissions here
sendLiveLocationPermissions = SendLiveLocationPermissions.GRANTED
)
if (locationConstraints is LocationConstraintsCheck.PermissionShouldBeRequested) {
permissionsState.eventSink(PermissionsEvents.RequestPermissions)
} }
isTrackMyLocation = locationConstraints is LocationConstraintsCheck.Success
dialogState = locationConstraints.toDialogState()
}
LaunchedEffect(permissionsState) {
checkLocationConstraints()
} }
fun handleEvent(event: ShowLocationEvent) { fun handleEvent(event: ShowLocationEvent) {
@@ -103,9 +115,7 @@ import kotlinx.coroutines.launch
} }
is ShowLocationEvent.TrackMyLocation -> { is ShowLocationEvent.TrackMyLocation -> {
if (event.enabled) { if (event.enabled) {
val locationConstraints = checkLocationConstraints(permissionsState, locationActions, SendLiveLocationPermissions.GRANTED) checkLocationConstraints()
isTrackMyLocation = locationConstraints is LocationConstraintsCheck.Success
dialogState = locationConstraints.toDialogState()
} else { } else {
isTrackMyLocation = false isTrackMyLocation = false
} }
@@ -2,4 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_share_location_live_location_disclaimer_title">"Ваша історія поточного місцезнаходження зберігатиметься у кімнаті та буде доступна учасникам після завершення сеансу."</string> <string name="screen_share_location_live_location_disclaimer_title">"Ваша історія поточного місцезнаходження зберігатиметься у кімнаті та буде доступна учасникам після завершення сеансу."</string>
<string name="screen_share_location_live_location_duration_picker_title">"Виберіть, як довго ділитися своїм місцезнаходженням."</string> <string name="screen_share_location_live_location_duration_picker_title">"Виберіть, як довго ділитися своїм місцезнаходженням."</string>
<string name="screen_share_location_live_location_missing_permissions">"У вас немає дозволу ділитися своїм місцезнаходженням у реальному часі в цій кімнаті"</string>
</resources> </resources>
@@ -13,10 +13,12 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
fun aPermissionsState( fun aPermissionsState(
permissions: PermissionsState.Permissions = PermissionsState.Permissions.NoneGranted, permissions: PermissionsState.Permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale: Boolean = false, shouldShowRationale: Boolean = false,
permissionsRequested: Boolean = false,
): PermissionsState { ): PermissionsState {
return PermissionsState( return PermissionsState(
permissions = permissions, permissions = permissions,
shouldShowRationale = shouldShowRationale, shouldShowRationale = shouldShowRationale,
permissionsAlreadyRequested = permissionsRequested,
eventSink = {}, eventSink = {},
) )
} }
@@ -64,10 +64,25 @@ class LocationConstraintsCheckTest {
} }
@Test @Test
fun `checkLocationConstraints returns PermissionDenied when permissions denied without rationale`() { fun `checkLocationConstraints returns PermissionShouldBeRequested when permissions not yet requested`() {
val permissionsState = aPermissionsState( val permissionsState = aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsRequested = false,
)
val locationActions = FakeLocationActions(isLocationEnabled = true)
val result = checkLocationConstraints(permissionsState, locationActions, SendLiveLocationPermissions.GRANTED)
assertThat(result).isEqualTo(LocationConstraintsCheck.PermissionShouldBeRequested)
}
@Test
fun `checkLocationConstraints returns PermissionDenied when permissions already requested and denied without rationale`() {
val permissionsState = aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = true,
) )
val locationActions = FakeLocationActions(isLocationEnabled = true) val locationActions = FakeLocationActions(isLocationEnabled = true)
@@ -20,6 +20,7 @@ class FakePermissionsPresenter : PermissionsPresenter {
private var state = PermissionsState( private var state = PermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsAlreadyRequested = false,
eventSink = ::handleEvent, eventSink = ::handleEvent,
) )
set(value) { set(value) {
@@ -160,6 +160,24 @@ class ShareLocationPresenterTest {
} }
} }
@Test
fun `initial state with permissions not yet requested triggers permission request`() = runTest {
val shareLocationPresenter = createShareLocationPresenter()
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = false,
)
)
shareLocationPresenter.test {
skipItems(2)
cancelAndIgnoreRemainingEvents()
assertThat(fakePermissionsPresenter.events).contains(PermissionsEvents.RequestPermissions)
}
}
@Test @Test
fun `initial state with permissions denied`() = runTest { fun `initial state with permissions denied`() = runTest {
val shareLocationPresenter = createShareLocationPresenter() val shareLocationPresenter = createShareLocationPresenter()
@@ -167,6 +185,7 @@ class ShareLocationPresenterTest {
aPermissionsState( aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsRequested = true,
) )
) )
@@ -291,6 +310,7 @@ class ShareLocationPresenterTest {
aPermissionsState( aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsRequested = true,
) )
) )
@@ -359,7 +379,12 @@ class ShareLocationPresenterTest {
@Test @Test
fun `ShowLiveLocationDurationPicker shows disclaimer when acceptance is missing`() = runTest { fun `ShowLiveLocationDurationPicker shows disclaimer when acceptance is missing`() = runTest {
val presenter = createShareLocationPresenter() val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
roomPermissions = grantedSendLiveLocationPermissions()
)
)
val presenter = createShareLocationPresenter(joinedRoom = room)
fakePermissionsPresenter.givenState( fakePermissionsPresenter.givenState(
aPermissionsState( aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted, permissions = PermissionsState.Permissions.AllGranted,
@@ -462,7 +487,12 @@ class ShareLocationPresenterTest {
@Test @Test
fun `ShowLiveLocationDurationPicker uses the active session disclaimer state`() = runTest { fun `ShowLiveLocationDurationPicker uses the active session disclaimer state`() = runTest {
val joinedRoom = FakeJoinedRoom(baseRoom = FakeBaseRoom(sessionId = SessionId("@alice:server"))) val joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
sessionId = SessionId("@alice:server"),
roomPermissions = grantedSendLiveLocationPermissions()
),
)
createLiveLocationStore(sessionId = SessionId("@bob:server")) createLiveLocationStore(sessionId = SessionId("@bob:server"))
.setAcceptedLiveLocationDisclaimer() .setAcceptedLiveLocationDisclaimer()
.getOrThrow() .getOrThrow()
@@ -505,12 +535,13 @@ class ShareLocationPresenterTest {
aPermissionsState( aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsRequested = true,
) )
) )
shareLocationPresenter.test { shareLocationPresenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
// Dismiss initial dialog // Dismiss initial dialog to allow re-triggering it
initialState.eventSink(ShareLocationEvent.DismissDialog) initialState.eventSink(ShareLocationEvent.DismissDialog)
val dismissedState = awaitItem() val dismissedState = awaitItem()
@@ -37,7 +37,6 @@ import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@@ -103,8 +102,8 @@ class ShowLocationPresenterTest {
val presenter = createShowLocationPresenter() val presenter = createShowLocationPresenter()
presenter.test { presenter.test {
val initialState = awaitItem() assertThat(awaitItem().isTrackMyLocation).isFalse()
assertThat(initialState.isTrackMyLocation).isFalse() assertThat(awaitItem().isTrackMyLocation).isTrue()
} }
} }
@@ -129,15 +128,12 @@ class ShowLocationPresenterTest {
) )
) )
presenter.test { presenter.test {
skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.isTrackMyLocation).isFalse() assertThat(initialState.isTrackMyLocation).isFalse()
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true)) initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
val trackMyLocationState = awaitItem() val trackMyLocationState = awaitItem()
delay(1)
assertThat(trackMyLocationState.isTrackMyLocation).isTrue() assertThat(trackMyLocationState.isTrackMyLocation).isTrue()
// Swipe the map to switch mode // Swipe the map to switch mode
@@ -201,12 +197,34 @@ class ShowLocationPresenterTest {
} }
} }
@Test
fun `TrackMyLocation with permissions not yet requested triggers permission request`() = runTest {
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = false,
)
)
val presenter = createShowLocationPresenter()
presenter.test {
val initialState = awaitItem()
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
assertThat(fakePermissionsPresenter.events).contains(PermissionsEvents.RequestPermissions)
cancelAndIgnoreRemainingEvents()
}
}
@Test @Test
fun `permission denied dialog dismiss`() = runTest { fun `permission denied dialog dismiss`() = runTest {
fakePermissionsPresenter.givenState( fakePermissionsPresenter.givenState(
aPermissionsState( aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsRequested = true,
) )
) )
@@ -235,6 +253,7 @@ class ShowLocationPresenterTest {
aPermissionsState( aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted, permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false, shouldShowRationale = false,
permissionsRequested = true,
) )
) )
@@ -35,5 +35,5 @@
</plurals> </plurals>
<string name="screen_app_lock_use_biometric_android">"Выкарыстоўваць біяметрыю"</string> <string name="screen_app_lock_use_biometric_android">"Выкарыстоўваць біяметрыю"</string>
<string name="screen_app_lock_use_pin_android">"Выкарыстоўваць PIN-код"</string> <string name="screen_app_lock_use_pin_android">"Выкарыстоўваць PIN-код"</string>
<string name="screen_signout_in_progress_dialog_content">"Выхад…"</string> <string name="screen_signout_in_progress_dialog_content">"Выдаленне прылады…"</string>
</resources> </resources>
@@ -15,9 +15,9 @@
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Используйте %1$s для разблокировки приложения"</string> <string name="screen_app_lock_setup_biometric_unlock_subtitle">"Используйте %1$s для разблокировки приложения"</string>
<string name="screen_app_lock_setup_choose_pin">"Выберите PIN-код"</string> <string name="screen_app_lock_setup_choose_pin">"Выберите PIN-код"</string>
<string name="screen_app_lock_setup_confirm_pin">"Подтвердите PIN-код"</string> <string name="screen_app_lock_setup_confirm_pin">"Подтвердите PIN-код"</string>
<string name="screen_app_lock_setup_pin_context">"Установите блокировку на %1$s, чтобы повысить безопасность ваших чатов. <string name="screen_app_lock_setup_pin_context">"Установите блокировку на %1$s, чтобы повысить безопасность Ваших чатов.
Выберите код, который трудно забыть. Если вы забудете PIN-код, вам придется выйти из приложения."</string> Выберите код, который трудно забыть. Если Вы забудете PIN-код, Вам придется выйти из аккаунта."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Этот PIN-код небезопасен."</string> <string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Этот PIN-код небезопасен."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Выбрать другой PIN-код"</string> <string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Выбрать другой PIN-код"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Повторите PIN-код"</string> <string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Повторите PIN-код"</string>
@@ -25,14 +25,14 @@
<string name="screen_app_lock_signout_alert_message">"Чтобы продолжить, вам необходимо повторно войти в систему и создать новый PIN-код"</string> <string name="screen_app_lock_signout_alert_message">"Чтобы продолжить, вам необходимо повторно войти в систему и создать новый PIN-код"</string>
<string name="screen_app_lock_signout_alert_title">"Это устройство удаляется"</string> <string name="screen_app_lock_signout_alert_title">"Это устройство удаляется"</string>
<plurals name="screen_app_lock_subtitle"> <plurals name="screen_app_lock_subtitle">
<item quantity="one">"У вас осталась %1$d попытка разблокировки"</item> <item quantity="one">"У Вас осталась %1$d попытка разблокировки"</item>
<item quantity="few">"У вас остались %1$d попытки разблокировки"</item> <item quantity="few">"У Вас остались %1$d попытки разблокировки"</item>
<item quantity="many">"У вас осталось %1$d попыток разблокировки"</item> <item quantity="many">"У Вас осталось %1$d попыток разблокировки"</item>
</plurals> </plurals>
<plurals name="screen_app_lock_subtitle_wrong_pin"> <plurals name="screen_app_lock_subtitle_wrong_pin">
<item quantity="one">"Неверный PIN-код. У вас осталась %1$d попытка"</item> <item quantity="one">"Неверный PIN-код. У Вас осталась %1$d попытка"</item>
<item quantity="few">"Неверный PIN-код. У вас остались %1$d попытки"</item> <item quantity="few">"Неверный PIN-код. У Вас остались %1$d попытки"</item>
<item quantity="many">"Неверный PIN-код. У вас осталось %1$d попыток"</item> <item quantity="many">"Неверный PIN-код. У Вас осталось %1$d попыток"</item>
</plurals> </plurals>
<string name="screen_app_lock_use_biometric_android">"Использовать биометрию"</string> <string name="screen_app_lock_use_biometric_android">"Использовать биометрию"</string>
<string name="screen_app_lock_use_pin_android">"Использовать PIN-код"</string> <string name="screen_app_lock_use_pin_android">"Использовать PIN-код"</string>
@@ -20,7 +20,7 @@
<string name="screen_change_server_subtitle">"Які адрас вашага сервера?"</string> <string name="screen_change_server_subtitle">"Які адрас вашага сервера?"</string>
<string name="screen_change_server_title">"Выберыце свой сервер"</string> <string name="screen_change_server_title">"Выберыце свой сервер"</string>
<string name="screen_create_account_title">"Стварыць уліковы запіс"</string> <string name="screen_create_account_title">"Стварыць уліковы запіс"</string>
<string name="screen_login_error_deactivated_account">"Гэты ўліковы запіс быў дэактываваны."</string> <string name="screen_login_error_deactivated_account">"Гэты ўліковы запіс быў выдалены."</string>
<string name="screen_login_error_invalid_credentials">"Няправільнае імя карыстальніка і/або пароль"</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_invalid_user_id">"Гэта несапраўдны ідэнтыфікатар карыстальніка. Чаканы фармат: ‘@user:homeserver.org’"</string>
<string name="screen_login_error_refresh_tokens">"Гэты сервер настроены на выкарыстанне маркераў абнаўлення. Яны не падтрымліваюцца пры ўваходзе на аснове пароля."</string> <string name="screen_login_error_refresh_tokens">"Гэты сервер настроены на выкарыстанне маркераў абнаўлення. Яны не падтрымліваюцца пры ўваходзе на аснове пароля."</string>
@@ -29,6 +29,7 @@
<string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string> <string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
<string name="screen_login_title">"Сардэчна запрашаем!"</string> <string name="screen_login_title">"Сардэчна запрашаем!"</string>
<string name="screen_login_title_with_homeserver">"Увайсці ў %1$s"</string> <string name="screen_login_title_with_homeserver">"Увайсці ў %1$s"</string>
<string name="screen_onboarding_app_version">"Версія %1$s"</string>
<string name="screen_onboarding_sign_in_manually">"Увайсці ўручную"</string> <string name="screen_onboarding_sign_in_manually">"Увайсці ўручную"</string>
<string name="screen_onboarding_sign_in_to">"Увайсці ў %1$s"</string> <string name="screen_onboarding_sign_in_to">"Увайсці ў %1$s"</string>
<string name="screen_onboarding_sign_in_with_qr_code">"Увайсці з QR-кодам"</string> <string name="screen_onboarding_sign_in_with_qr_code">"Увайсці з QR-кодам"</string>
@@ -35,7 +35,7 @@
<string name="screen_login_error_unsupported_authentication">"Выбранный сервер не поддерживает вход по паролю и OAuth. Пожалуйста, свяжитесь с администратором или выберите другой сервер."</string> <string name="screen_login_error_unsupported_authentication">"Выбранный сервер не поддерживает вход по паролю и OAuth. Пожалуйста, свяжитесь с администратором или выберите другой сервер."</string>
<string name="screen_login_form_header">"Введите свои данные"</string> <string name="screen_login_form_header">"Введите свои данные"</string>
<string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string> <string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
<string name="screen_login_title">"Рады видеть вас снова!"</string> <string name="screen_login_title">"Рады видеть Вас снова!"</string>
<string name="screen_login_title_with_homeserver">"Войти в %1$s"</string> <string name="screen_login_title_with_homeserver">"Войти в %1$s"</string>
<string name="screen_missing_key_backup_open_element_classic">"Открыть Element Classic"</string> <string name="screen_missing_key_backup_open_element_classic">"Открыть Element Classic"</string>
<string name="screen_missing_key_backup_step_1">"Откройте Element Classic на своем устройстве."</string> <string name="screen_missing_key_backup_step_1">"Откройте Element Classic на своем устройстве."</string>
@@ -69,9 +69,9 @@
<string name="screen_qr_code_login_error_cancelled_title">"Запрос на вход отменен"</string> <string name="screen_qr_code_login_error_cancelled_title">"Запрос на вход отменен"</string>
<string name="screen_qr_code_login_error_declined_subtitle">"Вход в систему был отклонен на другом устройстве."</string> <string name="screen_qr_code_login_error_declined_subtitle">"Вход в систему был отклонен на другом устройстве."</string>
<string name="screen_qr_code_login_error_declined_title">"Вход отклонен"</string> <string name="screen_qr_code_login_error_declined_title">"Вход отклонен"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"На этом все."</string> <string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"На этом всё."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Вход уже выполнен на другом устройстве"</string> <string name="screen_qr_code_login_error_device_already_signed_in_title">"Вход уже выполнен на другом устройстве"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте еще раз."</string> <string name="screen_qr_code_login_error_expired_subtitle">"Срок действия входа истек. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_qr_code_login_error_expired_title">"Вход в систему не был выполнен вовремя"</string> <string name="screen_qr_code_login_error_expired_title">"Вход в систему не был выполнен вовремя"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Другое устройство не поддерживает вход в %s с помощью QR-кода. <string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Другое устройство не поддерживает вход в %s с помощью QR-кода.
@@ -95,7 +95,7 @@
<string name="screen_qr_code_login_no_camera_permission_state_title">"Разрешите доступ к камере для сканирования QR-кода"</string> <string name="screen_qr_code_login_no_camera_permission_state_title">"Разрешите доступ к камере для сканирования QR-кода"</string>
<string name="screen_qr_code_login_scanning_state_title">"Сканировать QR-код"</string> <string name="screen_qr_code_login_scanning_state_title">"Сканировать QR-код"</string>
<string name="screen_qr_code_login_start_over_button">"Начать заново"</string> <string name="screen_qr_code_login_start_over_button">"Начать заново"</string>
<string name="screen_qr_code_login_unknown_error_description">"Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз."</string> <string name="screen_qr_code_login_unknown_error_description">"Произошла непредвиденная ошибка. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_qr_code_login_verify_code_loading">"Ожидание другого устройства"</string> <string name="screen_qr_code_login_verify_code_loading">"Ожидание другого устройства"</string>
<string name="screen_qr_code_login_verify_code_subtitle">"Сервер для аккаунта может запросить следующий код для подтверждения входа."</string> <string name="screen_qr_code_login_verify_code_subtitle">"Сервер для аккаунта может запросить следующий код для подтверждения входа."</string>
<string name="screen_qr_code_login_verify_code_title">"Ваш код подтверждения"</string> <string name="screen_qr_code_login_verify_code_title">"Ваш код подтверждения"</string>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <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_content">"Вы ўпэўненыя, што хочаце выдаліць гэту прыладу?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Выйсці"</string> <string name="screen_signout_confirmation_dialog_submit">"Выйсці"</string>
<string name="screen_signout_confirmation_dialog_title">"Выйсці"</string> <string name="screen_signout_confirmation_dialog_title">"Выйсці"</string>
<string name="screen_signout_in_progress_dialog_content">"Выхад…"</string> <string name="screen_signout_in_progress_dialog_content">"Выдаленне прылады…"</string>
<string name="screen_signout_key_backup_disabled_subtitle">"Вы збіраецеся выйсці з апошняга сеанса. Калі вы выйдзеце з сістэмы зараз, вы страціце доступ да зашыфраваных паведамленняў."</string> <string name="screen_signout_key_backup_disabled_subtitle">"Вы збіраецеся выйсці з апошняга сеанса. Калі вы выйдзеце з сістэмы зараз, вы страціце доступ да зашыфраваных паведамленняў."</string>
<string name="screen_signout_key_backup_disabled_title">"Вы адключылі рэзервовае капіраванне"</string> <string name="screen_signout_key_backup_disabled_title">"Вы адключылі рэзервовае капіраванне"</string>
<string name="screen_signout_key_backup_offline_subtitle">"Вашы ключы ўсё яшчэ захоўваліся, калі вы выйшлі з сеткі. Паўторна падключыцеся, каб можна было стварыць рэзервовую копію вашых ключоў перад выхадам."</string> <string name="screen_signout_key_backup_offline_subtitle">"Вашы ключы ўсё яшчэ захоўваліся, калі вы выйшлі з сеткі. Паўторна падключыцеся, каб можна было стварыць рэзервовую копію вашых ключоў перад выхадам."</string>
@@ -7,9 +7,9 @@
<string name="screen_signout_key_backup_disabled_subtitle">"Это ваше единственное устройство. Если вы его удалите, вам потребуется ключ восстановления, чтобы подтвердить свою цифровую личность и восстановить зашифрованные чаты при следующем входе в систему."</string> <string name="screen_signout_key_backup_disabled_subtitle">"Это ваше единственное устройство. Если вы его удалите, вам потребуется ключ восстановления, чтобы подтвердить свою цифровую личность и восстановить зашифрованные чаты при следующем входе в систему."</string>
<string name="screen_signout_key_backup_disabled_title">"Вы потеряете доступ к своим зашифрованным чатам"</string> <string name="screen_signout_key_backup_disabled_title">"Вы потеряете доступ к своим зашифрованным чатам"</string>
<string name="screen_signout_key_backup_offline_subtitle">"Когда вы отключились от сети, резервное копирование ваших ключей продолжалось. Подключитесь снова, чтобы резервная копия ваших ключей была создана, прежде чем вы отключите это устройство."</string> <string name="screen_signout_key_backup_offline_subtitle">"Когда вы отключились от сети, резервное копирование ваших ключей продолжалось. Подключитесь снова, чтобы резервная копия ваших ключей была создана, прежде чем вы отключите это устройство."</string>
<string name="screen_signout_key_backup_offline_title">"Резервное копирование ключей все еще продолжается"</string> <string name="screen_signout_key_backup_offline_title">"Резервное копирование ключей всё ещё продолжается"</string>
<string name="screen_signout_key_backup_ongoing_subtitle">"Пожалуйста, дождитесь завершения процесса, прежде чем выходить из системы."</string> <string name="screen_signout_key_backup_ongoing_subtitle">"Пожалуйста, дождитесь завершения процесса, прежде чем выходить из системы."</string>
<string name="screen_signout_key_backup_ongoing_title">"Резервное копирование ключей все еще продолжается"</string> <string name="screen_signout_key_backup_ongoing_title">"Резервное копирование ключей всё ещё продолжается"</string>
<string name="screen_signout_preference_item">"Удалить это устройство"</string> <string name="screen_signout_preference_item">"Удалить это устройство"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Это ваше единственное устройство. Если вы его удалите, вам потребуется ключ восстановления, чтобы подтвердить свою цифровую личность и восстановить зашифрованные чаты при следующем входе в систему."</string> <string name="screen_signout_recovery_disabled_subtitle">"Это ваше единственное устройство. Если вы его удалите, вам потребуется ключ восстановления, чтобы подтвердить свою цифровую личность и восстановить зашифрованные чаты при следующем входе в систему."</string>
<string name="screen_signout_recovery_disabled_title">"Вы потеряете доступ к своим зашифрованным чатам"</string> <string name="screen_signout_recovery_disabled_title">"Вы потеряете доступ к своим зашифрованным чатам"</string>
@@ -190,6 +190,9 @@ class MessagesFlowNode(
@Parcelize @Parcelize
data object ThreadsList : NavTarget data object ThreadsList : NavTarget
@Parcelize
data class AvatarPreview(val name: String, val avatarUrl: String) : NavTarget
} }
private val callback: MessagesEntryPoint.Callback = callback() private val callback: MessagesEntryPoint.Callback = callback()
@@ -327,6 +330,10 @@ class MessagesFlowNode(
override fun navigateToDeveloperSettings() { override fun navigateToDeveloperSettings() {
callback.navigateToDeveloperSettings() callback.navigateToDeveloperSettings()
} }
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
}
} }
val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId) val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId)
createNode<MessagesNode>(buildContext, listOf(callback, inputs)) createNode<MessagesNode>(buildContext, listOf(callback, inputs))
@@ -562,6 +569,10 @@ class MessagesFlowNode(
override fun navigateToDeveloperSettings() { override fun navigateToDeveloperSettings() {
callback.navigateToDeveloperSettings() callback.navigateToDeveloperSettings()
} }
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
}
} }
createNode<ThreadedMessagesNode>(buildContext, listOf(inputs, callback)) createNode<ThreadedMessagesNode>(buildContext, listOf(inputs, callback))
} }
@@ -573,6 +584,31 @@ class MessagesFlowNode(
} }
createNode<ThreadsListNode>(buildContext, listOf(callback)) createNode<ThreadsListNode>(buildContext, listOf(callback))
} }
is NavTarget.AvatarPreview -> {
val callback = object : MediaViewerEntryPoint.Callback {
override fun onDone() {
overlay.hide()
}
override fun viewInTimeline(eventId: EventId) {
// Cannot happen
}
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
// Cannot happen
}
}
val params = mediaViewerEntryPoint.createParamsForAvatar(
filename = navTarget.name,
avatarUrl = navTarget.avatarUrl,
)
mediaViewerEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
params = params,
callback = callback,
)
}
} }
} }
@@ -66,6 +66,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.alias.matches import io.element.android.libraries.matrix.api.room.alias.matches
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.mediaplayer.api.MediaPlayer
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
@@ -107,7 +108,7 @@ class MessagesNode(
private val timelineController = TimelineController(room, room.liveTimeline) private val timelineController = TimelineController(room, room.liveTimeline)
private val presenter = presenterFactory.create( private val presenter = presenterFactory.create(
navigator = this, navigator = this,
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = false), composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = null),
timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this), timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this),
actionListPresenter = actionListPresenterFactory.create( actionListPresenter = actionListPresenterFactory.create(
postProcessor = TimelineItemActionPostProcessor.Default, postProcessor = TimelineItemActionPostProcessor.Default,
@@ -136,6 +137,8 @@ class MessagesNode(
fun navigateToDeveloperSettings() fun navigateToDeveloperSettings()
fun navigateToThreadsList() fun navigateToThreadsList()
fun navigateToAvatarPreview(username: String, avatarUrl: String)
} }
override fun onBuilt() { override fun onBuilt() {
@@ -319,6 +322,11 @@ class MessagesNode(
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
} }
}, },
onAvatarClick = { user ->
user.avatarUrl?.let { url ->
callback.navigateToAvatarPreview(user.getBestName(), url)
}
},
modifier = Modifier, modifier = Modifier,
) )
@@ -49,6 +49,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@@ -109,7 +110,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes.Any as AnyMimeTypes
class MessageComposerPresenter( class MessageComposerPresenter(
@Assisted private val navigator: MessagesNavigator, @Assisted private val navigator: MessagesNavigator,
@Assisted private val timelineController: TimelineController, @Assisted private val timelineController: TimelineController,
@Assisted private val isInThread: Boolean, @Assisted private val threadRoot: ThreadId?,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
private val room: JoinedRoom, private val room: JoinedRoom,
private val mediaPickerProvider: PickerProvider, private val mediaPickerProvider: PickerProvider,
@@ -138,10 +139,11 @@ class MessageComposerPresenter(
fun create( fun create(
timelineController: TimelineController, timelineController: TimelineController,
navigator: MessagesNavigator, navigator: MessagesNavigator,
isInThread: Boolean, threadRoot: ThreadId?,
): MessageComposerPresenter ): MessageComposerPresenter
} }
private val isInThread = threadRoot != null
private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode()) private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode())
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA) private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
@@ -234,8 +236,7 @@ class MessageComposerPresenter(
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
val draft = draftService.loadDraft( val draft = draftService.loadDraft(
roomId = room.roomId, roomId = room.roomId,
// TODO support threads in composer threadRoot = threadRoot,
threadRoot = null,
isVolatile = false isVolatile = false
) )
if (draft != null) { if (draft != null) {
@@ -652,8 +653,7 @@ class MessageComposerPresenter(
roomId = room.roomId, roomId = room.roomId,
draft = draft, draft = draft,
isVolatile = isVolatile, isVolatile = isVolatile,
// TODO support threads in composer threadRoot = threadRoot,
threadRoot = null,
) )
} }
@@ -816,8 +816,7 @@ class MessageComposerPresenter(
// Use the volatile draft only when coming from edit mode otherwise. // Use the volatile draft only when coming from edit mode otherwise.
val draft = draftService.loadDraft( val draft = draftService.loadDraft(
roomId = room.roomId, roomId = room.roomId,
// TODO support threads in composer threadRoot = threadRoot,
threadRoot = null,
isVolatile = true isVolatile = true
).takeIf { fromEdit } ).takeIf { fromEdit }
if (draft != null) { if (draft != null) {
@@ -67,6 +67,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.alias.matches import io.element.android.libraries.matrix.api.room.alias.matches
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@@ -112,7 +113,7 @@ class ThreadedMessagesNode(
this.timelineController = timelineController this.timelineController = timelineController
return presenterFactory.create( return presenterFactory.create(
navigator = this, navigator = this,
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = true), composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = inputs.threadRootEventId),
timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this), timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this),
// TODO add special processor for threaded timeline // TODO add special processor for threaded timeline
actionListPresenter = actionListPresenterFactory.create( actionListPresenter = actionListPresenterFactory.create(
@@ -138,6 +139,8 @@ class ThreadedMessagesNode(
fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean)
fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?)
fun navigateToDeveloperSettings() fun navigateToDeveloperSettings()
fun navigateToAvatarPreview(username: String, avatarUrl: String)
} }
override fun onBuilt() { override fun onBuilt() {
@@ -315,6 +318,11 @@ class ThreadedMessagesNode(
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
} }
}, },
onAvatarClick = { user ->
user.avatarUrl?.let { url ->
callback.navigateToAvatarPreview(user.getBestName(), url)
}
},
modifier = Modifier, modifier = Modifier,
) )
@@ -16,6 +16,8 @@
<string name="emoji_picker_category_symbols">"Сімвалы"</string> <string name="emoji_picker_category_symbols">"Сімвалы"</string>
<string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string> <string name="screen_media_upload_preview_error_failed_processing">"Не атрымалася апрацаваць медыяфайл для загрузкі, паспрабуйце яшчэ раз."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string> <string name="screen_media_upload_preview_error_failed_sending">"Не атрымалася загрузіць медыяфайлы, паспрабуйце яшчэ раз."</string>
<string name="screen_media_upload_preview_processing">"Апрацоўка…"</string>
<string name="screen_media_upload_preview_title">"Дадаць медыя"</string>
<string name="screen_report_content_block_user">"Заблакіраваць карыстальніка"</string> <string name="screen_report_content_block_user">"Заблакіраваць карыстальніка"</string>
<string name="screen_report_content_block_user_hint">"Адзначце, ці хочаце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка"</string> <string name="screen_report_content_block_user_hint">"Адзначце, ці хочаце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка"</string>
<string name="screen_report_content_explanation">"Гэтае паведамленне будзе перададзена адміністратару вашага хатняга сервера. Яны не змогуць прачытаць зашыфраваныя паведамленні."</string> <string name="screen_report_content_explanation">"Гэтае паведамленне будзе перададзена адміністратару вашага хатняга сервера. Яны не змогуць прачытаць зашыфраваныя паведамленні."</string>
@@ -25,7 +27,7 @@
<string name="screen_room_attachment_source_camera_video">"Запісаць відэа"</string> <string name="screen_room_attachment_source_camera_video">"Запісаць відэа"</string>
<string name="screen_room_attachment_source_files">"Далучэнне"</string> <string name="screen_room_attachment_source_files">"Далучэнне"</string>
<string name="screen_room_attachment_source_gallery">"Бібліятэка фота &amp; відэа"</string> <string name="screen_room_attachment_source_gallery">"Бібліятэка фота &amp; відэа"</string>
<string name="screen_room_attachment_source_location">"Месцазнаходжанне"</string> <string name="screen_room_attachment_source_location">"Абагуліць месцазнаходжанне"</string>
<string name="screen_room_attachment_source_poll">"Апытанне"</string> <string name="screen_room_attachment_source_poll">"Апытанне"</string>
<string name="screen_room_attachment_text_formatting">"Фармаціраванне тэксту"</string> <string name="screen_room_attachment_text_formatting">"Фармаціраванне тэксту"</string>
<string name="screen_room_encrypted_history_banner">"Гісторыя паведамленняў зараз недаступна."</string> <string name="screen_room_encrypted_history_banner">"Гісторыя паведамленняў зараз недаступна."</string>
@@ -16,6 +16,12 @@
<string name="emoji_picker_category_places">"Путешествия и места"</string> <string name="emoji_picker_category_places">"Путешествия и места"</string>
<string name="emoji_picker_category_recent">"Недавние эмодзи"</string> <string name="emoji_picker_category_recent">"Недавние эмодзи"</string>
<string name="emoji_picker_category_symbols">"Символы"</string> <string name="emoji_picker_category_symbols">"Символы"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally">"Перевернуть изображение по горизонтали"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_flipped">"Перевернуто по горизонтали"</string>
<string name="screen_image_edition_a11y_flip_image_horizontally_state_original">"Оригинал"</string>
<string name="screen_image_edition_a11y_flip_image_vertically">"Перевернуть изображение по вертикали"</string>
<string name="screen_image_edition_a11y_flip_image_vertically_state_flipped">"Перевернуто по вертикали"</string>
<string name="screen_image_edition_a11y_flip_image_vertically_state_original">"Оригинал"</string>
<string name="screen_image_edition_a11y_rotate_to_the_left">"Повернуть изображение влево."</string> <string name="screen_image_edition_a11y_rotate_to_the_left">"Повернуть изображение влево."</string>
<plurals name="screen_image_edition_a11y_rotation_state"> <plurals name="screen_image_edition_a11y_rotation_state">
<item quantity="one">"%1$d градус"</item> <item quantity="one">"%1$d градус"</item>
@@ -26,8 +32,8 @@
<string name="screen_media_upload_preview_caption_warning">"Подпись может быть не видна пользователям старых приложений."</string> <string name="screen_media_upload_preview_caption_warning">"Подпись может быть не видна пользователям старых приложений."</string>
<string name="screen_media_upload_preview_change_video_quality_prompt">"Нажмите, чтобы изменить качество загружаемого видео."</string> <string name="screen_media_upload_preview_change_video_quality_prompt">"Нажмите, чтобы изменить качество загружаемого видео."</string>
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файл не может быть загружен."</string> <string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файл не может быть загружен."</string>
<string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте еще раз."</string> <string name="screen_media_upload_preview_error_failed_processing">"Не удалось обработать медиафайл для загрузки, попробуйте ещё раз."</string>
<string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</string> <string name="screen_media_upload_preview_error_failed_sending">"Не удалось загрузить медиафайлы, попробуйте ещё раз."</string>
<string name="screen_media_upload_preview_error_too_large_message">"Максимальный размер файла: %1$s."</string> <string name="screen_media_upload_preview_error_too_large_message">"Максимальный размер файла: %1$s."</string>
<string name="screen_media_upload_preview_error_too_large_title">"Файл слишком большой для загрузки."</string> <string name="screen_media_upload_preview_error_too_large_title">"Файл слишком большой для загрузки."</string>
<string name="screen_media_upload_preview_item_count">"%1$d из %2$d"</string> <string name="screen_media_upload_preview_item_count">"%1$d из %2$d"</string>
@@ -16,6 +16,13 @@
<string name="emoji_picker_category_places">"Подорожі та місця"</string> <string name="emoji_picker_category_places">"Подорожі та місця"</string>
<string name="emoji_picker_category_recent">"Нещодавні емодзі"</string> <string name="emoji_picker_category_recent">"Нещодавні емодзі"</string>
<string name="emoji_picker_category_symbols">"Символи"</string> <string name="emoji_picker_category_symbols">"Символи"</string>
<string name="screen_image_edition_a11y_rotate_to_the_left">"Повернути зображення вліво"</string>
<plurals name="screen_image_edition_a11y_rotation_state">
<item quantity="one">"%1$d градус"</item>
<item quantity="few">"%1$d градуси"</item>
<item quantity="many">"%1$d градусів"</item>
</plurals>
<string name="screen_image_edition_title">"Редагувати фото"</string>
<string name="screen_media_upload_preview_caption_warning">"Користувачі старих застосунків можуть не бачити підписи."</string> <string name="screen_media_upload_preview_caption_warning">"Користувачі старих застосунків можуть не бачити підписи."</string>
<string name="screen_media_upload_preview_change_video_quality_prompt">"Натисніть, щоб змінити якість вивантажуваного відео"</string> <string name="screen_media_upload_preview_change_video_quality_prompt">"Натисніть, щоб змінити якість вивантажуваного відео"</string>
<string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файл не може бути вивантажено."</string> <string name="screen_media_upload_preview_error_could_not_be_uploaded">"Файл не може бути вивантажено."</string>
@@ -26,6 +33,7 @@
<string name="screen_media_upload_preview_item_count">"Елемент %1$d з %2$d"</string> <string name="screen_media_upload_preview_item_count">"Елемент %1$d з %2$d"</string>
<string name="screen_media_upload_preview_optimize_image_quality_title">"Оптимізувати якість зображення"</string> <string name="screen_media_upload_preview_optimize_image_quality_title">"Оптимізувати якість зображення"</string>
<string name="screen_media_upload_preview_processing">"Обробка…"</string> <string name="screen_media_upload_preview_processing">"Обробка…"</string>
<string name="screen_media_upload_preview_title">"Додати медіа"</string>
<string name="screen_report_content_block_user">"Заблокувати користувача"</string> <string name="screen_report_content_block_user">"Заблокувати користувача"</string>
<string name="screen_report_content_block_user_hint">"Перевірте, чи хочете ви приховати всі поточні та майбутні повідомлення від цього користувача"</string> <string name="screen_report_content_block_user_hint">"Перевірте, чи хочете ви приховати всі поточні та майбутні повідомлення від цього користувача"</string>
<string name="screen_report_content_explanation">"Це повідомлення буде надіслано адміністраторам вашого домашнього сервера. Вони не зможуть прочитати зашифровані повідомлення."</string> <string name="screen_report_content_explanation">"Це повідомлення буде надіслано адміністраторам вашого домашнього сервера. Вони не зможуть прочитати зашифровані повідомлення."</string>
@@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.features.messages.impl.utils.TextPillificationHelper
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@@ -271,12 +272,12 @@ class MessageComposerPresenterSlashCommandTest {
isRichTextEditorEnabled: Boolean = true, isRichTextEditorEnabled: Boolean = true,
draftService: ComposerDraftService = FakeComposerDraftService(), draftService: ComposerDraftService = FakeComposerDraftService(),
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
isInThread: Boolean = false, threadRoot: ThreadId? = null,
slashCommandService: SlashCommandService = FakeSlashCommandService(), slashCommandService: SlashCommandService = FakeSlashCommandService(),
) = MessageComposerPresenter( ) = MessageComposerPresenter(
navigator = navigator, navigator = navigator,
sessionCoroutineScope = this, sessionCoroutineScope = this,
isInThread = isInThread, threadRoot = threadRoot,
room = room, room = room,
mediaPickerProvider = pickerProvider, mediaPickerProvider = pickerProvider,
sessionPreferencesStore = sessionPreferencesStore, sessionPreferencesStore = sessionPreferencesStore,
@@ -1534,12 +1534,12 @@ class MessageComposerPresenterTest {
isRichTextEditorEnabled: Boolean = true, isRichTextEditorEnabled: Boolean = true,
draftService: ComposerDraftService = FakeComposerDraftService(), draftService: ComposerDraftService = FakeComposerDraftService(),
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
isInThread: Boolean = false, threadRoot: ThreadId? = null,
slashCommandService: SlashCommandService = FakeSlashCommandService(), slashCommandService: SlashCommandService = FakeSlashCommandService(),
) = MessageComposerPresenter( ) = MessageComposerPresenter(
navigator = navigator, navigator = navigator,
sessionCoroutineScope = this, sessionCoroutineScope = this,
isInThread = isInThread, threadRoot = threadRoot,
room = room, room = room,
mediaPickerProvider = pickerProvider, mediaPickerProvider = pickerProvider,
sessionPreferencesStore = sessionPreferencesStore, sessionPreferencesStore = sessionPreferencesStore,
@@ -23,8 +23,8 @@
<string name="screen_advanced_settings_media_compression_description">"Загружайте фотографии и видео быстрее и сокращайте потребление трафика"</string> <string name="screen_advanced_settings_media_compression_description">"Загружайте фотографии и видео быстрее и сокращайте потребление трафика"</string>
<string name="screen_advanced_settings_media_compression_title">"Оптимизировать качество мультимедиа"</string> <string name="screen_advanced_settings_media_compression_title">"Оптимизировать качество мультимедиа"</string>
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Модерация и безопасность"</string> <string name="screen_advanced_settings_moderation_and_safety_section_title">"Модерация и безопасность"</string>
<string name="screen_advanced_settings_optimise_image_upload_quality_description">"Автоматически оптимизируйте изображения для более быстрой загрузки и уменьшения размера файлов."</string> <string name="screen_advanced_settings_optimise_image_upload_quality_description">"Автоматически оптимизировать изображения для более быстрой загрузки и уменьшения размера файлов."</string>
<string name="screen_advanced_settings_optimise_image_upload_quality_title">"Оптимизируйте качество загрузки изображения"</string> <string name="screen_advanced_settings_optimise_image_upload_quality_title">"Оптимизировать качество загрузки изображений"</string>
<string name="screen_advanced_settings_optimise_video_upload_quality_description">"%1$s. Нажмите здесь, чтобы изменить."</string> <string name="screen_advanced_settings_optimise_video_upload_quality_description">"%1$s. Нажмите здесь, чтобы изменить."</string>
<string name="screen_advanced_settings_optimise_video_upload_quality_high">"Высокое (1080p)"</string> <string name="screen_advanced_settings_optimise_video_upload_quality_high">"Высокое (1080p)"</string>
<string name="screen_advanced_settings_optimise_video_upload_quality_low">"Низкое (480p)"</string> <string name="screen_advanced_settings_optimise_video_upload_quality_low">"Низкое (480p)"</string>
@@ -35,14 +35,14 @@
<string name="screen_advanced_settings_send_read_receipts">"Уведомления о прочтении"</string> <string name="screen_advanced_settings_send_read_receipts">"Уведомления о прочтении"</string>
<string name="screen_advanced_settings_send_read_receipts_description">"Если этот параметр выключен, другие пользователи не будут видеть, прочитали ли вы сообщения. Вы по-прежнему будете видеть статус прочтения других пользователей."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Если этот параметр выключен, другие пользователи не будут видеть, прочитали ли вы сообщения. Вы по-прежнему будете видеть статус прочтения других пользователей."</string>
<string name="screen_advanced_settings_share_presence">"Делиться присутствием"</string> <string name="screen_advanced_settings_share_presence">"Делиться присутствием"</string>
<string name="screen_advanced_settings_share_presence_description">"Если выключено, вы не будете видеть, кто печатает и читает сообщения, а также другие пользователи не будут знать, когда вы печатаете или читаете сообщения."</string> <string name="screen_advanced_settings_share_presence_description">"Если выключено, Вы не будете видеть, кто печатает и читает сообщения, а также другие пользователи не будут знать, когда Вы печатаете или читаете сообщения."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Всегда скрывать"</string> <string name="screen_advanced_settings_show_media_timeline_always_hide">"Всегда скрывать"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Всегда показывать"</string> <string name="screen_advanced_settings_show_media_timeline_always_show">"Всегда показывать"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"В приватных комнатах"</string> <string name="screen_advanced_settings_show_media_timeline_private_rooms">"В приватных комнатах"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Скрытые медиа всегда можно просмотреть, нажав на них."</string> <string name="screen_advanced_settings_show_media_timeline_subtitle">"Скрытые медиа всегда можно просмотреть, нажав на них."</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Показать медиа в истории сообщений"</string> <string name="screen_advanced_settings_show_media_timeline_title">"Показать медиа в истории сообщений"</string>
<string name="screen_advanced_settings_view_source_description">"Включить опцию просмотра источника сообщения в истории сообщений."</string> <string name="screen_advanced_settings_view_source_description">"Включить опцию просмотра источника сообщения в истории сообщений."</string>
<string name="screen_blocked_users_empty">"У вас нет заблокированных пользователей"</string> <string name="screen_blocked_users_empty">"У Вас нет заблокированных пользователей"</string>
<string name="screen_blocked_users_unblock_alert_action">"Разблокировать"</string> <string name="screen_blocked_users_unblock_alert_action">"Разблокировать"</string>
<string name="screen_blocked_users_unblock_alert_description">"Вы снова сможете увидеть все сообщения."</string> <string name="screen_blocked_users_unblock_alert_description">"Вы снова сможете увидеть все сообщения."</string>
<string name="screen_blocked_users_unblock_alert_title">"Разблокировать пользователя"</string> <string name="screen_blocked_users_unblock_alert_title">"Разблокировать пользователя"</string>
@@ -59,11 +59,12 @@
<string name="screen_labs_header_title">"Хотите попробовать?"</string> <string name="screen_labs_header_title">"Хотите попробовать?"</string>
<string name="screen_labs_title">"Лаборатория"</string> <string name="screen_labs_title">"Лаборатория"</string>
<string name="screen_notification_settings_additional_settings_section_title">"Дополнительные параметры"</string> <string name="screen_notification_settings_additional_settings_section_title">"Дополнительные параметры"</string>
<string name="screen_notification_settings_call_ringtone_label">"Мелодия звонка"</string>
<string name="screen_notification_settings_calls_label">"Аудио и видео звонки"</string> <string name="screen_notification_settings_calls_label">"Аудио и видео звонки"</string>
<string name="screen_notification_settings_configuration_mismatch">"Несоответствие конфигурации"</string> <string name="screen_notification_settings_configuration_mismatch">"Несоответствие конфигурации"</string>
<string name="screen_notification_settings_configuration_mismatch_description">"Мы упростили настройки уведомлений, чтобы упростить поиск опций. Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они все еще активны. <string name="screen_notification_settings_configuration_mismatch_description">"Мы упростили настройки уведомлений, чтобы упростить поиск опций. Некоторые пользовательские настройки, выбранные вами ранее, не отображаются в данном меню, но они всё ещё активны.
Если вы продолжите, некоторые настройки могут быть изменены."</string> Если Вы продолжите, некоторые настройки могут быть изменены."</string>
<string name="screen_notification_settings_direct_chats">"В личных чатах"</string> <string name="screen_notification_settings_direct_chats">"В личных чатах"</string>
<string name="screen_notification_settings_edit_custom_settings_section_title">"Отдельные настройки для каждого чата"</string> <string name="screen_notification_settings_edit_custom_settings_section_title">"Отдельные настройки для каждого чата"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Произошла ошибка при обновлении настройки уведомления."</string> <string name="screen_notification_settings_edit_failed_updating_default_mode">"Произошла ошибка при обновлении настройки уведомления."</string>
@@ -72,22 +73,30 @@
<string name="screen_notification_settings_edit_screen_direct_section_header">"Уведомлять меня в личных чатах"</string> <string name="screen_notification_settings_edit_screen_direct_section_header">"Уведомлять меня в личных чатах"</string>
<string name="screen_notification_settings_edit_screen_group_section_header">"Уведомлять меня в групповых чатах"</string> <string name="screen_notification_settings_edit_screen_group_section_header">"Уведомлять меня в групповых чатах"</string>
<string name="screen_notification_settings_enable_notifications">"Включить уведомления на данном устройстве"</string> <string name="screen_notification_settings_enable_notifications">"Включить уведомления на данном устройстве"</string>
<string name="screen_notification_settings_failed_fixing_configuration">"Конфигурация не была исправлена, попробуйте еще раз."</string> <string name="screen_notification_settings_failed_fixing_configuration">"Конфигурация не была исправлена, попробуйте ещё раз."</string>
<string name="screen_notification_settings_group_chats">"В групповых чатах"</string> <string name="screen_notification_settings_group_chats">"В групповых чатах"</string>
<string name="screen_notification_settings_invite_for_me_label">"Приглашения"</string> <string name="screen_notification_settings_invite_for_me_label">"Приглашения"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш сервер не поддерживает эту опцию для зашифрованных комнат, вы можете не получать некоторые уведомления."</string> <string name="screen_notification_settings_mentions_only_disclaimer">"Ваш сервер не поддерживает эту опцию для зашифрованных комнат, Вы можете не получать некоторые уведомления."</string>
<string name="screen_notification_settings_mentions_section_title">"Упоминания"</string> <string name="screen_notification_settings_mentions_section_title">"Упоминания"</string>
<string name="screen_notification_settings_message_sound_dialog_choose_other">"Выбрать другой звук…"</string>
<string name="screen_notification_settings_message_sound_dialog_current_subtitle">"Сейчас используется %1$s"</string>
<string name="screen_notification_settings_message_sound_dialog_title">"Звук сообщения"</string>
<string name="screen_notification_settings_message_sound_label">"Звук сообщения"</string>
<string name="screen_notification_settings_mode_all">"Все"</string> <string name="screen_notification_settings_mode_all">"Все"</string>
<string name="screen_notification_settings_mode_mentions">"Упоминания"</string> <string name="screen_notification_settings_mode_mentions">"Упоминания"</string>
<string name="screen_notification_settings_notification_section_title">"Уведомлять меня"</string> <string name="screen_notification_settings_notification_section_title">"Уведомлять меня"</string>
<string name="screen_notification_settings_room_mention_label">"Уведомлять меня при упоминании @room"</string> <string name="screen_notification_settings_room_mention_label">"Уведомлять меня при упоминании @room"</string>
<string name="screen_notification_settings_sound_custom_fallback">"Пользовательский"</string>
<string name="screen_notification_settings_sound_custom_sound_button_title">"Пользовательский звук…"</string> <string name="screen_notification_settings_sound_custom_sound_button_title">"Пользовательский звук…"</string>
<string name="screen_notification_settings_sound_delete_sound_error_title">"Ошибка при удалении файла"</string> <string name="screen_notification_settings_sound_delete_sound_error_title">"Ошибка при удалении файла"</string>
<string name="screen_notification_settings_sound_element_default">"Element по умолчанию"</string> <string name="screen_notification_settings_sound_element_default">"Element по умолчанию"</string>
<string name="screen_notification_settings_sound_element_fade">"Element Fade"</string>
<string name="screen_notification_settings_sound_import_sound_error_title">"Ошибка импорта файла"</string> <string name="screen_notification_settings_sound_import_sound_error_title">"Ошибка импорта файла"</string>
<string name="screen_notification_settings_sound_preview_sound_error_title">"Ошибка предварительного прослушиванием звука"</string> <string name="screen_notification_settings_sound_preview_sound_error_title">"Ошибка предварительного прослушиванием звука"</string>
<string name="screen_notification_settings_sound_section_title">"Звук"</string> <string name="screen_notification_settings_sound_section_title">"Звук"</string>
<string name="screen_notification_settings_sound_set_sound_error_dismiss_a11y">"Отклонить ошибку о звуке"</string>
<string name="screen_notification_settings_sound_set_sound_error_title">"Проблема с настройкой звукового оповещения"</string> <string name="screen_notification_settings_sound_set_sound_error_title">"Проблема с настройкой звукового оповещения"</string>
<string name="screen_notification_settings_sound_silent">"Без звука"</string>
<string name="screen_notification_settings_sound_system_alert">"Оповещение"</string> <string name="screen_notification_settings_sound_system_alert">"Оповещение"</string>
<string name="screen_notification_settings_sound_system_anticipate">"Anticipate"</string> <string name="screen_notification_settings_sound_system_anticipate">"Anticipate"</string>
<string name="screen_notification_settings_sound_system_bell">"Bell"</string> <string name="screen_notification_settings_sound_system_bell">"Bell"</string>
@@ -95,6 +104,7 @@
<string name="screen_notification_settings_sound_system_calypso">"Calypso"</string> <string name="screen_notification_settings_sound_system_calypso">"Calypso"</string>
<string name="screen_notification_settings_sound_system_chime">"Chime"</string> <string name="screen_notification_settings_sound_system_chime">"Chime"</string>
<string name="screen_notification_settings_sound_system_choo_choo">"Choo Choo"</string> <string name="screen_notification_settings_sound_system_choo_choo">"Choo Choo"</string>
<string name="screen_notification_settings_sound_system_default">"Как в системе"</string>
<string name="screen_notification_settings_sound_system_descent">"Descent"</string> <string name="screen_notification_settings_sound_system_descent">"Descent"</string>
<string name="screen_notification_settings_sound_system_electronic">"Electronic"</string> <string name="screen_notification_settings_sound_system_electronic">"Electronic"</string>
<string name="screen_notification_settings_sound_system_fanfare">"Fanfare"</string> <string name="screen_notification_settings_sound_system_fanfare">"Fanfare"</string>
@@ -124,5 +134,5 @@
<string name="theme_system">"Системное"</string> <string name="theme_system">"Системное"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"История уведомлений"</string> <string name="troubleshoot_notifications_entry_point_push_history_title">"История уведомлений"</string>
<string name="troubleshoot_notifications_entry_point_section">"Устранение неполадок"</string> <string name="troubleshoot_notifications_entry_point_section">"Устранение неполадок"</string>
<string name="troubleshoot_notifications_entry_point_title">"Уведомления об устранении неполадок"</string> <string name="troubleshoot_notifications_entry_point_title">"Устранение неполадок с уведомлениями"</string>
</resources> </resources>
@@ -81,10 +81,45 @@
<string name="screen_notification_settings_mode_mentions">"Згадки"</string> <string name="screen_notification_settings_mode_mentions">"Згадки"</string>
<string name="screen_notification_settings_notification_section_title">"Сповіщати мене про"</string> <string name="screen_notification_settings_notification_section_title">"Сповіщати мене про"</string>
<string name="screen_notification_settings_room_mention_label">"Сповіщати про @room"</string> <string name="screen_notification_settings_room_mention_label">"Сповіщати про @room"</string>
<string name="screen_notification_settings_sound_custom_sound_button_title">"Користувацький звук…"</string>
<string name="screen_notification_settings_sound_delete_sound_error_title">"Помилка видалення файлу"</string>
<string name="screen_notification_settings_sound_element_default">"За замовчуванням Element"</string>
<string name="screen_notification_settings_sound_element_fade">"Згасання Element"</string>
<string name="screen_notification_settings_sound_import_sound_error_title">"Помилка імпорту файлу"</string>
<string name="screen_notification_settings_sound_preview_sound_error_title">"Проблема з попереднім прослуховуванням звуку сповіщення"</string>
<string name="screen_notification_settings_sound_section_title">"Звук"</string>
<string name="screen_notification_settings_sound_set_sound_error_title">"Проблема налаштовування звуку сповіщення"</string>
<string name="screen_notification_settings_sound_system_alert">"Тривога"</string>
<string name="screen_notification_settings_sound_system_anticipate">"Передбачення"</string>
<string name="screen_notification_settings_sound_system_bell">"Дзвоник"</string>
<string name="screen_notification_settings_sound_system_bloom">"Цвітіння"</string>
<string name="screen_notification_settings_sound_system_calypso">"Каліпсо"</string>
<string name="screen_notification_settings_sound_system_chime">"Передзвін"</string>
<string name="screen_notification_settings_sound_system_choo_choo">"Чу-чу"</string>
<string name="screen_notification_settings_sound_system_descent">"Спуск"</string>
<string name="screen_notification_settings_sound_system_electronic">"Електроніка"</string>
<string name="screen_notification_settings_sound_system_fanfare">"Фанфари"</string>
<string name="screen_notification_settings_sound_system_glass">"Скло"</string>
<string name="screen_notification_settings_sound_system_horn">"Ріг"</string>
<string name="screen_notification_settings_sound_system_ladder">"Драбина"</string>
<string name="screen_notification_settings_sound_system_minuet">"Менует"</string>
<string name="screen_notification_settings_sound_system_news_flash">"Екстренні новини"</string>
<string name="screen_notification_settings_sound_system_noir">"Нуар"</string>
<string name="screen_notification_settings_sound_system_sherwood_forest">"Шервудський ліс"</string>
<string name="screen_notification_settings_sound_system_spell">"Чаклування"</string>
<string name="screen_notification_settings_sound_system_suspense">"Інтрига"</string>
<string name="screen_notification_settings_sound_system_swish">"Свист"</string>
<string name="screen_notification_settings_sound_system_telegraph">"Телеграф"</string>
<string name="screen_notification_settings_sound_system_tiptoes">"Навшпиньки"</string>
<string name="screen_notification_settings_sound_system_tri_tone">"Тритональний"</string>
<string name="screen_notification_settings_sound_system_tweet">"Твіт"</string>
<string name="screen_notification_settings_sound_system_typewriters">"Друкарські машинки"</string>
<string name="screen_notification_settings_sound_system_update">"Оновити"</string>
<string name="screen_notification_settings_system_notifications_action_required">"Щоб отримувати сповіщення змініть свої %1$s."</string> <string name="screen_notification_settings_system_notifications_action_required">"Щоб отримувати сповіщення змініть свої %1$s."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"системні налаштування"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"системні налаштування"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Системні сповіщення вимкнені"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Системні сповіщення вимкнені"</string>
<string name="screen_notification_settings_title">"Сповіщення"</string> <string name="screen_notification_settings_title">"Сповіщення"</string>
<string name="theme_black">"Темна"</string>
<string name="theme_dark">"Темна"</string> <string name="theme_dark">"Темна"</string>
<string name="theme_light">"Світла"</string> <string name="theme_light">"Світла"</string>
<string name="theme_system">"Системна"</string> <string name="theme_system">"Системна"</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?"</string> <string name="crash_detection_dialog_content">"Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце абагуліць справаздачу аб збоі?"</string>
<string name="rageshake_detection_dialog_content">"Падобна, што вы трасеце тэлефон. Хочаце адкрыць экран паведамлення пра памылку?"</string> <string name="rageshake_detection_dialog_content">"Падобна, што вы трасеце тэлефон. Хочаце адкрыць экран паведамлення пра памылку?"</string>
<string name="settings_rageshake">"Rageshake"</string> <string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Парог выяўлення"</string> <string name="settings_rageshake_detection_threshold">"Парог выяўлення"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"При последнем использовании %1$s произошел сбой. Хотите отправить отчет?"</string> <string name="crash_detection_dialog_content">"При последнем использовании %1$s произошел сбой. Хотите отправить отчет?"</string>
<string name="rageshake_detection_dialog_content">"Похоже, что вы трясете телефон. Хотите открыть экран сообщения об ошибке?"</string> <string name="rageshake_detection_dialog_content">"Похоже, что Вы трясете телефон в раздражении. Хотите открыть экран сообщения об ошибке?"</string>
<string name="settings_rageshake">"Встряхните"</string> <string name="settings_rageshake">"Встряхните"</string>
<string name="settings_rageshake_detection_threshold">"Порог обнаружения"</string> <string name="settings_rageshake_detection_threshold">"Порог обнаружения"</string>
</resources> </resources>
@@ -12,6 +12,6 @@
<string name="screen_bug_report_include_logs">"Дазволіць журналы"</string> <string name="screen_bug_report_include_logs">"Дазволіць журналы"</string>
<string name="screen_bug_report_include_screenshot">"Адправіць здымак экрана"</string> <string name="screen_bug_report_include_screenshot">"Адправіць здымак экрана"</string>
<string name="screen_bug_report_logs_description">"Каб пераканацца, што ўсё працуе правільна, у паведамленне будуць уключаны часопісы. Каб адправіць паведамленне без часопісаў, адключыце гэтую наладу."</string> <string name="screen_bug_report_logs_description">"Каб пераканацца, што ўсё працуе правільна, у паведамленне будуць уключаны часопісы. Каб адправіць паведамленне без часопісаў, адключыце гэтую наладу."</string>
<string name="screen_bug_report_rash_logs_alert_title">"Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?"</string> <string name="screen_bug_report_rash_logs_alert_title">"Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце абагуліць справаздачу аб збоі?"</string>
<string name="screen_bug_report_view_logs">"Прагляд журналаў"</string> <string name="screen_bug_report_view_logs">"Прагляд журналаў"</string>
</resources> </resources>
@@ -8,13 +8,15 @@
<string name="screen_bug_report_editor_placeholder">"Опишите проблему…"</string> <string name="screen_bug_report_editor_placeholder">"Опишите проблему…"</string>
<string name="screen_bug_report_editor_supporting">"Если возможно, пожалуйста, напишите описание на английском языке."</string> <string name="screen_bug_report_editor_supporting">"Если возможно, пожалуйста, напишите описание на английском языке."</string>
<string name="screen_bug_report_error_description_too_short">"Описание слишком короткое, пожалуйста, расскажите подробнее о том, что произошло. Спасибо!"</string> <string name="screen_bug_report_error_description_too_short">"Описание слишком короткое, пожалуйста, расскажите подробнее о том, что произошло. Спасибо!"</string>
<string name="screen_bug_report_github_issue_description">"При наличии, Вы можете ввести номер связанной проблемы из GitHub."</string>
<string name="screen_bug_report_github_issue_label">"Проблема на GitHub"</string>
<string name="screen_bug_report_include_crash_logs">"Отправка журналов сбоев"</string> <string name="screen_bug_report_include_crash_logs">"Отправка журналов сбоев"</string>
<string name="screen_bug_report_include_logs">"Разрешить ведение журналов"</string> <string name="screen_bug_report_include_logs">"Разрешить ведение журналов"</string>
<string name="screen_bug_report_include_logs_error">"Ваши журналы слишком большие для включения в этот отчет. Пожалуйста, отправьте их нам другим способом."</string> <string name="screen_bug_report_include_logs_error">"Ваши журналы слишком большие для включения в этот отчет. Пожалуйста, отправьте их нам другим способом."</string>
<string name="screen_bug_report_include_screenshot">"Отправить скриншот"</string> <string name="screen_bug_report_include_screenshot">"Отправить скриншот"</string>
<string name="screen_bug_report_logs_description">"Чтобы убедиться, что всё работает правильно, к сообщению будут прикреплены журналы. Чтобы отправить сообщение без журналов, отключите эту настройку."</string> <string name="screen_bug_report_logs_description">"Чтобы убедиться, что всё работает правильно, к сообщению будут прикреплены журналы. Чтобы отправить сообщение без журналов, отключите эту настройку."</string>
<string name="screen_bug_report_rash_logs_alert_title">"При последнем использовании %1$s произошел сбой. Хотите отправить отчет?"</string> <string name="screen_bug_report_rash_logs_alert_title">"При последнем использовании %1$s произошел сбой. Хотите отправить отчет?"</string>
<string name="screen_bug_report_send_notification_settings_description">"Если у вас возникли проблемы с уведомлениями, загрузка настроек уведомлений может помочь нам определить основную причину."</string> <string name="screen_bug_report_send_notification_settings_description">"Если у Вас возникли проблемы с уведомлениями, загрузка настроек уведомлений может помочь нам определить причину проблемы. Обратите внимание, что эти правила могут содержать конфиденциальную информацию, такую ​​как Ваше отображаемое имя или ключевые слова, по которым должны срабатывать уведомления."</string>
<string name="screen_bug_report_send_notification_settings_title">"Настройки отправки уведомлений"</string> <string name="screen_bug_report_send_notification_settings_title">"Настройки отправки уведомлений"</string>
<string name="screen_bug_report_view_logs">"Просмотр журналов"</string> <string name="screen_bug_report_view_logs">"Просмотр журналов"</string>
</resources> </resources>
@@ -8,6 +8,8 @@
<string name="screen_bug_report_editor_placeholder">"Опишіть проблему…"</string> <string name="screen_bug_report_editor_placeholder">"Опишіть проблему…"</string>
<string name="screen_bug_report_editor_supporting">"Якщо можливо, будь ласка, напишіть опис англійською мовою."</string> <string name="screen_bug_report_editor_supporting">"Якщо можливо, будь ласка, напишіть опис англійською мовою."</string>
<string name="screen_bug_report_error_description_too_short">"Опис закороткий, будь ласка, надайте докладнішу інформацію про те, що сталося. Дякуємо!"</string> <string name="screen_bug_report_error_description_too_short">"Опис закороткий, будь ласка, надайте докладнішу інформацію про те, що сталося. Дякуємо!"</string>
<string name="screen_bug_report_github_issue_description">"Ви можете ввести номер вже існуючої проблеми на GitHub, якщо така є."</string>
<string name="screen_bug_report_github_issue_label">"Тікет на GitHub"</string>
<string name="screen_bug_report_include_crash_logs">"Надіслати журнали збоїв"</string> <string name="screen_bug_report_include_crash_logs">"Надіслати журнали збоїв"</string>
<string name="screen_bug_report_include_logs">"Дозволити журнали"</string> <string name="screen_bug_report_include_logs">"Дозволити журнали"</string>
<string name="screen_bug_report_include_logs_error">"Ваші журнали надмірно великі, тому їх не можна включити в цей звіт, будь ласка, надішліть їх нам іншим способом."</string> <string name="screen_bug_report_include_logs_error">"Ваші журнали надмірно великі, тому їх не можна включити в цей звіт, будь ласка, надішліть їх нам іншим способом."</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_report_room_leave_failed_alert_message">"Ваша жалоба была успешно отправлена, но мы столкнулись с проблемой при попытке покинуть комнату. Пожалуйста, попробуйте еще раз."</string> <string name="screen_report_room_leave_failed_alert_message">"Ваша жалоба была успешно отправлена, но мы столкнулись с проблемой при попытке покинуть комнату. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_report_room_leave_failed_alert_title">"Невозможно покинуть комнату"</string> <string name="screen_report_room_leave_failed_alert_title">"Невозможно покинуть комнату"</string>
<string name="screen_report_room_reason_footer">"Пожалуйтесь на комнату администратору. Если сообщения зашифрованы, ваш администратор не сможет их прочитать."</string> <string name="screen_report_room_reason_footer">"Пожалуйтесь на комнату администратору. Если сообщения зашифрованы, ваш администратор не сможет их прочитать."</string>
<string name="screen_report_room_reason_placeholder">"Опишите причину жалобы…"</string> <string name="screen_report_room_reason_placeholder">"Опишите причину жалобы…"</string>
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_change_permissions_administrators">"Толькі адміністратары"</string> <string name="screen_room_change_permissions_administrators">"Адміністратар"</string>
<string name="screen_room_change_permissions_ban_people">"Заблакіраваць людзей"</string> <string name="screen_room_change_permissions_ban_people">"Заблакіраваць людзей"</string>
<string name="screen_room_change_permissions_delete_messages">"Выдаліць паведамленні"</string> <string name="screen_room_change_permissions_delete_messages">"Выдаліць паведамленні"</string>
<string name="screen_room_change_permissions_invite_people">"Запрашайце людзей і прымайце запыты на далучэнне"</string> <string name="screen_room_change_permissions_everyone">"Удзельнік"</string>
<string name="screen_room_change_permissions_invite_people">"Запрасіць людзей"</string>
<string name="screen_room_change_permissions_messages_and_content">"Паведамленні і змест"</string> <string name="screen_room_change_permissions_messages_and_content">"Паведамленні і змест"</string>
<string name="screen_room_change_permissions_moderators">"Адміністратары і мадэратары"</string> <string name="screen_room_change_permissions_moderators">"Мадэратар"</string>
<string name="screen_room_change_permissions_remove_people">"Выдаляйце людзей і адхіляйце запыты на далучэнне"</string> <string name="screen_room_change_permissions_remove_people">"Выдаляйце людзей і адхіляйце запыты на далучэнне"</string>
<string name="screen_room_change_permissions_room_avatar">"Змяніць аватар пакоя"</string> <string name="screen_room_change_permissions_room_avatar">"Змяніць аватар пакоя"</string>
<string name="screen_room_change_permissions_room_details">"Рэдагаваць пакой"</string> <string name="screen_room_change_permissions_room_details">"Рэдагаваць пакой"</string>
@@ -39,8 +40,8 @@
<string name="screen_room_member_list_manage_member_unban_message">"Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць."</string> <string name="screen_room_member_list_manage_member_unban_message">"Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць."</string>
<string name="screen_room_member_list_mode_banned">"Заблакіраваныя"</string> <string name="screen_room_member_list_mode_banned">"Заблакіраваныя"</string>
<string name="screen_room_member_list_mode_members">"Удзельнікі"</string> <string name="screen_room_member_list_mode_members">"Удзельнікі"</string>
<string name="screen_room_member_list_role_administrator">"Толькі адміністратары"</string> <string name="screen_room_member_list_role_administrator">"Адміністратар"</string>
<string name="screen_room_member_list_role_moderator">"Адміністратары і мадэратары"</string> <string name="screen_room_member_list_role_moderator">"Мадэратар"</string>
<string name="screen_room_member_list_room_members_header_title">"Удзельнікі пакоя"</string> <string name="screen_room_member_list_room_members_header_title">"Удзельнікі пакоя"</string>
<string name="screen_room_member_list_unbanning_user">"Разблакіроўка %1$s"</string> <string name="screen_room_member_list_unbanning_user">"Разблакіроўка %1$s"</string>
<string name="screen_room_roles_and_permissions_admins">"Адміністратары"</string> <string name="screen_room_roles_and_permissions_admins">"Адміністратары"</string>
@@ -20,7 +20,7 @@
<string name="screen_room_change_permissions_send_messages">"Отправлять сообщения"</string> <string name="screen_room_change_permissions_send_messages">"Отправлять сообщения"</string>
<string name="screen_room_change_permissions_title">"Разрешения"</string> <string name="screen_room_change_permissions_title">"Разрешения"</string>
<string name="screen_room_change_role_administrators_title">"Редактировать администраторов"</string> <string name="screen_room_change_role_administrators_title">"Редактировать администраторов"</string>
<string name="screen_room_change_role_confirm_add_admin_description">"Вы не сможете отменить это действие. Вы даете пользователю такой же уровень прав, как и у вас"</string> <string name="screen_room_change_role_confirm_add_admin_description">"Вы не сможете отменить это действие. Вы даете пользователю такой же уровень прав, как и у Вас"</string>
<string name="screen_room_change_role_confirm_add_admin_title">"Добавить администратора?"</string> <string name="screen_room_change_role_confirm_add_admin_title">"Добавить администратора?"</string>
<string name="screen_room_change_role_confirm_change_owners_description">"Отменить данное действие будет невозможно. Права передадутся выбранным пользователям. После вашего выхода действие станет необратимым."</string> <string name="screen_room_change_role_confirm_change_owners_description">"Отменить данное действие будет невозможно. Права передадутся выбранным пользователям. После вашего выхода действие станет необратимым."</string>
<string name="screen_room_change_role_confirm_change_owners_title">"Передать владение?"</string> <string name="screen_room_change_role_confirm_change_owners_title">"Передать владение?"</string>
@@ -36,7 +36,7 @@
<string name="screen_room_change_role_section_administrators">"Администраторы"</string> <string name="screen_room_change_role_section_administrators">"Администраторы"</string>
<string name="screen_room_change_role_section_moderators">"Модераторы"</string> <string name="screen_room_change_role_section_moderators">"Модераторы"</string>
<string name="screen_room_change_role_section_users">"Участники"</string> <string name="screen_room_change_role_section_users">"Участники"</string>
<string name="screen_room_change_role_unsaved_changes_description">"У вас есть несохраненные изменения."</string> <string name="screen_room_change_role_unsaved_changes_description">"У Вас есть несохраненные изменения."</string>
<string name="screen_room_change_role_unsaved_changes_title">"Сохранить изменения?"</string> <string name="screen_room_change_role_unsaved_changes_title">"Сохранить изменения?"</string>
<string name="screen_room_member_list_banned_empty">"В этой комнате нет заблокированных пользователей."</string> <string name="screen_room_member_list_banned_empty">"В этой комнате нет заблокированных пользователей."</string>
<plurals name="screen_room_member_list_banned_header_title"> <plurals name="screen_room_member_list_banned_header_title">
@@ -6,6 +6,7 @@
<string name="screen_room_change_permissions_delete_messages">"Вилучати повідомлення"</string> <string name="screen_room_change_permissions_delete_messages">"Вилучати повідомлення"</string>
<string name="screen_room_change_permissions_everyone">"Учасник"</string> <string name="screen_room_change_permissions_everyone">"Учасник"</string>
<string name="screen_room_change_permissions_invite_people">"Запрошувати людей"</string> <string name="screen_room_change_permissions_invite_people">"Запрошувати людей"</string>
<string name="screen_room_change_permissions_live_location">"Ділитися місцезнаходженням у реальному часі"</string>
<string name="screen_room_change_permissions_manage_space">"Керувати простором"</string> <string name="screen_room_change_permissions_manage_space">"Керувати простором"</string>
<string name="screen_room_change_permissions_manage_space_rooms">"Керувати кімнатами"</string> <string name="screen_room_change_permissions_manage_space_rooms">"Керувати кімнатами"</string>
<string name="screen_room_change_permissions_member_moderation">"Керувати учасниками"</string> <string name="screen_room_change_permissions_member_moderation">"Керувати учасниками"</string>
@@ -258,6 +258,10 @@ class RoomDetailsFlowNode(
override fun navigateToInviteMembers() { override fun navigateToInviteMembers() {
backstack.push(NavTarget.InviteMembers) backstack.push(NavTarget.InviteMembers)
} }
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
}
} }
createNode<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback)) createNode<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback))
} }
@@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.appyx.launchMolecule
import io.element.android.libraries.architecture.callback import io.element.android.libraries.architecture.callback
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@ContributesNode(RoomScope::class) @ContributesNode(RoomScope::class)
@@ -41,6 +42,7 @@ class RoomMemberListNode(
interface Callback : Plugin { interface Callback : Plugin {
fun navigateToRoomMemberDetails(roomMemberId: UserId) fun navigateToRoomMemberDetails(roomMemberId: UserId)
fun navigateToInviteMembers() fun navigateToInviteMembers()
fun navigateToAvatarPreview(username: String, avatarUrl: String)
} }
private val callback: Callback = callback() private val callback: Callback = callback()
@@ -82,6 +84,11 @@ class RoomMemberListNode(
else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target)) else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
} }
}, },
onAvatarClick = { user ->
user.avatarUrl?.let { url ->
callback.navigateToAvatarPreview(user.getBestName(), url)
}
},
modifier = Modifier, modifier = Modifier,
) )
} }
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_edit_room_address_title">"Рэдагаваць адрас"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Пры абнаўленні налад апавяшчэнняў адбылася памылка."</string> <string name="screen_notification_settings_edit_failed_updating_default_mode">"Пры абнаўленні налад апавяшчэнняў адбылася памылка."</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш хатні сервер не падтрымлівае гэтую опцыю ў зашыфраваных пакоях, вы можаце не атрымаць апавяшчэнне ў некаторых пакоях."</string> <string name="screen_notification_settings_mentions_only_disclaimer">"Ваш хатні сервер не падтрымлівае гэтую опцыю ў зашыфраваных пакоях, вы можаце не атрымаць апавяшчэнне ў некаторых пакоях."</string>
<string name="screen_polls_history_title">"Апытанні"</string> <string name="screen_polls_history_title">"Апытанні"</string>
<string name="screen_room_change_permissions_administrators">"Толькі адміністратары"</string> <string name="screen_room_change_permissions_administrators">"Адміністратар"</string>
<string name="screen_room_change_permissions_ban_people">"Заблакіраваць людзей"</string> <string name="screen_room_change_permissions_ban_people">"Заблакіраваць людзей"</string>
<string name="screen_room_change_permissions_delete_messages">"Выдаліць паведамленні"</string> <string name="screen_room_change_permissions_delete_messages">"Выдаліць паведамленні"</string>
<string name="screen_room_change_permissions_invite_people">"Запрашайце людзей і прымайце запыты на далучэнне"</string> <string name="screen_room_change_permissions_everyone">"Удзельнік"</string>
<string name="screen_room_change_permissions_invite_people">"Запрасіць людзей"</string>
<string name="screen_room_change_permissions_messages_and_content">"Паведамленні і змест"</string> <string name="screen_room_change_permissions_messages_and_content">"Паведамленні і змест"</string>
<string name="screen_room_change_permissions_moderators">"Адміністратары і мадэратары"</string> <string name="screen_room_change_permissions_moderators">"Мадэратар"</string>
<string name="screen_room_change_permissions_remove_people">"Выдаляйце людзей і адхіляйце запыты на далучэнне"</string> <string name="screen_room_change_permissions_remove_people">"Выдаляйце людзей і адхіляйце запыты на далучэнне"</string>
<string name="screen_room_change_permissions_room_avatar">"Змяніць аватар пакоя"</string> <string name="screen_room_change_permissions_room_avatar">"Змяніць аватар пакоя"</string>
<string name="screen_room_change_permissions_room_details">"Рэдагаваць пакой"</string> <string name="screen_room_change_permissions_room_details">"Рэдагаваць пакой"</string>
@@ -52,8 +54,9 @@
<string name="screen_room_details_pinned_events_row_title">"Замацаваныя паведамленні"</string> <string name="screen_room_details_pinned_events_row_title">"Замацаваныя паведамленні"</string>
<string name="screen_room_details_profile_row_title">"Профіль"</string> <string name="screen_room_details_profile_row_title">"Профіль"</string>
<string name="screen_room_details_roles_and_permissions">"Ролі і дазволы"</string> <string name="screen_room_details_roles_and_permissions">"Ролі і дазволы"</string>
<string name="screen_room_details_room_name_label">"Імя"</string>
<string name="screen_room_details_security_title">"Бяспека"</string> <string name="screen_room_details_security_title">"Бяспека"</string>
<string name="screen_room_details_share_room_title">"Падзяліцца пакоем"</string> <string name="screen_room_details_share_room_title">"Абагуліць пакой"</string>
<string name="screen_room_details_title">"Інфармацыя аб пакоі"</string> <string name="screen_room_details_title">"Інфармацыя аб пакоі"</string>
<string name="screen_room_details_topic_title">"Тэма"</string> <string name="screen_room_details_topic_title">"Тэма"</string>
<string name="screen_room_details_updating_room">"Ідзе абнаўленне пакоя…"</string> <string name="screen_room_details_updating_room">"Ідзе абнаўленне пакоя…"</string>
@@ -69,8 +72,8 @@
<string name="screen_room_member_list_manage_member_unban_message">"Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць."</string> <string name="screen_room_member_list_manage_member_unban_message">"Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць."</string>
<string name="screen_room_member_list_mode_banned">"Заблакіраваныя"</string> <string name="screen_room_member_list_mode_banned">"Заблакіраваныя"</string>
<string name="screen_room_member_list_mode_members">"Удзельнікі"</string> <string name="screen_room_member_list_mode_members">"Удзельнікі"</string>
<string name="screen_room_member_list_role_administrator">"Толькі адміністратары"</string> <string name="screen_room_member_list_role_administrator">"Адміністратар"</string>
<string name="screen_room_member_list_role_moderator">"Адміністратары і мадэратары"</string> <string name="screen_room_member_list_role_moderator">"Мадэратар"</string>
<string name="screen_room_member_list_room_members_header_title">"Удзельнікі пакоя"</string> <string name="screen_room_member_list_room_members_header_title">"Удзельнікі пакоя"</string>
<string name="screen_room_member_list_unbanning_user">"Разблакіроўка %1$s"</string> <string name="screen_room_member_list_unbanning_user">"Разблакіроўка %1$s"</string>
<string name="screen_room_notification_settings_allow_custom">"Дазволіць уласную наладу"</string> <string name="screen_room_notification_settings_allow_custom">"Дазволіць уласную наладу"</string>
@@ -102,4 +105,5 @@
<string name="screen_room_roles_and_permissions_title">"Ролі і дазволы"</string> <string name="screen_room_roles_and_permissions_title">"Ролі і дазволы"</string>
<string name="screen_roomlist_mark_as_read">"Пазначыць як прачытанае"</string> <string name="screen_roomlist_mark_as_read">"Пазначыць як прачытанае"</string>
<string name="screen_roomlist_mark_as_unread">"Пазначыць як непрачытанае"</string> <string name="screen_roomlist_mark_as_unread">"Пазначыць як непрачытанае"</string>
<string name="screen_security_and_privacy_room_address_section_header">"Адрас"</string>
</resources> </resources>
@@ -6,7 +6,7 @@
<string name="screen_edit_room_address_room_address_section_footer">"Необходимо задать адрес комнаты, чтобы опубликовать ее в каталоге комнат."</string> <string name="screen_edit_room_address_room_address_section_footer">"Необходимо задать адрес комнаты, чтобы опубликовать ее в каталоге комнат."</string>
<string name="screen_edit_room_address_title">"Редактировать адрес комнаты"</string> <string name="screen_edit_room_address_title">"Редактировать адрес комнаты"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Произошла ошибка при обновлении настройки уведомления."</string> <string name="screen_notification_settings_edit_failed_updating_default_mode">"Произошла ошибка при обновлении настройки уведомления."</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш сервер не поддерживает эту опцию для зашифрованных комнат, вы можете не получать некоторые уведомления."</string> <string name="screen_notification_settings_mentions_only_disclaimer">"Ваш сервер не поддерживает эту опцию для зашифрованных комнат, Вы можете не получать некоторые уведомления."</string>
<string name="screen_polls_history_title">"Опросы"</string> <string name="screen_polls_history_title">"Опросы"</string>
<string name="screen_room_change_permissions_administrators">"Администраторы"</string> <string name="screen_room_change_permissions_administrators">"Администраторы"</string>
<string name="screen_room_change_permissions_ban_people">"Блокировать пользователей"</string> <string name="screen_room_change_permissions_ban_people">"Блокировать пользователей"</string>
@@ -23,7 +23,7 @@
<string name="screen_room_change_permissions_room_topic">"Менять тему комнаты"</string> <string name="screen_room_change_permissions_room_topic">"Менять тему комнаты"</string>
<string name="screen_room_change_permissions_send_messages">"Отправлять сообщения"</string> <string name="screen_room_change_permissions_send_messages">"Отправлять сообщения"</string>
<string name="screen_room_change_role_administrators_title">"Редактировать администраторов"</string> <string name="screen_room_change_role_administrators_title">"Редактировать администраторов"</string>
<string name="screen_room_change_role_confirm_add_admin_description">"Вы не сможете отменить это действие. Вы даете пользователю такой же уровень прав, как и у вас"</string> <string name="screen_room_change_role_confirm_add_admin_description">"Вы не сможете отменить это действие. Вы даете пользователю такой же уровень прав, как и у Вас"</string>
<string name="screen_room_change_role_confirm_add_admin_title">"Добавить администратора?"</string> <string name="screen_room_change_role_confirm_add_admin_title">"Добавить администратора?"</string>
<string name="screen_room_change_role_confirm_change_owners_description">"Отменить данное действие будет невозможно. Права передадутся выбранным пользователям. После вашего выхода действие станет необратимым."</string> <string name="screen_room_change_role_confirm_change_owners_description">"Отменить данное действие будет невозможно. Права передадутся выбранным пользователям. После вашего выхода действие станет необратимым."</string>
<string name="screen_room_change_role_confirm_change_owners_title">"Передать владение?"</string> <string name="screen_room_change_role_confirm_change_owners_title">"Передать владение?"</string>
@@ -39,7 +39,7 @@
<string name="screen_room_change_role_section_administrators">"Администраторы"</string> <string name="screen_room_change_role_section_administrators">"Администраторы"</string>
<string name="screen_room_change_role_section_moderators">"Модераторы"</string> <string name="screen_room_change_role_section_moderators">"Модераторы"</string>
<string name="screen_room_change_role_section_users">"Участники"</string> <string name="screen_room_change_role_section_users">"Участники"</string>
<string name="screen_room_change_role_unsaved_changes_description">"У вас есть несохраненные изменения."</string> <string name="screen_room_change_role_unsaved_changes_description">"У Вас есть несохраненные изменения."</string>
<string name="screen_room_change_role_unsaved_changes_title">"Сохранить изменения?"</string> <string name="screen_room_change_role_unsaved_changes_title">"Сохранить изменения?"</string>
<string name="screen_room_details_add_topic_title">"Добавить тему"</string> <string name="screen_room_details_add_topic_title">"Добавить тему"</string>
<string name="screen_room_details_badge_encrypted">"Зашифровано"</string> <string name="screen_room_details_badge_encrypted">"Зашифровано"</string>
@@ -51,8 +51,8 @@
<string name="screen_room_details_encryption_enabled_subtitle">"Сообщения зашифрованы. Только вы и ваши собеседники имеют доступ к сообщениям."</string> <string name="screen_room_details_encryption_enabled_subtitle">"Сообщения зашифрованы. Только вы и ваши собеседники имеют доступ к сообщениям."</string>
<string name="screen_room_details_encryption_enabled_title">"Шифрование сообщений включено"</string> <string name="screen_room_details_encryption_enabled_title">"Шифрование сообщений включено"</string>
<string name="screen_room_details_error_loading_notification_settings">"Произошла ошибка при загрузке настроек уведомлений."</string> <string name="screen_room_details_error_loading_notification_settings">"Произошла ошибка при загрузке настроек уведомлений."</string>
<string name="screen_room_details_error_muting">"Не удалось отключить звук в этой комнате, попробуйте еще раз."</string> <string name="screen_room_details_error_muting">"Не удалось отключить звук в этой комнате, попробуйте ещё раз."</string>
<string name="screen_room_details_error_unmuting">"Не удалось включить звук в эту комнату, попробуйте еще раз."</string> <string name="screen_room_details_error_unmuting">"Не удалось включить звук в этой комнате, попробуйте ещё раз."</string>
<string name="screen_room_details_invite_people_dont_close">"Не закрывайте приложение, пока не закончите."</string> <string name="screen_room_details_invite_people_dont_close">"Не закрывайте приложение, пока не закончите."</string>
<string name="screen_room_details_invite_people_preparing">"Подготовка приглашений…"</string> <string name="screen_room_details_invite_people_preparing">"Подготовка приглашений…"</string>
<string name="screen_room_details_invite_people_title">"Пригласить в комнату"</string> <string name="screen_room_details_invite_people_title">"Пригласить в комнату"</string>
@@ -63,7 +63,7 @@
<string name="screen_room_details_notification_mode_custom">"Пользовательские"</string> <string name="screen_room_details_notification_mode_custom">"Пользовательские"</string>
<string name="screen_room_details_notification_mode_default">"По умолчанию"</string> <string name="screen_room_details_notification_mode_default">"По умолчанию"</string>
<string name="screen_room_details_notification_title">"Уведомления"</string> <string name="screen_room_details_notification_title">"Уведомления"</string>
<string name="screen_room_details_pinned_events_row_title">"Закрепленные сообщения"</string> <string name="screen_room_details_pinned_events_row_title">"Закреплённые сообщения"</string>
<string name="screen_room_details_profile_row_title">"Профиль"</string> <string name="screen_room_details_profile_row_title">"Профиль"</string>
<string name="screen_room_details_requests_to_join_title">"Запросы на вступление"</string> <string name="screen_room_details_requests_to_join_title">"Запросы на вступление"</string>
<string name="screen_room_details_roles_and_permissions">"Роли и разрешения"</string> <string name="screen_room_details_roles_and_permissions">"Роли и разрешения"</string>
@@ -113,8 +113,8 @@
<string name="screen_room_notification_settings_default_setting_title">"Настройка по умолчанию"</string> <string name="screen_room_notification_settings_default_setting_title">"Настройка по умолчанию"</string>
<string name="screen_room_notification_settings_edit_remove_setting">"Удалить пользовательскую настройку"</string> <string name="screen_room_notification_settings_edit_remove_setting">"Удалить пользовательскую настройку"</string>
<string name="screen_room_notification_settings_error_loading_settings">"Произошла ошибка при загрузке настроек уведомлений."</string> <string name="screen_room_notification_settings_error_loading_settings">"Произошла ошибка при загрузке настроек уведомлений."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Не удалось восстановить режим по умолчанию, попробуйте еще раз."</string> <string name="screen_room_notification_settings_error_restoring_default">"Не удалось восстановить режим по умолчанию, попробуйте ещё раз."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Не удалось настроить режим, попробуйте еще раз."</string> <string name="screen_room_notification_settings_error_setting_mode">"Не удалось настроить режим, попробуйте ещё раз."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, вы не будете получать уведомления в этой комнате."</string> <string name="screen_room_notification_settings_mentions_only_disclaimer">"Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, вы не будете получать уведомления в этой комнате."</string>
<string name="screen_room_notification_settings_mode_all_messages">"О всех сообщениях"</string> <string name="screen_room_notification_settings_mode_all_messages">"О всех сообщениях"</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Только упоминания и ключевые слова"</string> <string name="screen_room_notification_settings_mode_mentions_and_keywords">"Только упоминания и ключевые слова"</string>
@@ -153,7 +153,7 @@
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Любой желающий может присоединиться"</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Любой желающий может присоединиться"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Кто угодно"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Кто угодно"</string>
<string name="screen_security_and_privacy_room_access_footer">"Выберите, участники каких пространств могут присоединиться к этой комнате без приглашения.%1$s"</string> <string name="screen_security_and_privacy_room_access_footer">"Выберите, участники каких пространств могут присоединиться к этой комнате без приглашения.%1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Управление пространством"</string> <string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Управление пространствами"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Присоединиться могут только приглашенные люди."</string> <string name="screen_security_and_privacy_room_access_invite_only_option_description">"Присоединиться могут только приглашенные люди."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Только по приглашению"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Только по приглашению"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Доступ"</string> <string name="screen_security_and_privacy_room_access_section_header">"Доступ"</string>
@@ -17,6 +17,7 @@ interface RoomMemberModerationRenderer {
fun Render( fun Render(
state: RoomMemberModerationState, state: RoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit, onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)?,
modifier: Modifier, modifier: Modifier,
) )
} }
@@ -25,10 +25,11 @@ class DefaultRoomMemberModerationRenderer : RoomMemberModerationRenderer {
override fun Render( override fun Render(
state: RoomMemberModerationState, state: RoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit, onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)?,
modifier: Modifier modifier: Modifier
) { ) {
if (state is InternalRoomMemberModerationState) { if (state is InternalRoomMemberModerationState) {
RoomMemberModerationView(state, onSelectAction, modifier) RoomMemberModerationView(modifier = modifier, state = state, onSelectAction = onSelectAction, onAvatarClick = onAvatarClick)
} else { } else {
SideEffect { SideEffect {
Timber.d("RoomMemberModerationRenderer: Render called with unsupported state: $state") Timber.d("RoomMemberModerationRenderer: Render called with unsupported state: $state")
@@ -12,6 +12,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
@@ -64,6 +65,7 @@ import timber.log.Timber
fun RoomMemberModerationView( fun RoomMemberModerationView(
state: InternalRoomMemberModerationState, state: InternalRoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit, onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
@@ -73,6 +75,7 @@ fun RoomMemberModerationView(
user = selectedUser, user = selectedUser,
actions = state.actions, actions = state.actions,
onSelectAction = onSelectAction, onSelectAction = onSelectAction,
onAvatarClick = onAvatarClick,
onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) }, onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) },
) )
} }
@@ -214,6 +217,7 @@ private fun RoomMemberActionsBottomSheet(
user: MatrixUser, user: MatrixUser,
actions: ImmutableList<ModerationActionState>, actions: ImmutableList<ModerationActionState>,
onSelectAction: (ModerationAction, MatrixUser) -> Unit, onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)? = null,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@@ -240,10 +244,11 @@ private fun RoomMemberActionsBottomSheet(
modifier = Modifier modifier = Modifier
.padding(bottom = 24.dp) .padding(bottom = 24.dp)
.align(Alignment.CenterHorizontally) .align(Alignment.CenterHorizontally)
.clickable { .clickable(enabled = user.avatarUrl != null && onAvatarClick != null) {
coroutineScope.launch { coroutineScope.launch {
onSelectAction(ModerationAction.DisplayProfile, user)
bottomSheetState.hide() bottomSheetState.hide()
onAvatarClick?.invoke(user)
onDismiss()
} }
} }
) )
@@ -282,8 +287,8 @@ private fun RoomMemberActionsBottomSheet(
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserProfile())), leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserProfile())),
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
onSelectAction(action, user)
bottomSheetState.hide() bottomSheetState.hide()
onSelectAction(action, user)
} }
}, },
enabled = actionState.isEnabled enabled = actionState.isEnabled
@@ -340,16 +345,18 @@ private fun RoomMemberActionsBottomSheet(
@PreviewsDayNight @PreviewsDayNight
@Composable @Composable
internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) { internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) {
val isDoingAction = listOf(state.kickUserAsyncAction, state.banUserAsyncAction, state.unbanUserAsyncAction).any { it is AsyncAction.Loading }
val modifier = if (isDoingAction) {
Modifier.fillMaxWidth().heightIn(min = 64.dp)
} else {
Modifier.fillMaxSize()
}
ElementPreview { ElementPreview {
Box( Box(modifier) {
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 64.dp)
) {
RoomMemberModerationView( RoomMemberModerationView(
state = state, state = state,
onSelectAction = { _, _ -> onSelectAction = { _, _ -> },
}, onAvatarClick = {},
) )
} }
} }
@@ -7,7 +7,7 @@
<string name="screen_bottom_sheet_manage_room_member_banning_user">"Блакіроўка %1$s"</string> <string name="screen_bottom_sheet_manage_room_member_banning_user">"Блакіроўка %1$s"</string>
<string name="screen_bottom_sheet_manage_room_member_kick_member_confirmation_description">"Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць."</string> <string name="screen_bottom_sheet_manage_room_member_kick_member_confirmation_description">"Яны змогуць зноў далучыцца да гэтага пакоя, калі іх запросяць."</string>
<string name="screen_bottom_sheet_manage_room_member_member_user_info">"Прагляд профілю"</string> <string name="screen_bottom_sheet_manage_room_member_member_user_info">"Прагляд профілю"</string>
<string name="screen_bottom_sheet_manage_room_member_remove">"Выдаліць удзельніка з пакоя"</string> <string name="screen_bottom_sheet_manage_room_member_remove">"Выдаліць удзельніка"</string>
<string name="screen_bottom_sheet_manage_room_member_remove_confirmation_title">"Выдаліць удзельніка і забараніць далучацца ў будучыні?"</string> <string name="screen_bottom_sheet_manage_room_member_remove_confirmation_title">"Выдаліць удзельніка і забараніць далучацца ў будучыні?"</string>
<string name="screen_bottom_sheet_manage_room_member_removing_user">"Выдаленне %1$s…"</string> <string name="screen_bottom_sheet_manage_room_member_removing_user">"Выдаленне %1$s…"</string>
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_action">"Разблакіраваць"</string> <string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_action">"Разблакіраваць"</string>
@@ -21,6 +21,7 @@ import io.element.android.features.roommembermoderation.api.RoomMemberModeration
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
@@ -48,6 +49,8 @@ class RoomMemberModerationViewTest {
onSelectAction = callback onSelectAction = callback
) )
clickOn(R.string.screen_bottom_sheet_manage_room_member_member_user_info) clickOn(R.string.screen_bottom_sheet_manage_room_member_member_user_info)
// Gives time for bottomsheet to hide
mainClock.advanceTimeBy(1_000)
} }
} }
@@ -217,11 +220,13 @@ class RoomMemberModerationViewTest {
private fun AndroidComposeUiTest<ComponentActivity>.setRoomMemberModerationView( private fun AndroidComposeUiTest<ComponentActivity>.setRoomMemberModerationView(
state: InternalRoomMemberModerationState, state: InternalRoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit = EnsureNeverCalledWithTwoParams(), onSelectAction: (ModerationAction, MatrixUser) -> Unit = EnsureNeverCalledWithTwoParams(),
onAvatarClick: ((MatrixUser) -> Unit)? = EnsureNeverCalledWithParam(),
) { ) {
setSafeContent { setSafeContent {
RoomMemberModerationView( RoomMemberModerationView(
state = state, state = state,
onSelectAction = onSelectAction, onSelectAction = onSelectAction,
onAvatarClick = onAvatarClick,
) )
} }
} }
@@ -8,13 +8,13 @@
<string name="screen_chat_backup_key_storage_toggle_description">"Загрузить ключи с этого устройства"</string> <string name="screen_chat_backup_key_storage_toggle_description">"Загрузить ключи с этого устройства"</string>
<string name="screen_chat_backup_key_storage_toggle_title">"Разрешить хранение ключей"</string> <string name="screen_chat_backup_key_storage_toggle_title">"Разрешить хранение ключей"</string>
<string name="screen_chat_backup_recovery_action_change">"Изменить ключ восстановления"</string> <string name="screen_chat_backup_recovery_action_change">"Изменить ключ восстановления"</string>
<string name="screen_chat_backup_recovery_action_change_description">"Если вы потеряли доступ к другим устройствам, то сможете восстановить свою идентификацию и историю сообщений с помощью ключа восстановления."</string> <string name="screen_chat_backup_recovery_action_change_description">"Ваши чаты автоматически резервируются с помощью сквозного шифрования. Для восстановления резервной копии и сохранения Вашей цифровой личности в случае потери доступа ко всем устройствам потребуется ключ восстановления."</string>
<string name="screen_chat_backup_recovery_action_confirm">"Введите ключ восстановления"</string> <string name="screen_chat_backup_recovery_action_confirm">"Введите ключ восстановления"</string>
<string name="screen_chat_backup_recovery_action_confirm_description">"В настоящее время резервная копия ваших чатов не синхронизирована."</string> <string name="screen_chat_backup_recovery_action_confirm_description">"В настоящее время резервная копия ваших чатов не синхронизирована."</string>
<string name="screen_chat_backup_recovery_action_setup">"Получить ключ восстановления"</string> <string name="screen_chat_backup_recovery_action_setup">"Получить ключ восстановления"</string>
<string name="screen_chat_backup_recovery_action_setup_description">"Ваши чаты автоматически резервируются с использованием сквозного шифрования. Для восстановления этой резервной копии и сохранения вашей цифровой личности в случае потери доступа ко всем вашим устройствам вам потребуется ключ восстановления."</string> <string name="screen_chat_backup_recovery_action_setup_description">"Ваши чаты автоматически резервируются с использованием сквозного шифрования. Для восстановления этой резервной копии и сохранения вашей цифровой личности в случае потери доступа ко всем вашим устройствам вам потребуется ключ восстановления."</string>
<string name="screen_create_new_recovery_key_list_item_1">"Откройте %1$s на компьютере"</string> <string name="screen_create_new_recovery_key_list_item_1">"Откройте %1$s на компьютере"</string>
<string name="screen_create_new_recovery_key_list_item_2">"Войдите в свой аккаунт еще раз"</string> <string name="screen_create_new_recovery_key_list_item_2">"Войдите в свой аккаунт ещё раз"</string>
<string name="screen_create_new_recovery_key_list_item_3">"Когда потребуется подтвердить устройство, выберите %1$s"</string> <string name="screen_create_new_recovery_key_list_item_3">"Когда потребуется подтвердить устройство, выберите %1$s"</string>
<string name="screen_create_new_recovery_key_list_item_3_reset_all">"«Сбросить все»"</string> <string name="screen_create_new_recovery_key_list_item_3_reset_all">"«Сбросить все»"</string>
<string name="screen_create_new_recovery_key_list_item_4">"Следуйте инструкциям, чтобы создать новый ключ восстановления"</string> <string name="screen_create_new_recovery_key_list_item_4">"Следуйте инструкциям, чтобы создать новый ключ восстановления"</string>
@@ -24,7 +24,7 @@
<string name="screen_encryption_reset_bullet_1">"Данные вашего аккаунта, контакты, настройки и список чатов будут сохранены"</string> <string name="screen_encryption_reset_bullet_1">"Данные вашего аккаунта, контакты, настройки и список чатов будут сохранены"</string>
<string name="screen_encryption_reset_bullet_2">"Вы потеряете историю тех сообщений, которые хранятся только на сервере"</string> <string name="screen_encryption_reset_bullet_2">"Вы потеряете историю тех сообщений, которые хранятся только на сервере"</string>
<string name="screen_encryption_reset_bullet_3">"Вам нужно будет заново подтвердить все существующие устройства и контакты."</string> <string name="screen_encryption_reset_bullet_3">"Вам нужно будет заново подтвердить все существующие устройства и контакты."</string>
<string name="screen_encryption_reset_footer">"Сбрасывайте ключ шифрования только в том случае, если у вас нет доступа к другому устройству, на котором выполнен вход, и вы потеряли ключ восстановления."</string> <string name="screen_encryption_reset_footer">"Сбрасывайте ключ шифрования только в том случае, если у Вас нет доступа к другому устройству, на котором выполнен вход, и Вы потеряли ключ восстановления."</string>
<string name="screen_encryption_reset_title">"Не можете подтвердить? Вам потребуется сбросить свою цифровую идентификацию."</string> <string name="screen_encryption_reset_title">"Не можете подтвердить? Вам потребуется сбросить свою цифровую идентификацию."</string>
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Удалить"</string> <string name="screen_key_backup_disable_confirmation_action_turn_off">"Удалить"</string>
<string name="screen_key_backup_disable_confirmation_description">"Вы потеряете зашифрованные сообщения, если выйдете из всех устройств."</string> <string name="screen_key_backup_disable_confirmation_description">"Вы потеряете зашифрованные сообщения, если выйдете из всех устройств."</string>
@@ -40,9 +40,9 @@
<string name="screen_recovery_key_change_title">"Изменить ключ восстановления?"</string> <string name="screen_recovery_key_change_title">"Изменить ключ восстановления?"</string>
<string name="screen_recovery_key_confirm_create_new_recovery_key">"Создать новый ключ восстановления"</string> <string name="screen_recovery_key_confirm_create_new_recovery_key">"Создать новый ключ восстановления"</string>
<string name="screen_recovery_key_confirm_description">"Убедитесь, что никто не подсматривает)"</string> <string name="screen_recovery_key_confirm_description">"Убедитесь, что никто не подсматривает)"</string>
<string name="screen_recovery_key_confirm_error_content">"Пожалуйста, попробуйте еще раз, чтобы подтвердить доступ к резервной копии чата."</string> <string name="screen_recovery_key_confirm_error_content">"Пожалуйста, попробуйте ещё раз, чтобы подтвердить доступ к резервной копии чата."</string>
<string name="screen_recovery_key_confirm_error_title">"Неверный ключ восстановления"</string> <string name="screen_recovery_key_confirm_error_title">"Неверный ключ восстановления"</string>
<string name="screen_recovery_key_confirm_key_description">"Если у вас есть пароль для восстановления или секретный пароль/ключ, это тоже сработает."</string> <string name="screen_recovery_key_confirm_key_description">"Если у Вас есть ключ/фраза для восстановления, это тоже сработает."</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Введите…"</string> <string name="screen_recovery_key_confirm_key_placeholder">"Введите…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Потеряли ключ восстановления?"</string> <string name="screen_recovery_key_confirm_lost_recovery_key">"Потеряли ключ восстановления?"</string>
<string name="screen_recovery_key_confirm_success">"Ключ восстановления подтвержден"</string> <string name="screen_recovery_key_confirm_success">"Ключ восстановления подтвержден"</string>
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_edit_room_address_title">"Рэдагаваць адрас"</string>
<string name="screen_manage_authorized_spaces_your_spaces_section_title">"Вашы прасторы"</string> <string name="screen_manage_authorized_spaces_your_spaces_section_title">"Вашы прасторы"</string>
<string name="screen_security_and_privacy_room_address_section_header">"Адрас"</string>
</resources> </resources>
@@ -3,7 +3,7 @@
<string name="screen_edit_room_address_room_address_section_footer">"Необходимо задать адрес комнаты, чтобы опубликовать ее в каталоге комнат."</string> <string name="screen_edit_room_address_room_address_section_footer">"Необходимо задать адрес комнаты, чтобы опубликовать ее в каталоге комнат."</string>
<string name="screen_edit_room_address_title">"Редактировать адрес комнаты"</string> <string name="screen_edit_room_address_title">"Редактировать адрес комнаты"</string>
<string name="screen_manage_authorized_spaces_header">"Пространства, где участники могут присоединиться к комнате без приглашения."</string> <string name="screen_manage_authorized_spaces_header">"Пространства, где участники могут присоединиться к комнате без приглашения."</string>
<string name="screen_manage_authorized_spaces_title">"Управление пространством"</string> <string name="screen_manage_authorized_spaces_title">"Управление пространствами"</string>
<string name="screen_manage_authorized_spaces_unknown_space">"(неизвестное пространство)"</string> <string name="screen_manage_authorized_spaces_unknown_space">"(неизвестное пространство)"</string>
<string name="screen_manage_authorized_spaces_unknown_spaces_section_title">"Другие пространства, в которых вы не состоите"</string> <string name="screen_manage_authorized_spaces_unknown_spaces_section_title">"Другие пространства, в которых вы не состоите"</string>
<string name="screen_manage_authorized_spaces_your_spaces_section_title">"Ваши пространства"</string> <string name="screen_manage_authorized_spaces_your_spaces_section_title">"Ваши пространства"</string>
@@ -23,7 +23,7 @@
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Любой желающий может присоединиться"</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Любой желающий может присоединиться"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Кто угодно"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Кто угодно"</string>
<string name="screen_security_and_privacy_room_access_footer">"Выберите, участники каких пространств могут присоединиться к этой комнате без приглашения.%1$s"</string> <string name="screen_security_and_privacy_room_access_footer">"Выберите, участники каких пространств могут присоединиться к этой комнате без приглашения.%1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Управление пространством"</string> <string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Управление пространствами"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Присоединиться могут только приглашенные люди."</string> <string name="screen_security_and_privacy_room_access_invite_only_option_description">"Присоединиться могут только приглашенные люди."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Только по приглашению"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Только по приглашению"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Доступ"</string> <string name="screen_security_and_privacy_room_access_section_header">"Доступ"</string>
@@ -3,6 +3,6 @@
<string name="screen_signed_out_reason_1">"Вы изменили свой пароль в другой сессии"</string> <string name="screen_signed_out_reason_1">"Вы изменили свой пароль в другой сессии"</string>
<string name="screen_signed_out_reason_2">"Вы удалили сессию из другой сессии"</string> <string name="screen_signed_out_reason_2">"Вы удалили сессию из другой сессии"</string>
<string name="screen_signed_out_reason_3">"Администратор вашего сервера аннулировал ваш доступ"</string> <string name="screen_signed_out_reason_3">"Администратор вашего сервера аннулировал ваш доступ"</string>
<string name="screen_signed_out_subtitle">"Возможно, вы вышли из системы по одной из причин, перечисленных ниже. Пожалуйста, войдите в систему еще раз, чтобы продолжить использование %s."</string> <string name="screen_signed_out_subtitle">"Возможно, Вы вышли из системы по одной из причин, перечисленных ниже. Пожалуйста, войдите в систему ещё раз, чтобы продолжить использовать %s."</string>
<string name="screen_signed_out_title">"Вы вышли из системы"</string> <string name="screen_signed_out_title">"Вы вышли из системы"</string>
</resources> </resources>
@@ -11,11 +11,11 @@
<string name="screen_identity_use_another_device">"Выкарыстоўвайце іншую прыладу"</string> <string name="screen_identity_use_another_device">"Выкарыстоўвайце іншую прыладу"</string>
<string name="screen_identity_waiting_on_other_device">"Чаканне на іншай прыладзе…"</string> <string name="screen_identity_waiting_on_other_device">"Чаканне на іншай прыладзе…"</string>
<string name="screen_session_verification_cancelled_subtitle">"Здаецца, нешта не так. Альбо час чакання запыту скончыўся, альбо запыт быў адхілены."</string> <string name="screen_session_verification_cancelled_subtitle">"Здаецца, нешта не так. Альбо час чакання запыту скончыўся, альбо запыт быў адхілены."</string>
<string name="screen_session_verification_compare_emojis_subtitle">"Пераканайцеся, што прыведзеныя ніжэй эмодзі супадаюць з эмодзі, паказанымі ў вашым іншым сеансе."</string> <string name="screen_session_verification_compare_emojis_subtitle">"Пераканайцеся, што эмодзі ніжэй супадаюць з тымі, што паказаны на вашай іншай прыладзе."</string>
<string name="screen_session_verification_compare_emojis_title">"Параўнайце эмодзі"</string> <string name="screen_session_verification_compare_emojis_title">"Параўнайце эмодзі"</string>
<string name="screen_session_verification_compare_numbers_subtitle">"Пераканайцеся, што прыведзеныя ніжэй лічбы супадаюць з лічбамі, паказанымі ў іншым сеансе."</string> <string name="screen_session_verification_compare_numbers_subtitle">"Пераканайцеся, што прыведзеныя ніжэй лічбы супадаюць з лічбамі, паказанымі ў іншым сеансе."</string>
<string name="screen_session_verification_compare_numbers_title">"Параўнайце лічбы"</string> <string name="screen_session_verification_compare_numbers_title">"Параўнайце лічбы"</string>
<string name="screen_session_verification_complete_subtitle">"Ваш новы сеанс пацверджаны. Ён мае доступ да вашых зашыфраваных паведамленняў, і іншыя карыстальнікі будуць лічыць яго давераным."</string> <string name="screen_session_verification_complete_subtitle">"Цяпер вы можаце бяспечна чытаць або адпраўляць паведамленні на іншай прыладзе."</string>
<string name="screen_session_verification_device_verified">"Прылада праверана"</string> <string name="screen_session_verification_device_verified">"Прылада праверана"</string>
<string name="screen_session_verification_enter_recovery_key">"Увядзіце ключ аднаўлення"</string> <string name="screen_session_verification_enter_recovery_key">"Увядзіце ключ аднаўлення"</string>
<string name="screen_session_verification_open_existing_session_subtitle">"Дакажыце, што гэта вы, каб атрымаць доступ да вашай зашыфраванай гісторыі паведамленняў."</string> <string name="screen_session_verification_open_existing_session_subtitle">"Дакажыце, што гэта вы, каб атрымаць доступ да вашай зашыфраванай гісторыі паведамленняў."</string>
@@ -25,11 +25,11 @@
<string name="screen_session_verification_positive_button_verifying_ongoing">"Чаканне супадзення"</string> <string name="screen_session_verification_positive_button_verifying_ongoing">"Чаканне супадзення"</string>
<string name="screen_session_verification_ready_subtitle">"Параўнайце ўнікальны набор эмодзі."</string> <string name="screen_session_verification_ready_subtitle">"Параўнайце ўнікальны набор эмодзі."</string>
<string name="screen_session_verification_request_accepted_subtitle">"Параўнайце ўнікальныя эмодзі, пераканаўшыся, што яны размешчаны ў тым жа парадку."</string> <string name="screen_session_verification_request_accepted_subtitle">"Параўнайце ўнікальныя эмодзі, пераканаўшыся, што яны размешчаны ў тым жа парадку."</string>
<string name="screen_session_verification_request_success_subtitle">"Ваш новы сеанс пацверджаны. Ён мае доступ да вашых зашыфраваных паведамленняў, і іншыя карыстальнікі будуць лічыць яго давераным."</string> <string name="screen_session_verification_request_success_subtitle">"Цяпер вы можаце бяспечна чытаць або адпраўляць паведамленні на іншай прыладзе."</string>
<string name="screen_session_verification_request_success_title">"Прылада праверана"</string> <string name="screen_session_verification_request_success_title">"Прылада праверана"</string>
<string name="screen_session_verification_they_dont_match">"Яны не супадаюць"</string> <string name="screen_session_verification_they_dont_match">"Яны не супадаюць"</string>
<string name="screen_session_verification_they_match">"Яны супадаюць"</string> <string name="screen_session_verification_they_match">"Яны супадаюць"</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"Для працягу працы прыміце запыт на запуск працэсу праверкі ў іншым сеансе."</string> <string name="screen_session_verification_waiting_to_accept_subtitle">"Для працягу працы прыміце запыт на запуск працэсу праверкі ў іншым сеансе."</string>
<string name="screen_session_verification_waiting_to_accept_title">"Чаканне прыняцця запыту"</string> <string name="screen_session_verification_waiting_to_accept_title">"Чаканне прыняцця запыту"</string>
<string name="screen_signout_in_progress_dialog_content">"Выхад…"</string> <string name="screen_signout_in_progress_dialog_content">"Выдаленне прылады…"</string>
</resources> </resources>
@@ -6,7 +6,7 @@
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string> <string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string> <string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string> <string name="screen_identity_confirmation_use_recovery_key">"Использовать ключ восстановления"</string>
<string name="screen_identity_confirmed_subtitle">"Теперь вы можете безопасно читать и отправлять сообщения, и все, с кем вы общаетесь в чате, также могут доверять этому устройству."</string> <string name="screen_identity_confirmed_subtitle">"Теперь Вы можете безопасно читать и отправлять сообщения, и все, с кем Вы общаетесь в чате, также могут доверять этому устройству."</string>
<string name="screen_identity_confirmed_title">"Устройство проверено"</string> <string name="screen_identity_confirmed_title">"Устройство проверено"</string>
<string name="screen_identity_use_another_device">"Использовать другое устройство"</string> <string name="screen_identity_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_waiting_on_other_device">"Ожидание другого устройства…"</string> <string name="screen_identity_waiting_on_other_device">"Ожидание другого устройства…"</string>
+10 -10
View File
@@ -3,9 +3,9 @@
[versions] [versions]
# Project # Project
android_gradle_plugin = "8.13.2" android_gradle_plugin = "9.2.1"
# When updating this, please also update the version in the file ./idea/kotlinc.xml # When updating this, please also update the version in the file ./idea/kotlinc.xml
kotlin = "2.3.21" kotlin = "2.4.0"
kotlinpoet = "2.3.0" kotlinpoet = "2.3.0"
ksp = "2.3.9" ksp = "2.3.9"
firebaseAppDistribution = "5.2.1" firebaseAppDistribution = "5.2.1"
@@ -46,12 +46,12 @@ showkase = "1.0.5"
# When upgrading this version, check state restoration still works fine. # When upgrading this version, check state restoration still works fine.
appyx = "1.7.1" appyx = "1.7.1"
sqldelight = "2.3.2" sqldelight = "2.3.2"
wysiwyg = "2.41.3" wysiwyg = "2.42.0"
telephoto = "0.19.0" telephoto = "0.19.0"
haze = "1.7.2" haze = "1.7.2"
# Dependency analysis # Dependency analysis
dependencyAnalysis = "3.14.0" dependencyAnalysis = "3.14.1"
# DI # DI
metro = "1.1.1" metro = "1.1.1"
@@ -84,7 +84,7 @@ kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlin
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
# https://firebase.google.com/docs/android/setup#available-libraries # https://firebase.google.com/docs/android/setup#available-libraries
google_firebase_bom = "com.google.firebase:firebase-bom:34.14.0" google_firebase_bom = "com.google.firebase:firebase-bom:34.14.1"
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" } firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" } autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" } ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
@@ -183,7 +183,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
# All new features should not be implemented in the pull request that upgrades the version, developers should # All new features should not be implemented in the pull request that upgrades the version, developers should
# only fix API breaks and may add some TODOs. # only fix API breaks and may add some TODOs.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.06.3" matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.06.11"
# Others # Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
@@ -212,7 +212,7 @@ vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil3", version.ref = "telephoto" } telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil3", version.ref = "telephoto" }
telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" } telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
statemachine = "com.freeletics.flowredux:compose:1.2.2" statemachine = "com.freeletics.flowredux:compose:1.2.2"
maplibre = "org.maplibre.gl:android-sdk:13.2.0" maplibre = "org.maplibre.gl:android-sdk:13.3.0"
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2"
maplibre_compose = "org.maplibre.compose:maplibre-compose:0.13.0" maplibre_compose = "org.maplibre.compose:maplibre-compose:0.13.0"
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2"
@@ -227,7 +227,7 @@ color_picker = "io.mhssn:colorpicker:1.0.0"
# Analytics # Analytics
posthog = "com.posthog:posthog-android:3.47.0" posthog = "com.posthog:posthog-android:3.47.0"
sentry = "io.sentry:sentry-android:8.41.0" sentry = "io.sentry:sentry-android:8.43.1"
# main branch can be tested replacing the version with main-SNAPSHOT # main branch can be tested replacing the version with main-SNAPSHOT
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.36.1" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.36.1"
@@ -269,11 +269,11 @@ ktlint = "org.jlleitschuh.gradle.ktlint:14.2.0"
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
dependencycheck = "org.owasp.dependencycheck:12.2.2" dependencycheck = "org.owasp.dependencycheck:12.2.2"
dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" }
paparazzi = "app.cash.paparazzi:2.0.0-alpha04" paparazzi = "app.cash.paparazzi:2.0.0-alpha05"
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" } roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" }
sonarqube = "org.sonarqube:7.3.0.8198" sonarqube = "org.sonarqube:7.3.1.8318"
licensee = "app.cash.licensee:1.14.1" licensee = "app.cash.licensee:1.14.1"
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
gms_google_services = { id = "com.google.gms.google-services", version = "4.4.4" } gms_google_services = { id = "com.google.gms.google-services", version = "4.4.4" }
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

@@ -27,7 +27,7 @@
<string name="state_event_room_invite_accepted">"%1$s принял(а) приглашение"</string> <string name="state_event_room_invite_accepted">"%1$s принял(а) приглашение"</string>
<string name="state_event_room_invite_accepted_by_you">"Вы приняли приглашение"</string> <string name="state_event_room_invite_accepted_by_you">"Вы приняли приглашение"</string>
<string name="state_event_room_invite_by_you">"Вы пригласили %1$s"</string> <string name="state_event_room_invite_by_you">"Вы пригласили %1$s"</string>
<string name="state_event_room_invite_you">"%1$s пригласил(а) вас"</string> <string name="state_event_room_invite_you">"%1$s пригласил(а) Вас"</string>
<string name="state_event_room_join">"%1$s присоединился(ась)"</string> <string name="state_event_room_join">"%1$s присоединился(ась)"</string>
<string name="state_event_room_join_by_you">"Вы присоединились к комнате"</string> <string name="state_event_room_join_by_you">"Вы присоединились к комнате"</string>
<string name="state_event_room_knock">"%1$s хочет присоединиться"</string> <string name="state_event_room_knock">"%1$s хочет присоединиться"</string>
@@ -47,8 +47,8 @@
<string name="state_event_room_name_removed_by_you">"Вы удалили название"</string> <string name="state_event_room_name_removed_by_you">"Вы удалили название"</string>
<string name="state_event_room_none">"%1$s не внес(ла) изменений"</string> <string name="state_event_room_none">"%1$s не внес(ла) изменений"</string>
<string name="state_event_room_none_by_you">"Вы не внесли изменений"</string> <string name="state_event_room_none_by_you">"Вы не внесли изменений"</string>
<string name="state_event_room_pinned_events_changed">"%1$s изменил(а) закрепленные сообщения"</string> <string name="state_event_room_pinned_events_changed">"%1$s изменил(а) закреплённые сообщения"</string>
<string name="state_event_room_pinned_events_changed_by_you">"Вы изменили закрепленные сообщения"</string> <string name="state_event_room_pinned_events_changed_by_you">"Вы изменили закреплённые сообщения"</string>
<string name="state_event_room_pinned_events_pinned">"%1$s закрепил(а) сообщение"</string> <string name="state_event_room_pinned_events_pinned">"%1$s закрепил(а) сообщение"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Вы закрепили сообщение"</string> <string name="state_event_room_pinned_events_pinned_by_you">"Вы закрепили сообщение"</string>
<string name="state_event_room_pinned_events_unpinned">"%1$s открепил(а) сообщение"</string> <string name="state_event_room_pinned_events_unpinned">"%1$s открепил(а) сообщение"</string>
@@ -15,6 +15,7 @@ sealed class ClientException(message: String, val details: String?, cause: Throw
details = details, details = details,
cause = cause cause = cause
) )
class ContentScanner(message: String, val reason: ContentScannerErrorReason) : ClientException(message, null, null)
class Other(message: String, cause: Throwable? = null) : ClientException(message, null, cause) class Other(message: String, cause: Throwable? = null) : ClientException(message, null, cause)
} }
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.api.exception
enum class ContentScannerErrorReason {
MCS_MALFORMED_JSON,
MCS_MEDIA_FAILED_TO_DECRYPT,
M_MISSING_TOKEN,
M_UNKNOWN_TOKEN,
M_NOT_FOUND,
MCS_MEDIA_NOT_CLEAN,
MCS_MIME_TYPE_FORBIDDEN,
MCS_BAD_DECRYPTION,
M_UNKNOWN,
MCS_MEDIA_REQUEST_FAILED;
companion object;
fun isDangerous(): Boolean = when (this) {
MCS_MEDIA_NOT_CLEAN, MCS_MIME_TYPE_FORBIDDEN -> true
else -> false
}
}
@@ -9,6 +9,7 @@
package io.element.android.libraries.matrix.impl.exception package io.element.android.libraries.matrix.impl.exception
import io.element.android.libraries.matrix.api.exception.ClientException import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.api.exception.ContentScannerErrorReason
import org.matrix.rustcomponents.sdk.ClientException as RustClientException import org.matrix.rustcomponents.sdk.ClientException as RustClientException
fun Throwable.mapClientException(): ClientException { fun Throwable.mapClientException(): ClientException {
@@ -23,6 +24,10 @@ fun Throwable.mapClientException(): ClientException {
details = details, details = details,
cause = this, cause = this,
) )
is RustClientException.ContentScanner -> ClientException.ContentScanner(
message = message,
reason = ContentScannerErrorReason.fromRust(reason),
)
} }
} }
else -> ClientException.Other(message ?: "Unknown error", this) else -> ClientException.Other(message ?: "Unknown error", this)
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.exception
import io.element.android.libraries.matrix.api.exception.ContentScannerErrorReason
import uniffi.matrix_sdk_contentscanner.ErrorReason
import uniffi.matrix_sdk_contentscanner.ErrorReason.MCS_BAD_DECRYPTION
import uniffi.matrix_sdk_contentscanner.ErrorReason.MCS_MALFORMED_JSON
import uniffi.matrix_sdk_contentscanner.ErrorReason.MCS_MEDIA_FAILED_TO_DECRYPT
import uniffi.matrix_sdk_contentscanner.ErrorReason.MCS_MEDIA_NOT_CLEAN
import uniffi.matrix_sdk_contentscanner.ErrorReason.MCS_MEDIA_REQUEST_FAILED
import uniffi.matrix_sdk_contentscanner.ErrorReason.MCS_MIME_TYPE_FORBIDDEN
import uniffi.matrix_sdk_contentscanner.ErrorReason.M_MISSING_TOKEN
import uniffi.matrix_sdk_contentscanner.ErrorReason.M_NOT_FOUND
import uniffi.matrix_sdk_contentscanner.ErrorReason.M_UNKNOWN
import uniffi.matrix_sdk_contentscanner.ErrorReason.M_UNKNOWN_TOKEN
fun ContentScannerErrorReason.Companion.fromRust(reason: ErrorReason) = when (reason) {
M_UNKNOWN -> ContentScannerErrorReason.M_UNKNOWN
M_MISSING_TOKEN -> ContentScannerErrorReason.M_MISSING_TOKEN
M_UNKNOWN_TOKEN -> ContentScannerErrorReason.M_UNKNOWN_TOKEN
MCS_MALFORMED_JSON -> ContentScannerErrorReason.MCS_MALFORMED_JSON
MCS_MEDIA_FAILED_TO_DECRYPT -> ContentScannerErrorReason.MCS_MEDIA_FAILED_TO_DECRYPT
M_NOT_FOUND -> ContentScannerErrorReason.M_NOT_FOUND
MCS_MEDIA_NOT_CLEAN -> ContentScannerErrorReason.MCS_MEDIA_NOT_CLEAN
MCS_MIME_TYPE_FORBIDDEN -> ContentScannerErrorReason.MCS_MIME_TYPE_FORBIDDEN
MCS_BAD_DECRYPTION -> ContentScannerErrorReason.MCS_BAD_DECRYPTION
MCS_MEDIA_REQUEST_FAILED -> ContentScannerErrorReason.MCS_MEDIA_REQUEST_FAILED
}
@@ -51,6 +51,10 @@ class RustLinkMobileHandler(
) )
// We emit Done in case the progress listener was deallocated before generate() sent the Done // We emit Done in case the progress listener was deallocated before generate() sent the Done
_linkMobileStep.emit(LinkMobileStep.Done) _linkMobileStep.emit(LinkMobileStep.Done)
} catch (e: HumanQrGrantLoginException.Expired) {
// Note: the SDK does not return this error, but is returning `.NotFound` instead when the QR code expires. We catch both just in case.
Timber.tag(tag.value).w(e, "QR code has expired")
_linkMobileStep.emit(LinkMobileStep.QrRotating)
} catch (e: HumanQrGrantLoginException.NotFound) { } catch (e: HumanQrGrantLoginException.NotFound) {
Timber.tag(tag.value).w(e, "Error during QR login grant") Timber.tag(tag.value).w(e, "Error during QR login grant")
// Catch timeout here? // Catch timeout here?
@@ -147,6 +147,32 @@ class RustLinkMobileHandlerTest {
} }
} }
@Test
fun `when start throws HumanQrGrantLoginException_Expired, the handler emits QrRotating step`() = runTest {
val completable = CompletableDeferred<Unit>()
val handler = FakeFfiGrantLoginWithQrCodeHandler(
generateResult = {
completable.await()
throw HumanQrGrantLoginException.Expired("Expired")
}
)
val sut = createRustLinkMobileHandler(
handler,
)
sut.linkMobileStep.test {
val initialItem = awaitItem()
assertThat(initialItem).isEqualTo(LinkMobileStep.Uninitialized)
backgroundScope.launch {
sut.start()
}
runCurrent()
// generate returns, error is emitted
completable.complete(Unit)
val qrRotatingState = awaitItem()
assertThat(qrRotatingState).isEqualTo(LinkMobileStep.QrRotating)
}
}
private fun TestScope.createRustLinkMobileHandler( private fun TestScope.createRustLinkMobileHandler(
handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(), handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(),
) = RustLinkMobileHandler( ) = RustLinkMobileHandler(
@@ -78,7 +78,7 @@ fun SpaceRoomItemView(
indication = ripple(), indication = ripple(),
interactionSource = remember { MutableInteractionSource() } interactionSource = remember { MutableInteractionSource() }
) )
.onKeyboardContextMenuAction { onLongClick } .onKeyboardContextMenuAction(onLongClick)
Column( Column(
modifier = modifier modifier = modifier
.then(clickModifier) .then(clickModifier)
@@ -5,5 +5,5 @@
<string name="screen_bottom_sheet_create_dm_title">"Отправить приглашение?"</string> <string name="screen_bottom_sheet_create_dm_title">"Отправить приглашение?"</string>
<string name="screen_bottom_sheet_create_dm_unknown_user_content">"У Вас нет других чатов с этим пользователем. Подтвердите, что это действительно кого Вы хотите пригласить, прежде чем продолжить."</string> <string name="screen_bottom_sheet_create_dm_unknown_user_content">"У Вас нет других чатов с этим пользователем. Подтвердите, что это действительно кого Вы хотите пригласить, прежде чем продолжить."</string>
<string name="screen_bottom_sheet_create_dm_unknown_user_title">"Начать чат с этим новым контактом?"</string> <string name="screen_bottom_sheet_create_dm_unknown_user_title">"Начать чат с этим новым контактом?"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) вас"</string> <string name="screen_invites_invited_you">"%1$s (%2$s) пригласил(а) Вас"</string>
</resources> </resources>
@@ -258,7 +258,7 @@ class ExoPlayerForPreview(
override fun setPriorityTaskManager(priorityTaskManager: PriorityTaskManager?) {} override fun setPriorityTaskManager(priorityTaskManager: PriorityTaskManager?) {}
override fun isSleepingForOffload(): Boolean = throw NotImplementedError() override fun isSleepingForOffload(): Boolean = throw NotImplementedError()
override fun isTunnelingEnabled(): Boolean = throw NotImplementedError() override fun isTunnelingEnabled(): Boolean = throw NotImplementedError()
override fun isReleased(): Boolean = throw NotImplementedError() override fun isReleased(): Boolean = false
override fun setImageOutput(imageOutput: ImageOutput?) {} override fun setImageOutput(imageOutput: ImageOutput?) {}
override fun setAudioCodecParameters(codecParameters: CodecParameters) {} override fun setAudioCodecParameters(codecParameters: CodecParameters) {}
override fun addAudioCodecParametersChangeListener(listener: CodecParametersChangeListener, keys: List<String>) {} override fun addAudioCodecParametersChangeListener(listener: CodecParametersChangeListener, keys: List<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_media_details_title">"Інфармацыя пра файл"</string>
</resources>

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