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
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# 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
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
# Skip in forks
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+2 -2
View File
@@ -38,7 +38,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -78,7 +78,7 @@ jobs:
concurrency:
group: maestro-test
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'
with:
# 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
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Use JDK 21
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
name: Dependency analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Use JDK 21
+12 -12
View File
@@ -33,7 +33,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
@@ -51,7 +51,7 @@ jobs:
name: Search for invalid screenshot files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Python 3.12
@@ -65,7 +65,7 @@ jobs:
name: Search for invalid dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- 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) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# 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) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# 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) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# 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) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# 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) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# 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) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
@@ -319,7 +319,7 @@ jobs:
name: Check shell scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Run shellcheck
@@ -333,7 +333,7 @@ jobs:
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
@@ -344,7 +344,7 @@ jobs:
needs: [konsist, lint, ktlint, detekt]
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
+3 -3
View File
@@ -34,7 +34,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Use JDK 21
@@ -71,7 +71,7 @@ jobs:
group: ${{ format('build-release-main-enterprise-{0}', github.sha) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
@@ -122,7 +122,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Use JDK 21
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
docker-images: true
swap-storage: false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
# Ensure we are building the branch and not the branch after being merged on develop
# https://github.com/actions/checkout/issues/881
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
# Skip in forks
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- 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' }}
# No concurrency required, runs every time on a schedule.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Set up Python 3.12
+1 -1
View File
@@ -108,7 +108,7 @@ jobs:
# https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
+1 -1
View File
@@ -2,6 +2,6 @@
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.3.21" />
<option name="version" value="2.4.0" />
</component>
</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
=============================
+1 -1
View File
@@ -28,7 +28,6 @@ import java.util.Locale
plugins {
id("io.element.android-compose-application")
alias(libs.plugins.kotlin.android)
// When using precompiled plugins, we need to apply the firebase plugin like this
id(libs.plugins.firebaseAppDistribution.get().pluginId)
id("kotlin-parcelize")
@@ -189,6 +188,7 @@ android {
buildFeatures {
buildConfig = true
resValues = true
}
flavorDimensions += "store"
productFlavors {
-2
View File
@@ -106,8 +106,6 @@ allprojects {
// Fix compilation warning for annotations
// See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details
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(libs.androidx.webkit)
implementation(libs.coil.compose)
implementation(libs.network.retrofit)
implementation(libs.serialization.json)
implementation(libs.element.call.embedded)
api(projects.features.call.api)
@@ -13,6 +13,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
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.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -36,6 +39,13 @@ fun AddPeopleView(
modifier: Modifier = Modifier,
invitePeopleView: @Composable () -> Unit,
) {
val currentOnFinish by rememberUpdatedState(onFinish)
LaunchedEffect(state.sendInvitesAction, state.createRoomFromDmAction) {
if (state.sendInvitesAction.isSuccess() || state.createRoomFromDmAction.isSuccess()) {
currentOnFinish()
}
}
HeaderFooterPage(
modifier = modifier,
contentPadding = PaddingValues(0.dp),
@@ -47,7 +57,6 @@ fun AddPeopleView(
text = stringResource(CommonStrings.action_finish),
onClick = {
state.eventSink(InvitePeopleEvents.SendInvites)
onFinish()
},
enabled = state.canInvite,
modifier = Modifier
@@ -3,9 +3,15 @@
<string name="screen_create_room_action_create_room">"Новы пакой"</string>
<string name="screen_create_room_add_people_title">"Запрасіць карыстальнікаў"</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_public_option_description">"Любы можа знайсці гэты пакой.
Вы можаце змяніць гэта ў любы час у наладах пакоя."</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_placeholder">"Дадаць апісанне…"</string>
</resources>
@@ -9,6 +9,6 @@
<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_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>
</resources>
@@ -6,7 +6,7 @@
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</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_use_another_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_title">"Отклонить чат"</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_title">"Настройка Вашего аккаунта."</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_empty_state_subtitle">"Добавить чат в избранное можно в настройках чата.
На данный момент вы можете убрать фильтры, чтобы увидеть другие ваши чаты."</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_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_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_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_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_empty_state_title">"Вас пока нет ни в одной комнате"</string>
<string name="screen_roomlist_filter_unreads">"Новые"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Поздравляем!
У вас нет непрочитанных сообщений!"</string>
У Вас нет непрочитанных сообщений!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Запрос на присоединение отправлен"</string>
<string name="screen_roomlist_main_space_title">"Чаты"</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_title">"Отклонить чат"</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_message">"Вы действительно хотите отклонить приглашение в комнату? Также %1$s больше не сможет связаться с вами или приглашать в комнаты."</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_invited">"Уже приглашен(а)"</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_one_user">"Пригласить нового участника в эту комнату?"</string>
</resources>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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_join_action">"Далучыцца"</string>
<string name="screen_join_room_knock_action">"Націсніце, каб далучыцца"</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_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_reason">"Причина: %1$s."</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_space_not_supported_description">"%1$s еще не поддерживает пространства. Вы можете получить к ним доступ в веб-версии."</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_title_knock">"Хотите присоединиться к этой комнате?"</string>
<string name="screen_join_room_title_no_preview">"Предпросмотр недоступен"</string>
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
</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_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_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_loading_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_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_loading_title">"Отклонение заявки на присоединение"</string>
<string name="screen_knock_requests_list_empty_state_description">"Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате."</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Вы уверены, что хотите покинуть беседу? Эта беседа не является общедоступной, и вы не сможете присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_empty_subtitle">"Вы уверены, что хотите покинуть эту комнату? Здесь есть только вы. Если вы покинете комнату, никто не сможет присоединиться к ней в будущем, включая вас."</string>
<string name="leave_conversation_alert_subtitle">"Вы уверены, что хотите покинуть беседу? Эта беседа не является общедоступной, и Вы не сможете снова присоединиться к ней без приглашения."</string>
<string name="leave_room_alert_empty_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_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_request_cancelled_subtitle">"Вход на другом устройстве был отменен."</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_mobile_step1">"Откройте %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_mobile_device">"Смартфон"</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_qr_code_login_connection_note_secure_state_description">"Не удалось установить безопасное соединение с новым устройством. Не беспокойтесь, существующие устройства в безопасности."</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_declined_subtitle">"Вход в систему был отклонен на другом устройстве."</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_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_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_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_unknown_error_description">"Произошла непредвиденная ошибка. Пожалуйста, попробуйте еще раз."</string>
<string name="screen_qr_code_login_unknown_error_description">"Произошла непредвиденная ошибка. Пожалуйста, попробуйте ещё раз."</string>
<string name="screen_qr_code_login_verify_code_loading">"Ожидание другого устройства"</string>
</resources>
@@ -8,6 +8,7 @@
package io.element.android.features.location.api
import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
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.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.graphics.createBitmap
import coil3.Extras
import coil3.asImage
import coil3.compose.AsyncImagePainter
import coil3.compose.rememberAsyncImagePainter
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.location.api.internal.StaticMapPlaceholder
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.preview.ElementPreview
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.
@@ -128,32 +134,46 @@ private fun BoxWithConstraintsScope.LoadableMapContent(
var retryHash by remember { mutableIntStateOf(0) }
val builder = remember { StaticMapUrlBuilder() }
val painter = rememberAsyncImagePainter(
model = if (constraints.isZero) {
// Avoid building a URL if any of the size constraints is zero
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,
val (painter, state, contentScale) = if (LocalInspectionMode.current) {
val painter = painterResource(CommonDrawables.sample_map)
val state = AsyncImagePainter.State.Success(
painter = painter,
result = SuccessResult(
image = createBitmap(1, 1, Bitmap.Config.ALPHA_8).asImage(),
request = ImageRequest.Builder(context).build()
)
)
Triple(painter, state, ContentScale.Crop)
} else {
val painter = rememberAsyncImagePainter(
model = if (constraints.isZero) {
// Avoid building a URL if any of the size constraints is zero
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)
.apply {
extras.set(Extras.Key("retry_hash"), retryHash).build()
}
.build()
}
)
.size(width = constraints.maxWidth, height = constraints.maxHeight)
.apply {
extras.set(Extras.Key("retry_hash"), retryHash).build()
}
.build()
}
)
val state by painter.state.collectAsState()
Triple(painter, state, ContentScale.Fit)
}
val state by painter.state.collectAsState()
when (state) {
is AsyncImagePainter.State.Success -> {
Image(
@@ -162,7 +182,7 @@ private fun BoxWithConstraintsScope.LoadableMapContent(
modifier = Modifier.size(width = maxWidth, height = maxHeight),
// 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.
contentScale = ContentScale.Fit,
contentScale = contentScale,
)
LocationPin(variant = pinVariant, modifier = Modifier.centerBottomEdge(this))
}
@@ -14,6 +14,7 @@ import io.element.android.features.location.impl.common.ui.LocationConstraintsDi
sealed interface LocationConstraintsCheck {
data object Success : LocationConstraintsCheck
data object PermissionRationale : LocationConstraintsCheck
data object PermissionShouldBeRequested : LocationConstraintsCheck
data object PermissionDenied : LocationConstraintsCheck
data object LocationServiceDisabled : LocationConstraintsCheck
data object NotEnoughPowerLevel : LocationConstraintsCheck
@@ -34,6 +35,7 @@ fun checkLocationConstraints(
}
}
permissionsState.shouldShowRationale -> LocationConstraintsCheck.PermissionRationale
!permissionsState.permissionsAlreadyRequested -> LocationConstraintsCheck.PermissionShouldBeRequested
else -> LocationConstraintsCheck.PermissionDenied
}
}
@@ -41,6 +43,7 @@ fun checkLocationConstraints(
fun LocationConstraintsCheck.toDialogState(): LocationConstraintsDialogState {
return when (this) {
LocationConstraintsCheck.Success -> LocationConstraintsDialogState.None
LocationConstraintsCheck.PermissionShouldBeRequested -> LocationConstraintsDialogState.None
LocationConstraintsCheck.PermissionRationale -> LocationConstraintsDialogState.PermissionRationale
LocationConstraintsCheck.PermissionDenied -> LocationConstraintsDialogState.PermissionDenied
LocationConstraintsCheck.LocationServiceDisabled -> LocationConstraintsDialogState.LocationServiceDisabled
@@ -9,6 +9,10 @@
package io.element.android.features.location.impl.common.permissions
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.isGranted
import com.google.accompanist.permissions.rememberMultiplePermissionsState
@@ -32,11 +36,16 @@ class DefaultPermissionsPresenter(
@OptIn(ExperimentalPermissionsApi::class)
@Composable
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) {
when (event) {
PermissionsEvents.RequestPermissions -> multiplePermissionsState.launchMultiplePermissionRequest()
PermissionsEvents.RequestPermissions -> {
multiplePermissionsState.launchMultiplePermissionRequest()
}
}
}
@@ -47,6 +56,7 @@ class DefaultPermissionsPresenter(
else -> PermissionsState.Permissions.NoneGranted
},
shouldShowRationale = multiplePermissionsState.shouldShowRationale,
permissionsAlreadyRequested = permissionsRequested,
eventSink = ::handleEvent,
)
}
@@ -11,6 +11,7 @@ package io.element.android.features.location.impl.common.permissions
data class PermissionsState(
val permissions: Permissions,
val shouldShowRationale: Boolean,
val permissionsAlreadyRequested: Boolean,
val eventSink: (PermissionsEvents) -> Unit,
) {
sealed interface Permissions {
@@ -88,6 +88,8 @@ class ShareLocationPresenter(
var dialogState: ShareLocationState.Dialog by remember {
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 currentUser by client.userProfile.collectAsState()
val customMapStyleUrl by produceState(AsyncData.Loading()) {
@@ -100,34 +102,55 @@ class ShareLocationPresenter(
val scope = rememberCoroutineScope()
fun checkLocationConstraints() {
// No need to check SendLiveLocationPermissions here
val locationConstraints = checkLocationConstraints(permissionsState, locationActions, SendLiveLocationPermissions.GRANTED)
val locationConstraints = checkLocationConstraints(
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
dialogState = ShareLocationState.Dialog.Constraints(locationConstraints.toDialogState())
}
suspend fun computeLiveLocationDialogState(): ShareLocationState.Dialog {
val hasAcceptedDisclaimer = liveLocationStore.hasAcceptedLiveLocationDisclaimer()
val constraintsResult = checkLocationConstraints(permissionsState, locationActions, sendLiveLocationPermissions)
return when {
!hasAcceptedDisclaimer -> {
ShareLocationState.Dialog.LiveLocationDisclaimer
}
constraintsResult is LocationConstraintsCheck.Success -> {
val durations = LIVE_LOCATION_DURATIONS.map {
LiveLocationDuration(duration = it, formatted = durationFormatter.format(it))
suspend fun checkLiveLocationConstraints() {
val locationConstraints = checkLocationConstraints(
permissionsState = permissionsState,
locationActions = locationActions,
sendLiveLocationPermissions = sendLiveLocationPermissions,
)
when (locationConstraints) {
LocationConstraintsCheck.Success -> {
val hasAcceptedDisclaimer = liveLocationStore.hasAcceptedLiveLocationDisclaimer()
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 -> {
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)
LaunchedEffect(permissionsState.permissions) { checkLocationConstraints() }
LaunchedEffect(permissionsState) {
if (pendingLiveLocationShare) {
checkLiveLocationConstraints()
} else {
checkLocationConstraints()
}
}
fun handleEvent(event: ShareLocationEvent) {
when (event) {
@@ -136,7 +159,10 @@ class ShareLocationPresenter(
}
ShareLocationEvent.StartTrackingUserLocation -> checkLocationConstraints()
ShareLocationEvent.StopTrackingUserLocation -> trackUserPosition = false
ShareLocationEvent.DismissDialog -> dialogState = ShareLocationState.Dialog.None
ShareLocationEvent.DismissDialog -> {
pendingLiveLocationShare = false
dialogState = ShareLocationState.Dialog.None
}
ShareLocationEvent.OpenAppSettings -> {
locationActions.openAppSettings()
dialogState = ShareLocationState.Dialog.None
@@ -146,15 +172,17 @@ class ShareLocationPresenter(
dialogState = ShareLocationState.Dialog.None
}
ShareLocationEvent.InitiateLiveLocationShare -> scope.launch {
dialogState = computeLiveLocationDialogState()
pendingLiveLocationShare = true
checkLiveLocationConstraints()
}
ShareLocationEvent.AcceptLiveLocationDisclaimer -> scope.launch {
liveLocationStore.setAcceptedLiveLocationDisclaimer()
.onSuccess {
dialogState = computeLiveLocationDialogState()
checkLiveLocationConstraints()
}
}
is ShareLocationEvent.StartLiveLocationShare -> scope.launch {
pendingLiveLocationShare = false
dialogState = ShareLocationState.Dialog.None
startLiveLocationAction.runUpdatingState {
liveLocationShareManager.startShare(
@@ -90,10 +90,22 @@ import kotlinx.coroutines.launch
value = AsyncData.Success(client.getMapStyleUrl().getOrNull())
}
LaunchedEffect(permissionsState.permissions) {
if (permissionsState.isAnyGranted) {
dialogState = LocationConstraintsDialogState.None
fun checkLocationConstraints() {
val locationConstraints = checkLocationConstraints(
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) {
@@ -103,9 +115,7 @@ import kotlinx.coroutines.launch
}
is ShowLocationEvent.TrackMyLocation -> {
if (event.enabled) {
val locationConstraints = checkLocationConstraints(permissionsState, locationActions, SendLiveLocationPermissions.GRANTED)
isTrackMyLocation = locationConstraints is LocationConstraintsCheck.Success
dialogState = locationConstraints.toDialogState()
checkLocationConstraints()
} else {
isTrackMyLocation = false
}
@@ -2,4 +2,5 @@
<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_duration_picker_title">"Виберіть, як довго ділитися своїм місцезнаходженням."</string>
<string name="screen_share_location_live_location_missing_permissions">"У вас немає дозволу ділитися своїм місцезнаходженням у реальному часі в цій кімнаті"</string>
</resources>
@@ -13,10 +13,12 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
fun aPermissionsState(
permissions: PermissionsState.Permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale: Boolean = false,
permissionsRequested: Boolean = false,
): PermissionsState {
return PermissionsState(
permissions = permissions,
shouldShowRationale = shouldShowRationale,
permissionsAlreadyRequested = permissionsRequested,
eventSink = {},
)
}
@@ -64,10 +64,25 @@ class LocationConstraintsCheckTest {
}
@Test
fun `checkLocationConstraints returns PermissionDenied when permissions denied without rationale`() {
fun `checkLocationConstraints returns PermissionShouldBeRequested when permissions not yet requested`() {
val permissionsState = aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
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)
@@ -20,6 +20,7 @@ class FakePermissionsPresenter : PermissionsPresenter {
private var state = PermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsAlreadyRequested = false,
eventSink = ::handleEvent,
)
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
fun `initial state with permissions denied`() = runTest {
val shareLocationPresenter = createShareLocationPresenter()
@@ -167,6 +185,7 @@ class ShareLocationPresenterTest {
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = true,
)
)
@@ -291,6 +310,7 @@ class ShareLocationPresenterTest {
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = true,
)
)
@@ -359,7 +379,12 @@ class ShareLocationPresenterTest {
@Test
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(
aPermissionsState(
permissions = PermissionsState.Permissions.AllGranted,
@@ -462,7 +487,12 @@ class ShareLocationPresenterTest {
@Test
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"))
.setAcceptedLiveLocationDisclaimer()
.getOrThrow()
@@ -505,12 +535,13 @@ class ShareLocationPresenterTest {
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = true,
)
)
shareLocationPresenter.test {
val initialState = awaitFirstItem()
// Dismiss initial dialog
// Dismiss initial dialog to allow re-triggering it
initialState.eventSink(ShareLocationEvent.DismissDialog)
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.test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@@ -103,8 +102,8 @@ class ShowLocationPresenterTest {
val presenter = createShowLocationPresenter()
presenter.test {
val initialState = awaitItem()
assertThat(initialState.isTrackMyLocation).isFalse()
assertThat(awaitItem().isTrackMyLocation).isFalse()
assertThat(awaitItem().isTrackMyLocation).isTrue()
}
}
@@ -129,15 +128,12 @@ class ShowLocationPresenterTest {
)
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.isTrackMyLocation).isFalse()
initialState.eventSink(ShowLocationEvent.TrackMyLocation(true))
val trackMyLocationState = awaitItem()
delay(1)
assertThat(trackMyLocationState.isTrackMyLocation).isTrue()
// 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
fun `permission denied dialog dismiss`() = runTest {
fakePermissionsPresenter.givenState(
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = true,
)
)
@@ -235,6 +253,7 @@ class ShowLocationPresenterTest {
aPermissionsState(
permissions = PermissionsState.Permissions.NoneGranted,
shouldShowRationale = false,
permissionsRequested = true,
)
)
@@ -35,5 +35,5 @@
</plurals>
<string name="screen_app_lock_use_biometric_android">"Выкарыстоўваць біяметрыю"</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>
@@ -15,9 +15,9 @@
<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_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_title">"Выбрать другой 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_title">"Это устройство удаляется"</string>
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"У вас осталась %1$d попытка разблокировки"</item>
<item quantity="few">"У вас остались %1$d попытки разблокировки"</item>
<item quantity="many">"У вас осталось %1$d попыток разблокировки"</item>
<item quantity="one">"У Вас осталась %1$d попытка разблокировки"</item>
<item quantity="few">"У Вас остались %1$d попытки разблокировки"</item>
<item quantity="many">"У Вас осталось %1$d попыток разблокировки"</item>
</plurals>
<plurals name="screen_app_lock_subtitle_wrong_pin">
<item quantity="one">"Неверный PIN-код. У вас осталась %1$d попытка"</item>
<item quantity="few">"Неверный PIN-код. У вас остались %1$d попытки"</item>
<item quantity="many">"Неверный PIN-код. У вас осталось %1$d попыток"</item>
<item quantity="one">"Неверный PIN-код. У Вас осталась %1$d попытка"</item>
<item quantity="few">"Неверный PIN-код. У Вас остались %1$d попытки"</item>
<item quantity="many">"Неверный PIN-код. У Вас осталось %1$d попыток"</item>
</plurals>
<string name="screen_app_lock_use_biometric_android">"Использовать биометрию"</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_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_user_id">"Гэта несапраўдны ідэнтыфікатар карыстальніка. Чаканы фармат: @user:homeserver.org"</string>
<string name="screen_login_error_refresh_tokens">"Гэты сервер настроены на выкарыстанне маркераў абнаўлення. Яны не падтрымліваюцца пры ўваходзе на аснове пароля."</string>
@@ -29,6 +29,7 @@
<string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
<string name="screen_login_title">"Сардэчна запрашаем!"</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_to">"Увайсці ў %1$s"</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_form_header">"Введите свои данные"</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_missing_key_backup_open_element_classic">"Открыть 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_declined_subtitle">"Вход в систему был отклонен на другом устройстве."</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_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_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_scanning_state_title">"Сканировать QR-код"</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_subtitle">"Сервер для аккаунта может запросить следующий код для подтверждения входа."</string>
<string name="screen_qr_code_login_verify_code_title">"Ваш код подтверждения"</string>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Вы ўпэўнены, што хочаце выйсці?"</string>
<string name="screen_signout_confirmation_dialog_content">"Вы ўпэўненыя, што хочаце выдаліць гэту прыладу?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Выйсці"</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_title">"Вы адключылі рэзервовае капіраванне"</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_title">"Вы потеряете доступ к своим зашифрованным чатам"</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_title">"Резервное копирование ключей все еще продолжается"</string>
<string name="screen_signout_key_backup_ongoing_title">"Резервное копирование ключей всё ещё продолжается"</string>
<string name="screen_signout_preference_item">"Удалить это устройство"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Это ваше единственное устройство. Если вы его удалите, вам потребуется ключ восстановления, чтобы подтвердить свою цифровую личность и восстановить зашифрованные чаты при следующем входе в систему."</string>
<string name="screen_signout_recovery_disabled_title">"Вы потеряете доступ к своим зашифрованным чатам"</string>
@@ -190,6 +190,9 @@ class MessagesFlowNode(
@Parcelize
data object ThreadsList : NavTarget
@Parcelize
data class AvatarPreview(val name: String, val avatarUrl: String) : NavTarget
}
private val callback: MessagesEntryPoint.Callback = callback()
@@ -327,6 +330,10 @@ class MessagesFlowNode(
override fun navigateToDeveloperSettings() {
callback.navigateToDeveloperSettings()
}
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
}
}
val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId)
createNode<MessagesNode>(buildContext, listOf(callback, inputs))
@@ -562,6 +569,10 @@ class MessagesFlowNode(
override fun navigateToDeveloperSettings() {
callback.navigateToDeveloperSettings()
}
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
}
}
createNode<ThreadedMessagesNode>(buildContext, listOf(inputs, callback))
}
@@ -573,6 +584,31 @@ class MessagesFlowNode(
}
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.timeline.Timeline
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.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
@@ -107,7 +108,7 @@ class MessagesNode(
private val timelineController = TimelineController(room, room.liveTimeline)
private val presenter = presenterFactory.create(
navigator = this,
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = false),
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = null),
timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this),
actionListPresenter = actionListPresenterFactory.create(
postProcessor = TimelineItemActionPostProcessor.Default,
@@ -136,6 +137,8 @@ class MessagesNode(
fun navigateToDeveloperSettings()
fun navigateToThreadsList()
fun navigateToAvatarPreview(username: String, avatarUrl: String)
}
override fun onBuilt() {
@@ -319,6 +322,11 @@ class MessagesNode(
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
}
},
onAvatarClick = { user ->
user.avatarUrl?.let { url ->
callback.navigateToAvatarPreview(user.getBestName(), url)
}
},
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.di.annotations.SessionCoroutineScope
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.permalink.PermalinkBuilder
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(
@Assisted private val navigator: MessagesNavigator,
@Assisted private val timelineController: TimelineController,
@Assisted private val isInThread: Boolean,
@Assisted private val threadRoot: ThreadId?,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
private val room: JoinedRoom,
private val mediaPickerProvider: PickerProvider,
@@ -138,10 +139,11 @@ class MessageComposerPresenter(
fun create(
timelineController: TimelineController,
navigator: MessagesNavigator,
isInThread: Boolean,
threadRoot: ThreadId?,
): MessageComposerPresenter
}
private val isInThread = threadRoot != null
private val mediaSender = mediaSenderFactory.create(timelineMode = timelineController.mainTimelineMode())
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
@@ -234,8 +236,7 @@ class MessageComposerPresenter(
LaunchedEffect(Unit) {
val draft = draftService.loadDraft(
roomId = room.roomId,
// TODO support threads in composer
threadRoot = null,
threadRoot = threadRoot,
isVolatile = false
)
if (draft != null) {
@@ -652,8 +653,7 @@ class MessageComposerPresenter(
roomId = room.roomId,
draft = draft,
isVolatile = isVolatile,
// TODO support threads in composer
threadRoot = null,
threadRoot = threadRoot,
)
}
@@ -816,8 +816,7 @@ class MessageComposerPresenter(
// Use the volatile draft only when coming from edit mode otherwise.
val draft = draftService.loadDraft(
roomId = room.roomId,
// TODO support threads in composer
threadRoot = null,
threadRoot = threadRoot,
isVolatile = true
).takeIf { fromEdit }
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.timeline.Timeline
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.isTalkbackActive
import io.element.android.services.analytics.api.AnalyticsService
@@ -112,7 +113,7 @@ class ThreadedMessagesNode(
this.timelineController = timelineController
return presenterFactory.create(
navigator = this,
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, isInThread = true),
composerPresenter = messageComposerPresenterFactory.create(timelineController, this, threadRoot = inputs.threadRootEventId),
timelinePresenter = timelinePresenterFactory.create(timelineController = timelineController, this),
// TODO add special processor for threaded timeline
actionListPresenter = actionListPresenterFactory.create(
@@ -138,6 +139,8 @@ class ThreadedMessagesNode(
fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean)
fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?)
fun navigateToDeveloperSettings()
fun navigateToAvatarPreview(username: String, avatarUrl: String)
}
override fun onBuilt() {
@@ -315,6 +318,11 @@ class ThreadedMessagesNode(
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
}
},
onAvatarClick = { user ->
user.avatarUrl?.let { url ->
callback.navigateToAvatarPreview(user.getBestName(), url)
}
},
modifier = Modifier,
)
@@ -16,6 +16,8 @@
<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_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_hint">"Адзначце, ці хочаце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка"</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_files">"Далучэнне"</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_text_formatting">"Фармаціраванне тэксту"</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_recent">"Недавние эмодзи"</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>
<plurals name="screen_image_edition_a11y_rotation_state">
<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_change_video_quality_prompt">"Нажмите, чтобы изменить качество загружаемого видео."</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_sending">"Не удалось загрузить медиафайлы, попробуйте еще раз."</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_too_large_message">"Максимальный размер файла: %1$s."</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>
@@ -16,6 +16,13 @@
<string name="emoji_picker_category_places">"Подорожі та місця"</string>
<string name="emoji_picker_category_recent">"Нещодавні емодзі"</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_change_video_quality_prompt">"Натисніть, щоб змінити якість вивантажуваного відео"</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_optimize_image_quality_title">"Оптимізувати якість зображення"</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_hint">"Перевірте, чи хочете ви приховати всі поточні та майбутні повідомлення від цього користувача"</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.TextPillificationHelper
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.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@@ -271,12 +272,12 @@ class MessageComposerPresenterSlashCommandTest {
isRichTextEditorEnabled: Boolean = true,
draftService: ComposerDraftService = FakeComposerDraftService(),
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
isInThread: Boolean = false,
threadRoot: ThreadId? = null,
slashCommandService: SlashCommandService = FakeSlashCommandService(),
) = MessageComposerPresenter(
navigator = navigator,
sessionCoroutineScope = this,
isInThread = isInThread,
threadRoot = threadRoot,
room = room,
mediaPickerProvider = pickerProvider,
sessionPreferencesStore = sessionPreferencesStore,
@@ -1534,12 +1534,12 @@ class MessageComposerPresenterTest {
isRichTextEditorEnabled: Boolean = true,
draftService: ComposerDraftService = FakeComposerDraftService(),
mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(),
isInThread: Boolean = false,
threadRoot: ThreadId? = null,
slashCommandService: SlashCommandService = FakeSlashCommandService(),
) = MessageComposerPresenter(
navigator = navigator,
sessionCoroutineScope = this,
isInThread = isInThread,
threadRoot = threadRoot,
room = room,
mediaPickerProvider = pickerProvider,
sessionPreferencesStore = sessionPreferencesStore,
@@ -23,8 +23,8 @@
<string name="screen_advanced_settings_media_compression_description">"Загружайте фотографии и видео быстрее и сокращайте потребление трафика"</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_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_description">"Автоматически оптимизировать изображения для более быстрой загрузки и уменьшения размера файлов."</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_high">"Высокое (1080p)"</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_description">"Если этот параметр выключен, другие пользователи не будут видеть, прочитали ли вы сообщения. Вы по-прежнему будете видеть статус прочтения других пользователей."</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_show">"Всегда показывать"</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_title">"Показать медиа в истории сообщений"</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_description">"Вы снова сможете увидеть все сообщения."</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_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_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_edit_custom_settings_section_title">"Отдельные настройки для каждого чата"</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_group_section_header">"Уведомлять меня в групповых чатах"</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_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_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_mentions">"Упоминания"</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_sound_custom_fallback">"Пользовательский"</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 Fade"</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_dismiss_a11y">"Отклонить ошибку о звуке"</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_anticipate">"Anticipate"</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_chime">"Chime"</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_electronic">"Electronic"</string>
<string name="screen_notification_settings_sound_system_fanfare">"Fanfare"</string>
@@ -124,5 +134,5 @@
<string name="theme_system">"Системное"</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_title">"Уведомления об устранении неполадок"</string>
<string name="troubleshoot_notifications_entry_point_title">"Устранение неполадок с уведомлениями"</string>
</resources>
@@ -81,10 +81,45 @@
<string name="screen_notification_settings_mode_mentions">"Згадки"</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_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_content_link">"системні налаштування"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Системні сповіщення вимкнені"</string>
<string name="screen_notification_settings_title">"Сповіщення"</string>
<string name="theme_black">"Темна"</string>
<string name="theme_dark">"Темна"</string>
<string name="theme_light">"Світла"</string>
<string name="theme_system">"Системна"</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?"</string>
<string name="crash_detection_dialog_content">"Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце абагуліць справаздачу аб збоі?"</string>
<string name="rageshake_detection_dialog_content">"Падобна, што вы трасеце тэлефон. Хочаце адкрыць экран паведамлення пра памылку?"</string>
<string name="settings_rageshake">"Rageshake"</string>
<string name="settings_rageshake_detection_threshold">"Парог выяўлення"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="crash_detection_dialog_content">"При последнем использовании %1$s произошел сбой. Хотите отправить отчет?"</string>
<string name="rageshake_detection_dialog_content">"Похоже, что вы трясете телефон. Хотите открыть экран сообщения об ошибке?"</string>
<string name="rageshake_detection_dialog_content">"Похоже, что Вы трясете телефон в раздражении. Хотите открыть экран сообщения об ошибке?"</string>
<string name="settings_rageshake">"Встряхните"</string>
<string name="settings_rageshake_detection_threshold">"Порог обнаружения"</string>
</resources>
@@ -12,6 +12,6 @@
<string name="screen_bug_report_include_logs">"Дазволіць журналы"</string>
<string name="screen_bug_report_include_screenshot">"Адправіць здымак экрана"</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>
</resources>
@@ -8,13 +8,15 @@
<string name="screen_bug_report_editor_placeholder">"Опишите проблему…"</string>
<string name="screen_bug_report_editor_supporting">"Если возможно, пожалуйста, напишите описание на английском языке."</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_logs">"Разрешить ведение журналов"</string>
<string name="screen_bug_report_include_logs_error">"Ваши журналы слишком большие для включения в этот отчет. Пожалуйста, отправьте их нам другим способом."</string>
<string name="screen_bug_report_include_screenshot">"Отправить скриншот"</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_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_view_logs">"Просмотр журналов"</string>
</resources>
@@ -8,6 +8,8 @@
<string name="screen_bug_report_editor_placeholder">"Опишіть проблему…"</string>
<string name="screen_bug_report_editor_supporting">"Якщо можливо, будь ласка, напишіть опис англійською мовою."</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_logs">"Дозволити журнали"</string>
<string name="screen_bug_report_include_logs_error">"Ваші журнали надмірно великі, тому їх не можна включити в цей звіт, будь ласка, надішліть їх нам іншим способом."</string>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_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_reason_footer">"Пожалуйтесь на комнату администратору. Если сообщения зашифрованы, ваш администратор не сможет их прочитать."</string>
<string name="screen_report_room_reason_placeholder">"Опишите причину жалобы…"</string>
@@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<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_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_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_room_avatar">"Змяніць аватар пакоя"</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_mode_banned">"Заблакіраваныя"</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_moderator">"Адміністратары і мадэратары"</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_room_members_header_title">"Удзельнікі пакоя"</string>
<string name="screen_room_member_list_unbanning_user">"Разблакіроўка %1$s"</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_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_change_owners_description">"Отменить данное действие будет невозможно. Права передадутся выбранным пользователям. После вашего выхода действие станет необратимым."</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_moderators">"Модераторы"</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_member_list_banned_empty">"В этой комнате нет заблокированных пользователей."</string>
<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_everyone">"Учасник"</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_rooms">"Керувати кімнатами"</string>
<string name="screen_room_change_permissions_member_moderation">"Керувати учасниками"</string>
@@ -258,6 +258,10 @@ class RoomDetailsFlowNode(
override fun navigateToInviteMembers() {
backstack.push(NavTarget.InviteMembers)
}
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
}
}
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.di.RoomScope
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
@ContributesNode(RoomScope::class)
@@ -41,6 +42,7 @@ class RoomMemberListNode(
interface Callback : Plugin {
fun navigateToRoomMemberDetails(roomMemberId: UserId)
fun navigateToInviteMembers()
fun navigateToAvatarPreview(username: String, avatarUrl: String)
}
private val callback: Callback = callback()
@@ -82,6 +84,11 @@ class RoomMemberListNode(
else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
}
},
onAvatarClick = { user ->
user.avatarUrl?.let { url ->
callback.navigateToAvatarPreview(user.getBestName(), url)
}
},
modifier = Modifier,
)
}
@@ -1,14 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<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_mentions_only_disclaimer">"Ваш хатні сервер не падтрымлівае гэтую опцыю ў зашыфраваных пакоях, вы можаце не атрымаць апавяшчэнне ў некаторых пакоях."</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_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_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_room_avatar">"Змяніць аватар пакоя"</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_profile_row_title">"Профіль"</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_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_topic_title">"Тэма"</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_mode_banned">"Заблакіраваныя"</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_moderator">"Адміністратары і мадэратары"</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_room_members_header_title">"Удзельнікі пакоя"</string>
<string name="screen_room_member_list_unbanning_user">"Разблакіроўка %1$s"</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_roomlist_mark_as_read">"Пазначыць як прачытанае"</string>
<string name="screen_roomlist_mark_as_unread">"Пазначыць як непрачытанае"</string>
<string name="screen_security_and_privacy_room_address_section_header">"Адрас"</string>
</resources>
@@ -6,7 +6,7 @@
<string name="screen_edit_room_address_room_address_section_footer">"Необходимо задать адрес комнаты, чтобы опубликовать ее в каталоге комнат."</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_mentions_only_disclaimer">"Ваш сервер не поддерживает эту опцию для зашифрованных комнат, вы можете не получать некоторые уведомления."</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш сервер не поддерживает эту опцию для зашифрованных комнат, Вы можете не получать некоторые уведомления."</string>
<string name="screen_polls_history_title">"Опросы"</string>
<string name="screen_room_change_permissions_administrators">"Администраторы"</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_send_messages">"Отправлять сообщения"</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_change_owners_description">"Отменить данное действие будет невозможно. Права передадутся выбранным пользователям. После вашего выхода действие станет необратимым."</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_moderators">"Модераторы"</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_details_add_topic_title">"Добавить тему"</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_title">"Шифрование сообщений включено"</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_unmuting">"Не удалось включить звук в эту комнату, попробуйте еще раз."</string>
<string name="screen_room_details_error_muting">"Не удалось отключить звук в этой комнате, попробуйте ещё раз."</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_preparing">"Подготовка приглашений…"</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_default">"По умолчанию"</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_requests_to_join_title">"Запросы на вступление"</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_edit_remove_setting">"Удалить пользовательскую настройку"</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_setting_mode">"Не удалось настроить режим, попробуйте еще раз."</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_mentions_only_disclaimer">"Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, вы не будете получать уведомления в этой комнате."</string>
<string name="screen_room_notification_settings_mode_all_messages">"О всех сообщениях"</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_title">"Кто угодно"</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_title">"Только по приглашению"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Доступ"</string>
@@ -17,6 +17,7 @@ interface RoomMemberModerationRenderer {
fun Render(
state: RoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)?,
modifier: Modifier,
)
}
@@ -25,10 +25,11 @@ class DefaultRoomMemberModerationRenderer : RoomMemberModerationRenderer {
override fun Render(
state: RoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)?,
modifier: Modifier
) {
if (state is InternalRoomMemberModerationState) {
RoomMemberModerationView(state, onSelectAction, modifier)
RoomMemberModerationView(modifier = modifier, state = state, onSelectAction = onSelectAction, onAvatarClick = onAvatarClick)
} else {
SideEffect {
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.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
@@ -64,6 +65,7 @@ import timber.log.Timber
fun RoomMemberModerationView(
state: InternalRoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)?,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
@@ -73,6 +75,7 @@ fun RoomMemberModerationView(
user = selectedUser,
actions = state.actions,
onSelectAction = onSelectAction,
onAvatarClick = onAvatarClick,
onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) },
)
}
@@ -214,6 +217,7 @@ private fun RoomMemberActionsBottomSheet(
user: MatrixUser,
actions: ImmutableList<ModerationActionState>,
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
onAvatarClick: ((MatrixUser) -> Unit)? = null,
onDismiss: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
@@ -240,10 +244,11 @@ private fun RoomMemberActionsBottomSheet(
modifier = Modifier
.padding(bottom = 24.dp)
.align(Alignment.CenterHorizontally)
.clickable {
.clickable(enabled = user.avatarUrl != null && onAvatarClick != null) {
coroutineScope.launch {
onSelectAction(ModerationAction.DisplayProfile, user)
bottomSheetState.hide()
onAvatarClick?.invoke(user)
onDismiss()
}
}
)
@@ -282,8 +287,8 @@ private fun RoomMemberActionsBottomSheet(
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserProfile())),
onClick = {
coroutineScope.launch {
onSelectAction(action, user)
bottomSheetState.hide()
onSelectAction(action, user)
}
},
enabled = actionState.isEnabled
@@ -340,16 +345,18 @@ private fun RoomMemberActionsBottomSheet(
@PreviewsDayNight
@Composable
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 {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 64.dp)
) {
Box(modifier) {
RoomMemberModerationView(
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_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_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_removing_user">"Выдаленне %1$s…"</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.matrix.api.user.MatrixUser
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.EventsRecorder
import io.element.android.tests.testutils.clickOn
@@ -48,6 +49,8 @@ class RoomMemberModerationViewTest {
onSelectAction = callback
)
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(
state: InternalRoomMemberModerationState,
onSelectAction: (ModerationAction, MatrixUser) -> Unit = EnsureNeverCalledWithTwoParams(),
onAvatarClick: ((MatrixUser) -> Unit)? = EnsureNeverCalledWithParam(),
) {
setSafeContent {
RoomMemberModerationView(
state = state,
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_title">"Разрешить хранение ключей"</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_description">"В настоящее время резервная копия ваших чатов не синхронизирована."</string>
<string name="screen_chat_backup_recovery_action_setup">"Получить ключ восстановления"</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_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_reset_all">"«Сбросить все»"</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_2">"Вы потеряете историю тех сообщений, которые хранятся только на сервере"</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_key_backup_disable_confirmation_action_turn_off">"Удалить"</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_confirm_create_new_recovery_key">"Создать новый ключ восстановления"</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_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_lost_recovery_key">"Потеряли ключ восстановления?"</string>
<string name="screen_recovery_key_confirm_success">"Ключ восстановления подтвержден"</string>
@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<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_security_and_privacy_room_address_section_header">"Адрас"</string>
</resources>
@@ -3,7 +3,7 @@
<string name="screen_edit_room_address_room_address_section_footer">"Необходимо задать адрес комнаты, чтобы опубликовать ее в каталоге комнат."</string>
<string name="screen_edit_room_address_title">"Редактировать адрес комнаты"</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_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_title">"Кто угодно"</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_title">"Только по приглашению"</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_2">"Вы удалили сессию из другой сессии"</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>
</resources>
@@ -11,11 +11,11 @@
<string name="screen_identity_use_another_device">"Выкарыстоўвайце іншую прыладу"</string>
<string name="screen_identity_waiting_on_other_device">"Чаканне на іншай прыладзе…"</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_numbers_subtitle">"Пераканайцеся, што прыведзеныя ніжэй лічбы супадаюць з лічбамі, паказанымі ў іншым сеансе."</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_enter_recovery_key">"Увядзіце ключ аднаўлення"</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_ready_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_they_dont_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_title">"Чаканне прыняцця запыту"</string>
<string name="screen_signout_in_progress_dialog_content">"Выхад…"</string>
<string name="screen_signout_in_progress_dialog_content">"Выдаленне прылады…"</string>
</resources>
@@ -6,7 +6,7 @@
<string name="screen_identity_confirmation_title">"Подтвердите личность"</string>
<string name="screen_identity_confirmation_use_another_device">"Использовать другое устройство"</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_use_another_device">"Использовать другое устройство"</string>
<string name="screen_identity_waiting_on_other_device">"Ожидание другого устройства…"</string>
+10 -10
View File
@@ -3,9 +3,9 @@
[versions]
# 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
kotlin = "2.3.21"
kotlin = "2.4.0"
kotlinpoet = "2.3.0"
ksp = "2.3.9"
firebaseAppDistribution = "5.2.1"
@@ -46,12 +46,12 @@ showkase = "1.0.5"
# When upgrading this version, check state restoration still works fine.
appyx = "1.7.1"
sqldelight = "2.3.2"
wysiwyg = "2.41.3"
wysiwyg = "2.42.0"
telephoto = "0.19.0"
haze = "1.7.2"
# Dependency analysis
dependencyAnalysis = "3.14.0"
dependencyAnalysis = "3.14.1"
# DI
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" }
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
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" }
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" }
@@ -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
# 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.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.06.3"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.06.11"
# Others
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_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
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_compose = "org.maplibre.compose:maplibre-compose:0.13.0"
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
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
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"
dependencycheck = "org.owasp.dependencycheck:12.2.2"
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" }
sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
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"
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
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
distributionPath=wrapper/dists
distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
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_by_you">"Вы приняли приглашение"</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_by_you">"Вы присоединились к комнате"</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_none">"%1$s не внес(ла) изменений"</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_by_you">"Вы изменили закрепленные сообщения"</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_pinned">"%1$s закрепил(а) сообщение"</string>
<string name="state_event_room_pinned_events_pinned_by_you">"Вы закрепили сообщение"</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,
cause = cause
)
class ContentScanner(message: String, val reason: ContentScannerErrorReason) : ClientException(message, null, null)
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
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
fun Throwable.mapClientException(): ClientException {
@@ -23,6 +24,10 @@ fun Throwable.mapClientException(): ClientException {
details = details,
cause = this,
)
is RustClientException.ContentScanner -> ClientException.ContentScanner(
message = message,
reason = ContentScannerErrorReason.fromRust(reason),
)
}
}
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
_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) {
Timber.tag(tag.value).w(e, "Error during QR login grant")
// 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(
handler: FakeFfiGrantLoginWithQrCodeHandler = FakeFfiGrantLoginWithQrCodeHandler(),
) = RustLinkMobileHandler(
@@ -78,7 +78,7 @@ fun SpaceRoomItemView(
indication = ripple(),
interactionSource = remember { MutableInteractionSource() }
)
.onKeyboardContextMenuAction { onLongClick }
.onKeyboardContextMenuAction(onLongClick)
Column(
modifier = modifier
.then(clickModifier)
@@ -5,5 +5,5 @@
<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_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>
@@ -258,7 +258,7 @@ class ExoPlayerForPreview(
override fun setPriorityTaskManager(priorityTaskManager: PriorityTaskManager?) {}
override fun isSleepingForOffload(): 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 setAudioCodecParameters(codecParameters: CodecParameters) {}
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