Merge branch 'develop' into valere/call/fix_join_button_on_several_items

This commit is contained in:
Valere 2026-04-21 11:05:14 +02:00
commit af47e2b405
376 changed files with 5383 additions and 2535 deletions

View file

@ -74,7 +74,7 @@ jobs:
run: ./gradlew :app:assembleGplayDebug app:assembleFDroidDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Upload debug APKs
if: ${{ matrix.variant == 'debug' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-debug
path: |

View file

@ -79,7 +79,7 @@ jobs:
run: ./gradlew :app:assembleGplayDebug -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES
- name: Upload debug Enterprise APKs
if: ${{ matrix.variant == 'debug' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-enterprise-debug
path: |

View file

@ -20,7 +20,7 @@ jobs:
if: github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name
steps:
- name: Add auto-generated commit warning
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
github.rest.issues.createComment({

View file

@ -60,7 +60,7 @@ jobs:
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
- name: Upload APK as artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-apk-maestro
path: |
@ -119,7 +119,7 @@ jobs:
script: |
.github/workflows/scripts/maestro/maestro-local-with-screen-recording.sh app-gplay-x86_64-debug.apk
- name: Upload test results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-results
path: |

View file

@ -58,7 +58,7 @@ jobs:
- name: ✅ Upload kover report
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: kover-results
path: |
@ -92,7 +92,7 @@ jobs:
run: ./gradlew dependencyCheckAnalyze $CI_GRADLE_ARG_PROPERTIES
- name: Upload dependency analysis
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dependency-analysis
path: build/reports/dependency-check-report.html

View file

@ -15,7 +15,7 @@ jobs:
steps:
- name: Trigger pipeline
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.ENTERPRISE_ACTIONS_TOKEN }}
script: |

View file

@ -17,7 +17,7 @@ jobs:
pull-requests: read
steps:
- name: Add notice
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
if: contains(github.event.pull_request.labels.*.name, 'X-Blocked')
with:
script: |
@ -41,7 +41,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN_READ_ORG }}
- name: Add label
if: steps.teams.outputs.isTeamMember == 'false'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
github.rest.issues.addLabels({
@ -63,7 +63,7 @@ jobs:
github.event.pull_request.head.repo.full_name != github.repository
steps:
- name: Close pull request
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
github.rest.issues.createComment({

View file

@ -120,7 +120,7 @@ jobs:
run: ./gradlew :tests:konsist:testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES --no-daemon
- name: Upload reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: konsist-report
path: |
@ -199,7 +199,7 @@ jobs:
run: ./gradlew :app:lintGplayDebug :app:lintFdroidDebug lintDebug $CI_GRADLE_ARG_PROPERTIES --continue
- name: Upload reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linting-report
path: |
@ -240,7 +240,7 @@ jobs:
run: ./gradlew detekt $CI_GRADLE_ARG_PROPERTIES --no-daemon
- name: Upload reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detekt-report
path: |
@ -281,7 +281,7 @@ jobs:
run: ./gradlew ktlintCheck $CI_GRADLE_ARG_PROPERTIES
- name: Upload reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ktlint-report
path: |

View file

@ -57,7 +57,7 @@ jobs:
ELEMENT_CALL_RAGESHAKE_URL: ${{ secrets.ELEMENT_CALL_RAGESHAKE_URL }}
run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload bundle as artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-app-gplay-bundle-unsigned
path: |
@ -95,7 +95,7 @@ jobs:
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload bundle as artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-enterprise-app-gplay-bundle-unsigned
path: |
@ -139,7 +139,7 @@ jobs:
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
run: ./gradlew assembleFdroidRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload apks as artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-app-fdroid-apks-unsigned
path: |

View file

@ -40,7 +40,7 @@ jobs:
./tools/localazy/importSupportedLocalesFromLocalazy.py
./tools/test/generateAllScreenshots.py
- name: Create Pull Request for Strings
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
commit-message: Sync Strings from Localazy

View file

@ -27,7 +27,7 @@ jobs:
- name: Run SAS String script
run: ./tools/sas/import_sas_strings.py
- name: Create Pull Request for SAS Strings
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
commit-message: Sync SAS Strings
title: Sync SAS Strings

View file

@ -77,7 +77,7 @@ jobs:
- name: 🚫 Upload kover failed coverage reports
if: failure()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: kover-error-report
path: |
@ -89,7 +89,7 @@ jobs:
- name: 🚫 Upload test results on error
if: failure()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: tests-and-screenshot-tests-results
path: |

View file

@ -71,6 +71,7 @@ class MainActivity : NodeActivity() {
}.collectAsState(SemanticColorsLightDark.default)
ElementThemeApp(
appPreferencesStore = appBindings.preferencesStore(),
featureFlagService = appBindings.featureFlagService(),
compoundLight = colors.light,
compoundDark = colors.dark,
buildMeta = appBindings.buildMeta()

View file

@ -77,6 +77,7 @@ import io.element.android.libraries.designsystem.theme.ElementThemeApp
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
@ -144,6 +145,7 @@ class LoggedInFlowNode(
private val syncService: SyncService,
private val enterpriseService: EnterpriseService,
private val appPreferencesStore: AppPreferencesStore,
private val featureFlagService: FeatureFlagService,
private val buildMeta: BuildMeta,
snackbarDispatcher: SnackbarDispatcher,
private val analyticsService: AnalyticsService,
@ -671,6 +673,7 @@ class LoggedInFlowNode(
}.collectAsState(SemanticColorsLightDark.default)
ElementThemeApp(
appPreferencesStore = appPreferencesStore,
featureFlagService = featureFlagService,
compoundLight = colors.light,
compoundDark = colors.dark,
buildMeta = buildMeta,

View file

@ -31,7 +31,6 @@ import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
@ -177,7 +176,6 @@ class LoggedInPresenter(
}
private fun CoroutineScope.preloadAccountManagementUrl() = launch {
matrixClient.getAccountManagementUrl(AccountManagementAction.Profile)
matrixClient.getAccountManagementUrl(AccountManagementAction.DevicesList)
matrixClient.getAccountManagementUrl(null)
}
}

View file

@ -71,7 +71,7 @@ class LoggedInPresenterTest {
}
@Test
fun `present - ensure that account urls are preloaded`() = runTest {
fun `present - ensure that account url is preloaded`() = runTest {
val accountManagementUrlResult = lambdaRecorder<AccountManagementAction?, Result<String?>> { Result.success("aUrl") }
val matrixClient = FakeMatrixClient(
accountManagementUrlResult = accountManagementUrlResult,
@ -81,11 +81,8 @@ class LoggedInPresenterTest {
).test {
awaitItem()
advanceUntilIdle()
accountManagementUrlResult.assertions().isCalledExactly(2)
.withSequence(
listOf(value(AccountManagementAction.Profile)),
listOf(value(AccountManagementAction.DevicesList)),
)
accountManagementUrlResult.assertions().isCalledOnce()
.with(value(null))
}
}

View file

@ -52,6 +52,9 @@ allprojects {
tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
exclude("io/element/android/tests/konsist/failures/**")
// This file comes from another project and we want to keep it as close to the original as possible
exclude("org/rustls/platformverifier/**")
}
// KtLint
@ -79,6 +82,9 @@ allprojects {
// This file comes from another project and we want to keep it as close to the original as possible
exclude("**/SafeChildrenTransitionScope.kt")
// This file comes from another project and we want to keep it as close to the original as possible
exclude("org/rustls/platformverifier/**")
}
}
// Dependency check

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item4">"Присъединете се към обществени пространства"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Zobrazit prostory, které jste vytvořili nebo ke kterým jste se připojili"</string>
<string name="screen_space_announcement_item2">"Přijmout nebo odmítnout pozvánky do prostorů"</string>
<string name="screen_space_announcement_item3">"Objevte všechny místnosti, do kterých můžete vstoupit ve svých prostorech"</string>
<string name="screen_space_announcement_item4">"Připojit se k veřejným prostorům"</string>
<string name="screen_space_announcement_item5">"Opustit všechny prostory, ke kterým jste se připojili"</string>
<string name="screen_space_announcement_notice">"Filtrování, vytváření a správa prostorů bude brzy k dispozici."</string>
<string name="screen_space_announcement_subtitle">"Vítejte v beta verzi prostorů! S touto první verzí můžete:"</string>
<string name="screen_space_announcement_title">"Představujeme prostory"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Se klynger, du har oprettet eller tilmeldt dig"</string>
<string name="screen_space_announcement_item2">"Acceptere eller afvise invitationer til klynger"</string>
<string name="screen_space_announcement_item3">"Finde alle rum, du kan deltage i, i dine klynger"</string>
<string name="screen_space_announcement_item4">"Deltage i offentlige klynger"</string>
<string name="screen_space_announcement_item5">"Forlade de klynger, du har tilsluttet dig"</string>
<string name="screen_space_announcement_notice">"Filtrering, oprettelse og administration af klynger kommer snart."</string>
<string name="screen_space_announcement_subtitle">"Velkommen til betaversionen af Klynger! Med denne første version kan du:"</string>
<string name="screen_space_announcement_title">"Introduktion til Klynger"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Von dir erstellte oder beigetretene Spaces anzeigen"</string>
<string name="screen_space_announcement_item2">"Einladungen zu Spaces annehmen oder ablehnen"</string>
<string name="screen_space_announcement_item3">"Chats innerhalb deiner Spaces entdecken, um ihnen beizutreten"</string>
<string name="screen_space_announcement_item4">"Öffentlichen Spaces beitreten"</string>
<string name="screen_space_announcement_item5">"Spaces verlassen, bei denen du Mitglied bist"</string>
<string name="screen_space_announcement_notice">"Das Filtern, Erstellen und Verwalten von Spaces ist bald verfügbar."</string>
<string name="screen_space_announcement_subtitle">"Willkommen bei der Beta-Version von Spaces! Mit dieser ersten Version kannst du:"</string>
<string name="screen_space_announcement_title">"Einführung in Spaces"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Δείτε τους χώρους που έχετε δημιουργήσει ή στους οποίους έχετε εγγραφεί"</string>
<string name="screen_space_announcement_item2">"Να αποδεχθείτε ή να απορρίψετε προσκλήσεις σε χώρους"</string>
<string name="screen_space_announcement_item3">"Να ανακαλύψτε όλες τις αίθουσες που μπορείτε να συμμετάσχετε στους χώρους σας"</string>
<string name="screen_space_announcement_item4">"Να συμμετάσχετε σε δημόσιους χώρους"</string>
<string name="screen_space_announcement_item5">"Να αποχωρήστε από χώρους στους οποίους έχετε συμμετάσχει"</string>
<string name="screen_space_announcement_notice">"Το φιλτράρισμα, η δημιουργία και η διαχείριση χώρων θα είναι σύντομα διαθέσιμα."</string>
<string name="screen_space_announcement_subtitle">"Καλώς ορίσατε στην δοκιμαστική έκδοση των Χώρων! Με αυτήν την πρώτη έκδοση μπορείτε:"</string>
<string name="screen_space_announcement_title">"Παρουσιάζοντας τους Χώρους"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Vaadata kogukondi, mille oled loonud või millega oled liitunud"</string>
<string name="screen_space_announcement_item2">"Nõustuda kutsetega liitumiseks kogukonnaga või sellest keelduda"</string>
<string name="screen_space_announcement_item3">"Uurida neis kogukondades leiduvaid jututube ning nendega liituda"</string>
<string name="screen_space_announcement_item4">"Liituda avalike kogukondadega"</string>
<string name="screen_space_announcement_item5">"Lahkuda kogukonnast, millega oled liitunud"</string>
<string name="screen_space_announcement_notice">"Kogukondade filtreerimine, loomine ja haldamine lisandub peagi"</string>
<string name="screen_space_announcement_subtitle">"Tere tulemast kasutama kogukondade beetaversiooni! Selles esimeses versioonis saad sa:"</string>
<string name="screen_space_announcement_title">"Võtame kasutusele kogukonnad"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"دیدن فضاهایی که ساخته یا پیوسته‌اید"</string>
<string name="screen_space_announcement_item2">"پذیرش یا رد دعوت‌ها به فضاها"</string>
<string name="screen_space_announcement_item3">"کشف تمامی اتاق‌هایی که می‌توانید در فضاهایتان بپیوندید"</string>
<string name="screen_space_announcement_item4">"پیوستن به فضاهای عمومی"</string>
<string name="screen_space_announcement_item5">"ترک هر فضایی که پیوسته‌اید"</string>
<string name="screen_space_announcement_notice">"پالایش، ایجاد و مدیریت کردن فضاها به زودی."</string>
<string name="screen_space_announcement_subtitle">"به نگارش آزمایشی فضاها خوش آمدید! در این نگارش می‌توانید:"</string>
<string name="screen_space_announcement_title">"معرّفی فضاها"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Nähdä luomasi tai liittymäsi tilat"</string>
<string name="screen_space_announcement_item2">"Hyväksyä tai hylätä kutsuja tiloihin"</string>
<string name="screen_space_announcement_item3">"Löytää kaikki huoneet, joihin voit liittyä tiloissasi"</string>
<string name="screen_space_announcement_item4">"Liittyä julkisiin tiloihin"</string>
<string name="screen_space_announcement_item5">"Poistua mistä tahansa tilasta, johon olet liittynyt"</string>
<string name="screen_space_announcement_notice">"Tilojen suodatus, luominen ja hallinta on tulossa pian."</string>
<string name="screen_space_announcement_subtitle">"Tervetuloa tilojen beetaversioon! Tämän ensimmäisen version avulla voit:"</string>
<string name="screen_space_announcement_title">"Esittelyssä tilat"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Voir les espaces que vous avez créés ou rejoints"</string>
<string name="screen_space_announcement_item2">"Accepter ou refuser les invitations aux espaces"</string>
<string name="screen_space_announcement_item3">"Découvrir les salons que vous pouvez joindre depuis vos espaces"</string>
<string name="screen_space_announcement_item4">"Rejoindre les espaces publics"</string>
<string name="screen_space_announcement_item5">"Quitter les espaces dont vous êtes membre."</string>
<string name="screen_space_announcement_notice">"Le filtrage, la création et la gestion des espaces seront bientôt disponibles."</string>
<string name="screen_space_announcement_subtitle">"Bienvenue dans la version bêta des espaces! Avec cette première version, vous pourrez :"</string>
<string name="screen_space_announcement_title">"Ajout des espaces"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Pregledajte prostore koje ste stvorili ili kojima ste se pridružili"</string>
<string name="screen_space_announcement_item2">"Prihvatite ili odbijte pozivnice za prostore"</string>
<string name="screen_space_announcement_item3">"Otkrijte sve sobe kojima se možete pridružiti u svojim prostorima"</string>
<string name="screen_space_announcement_item4">"Pridružite se javnim prostorima"</string>
<string name="screen_space_announcement_item5">"Napustite sve prostore kojima ste se pridružili"</string>
<string name="screen_space_announcement_notice">"Uskoro stiže filtriranje i stvaranje prostora te upravljanje njima."</string>
<string name="screen_space_announcement_subtitle">"Dobrodošli u beta inačicu prostora! S ovom prvom inačicom možete:"</string>
<string name="screen_space_announcement_title">"Predstavljamo prostore"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Az Ön által létrehozott vagy csatlakozott térek megtekintése"</string>
<string name="screen_space_announcement_item2">"A meghívások elfogadására vagy elutasítására a terekhez"</string>
<string name="screen_space_announcement_item3">"Szobák felfedezése a terekben, amelyekhez csatlakozhat"</string>
<string name="screen_space_announcement_item4">"Csatlakozás nyilvános terekhez"</string>
<string name="screen_space_announcement_item5">"Terek elhagyása"</string>
<string name="screen_space_announcement_notice">"Terek szűrése, készítése és kezelése hamarosan érkezik."</string>
<string name="screen_space_announcement_subtitle">"Üdvözöljük a tér béta verziójában! Ezzel az első verzióval a következőket teheti:"</string>
<string name="screen_space_announcement_title">"Bemutatkoznak a terek"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Visualizza gli spazi che hai creato o a cui partecipi"</string>
<string name="screen_space_announcement_item2">"Accetta o rifiuta gli inviti agli spazi"</string>
<string name="screen_space_announcement_item3">"Scopri tutte le stanze a cui puoi partecipare nei tuoi spazi"</string>
<string name="screen_space_announcement_item4">"Unisciti agli spazi pubblici"</string>
<string name="screen_space_announcement_item5">"Lascia tutti gli spazi a cui ti sei unito"</string>
<string name="screen_space_announcement_notice">"A breve saranno disponibili le funzionalità di filtraggio, creazione e gestione degli spazi."</string>
<string name="screen_space_announcement_subtitle">"Benvenuti alla versione beta degli Spazi! Con questa prima versione potrete:"</string>
<string name="screen_space_announcement_title">"Ti presentiamo gli Spazi"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"作成または参加したスペースを表示できます"</string>
<string name="screen_space_announcement_item2">"スペースへの招待を受諾または拒否できます"</string>
<string name="screen_space_announcement_item3">"スペース内の参加可能なルームを検索できます"</string>
<string name="screen_space_announcement_item4">"公開スペースに参加できます"</string>
<string name="screen_space_announcement_item5">"参加したスペースを退出できます"</string>
<string name="screen_space_announcement_notice">"スペースの作成や管理, フィルター検索は近日実装予定です。"</string>
<string name="screen_space_announcement_subtitle">"ベータ版のスペースにようこそ。この最新のバージョンでは:"</string>
<string name="screen_space_announcement_title">"スペースの紹介"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"직접 만들거나 참여 중인 스페이스 보기"</string>
<string name="screen_space_announcement_item2">"스페이스 초대 수락 또는 거절"</string>
<string name="screen_space_announcement_item3">"참여 가능한 스페이스 내 모든 방 탐색"</string>
<string name="screen_space_announcement_item4">"공개 스페이스 참여"</string>
<string name="screen_space_announcement_item5">"참여 중인 스페이스 나가기"</string>
<string name="screen_space_announcement_notice">"스페이스 필터링, 생성 및 관리 기능이 곧 추가될 예정입니다."</string>
<string name="screen_space_announcement_subtitle">"스페이스 베타 버전에 오신 것을 환영합니다! 이번 첫 번째 버전에서는 다음과 같은 기능을 이용하실 수 있습니다.:"</string>
<string name="screen_space_announcement_title">"스페이스 소개"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Se områder du har opprettet eller blitt med i"</string>
<string name="screen_space_announcement_item2">"Godta eller avslå invitasjoner til områder"</string>
<string name="screen_space_announcement_item3">"Oppdag alle rom du kan bli med i i dine områder"</string>
<string name="screen_space_announcement_item4">"Bli med i offentlige områder"</string>
<string name="screen_space_announcement_item5">"Forlat områder du har blitt med i"</string>
<string name="screen_space_announcement_notice">"Oppretting, filtrering og administrasjon av områder kommer snart."</string>
<string name="screen_space_announcement_subtitle">"Velkommen til betaversjonen av Områder! Med denne første versjonen kan du:"</string>
<string name="screen_space_announcement_title">"Vi introduserer Områder"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Wyświetlić przestrzenie, które stworzyłeś lub do których dołączyłeś"</string>
<string name="screen_space_announcement_item2">"Akceptować lub odrzucać zaproszenia"</string>
<string name="screen_space_announcement_item3">"Odkrywać wszystkie pokoje, do których możesz dołączyć w swoich przestrzeniach"</string>
<string name="screen_space_announcement_item4">"Dołączać do przestrzeni publicznych"</string>
<string name="screen_space_announcement_item5">"Opuszczać jakąkolwiek przestrzeń, do której dołączyłeś"</string>
<string name="screen_space_announcement_notice">"Filtrowanie, tworzenie i zarządzanie przestrzeniami pojawi się wkrótce."</string>
<string name="screen_space_announcement_subtitle">"Witamy w wersji beta przestrzeni! W tej wersji możesz:"</string>
<string name="screen_space_announcement_title">"Przedstawiamy przestrzenie"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Visualizar espaços que criou ou entrou"</string>
<string name="screen_space_announcement_item2">"Aceitar ou recusar convites aos espaços"</string>
<string name="screen_space_announcement_item3">"Descobrir quaisquer salas que você pode entrar nos espaços"</string>
<string name="screen_space_announcement_item4">"Entrar espaços públicos"</string>
<string name="screen_space_announcement_item5">"Sair de quaisquer espaços que entrou"</string>
<string name="screen_space_announcement_notice">"Filtrar, criar, e gerenciar espaços virão em breve."</string>
<string name="screen_space_announcement_subtitle">"Boas-vindas à versão beta dos Espaços! Com essa primeira versão, você pode:"</string>
<string name="screen_space_announcement_title">"Apresentando Espaços"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Ver espaços que criaste ou nos quais entraste"</string>
<string name="screen_space_announcement_item2">"Aceitar ou recusar convites para espaços"</string>
<string name="screen_space_announcement_item3">"Descobrir todas as salas dos seus espaços nas quais podes entrar"</string>
<string name="screen_space_announcement_item4">"Entrar em espaços públicos"</string>
<string name="screen_space_announcement_item5">"Deixar todos os espaços em que entraste"</string>
<string name="screen_space_announcement_notice">"Em breve, será possível filtrar, criar e gerir espaços."</string>
<string name="screen_space_announcement_subtitle">"Eis a versão beta dos Espaços! Nesta primeira versão, podes:"</string>
<string name="screen_space_announcement_title">"Apresentamos os Espaços"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Vizualizați spațiile pe care le-ați creat sau la care v-ați alăturat"</string>
<string name="screen_space_announcement_item2">"Acceptați sau refuzați invitațiile la spații"</string>
<string name="screen_space_announcement_item3">"Descoperiți toate camerele la care vă puteți alătura în spațiile dumneavoastră."</string>
<string name="screen_space_announcement_item4">"Alăturați-vă spațiilor publice"</string>
<string name="screen_space_announcement_item5">"Părăsiți spațiile la care v-ați alăturat."</string>
<string name="screen_space_announcement_notice">"Filtrarea, crearea și gestionarea spațiilor vor fi disponibile în curând."</string>
<string name="screen_space_announcement_subtitle">"Bun venit la versiunea beta a Spațiilor! Cu această primă versiune puteți:"</string>
<string name="screen_space_announcement_title">"Vă prezentăm Spații"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Просматривать пространства, которые вы создали или к которым присоединились"</string>
<string name="screen_space_announcement_item2">"Принимать или отклонять приглашения в пространства"</string>
<string name="screen_space_announcement_item3">"Находить все комнаты, к которым можно присоединиться в ваших пространствах"</string>
<string name="screen_space_announcement_item4">"Присоединяться к публичным пространствам"</string>
<string name="screen_space_announcement_item5">"Покидать все пространства, к которым вы присоединились"</string>
<string name="screen_space_announcement_notice">"Фильтровать, создавать пространства и управлять ими можно будет позже."</string>
<string name="screen_space_announcement_subtitle">"Добро пожаловать в бета-версию пространств! Сейчас вы сможете:"</string>
<string name="screen_space_announcement_title">"Представляем пространства"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Zobraziť priestory, ktoré ste vytvorili alebo ku ktorým ste sa pripojili"</string>
<string name="screen_space_announcement_item2">"Prijímať alebo odmietať pozvánky do priestorov"</string>
<string name="screen_space_announcement_item3">"Objaviť všetky miestnosti, do ktorých sa môžete pripojiť vo svojich priestoroch"</string>
<string name="screen_space_announcement_item4">"Pripojiť sa k verejnému priestoru"</string>
<string name="screen_space_announcement_item5">"Opustiť akékoľvek priestory, ku ktorým ste sa pridali"</string>
<string name="screen_space_announcement_notice">"Filtrovanie, vytváranie a správa priestorov bude čoskoro k dispozícii."</string>
<string name="screen_space_announcement_subtitle">"Vitajte v beta verzii priestorov! S touto prvou verziou môžete:"</string>
<string name="screen_space_announcement_title">"Predstavujeme priestory"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Oluşturduğunuz veya katıldığınız alanları görüntüleyin"</string>
<string name="screen_space_announcement_item2">"Alan davetlerini kabul edin veya reddedin"</string>
<string name="screen_space_announcement_item3">"Alanlarınızdaki katılabileceğiniz odaları keşfedin"</string>
<string name="screen_space_announcement_item4">"Herkese açık alanlara katılın"</string>
<string name="screen_space_announcement_item5">"Katıldığınız alanlardan ayrılın"</string>
<string name="screen_space_announcement_notice">"Alanları filtreleme, oluşturma ve yönetme yakında geliyor."</string>
<string name="screen_space_announcement_subtitle">"Alanların beta sürümüne hoş geldiniz! Bu ilk sürümle şunları yapabilirsiniz:"</string>
<string name="screen_space_announcement_title">"Alanlar ile tanışın"</string>
</resources>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item3">"Знаходьте у своїх просторах кімнати, до яких можна приєднатися"</string>
<string name="screen_space_announcement_notice">"Фільтрування, створення та керування просторами стане доступним найближчим часом."</string>
<string name="screen_space_announcement_subtitle">"Ласкаво просимо до бета-версії Просторів! У цій першій версії ви можете:"</string>
<string name="screen_space_announcement_title">"Представляємо Простори"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"Siz yaratgan yoki qoshilgan maydonlarni korish"</string>
<string name="screen_space_announcement_item2">"Maydonlarga takliflarni qabul qilish yoki rad etish"</string>
<string name="screen_space_announcement_item3">"Maydonlaringizga qoshilishingiz mumkin bolgan xonalarni kashf eting"</string>
<string name="screen_space_announcement_item4">"Jamoat maydonlariga qoshilish"</string>
<string name="screen_space_announcement_item5">"Kirgan maydonlaringizni tark eting"</string>
<string name="screen_space_announcement_notice">"Maydonlarni filtrlash, yaratish va boshqarish tez orada amalga oshiriladi."</string>
<string name="screen_space_announcement_subtitle">"Maydonlar beta versiyasiga xush kelibsiz! Bu birinchi versiya bilan siz:"</string>
<string name="screen_space_announcement_title">"Maydonlar bilan tanishish"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"檢視您建立或加入的空間"</string>
<string name="screen_space_announcement_item2">"接受或拒絕空間邀請"</string>
<string name="screen_space_announcement_item3">"探索空間內您可以加入的任何聊天室"</string>
<string name="screen_space_announcement_item4">"加入公開空間"</string>
<string name="screen_space_announcement_item5">"離開任何您已加入的空間"</string>
<string name="screen_space_announcement_notice">"篩選、建立與管理空間功能即將推出。"</string>
<string name="screen_space_announcement_subtitle">"歡迎使用空間的測試版!此初始版本可讓您:"</string>
<string name="screen_space_announcement_title">"介紹空間"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"查看您创建或加入的空间"</string>
<string name="screen_space_announcement_item2">"接受或拒绝空间邀请"</string>
<string name="screen_space_announcement_item3">"发现您可以加入空间的所有房间"</string>
<string name="screen_space_announcement_item4">"加入公共空间"</string>
<string name="screen_space_announcement_item5">"离开你加入的所有空间"</string>
<string name="screen_space_announcement_notice">"筛选、创建及管理空间功能即将上线。"</string>
<string name="screen_space_announcement_subtitle">"欢迎使用空间测试版!使用首个版本,您可以:"</string>
<string name="screen_space_announcement_title">"空间简介"</string>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_space_announcement_item1">"View spaces you\'ve created or joined"</string>
<string name="screen_space_announcement_item2">"Accept or decline invites to spaces"</string>
<string name="screen_space_announcement_item3">"Discover any rooms you can join in your spaces"</string>
<string name="screen_space_announcement_item4">"Join public spaces"</string>
<string name="screen_space_announcement_item5">"Leave any spaces youve joined"</string>
<string name="screen_space_announcement_notice">"Filtering, creating and managing spaces is coming soon."</string>
<string name="screen_space_announcement_subtitle">"Welcome to the beta version of Spaces! With this first version you can:"</string>
<string name="screen_space_announcement_title">"Introducing Spaces"</string>
</resources>

View file

@ -54,6 +54,7 @@ import io.element.android.libraries.audio.api.AudioFocusRequester
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.theme.ElementThemeApp
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import timber.log.Timber
@ -66,6 +67,7 @@ class ElementCallActivity :
@Inject lateinit var callIntentDataParser: CallIntentDataParser
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
@Inject lateinit var appPreferencesStore: AppPreferencesStore
@Inject lateinit var featureFlagService: FeatureFlagService
@Inject lateinit var enterpriseService: EnterpriseService
@Inject lateinit var pictureInPicturePresenter: PictureInPicturePresenter
@Inject lateinit var buildMeta: BuildMeta
@ -114,6 +116,7 @@ class ElementCallActivity :
}.collectAsState(SemanticColorsLightDark.default)
ElementThemeApp(
appPreferencesStore = appPreferencesStore,
featureFlagService = featureFlagService,
compoundLight = colors.light,
compoundDark = colors.dark,
buildMeta = buildMeta,

View file

@ -30,6 +30,7 @@ import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.theme.ElementThemeApp
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
@ -57,6 +58,9 @@ class IncomingCallActivity : AppCompatActivity() {
@Inject
lateinit var appPreferencesStore: AppPreferencesStore
@Inject
lateinit var featureFlagService: FeatureFlagService
@Inject
lateinit var enterpriseService: EnterpriseService
@ -88,6 +92,7 @@ class IncomingCallActivity : AppCompatActivity() {
}.collectAsState(SemanticColorsLightDark.default)
ElementThemeApp(
appPreferencesStore = appPreferencesStore,
featureFlagService = featureFlagService,
compoundLight = colors.light,
compoundDark = colors.dark,
buildMeta = buildMeta,

View file

@ -8,15 +8,19 @@
<string name="screen_create_room_new_room_title">"Neuer Chat"</string>
<string name="screen_create_room_new_space_title">"Neuer Space"</string>
<string name="screen_create_room_private_option_description">"Nur eingeladene Personen haben Zutritt zu diesem Chat."</string>
<string name="screen_create_room_private_option_title">"Privat"</string>
<string name="screen_create_room_public_option_description">"Jeder kann diesen Chat finden.
Du kannst dies jederzeit in den Einstellungen des Chats ändern."</string>
<string name="screen_create_room_public_option_short_description">"Jeder kann beitreten."</string>
<string name="screen_create_room_public_option_title">"Öffentlich"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Jeder kann den Beitritt zum Chat erbitten, aber ein Admin oder Moderator muss die Anfrage akzeptieren."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Anfrage zum Beitritt zulassen"</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Jeder in %1$s kann beitreten, aber alle anderen müssen den Beitritt anfragen."</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Beitritt anfragen"</string>
<string name="screen_create_room_room_access_section_private_option_description">"Nur eingeladene Personen können beitreten."</string>
<string name="screen_create_room_room_access_section_private_option_title">"Privat"</string>
<string name="screen_create_room_room_access_section_public_option_description">"Jeder darf diesem Chat beitreten."</string>
<string name="screen_create_room_room_access_section_public_option_title">"Öffentlich"</string>
<string name="screen_create_room_room_access_section_restricted_option_description">"Jeder in %1$s kann beitreten."</string>
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
<string name="screen_create_room_room_access_section_title">"Wer hat Zugang"</string>

View file

@ -3,15 +3,34 @@
<string name="screen_create_room_action_create_room">"Nova soba"</string>
<string name="screen_create_room_add_people_title">"Pozovi osobe"</string>
<string name="screen_create_room_error_creating_room">"Došlo je do pogreške prilikom stvaranja sobe"</string>
<string name="screen_create_room_error_creating_space">"Prostor nije moguće stvoriti zbog nepoznate pogreške. Pokušajte ponovno kasnije."</string>
<string name="screen_create_room_name_placeholder">"Dodaj ime…"</string>
<string name="screen_create_room_new_room_title">"Nova soba"</string>
<string name="screen_create_room_private_option_description">"Samo pozvane osobe mogu pristupiti ovoj sobi. Sve su poruke sveobuhvatno šifrirane."</string>
<string name="screen_create_room_new_space_title">"Novi prostor"</string>
<string name="screen_create_room_private_option_description">"Samo pozvane osobe mogu se pridružiti."</string>
<string name="screen_create_room_private_option_title">"Privatno"</string>
<string name="screen_create_room_public_option_description">"Svatko može pronaći ovu sobu.
To možete u svakom trenutku promijeniti u postavkama sobe."</string>
<string name="screen_create_room_public_option_short_description">"Svatko se može pridružiti."</string>
<string name="screen_create_room_public_option_title">"Javno"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Svatko može zatražiti pridruživanje sobi, ali administrator ili moderator morat će prihvatiti zahtjev."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Zatraži pridruživanje"</string>
<string name="screen_create_room_room_access_section_public_option_description">"Svatko se može pridružiti ovoj sobi"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Dopusti traženje pridruživanja"</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"Svatko u %1$s može se pridružiti, ali svi ostali moraju zatražiti pristup."</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Zatraži pridruživanje"</string>
<string name="screen_create_room_room_access_section_private_option_description">"Samo pozvane osobe mogu pristupiti ovoj sobi. Sve su poruke sveobuhvatno šifrirane."</string>
<string name="screen_create_room_room_access_section_private_option_title">"Privatno"</string>
<string name="screen_create_room_room_access_section_public_option_description">"Svatko se može pridružiti."</string>
<string name="screen_create_room_room_access_section_public_option_title">"Javno"</string>
<string name="screen_create_room_room_access_section_restricted_option_description">"Svatko u %1$s može se pridružiti."</string>
<string name="screen_create_room_room_access_section_restricted_option_title">"Standard"</string>
<string name="screen_create_room_room_access_section_title">"Tko ima pristup"</string>
<string name="screen_create_room_room_address_section_footer">"Da bi ova soba bila vidljiva u javnom direktoriju soba, trebat će vam adresa sobe."</string>
<string name="screen_create_room_room_address_section_title">"Adresa sobe"</string>
<string name="screen_create_room_room_address_section_title">"Adresa"</string>
<string name="screen_create_room_room_visibility_section_title">"Vidljivost sobe"</string>
<string name="screen_create_room_space_selection_no_space_description">"(bez razmaka)"</string>
<string name="screen_create_room_space_selection_no_space_option">"Ne dodavaj u prostor"</string>
<string name="screen_create_room_space_selection_no_space_title">"Nije odabran nijedan prostor"</string>
<string name="screen_create_room_space_selection_sheet_title">"Dodaj u prostor"</string>
<string name="screen_create_room_topic_label">"Tema (neobavezno)"</string>
<string name="screen_create_room_topic_placeholder">"Dodaj opis…"</string>
</resources>

View file

@ -3,14 +3,34 @@
<string name="screen_create_room_action_create_room">"Yangi xona"</string>
<string name="screen_create_room_add_people_title">"Odamlarni taklif qiling"</string>
<string name="screen_create_room_error_creating_room">"Xonani yaratishda xatolik yuz berdi"</string>
<string name="screen_create_room_error_creating_space">"Nomalum xatolik tufayli maydon yaratilmadi. Keyinroq qayta urining."</string>
<string name="screen_create_room_name_placeholder">"Ism qoshish…"</string>
<string name="screen_create_room_new_room_title">"Yangi xona"</string>
<string name="screen_create_room_new_space_title">"Yangi maydon"</string>
<string name="screen_create_room_private_option_description">"Faqat taklif etilgan shaxslargina bu xonaga kira oladi. Barcha xabarlar boshdan-oxirigacha shifrlanadi."</string>
<string name="screen_create_room_private_option_title">"Maxfiy"</string>
<string name="screen_create_room_public_option_description">"Bu xonani har kim topishi mumkin.
Buni xona sozlamalaridan istalgan vaqtda oʻzgartirishingiz mumkin."</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Xonaga qoshilishni istalgan kishi sorashi mumkin, lekin administrator yoki moderator sorovni qabul qilishi kerak"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Qoshilishni sorang"</string>
<string name="screen_create_room_room_access_section_public_option_description">"Bu xonaga istalgan kishi qoshilishi mumkin"</string>
<string name="screen_create_room_room_address_section_footer">"Ushbu xona ommaviy xonalar royxatida korinishi uchun sizga xona manzili kerak boladi."</string>
<string name="screen_create_room_public_option_short_description">"Istalgan kishi qoshilishi mumkin"</string>
<string name="screen_create_room_public_option_title">"Ommaviy"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Istalgan kishi qoshilishni sorashi mumkin, lekin administrator yoki moderator sorovni qabul qilishi kerak."</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Qoshilish uchun ruxsat sorash"</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"%1$s ichidagi har kim kirishi mumkin, lekin boshqalar ruxsat sorashi kerak."</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"Qoshilish uchun sorash"</string>
<string name="screen_create_room_room_access_section_private_option_description">"Faqat taklif qilinganlar qoshilishi mumkin."</string>
<string name="screen_create_room_room_access_section_private_option_title">"Maxfiy"</string>
<string name="screen_create_room_room_access_section_public_option_description">"Istalgan kishi qoshilishi mumkin"</string>
<string name="screen_create_room_room_access_section_public_option_title">"Ommaviy"</string>
<string name="screen_create_room_room_access_section_restricted_option_description">"%1$s ichidagi har kim qoshilishi mumkin."</string>
<string name="screen_create_room_room_access_section_restricted_option_title">"Standart"</string>
<string name="screen_create_room_room_access_section_title">"Kimning kirish huquqi bor"</string>
<string name="screen_create_room_room_address_section_footer">"Ommaviy katalogda korinadigan qilish uchun manzil kerak boladi."</string>
<string name="screen_create_room_room_address_section_title">"Xona manzili"</string>
<string name="screen_create_room_room_visibility_section_title">"Xonaning korinishi"</string>
<string name="screen_create_room_space_selection_no_space_description">"(maydon yoq)"</string>
<string name="screen_create_room_space_selection_no_space_option">"Maydonga kiritilmasin"</string>
<string name="screen_create_room_space_selection_no_space_title">"Hech qanday maydon tanlanmagan"</string>
<string name="screen_create_room_space_selection_sheet_title">"Maydonga qoshish"</string>
<string name="screen_create_room_topic_label">"Mavzu (ixtiyoriy)"</string>
<string name="screen_create_room_topic_placeholder">"Tavsif kiritish…"</string>
</resources>

View file

@ -4,8 +4,12 @@
<string name="screen_create_room_add_people_title">"Mời ai đó"</string>
<string name="screen_create_room_error_creating_room">"Đã xảy ra lỗi khi tạo phòng."</string>
<string name="screen_create_room_private_option_description">"Chỉ những người được mời mới có thể tham gia."</string>
<string name="screen_create_room_private_option_title">"Riêng tư"</string>
<string name="screen_create_room_public_option_description">"Bất kỳ ai cũng có thể tìm thấy phòng này.
Bạn có thể thay đổi cài đặt phòng bất cứ lúc nào."</string>
<string name="screen_create_room_public_option_title">"Công cộng"</string>
<string name="screen_create_room_room_access_section_private_option_title">"Riêng tư"</string>
<string name="screen_create_room_room_access_section_public_option_title">"Công cộng"</string>
<string name="screen_create_room_topic_label">"Chủ đề (tùy chọn)"</string>
<string name="screen_create_room_topic_placeholder">"Thêm mô tả…"</string>
</resources>

View file

@ -3,14 +3,34 @@
<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_private_option_description">"僅被邀請的人才能存取此聊天室。所有訊息均會端到端加密。"</string>
<string name="screen_create_room_error_creating_space">"因為未知錯誤,無法建立空間。請稍後再試。"</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">"僅被邀請的人才能加入。"</string>
<string name="screen_create_room_private_option_title">"私人"</string>
<string name="screen_create_room_public_option_description">"任何人都可以找到此聊天室。
您隨時都可以在聊天室設定中變更此設定。"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以要求加入聊天室,但管理員或版主必須接受該請求"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"要求加入"</string>
<string name="screen_create_room_room_access_section_public_option_description">"任何人都可以加入此聊天室"</string>
<string name="screen_create_room_room_address_section_footer">"為了讓此聊天室在公開聊天室目錄中可見,您需要聊天室地址。"</string>
<string name="screen_create_room_room_address_section_title">"聊天室地址"</string>
<string name="screen_create_room_public_option_short_description">"任何人都可以加入。"</string>
<string name="screen_create_room_public_option_title">"公開"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"任何人都可以要求加入,但管理員或版主必須接受該請求"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"允許要求加入"</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_description">"任何在 %1$s 中的人都可以加入,但其他人就必須申請存取權。"</string>
<string name="screen_create_room_room_access_section_knocking_restricted_option_title">"要求加入"</string>
<string name="screen_create_room_room_access_section_private_option_description">"僅被邀請的人才可以加入。"</string>
<string name="screen_create_room_room_access_section_private_option_title">"私人"</string>
<string name="screen_create_room_room_access_section_public_option_description">"任何人都可以加入"</string>
<string name="screen_create_room_room_access_section_public_option_title">"公開"</string>
<string name="screen_create_room_room_access_section_restricted_option_description">"在 %1$s 中的任何人都可以加入。"</string>
<string name="screen_create_room_room_access_section_restricted_option_title">"標準"</string>
<string name="screen_create_room_room_access_section_title">"誰有權存取"</string>
<string name="screen_create_room_room_address_section_footer">"您需要地址才能讓該資訊在公開目錄中顯示。"</string>
<string name="screen_create_room_room_address_section_title">"地址"</string>
<string name="screen_create_room_room_visibility_section_title">"聊天室能見度"</string>
<string name="screen_create_room_space_selection_no_space_description">"(沒有空間)"</string>
<string name="screen_create_room_space_selection_no_space_option">"不要新增至空間"</string>
<string name="screen_create_room_space_selection_no_space_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>

View file

@ -1,7 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_deactivate_account_confirmation_dialog_content">"Vui lòng xác nhận rằng bạn muốn vô hiệu hóa tài khoản của mình. Hành động này không thể hoàn tác."</string>
<string name="screen_deactivate_account_delete_all_messages">"Xóa tất cả tin nhắn của tôi"</string>
<string name="screen_deactivate_account_delete_all_messages_notice">"Cảnh báo: Người dùng sau này có thể thấy các cuộc trò chuyện chưa hoàn chỉnh."</string>
<string name="screen_deactivate_account_description">"Việc vô hiệu hóa tài khoản của bạn là %1$s , nó sẽ:"</string>
<string name="screen_deactivate_account_description_bold_part">"không thể đảo ngược"</string>
<string name="screen_deactivate_account_list_item_1">"%1$s Tài khoản của bạn (bạn không thể đăng nhập lại và ID của bạn không thể được sử dụng lại)."</string>
<string name="screen_deactivate_account_list_item_1_bold_part">"Vô hiệu hóa vĩnh viễn"</string>
<string name="screen_deactivate_account_list_item_2">"Loại bỏ bạn khỏi tất cả các phòng chat."</string>
<string name="screen_deactivate_account_list_item_3">"Xóa thông tin tài khoản của bạn khỏi máy chủ nhận dạng của chúng tôi."</string>
<string name="screen_deactivate_account_list_item_4">"Tin nhắn của bạn vẫn sẽ hiển thị cho người dùng đã đăng ký nhưng sẽ không hiển thị cho người dùng mới hoặc chưa đăng ký nếu bạn chọn xóa chúng."</string>
<string name="screen_deactivate_account_title">"Vô hiệu hóa tài khoản"</string>
</resources>

View file

@ -2,7 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"Bestätigung unmöglich?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Erstelle einen neuen Wiederherstellungsschlüssel"</string>
<string name="screen_identity_confirmation_subtitle">"Verifiziere dieses Gerät, um sichere Chats einzurichten."</string>
<string name="screen_identity_confirmation_subtitle">"Wähle eine Verifizierungsmethode, um den sicheren Nachrichtenversand einzurichten."</string>
<string name="screen_identity_confirmation_title">"Bestätige deine Identität"</string>
<string name="screen_identity_confirmation_use_another_device">"Ein anderes Gerät verwenden"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Wiederherstellungsschlüssel verwenden"</string>

View file

@ -3,7 +3,7 @@
<string name="screen_identity_confirmation_cannot_confirm">"Kas kinnitamine pole võimalik?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Loo uus taastevõti"</string>
<string name="screen_identity_confirmation_subtitle">"Krüptitud sõnumivahetuse tagamiseks verifitseeri see seade."</string>
<string name="screen_identity_confirmation_title">"Kinnita, et see oled sina"</string>
<string name="screen_identity_confirmation_title">"Kinnita oma digitaalne identiteet"</string>
<string name="screen_identity_confirmation_use_another_device">"Kasuta teist seadet"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Kasuta taastevõtit"</string>
<string name="screen_identity_confirmed_subtitle">"Nüüd saad saata või lugeda sõnumeid turvaliselt ning kõik sinu vestluspartnerid võivad usaldada seda seadet."</string>

View file

@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"Ne možete potvrditi?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Izradi novi ključ za oporavak"</string>
<string name="screen_identity_confirmation_subtitle">"Potvrdite ovaj uređaj kako biste postavili sigurnu razmjenu poruka."</string>
<string name="screen_identity_confirmation_title">"Potvrdite svoj identitet"</string>
<string name="screen_identity_confirmation_subtitle">"Odaberite način potvrde za postavljanje sigurne razmjene poruka."</string>
<string name="screen_identity_confirmation_title">"Potvrdite svoj digitalni identitet"</string>
<string name="screen_identity_confirmation_use_another_device">"Upotrijebite drugi uređaj"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Upotrijebi ključ za oporavak"</string>
<string name="screen_identity_confirmed_subtitle">"Sada možete sigurno čitati ili slati poruke, a svatko s kim razgovarate također može vjerovati ovom uređaju."</string>

View file

@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"Tasdiqlay olmayapsizmi?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Yangi tiklash kalitini yarating"</string>
<string name="screen_identity_confirmation_subtitle">"Xavfsiz xabarlashuvni sozlash uchun ushbu qurilmani tasdiqlang."</string>
<string name="screen_identity_confirmation_title">"Shaxsingizni tasdiqlang"</string>
<string name="screen_identity_confirmation_subtitle">"Xavfsiz xabar almashinuvni sozlash uchun tasdiqlash usulini tanlang."</string>
<string name="screen_identity_confirmation_title">"Raqamli shaxsingizni tasdiqlang"</string>
<string name="screen_identity_confirmation_use_another_device">"Boshqa qurilmadan foydalanish"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Qayta tiklash kalitidan foydalaning"</string>
<string name="screen_identity_confirmed_subtitle">"Endi xabarlarni xavfsiz tarzda oqish yoki yuborish imkoniyatiga egasiz, shuningdek, siz bilan muloqot qilayotgan har qanday kishi ham bu qurilmaga ishonch bildirishi mumkin."</string>

View file

@ -1,9 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"Không thể xác nhận?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Tạo khóa khôi phục mới"</string>
<string name="screen_identity_confirmation_subtitle">"Chọn phương thức xác minh để bật nhắn tin bảo mật."</string>
<string name="screen_identity_confirmation_title">"Xác nhận danh tính kỹ thuật số của bạn"</string>
<string name="screen_identity_confirmation_use_another_device">"Dùng thiết bị khác"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Sử dụng khóa khôi phục"</string>
<string name="screen_identity_confirmed_subtitle">"Giờ đây bạn có thể đọc và gửi tin nhắn một cách an toàn, và những người bạn trò chuyện cũng có thể tin tưởng thiết bị này."</string>
<string name="screen_identity_confirmed_title">"Thiết bị được xác thực"</string>
<string name="screen_identity_use_another_device">"Dùng thiết bị khác"</string>
<string name="screen_identity_waiting_on_other_device">"Đang chờ trên thiết bị khác…"</string>
<string name="screen_notification_optin_subtitle">"Bạn có thể thay đổi cài đặt sau."</string>
<string name="screen_notification_optin_title">"Cho phép thông báo để không bỏ lỡ bất kỳ tin nhắn nào"</string>

View file

@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"無法確認?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"建立新的復原金鑰"</string>
<string name="screen_identity_confirmation_subtitle">"驗證這部裝置以設定安全通訊。"</string>
<string name="screen_identity_confirmation_title">"確認這是你本人"</string>
<string name="screen_identity_confirmation_subtitle">"選擇驗證方式以設定安全訊息傳遞。"</string>
<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>

View file

@ -5,9 +5,9 @@
<string name="banner_battery_optimization_title_android">"Kommen die Benachrichtigungen nicht an?"</string>
<string name="banner_new_sound_message">"Dein Benachrichtigungs-Ping wurde aktualisiert klarer, schneller und weniger störend."</string>
<string name="banner_new_sound_title">"Wir haben deine Sounds aktualisiert"</string>
<string name="banner_set_up_recovery_content">"Stelle Deine kryptographische Identität und Deinen Nachrichtenverlauf mit Hilfe eines Wiederherstellungsschlüssels wieder her, falls du alle deine Geräte verloren haben solltest"</string>
<string name="banner_set_up_recovery_submit">"Wiederherstellung einrichten"</string>
<string name="banner_set_up_recovery_title">"Wiederherstellung einrichten"</string>
<string name="banner_set_up_recovery_content">"Deine Chats werden automatisch gesichert und mit einer Ende-zu-Ende-Verschlüsselung geschützt. Um dieses Backup wiederherzustellen und deine digitale Identität zu bewahren, falls du den Zugriff auf alle deine Geräte verlierst, benötigst du deinen Wiederherstellungsschlüssel."</string>
<string name="banner_set_up_recovery_submit">"Wiederherstellungsschlüssel einrichten"</string>
<string name="banner_set_up_recovery_title">"Sichere deine Chats"</string>
<string name="confirm_recovery_key_banner_message">"Bestätige deinen Wiederherstellungsschlüssel, um weiterhin auf deinen Schlüsselspeicher und den Nachrichtenverlauf zugreifen zu können."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Gib deinen Wiederherstellungsschlüssel ein"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Hast du deinen Wiederherstellungsschlüssel vergessen?"</string>

View file

@ -5,9 +5,9 @@
<string name="banner_battery_optimization_title_android">"Obavijesti ne stižu?"</string>
<string name="banner_new_sound_message">"Vaš je signal obavijesti ažuriran jasniji je, brži i manje ometajući."</string>
<string name="banner_new_sound_title">"Ažurirali smo vaše zvukove"</string>
<string name="banner_set_up_recovery_content">"Ako ste izgubili sve postojeće uređaje, oporavite svoj kriptografski identitet i povijest poruka pomoću ključa za oporavak."</string>
<string name="banner_set_up_recovery_submit">"Postavljanje oporavka"</string>
<string name="banner_set_up_recovery_title">"Postavite oporavak kako biste zaštitili svoj račun"</string>
<string name="banner_set_up_recovery_content">"Vaši se razgovori automatski sigurnosno kopiraju enkripcijom od početka do kraja. Da biste vratili ovu sigurnosnu kopiju i zadržali svoj digitalni identitet kada izgubite pristup svim svojim uređajima, trebat će vam ključ za oporavak."</string>
<string name="banner_set_up_recovery_submit">"ključ za oporavak"</string>
<string name="banner_set_up_recovery_title">"Napravite sigurnosnu kopiju svojih razgovora"</string>
<string name="confirm_recovery_key_banner_message">"Potvrdite svoj ključ za oporavak kako biste zadržali pristup pohrani ključeva i povijesti poruka."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Unesite svoj ključ za oporavak"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Zaboravili ste ključ za oporavak?"</string>
@ -50,6 +50,7 @@ Nemate nepročitanih poruka!"</string>
<string name="screen_roomlist_mark_as_read">"Označi kao pročitano"</string>
<string name="screen_roomlist_mark_as_unread">"Označi kao nepročitano"</string>
<string name="screen_roomlist_tombstoned_room_description">"Ova je soba nadograđena"</string>
<string name="screen_roomlist_your_spaces">"Vaši prostori"</string>
<string name="session_verification_banner_message">"Izgleda da koristite novi uređaj. Izvršite provjeru drugim uređajem da biste pristupili svojim šifriranim porukama."</string>
<string name="session_verification_banner_title">"Potvrdi identitet"</string>
</resources>

View file

@ -38,7 +38,7 @@
<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_people">"人"</string>
<string name="screen_roomlist_filter_people">"人"</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>

View file

@ -5,9 +5,9 @@
<string name="banner_battery_optimization_title_android">"Bildirishnoma kelmayaptimi?"</string>
<string name="banner_new_sound_message">"Xabarnoma signali yangilandi — endi u aniqroq, tezroq va kamroq halal beradigan boldi."</string>
<string name="banner_new_sound_title">"Tovushlaringiz yangilandi"</string>
<string name="banner_set_up_recovery_content">"Mavjud barcha qurilmalarni yoʻqotgan boʻlsangiz, kriptografik kimligingizni va xabarlar tarixini qayta tiklovchi kalit bilan saqlab qoʻying."</string>
<string name="banner_set_up_recovery_content">"Chatlaringiz avtomatik ravishda boshidan oxirigacha shifrlash bilan zaxiralanadi. Bu zaxirani tiklash va barcha qurilmalaringizdan foydalana olmay qolganingizda raqamli identifikatoringizni saqlab qolish uchun sizga tiklash kaliti kerak boladi."</string>
<string name="banner_set_up_recovery_submit">"Qayta tiklashni sozlang"</string>
<string name="banner_set_up_recovery_title">"Hisobingizni himoya qilish uchun tiklashni sozlang"</string>
<string name="banner_set_up_recovery_title">"Chatlaringizni zaxiralang"</string>
<string name="confirm_recovery_key_banner_message">"Kalit saqlash joyingiz va xabarlar tarixingizga kirishni saqlab qolish uchun tiklash kalitingizni tasdiqlang."</string>
<string name="confirm_recovery_key_banner_primary_button_title">"Qayta tiklash kalitingizni kiriting"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Tiklash kalitini unutdingizmi?"</string>
@ -50,6 +50,7 @@ Sizda oʻqilmagan xabarlar yoʻq!"</string>
<string name="screen_roomlist_mark_as_read">"Oʻqilgan deb belgilash"</string>
<string name="screen_roomlist_mark_as_unread">"Oʻqilmagan deb belgilash"</string>
<string name="screen_roomlist_tombstoned_room_description">"Bu xona yangilandi"</string>
<string name="screen_roomlist_your_spaces">"Maydonlaringiz"</string>
<string name="session_verification_banner_message">"Siz yangi qurilmadan foydalanayotganga oxshaysiz. Shifrlangan xabarlaringizga kirish uchun boshqa qurilma bilan tasdiqlang."</string>
<string name="session_verification_banner_title">"Siz ekanligingizni tasdiqlang"</string>
</resources>

View file

@ -12,6 +12,8 @@
<string name="confirm_recovery_key_banner_primary_button_title">"Nhập khóa khôi phục của bạn."</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"Bạn quên khóa khôi phục?”"</string>
<string name="confirm_recovery_key_banner_title">"Dữ liệu khóa của bạn không còn đồng bộ"</string>
<string name="full_screen_intent_banner_message">"Để đảm bảo bạn không bỏ lỡ bất kỳ cuộc gọi quan trọng nào, vui lòng thay đổi cài đặt để cho phép thông báo toàn màn hình khi điện thoại của bạn bị khóa."</string>
<string name="full_screen_intent_banner_title">"Nâng cao trải nghiệm cuộc gọi của bạn"</string>
<string name="screen_home_tab_chats">"Cuộc trò chuyện"</string>
<string name="screen_invites_decline_chat_message">"Bạn có chắc muốn từ chối lời mời tham gia %1$s không?"</string>
<string name="screen_invites_decline_chat_title">"Từ chối lời mời"</string>

View file

@ -5,9 +5,9 @@
<string name="banner_battery_optimization_title_android">"沒收到通知?"</string>
<string name="banner_new_sound_message">"您的通知提示音已更新,更清晰、更快、更不易分心。"</string>
<string name="banner_new_sound_title">"我們已更新您的音效設定"</string>
<string name="banner_set_up_recovery_content">"若您遺失了所有現有裝置,則請使用復原金鑰以救援您的密碼學身份與訊息歷史紀錄。"</string>
<string name="banner_set_up_recovery_submit">"設定復原"</string>
<string name="banner_set_up_recovery_title">"設定備援以保護您的帳號"</string>
<string name="banner_set_up_recovery_content">"您的聊天會自動使用端到端加密備份。若您失去對您所有裝置的存取權,且要還原此備份並保留您的數位身份的話,您就會需要您的還原金鑰。"</string>
<string name="banner_set_up_recovery_submit">"取得還原金鑰"</string>
<string name="banner_set_up_recovery_title">"備份您的聊天"</string>
<string name="confirm_recovery_key_banner_message">"確認您的復原金鑰以維持對金鑰儲存空間與訊息歷史紀錄的存取權。"</string>
<string name="confirm_recovery_key_banner_primary_button_title">"輸入您的復原金鑰"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"忘記了您的復原金鑰?"</string>
@ -50,6 +50,7 @@
<string name="screen_roomlist_mark_as_read">"標為已讀"</string>
<string name="screen_roomlist_mark_as_unread">"標為未讀"</string>
<string name="screen_roomlist_tombstoned_room_description">"此聊天室已升級"</string>
<string name="screen_roomlist_your_spaces">"您的空間"</string>
<string name="session_verification_banner_message">"您似乎正在使用新的裝置。請使用另一個裝置進行驗證,以存取您的加密訊息。"</string>
<string name="session_verification_banner_title">"驗證這是您本人"</string>
</resources>

View file

@ -7,7 +7,7 @@
<string name="banner_new_sound_title">"我们已更新您的声音"</string>
<string name="banner_set_up_recovery_content">"生成新的恢复密钥,该密钥可用于在您无法访问设备时恢复加密的消息历史记录。"</string>
<string name="banner_set_up_recovery_submit">"获取恢复密钥"</string>
<string name="banner_set_up_recovery_title">"设置恢复"</string>
<string name="banner_set_up_recovery_title">"备份聊天"</string>
<string name="confirm_recovery_key_banner_message">"确认恢复密钥,以保持对密钥存储和消息历史的访问。"</string>
<string name="confirm_recovery_key_banner_primary_button_title">"输入恢复密钥"</string>
<string name="confirm_recovery_key_banner_secondary_button_title">"忘记了恢复密钥?"</string>

View file

@ -23,7 +23,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
@ -264,8 +263,16 @@ private fun InvitePeopleConfirmModal(
dragHandle = null,
) {
IconTitleSubtitleMolecule(
title = pluralStringResource(R.plurals.screen_invite_users_confirm_dialog_title, users.size),
subTitle = pluralStringResource(R.plurals.screen_invite_users_confirm_dialog_subtitle, users.size),
title = if (users.size > 1) {
stringResource(R.string.screen_invite_users_confirm_dialog_title_mutiple_users)
} else {
stringResource(R.string.screen_invite_users_confirm_dialog_title_one_user)
},
subTitle = if (users.size > 1) {
stringResource(R.string.screen_invite_users_confirm_dialog_subtitle_multiple_users)
} else {
stringResource(R.string.screen_invite_users_confirm_dialog_subtitle_one_user)
},
iconStyle = BigIcon.Style.Default(CompoundIcons.UserAddSolid()),
modifier = Modifier.padding(
top = 32.dp,

View file

@ -2,10 +2,4 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invite_users_already_a_member">"既に参加しています"</string>
<string name="screen_invite_users_already_invited">"既に招待しています"</string>
<plurals name="screen_invite_users_confirm_dialog_subtitle">
<item quantity="other">"この連絡先とのチャットがありません。続行する前に、このルームに招待してください。"</item>
</plurals>
<plurals name="screen_invite_users_confirm_dialog_title">
<item quantity="other">"このルームに新しい連絡先を追加しますか?"</item>
</plurals>
</resources>

View file

@ -2,12 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invite_users_already_a_member">"Already a member"</string>
<string name="screen_invite_users_already_invited">"Already invited"</string>
<plurals name="screen_invite_users_confirm_dialog_subtitle">
<item quantity="one">"You currently dont have any chats with this contact. Confirm inviting them to this room before continuing."</item>
<item quantity="other">"You currently dont have any chats with these contacts. Confirm inviting them to this room before continuing."</item>
</plurals>
<plurals name="screen_invite_users_confirm_dialog_title">
<item quantity="one">"Invite a new contact to this room?"</item>
<item quantity="other">"Invite new contacts to this room?"</item>
</plurals>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"You currently dont have any chats with these contacts. Confirm inviting them to this room before continuing."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"You currently dont have any chats with this contact. Confirm inviting them to this room before continuing."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Invite new contacts to this room?"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"Invite new contact to this room?"</string>
</resources>

View file

@ -1,5 +1,7 @@
<?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">"Bạn đã bị cấm bởi %1$s ."</string>
<string name="screen_join_room_ban_message">"Bạn đã bị cấm"</string>
<string name="screen_join_room_cancel_knock_action">"Hủy yêu cầu"</string>
<string name="screen_join_room_cancel_knock_alert_confirmation">"Có, hủy"</string>
<string name="screen_join_room_cancel_knock_alert_description">"Bạn có chắc chắn muốn hủy yêu cầu tham gia phòng này không?"</string>

View file

@ -24,6 +24,9 @@
<string name="screen_knock_requests_list_empty_state_description">"Khi ai đó xin vào phòng, bạn sẽ thấy yêu cầu ở đây."</string>
<string name="screen_knock_requests_list_empty_state_title">"Không có yêu cầu tham gia nào đang chờ xử lý"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Đang tải các yêu cầu tham gia…"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="other">"%1$s + %2$d người khác muốn tham gia phòng này"</item>
</plurals>
<string name="screen_room_single_knock_request_accept_button_title">"Đồng ý"</string>
<string name="screen_room_single_knock_request_view_button_title">"Xem"</string>
</resources>

View file

@ -3,5 +3,8 @@
<string name="leave_conversation_alert_subtitle">"Bạn có chắc chắn muốn rời khỏi cuộc trò chuyện này không? Cuộc trò chuyện này không công khai và bạn sẽ không thể tham gia lại nếu không được mời."</string>
<string name="leave_room_alert_empty_subtitle">"Bạn có chắc chắn muốn rời khỏi phòng này không? Bạn là người duy nhất ở đây. Nếu bạn rời đi, sẽ không ai có thể tham gia nữa, kể cả bạn."</string>
<string name="leave_room_alert_private_subtitle">"Bạn có chắc chắn muốn rời khỏi phòng này không? Phòng này không công khai và bạn sẽ không thể tham gia lại nếu không có lời mời."</string>
<string name="leave_room_alert_select_new_owner_action">"Chọn chủ sở hữu"</string>
<string name="leave_room_alert_select_new_owner_subtitle">"Bạn là chủ sở hữu duy nhất của căn phòng này. Bạn cần chuyển quyền sở hữu cho người khác trước khi rời khỏi phòng."</string>
<string name="leave_room_alert_select_new_owner_title">"Chuyển quyền sở hữu"</string>
<string name="leave_room_alert_subtitle">"Bạn có chắc chắn muốn rời khỏi phòng không?"</string>
</resources>

View file

@ -26,6 +26,7 @@
<string name="screen_link_new_device_root_loading_qr_code">"QR kod yuklanmoqda…"</string>
<string name="screen_link_new_device_root_mobile_device">"Mobil qurilma"</string>
<string name="screen_link_new_device_root_title">"Qaysi turdagi qurilmani boglashni xohlaysiz?"</string>
<string name="screen_link_new_device_wrong_number_subtitle">"Qayta urining va 2 xonali kodni bexato kiritganingizni tekshiring. Agar raqamlar hali ham mos kelmasa, hisobingiz provayderiga murojaat qiling."</string>
<string name="screen_link_new_device_wrong_number_title">"Raqamlar mos kelmaydi"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Yangi qurilmaga xavfsiz ulanish amalga oshirilmadi. Mavjud qurilmalaringiz hali ham xavfsiz va ular haqida qaygʻurishingiz shart emas."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Endi nima?"</string>
@ -37,6 +38,8 @@
<string name="screen_qr_code_login_error_cancelled_title">"Tizimga kirish soʻrovi bekor qilindi"</string>
<string name="screen_qr_code_login_error_declined_subtitle">"Boshqa qurilmadan hisobga kirish bekor qilindi."</string>
<string name="screen_qr_code_login_error_declined_title">"Tizimga kirish rad etildi"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Boshqa hech narsa qilishingiz shart emas."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Boshqa qurilmangiz allaqachon tizimga kirgan"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Kirish muddati tugagan. Iltimos, qayta urinib koʻring."</string>
<string name="screen_qr_code_login_error_expired_title">"Kirish oʻz vaqtida tugallanmagan"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Boshqa qurilmangiz %s hisobiga QR kod orqali kirishni qoʻllab-quvvatlamaydi.

View file

@ -1,16 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_link_new_device_desktop_scanning_title">"掃描 QR code"</string>
<string name="screen_link_new_device_desktop_step1">"在筆記型電腦或桌上型電腦上開啟 %1$s"</string>
<string name="screen_link_new_device_desktop_step3">"使用此裝置掃描 QR code"</string>
<string name="screen_link_new_device_desktop_submit">"準備掃描"</string>
<string name="screen_link_new_device_desktop_title">"在桌上型電腦上開啟 %1$s 以取得 QR code"</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"數字不符"</string>
<string name="screen_link_new_device_enter_number_notice">"輸入兩位數代碼"</string>
<string name="screen_link_new_device_enter_number_subtitle">"這將確認您與另一台裝置之間的連線是否安全。"</string>
<string name="screen_link_new_device_enter_number_title">"輸入顯示在您的其他裝置上的數字"</string>
<string name="screen_link_new_device_error_app_not_supported_subtitle">"您的帳號提供者不支援 %1$s。"</string>
<string name="screen_link_new_device_error_app_not_supported_title">"不支援 %1$s"</string>
<string name="screen_link_new_device_error_not_supported_subtitle">"您的帳號提供者不支援使用 QR code 登入新裝置。"</string>
<string name="screen_link_new_device_error_not_supported_title">"不支援 QR code"</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_title">"未及時完成登入"</string>
<string name="screen_link_new_device_mobile_step1">"在其他裝置上開啟 %1$s"</string>
<string name="screen_link_new_device_mobile_step2">"選取 %1$s"</string>
<string name="screen_link_new_device_mobile_step2_action">"「使用 QR code 登入」"</string>
<string name="screen_link_new_device_mobile_step3">"使用其他裝置掃描此處顯示的 QR code"</string>
<string name="screen_link_new_device_mobile_title">"在其他裝置上開啟 %1$s"</string>
<string name="screen_link_new_device_root_desktop_computer">"桌上型電腦"</string>
<string name="screen_link_new_device_root_loading_qr_code">"正在載入 QR code……"</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">"請重試,並確定您已輸入兩位數代碼。若數字仍然不符,請聯絡您的帳號提供者。"</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>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"嘗試再次使用 QR code 登入以確認不是網路問題"</string>
@ -21,6 +38,8 @@
<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_title">"您的其他裝置已登入"</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">"您的其他裝置不支援使用 QR cpde 登入 %s。

View file

@ -24,5 +24,7 @@ sealed interface ShowLocationMode : Parcelable {
) : ShowLocationMode
@Parcelize
data object Live : ShowLocationMode
data class Live(
val senderId: UserId
) : ShowLocationMode
}

View file

@ -9,7 +9,9 @@
package io.element.android.features.location.api
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@ -22,6 +24,8 @@ 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.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import coil3.Extras
import coil3.compose.AsyncImagePainter
@ -38,11 +42,16 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
/**
* Shows a static map image downloaded via a third party service's static maps API.
*
* Handles 4 distinct cases:
* 1. Stale location (pinVariant is StaleLocation) - shows stale map with stale pin, no fetching
* 2. Null location - shows blurred placeholder, no pin, no loading
* 3. Loading (location != null, fetching) - shows blurred placeholder with loading indicator
* 4. Success (location != null, loaded) - shows actual map with pin
*/
@Composable
fun StaticMapView(
lat: Double,
lon: Double,
location: Location?,
zoom: Double,
pinVariant: PinVariant,
contentDescription: String?,
@ -56,50 +65,111 @@ fun StaticMapView(
modifier = modifier,
contentAlignment = Alignment.Center
) {
val context = LocalContext.current
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 (else it will thrown an exception).
null
} else {
ImageRequest.Builder(context)
.data(
builder.build(
lat = lat,
lon = 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()
// Case 1: Stale location - show stale map with stale pin, no fetching
when {
pinVariant is PinVariant.StaleLocation -> {
StaleMapContent(
pinVariant = pinVariant,
contentDescription = contentDescription,
width = maxWidth,
height = maxHeight,
)
}
)
// Case 2: Null location - show blurred placeholder, no pin, no loading
location == null -> {
StaticMapPlaceholder(
painter = painterResource(R.drawable.blurred_map),
canReload = false,
contentDescription = contentDescription,
width = maxWidth,
height = maxHeight,
onLoadMapClick = {}
)
}
// Cases 3 & 4: Non-null location - fetch map
else -> LoadableMapContent(
location = location,
zoom = zoom,
pinVariant = pinVariant,
contentDescription = contentDescription,
darkMode = darkMode,
)
}
}
}
val collectedState = painter.state.collectAsState()
if (collectedState.value is AsyncImagePainter.State.Success) {
@Composable
private fun BoxWithConstraintsScope.StaleMapContent(
pinVariant: PinVariant,
contentDescription: String?,
width: Dp,
height: Dp,
) {
Box(contentAlignment = Alignment.Center) {
Image(
painter = painterResource(R.drawable.stale_map),
contentDescription = contentDescription,
contentScale = ContentScale.FillBounds,
modifier = Modifier.size(width = width, height = height)
)
LocationPin(variant = pinVariant, modifier = Modifier.centerBottomEdge(this@StaleMapContent))
}
}
@Composable
private fun BoxWithConstraintsScope.LoadableMapContent(
location: Location,
zoom: Double,
pinVariant: PinVariant,
contentDescription: String?,
darkMode: Boolean,
) {
val context = LocalContext.current
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,
)
)
.size(width = constraints.maxWidth, height = constraints.maxHeight)
.apply {
extras.set(Extras.Key("retry_hash"), retryHash).build()
}
.build()
}
)
val state by painter.state.collectAsState()
when (state) {
is AsyncImagePainter.State.Success -> {
Image(
painter = painter,
contentDescription = contentDescription,
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. See buildStaticMapsApiUrl() for more details.
// We apply ContentScale.Fit to scale the image to fill the AsyncImage should this be the case.
// a max width and height of 2048 px. We apply ContentScale.Fit to handle this.
contentScale = ContentScale.Fit,
)
LocationPin(variant = pinVariant, modifier = Modifier.centerBottomEdge(this))
} else {
}
else -> {
StaticMapPlaceholder(
showProgress = collectedState.value.isLoading(),
canReload = builder.isServiceAvailable(),
painter = painterResource(R.drawable.blurred_map),
canReload = builder.isServiceAvailable() && state is AsyncImagePainter.State.Error,
contentDescription = contentDescription,
width = maxWidth,
height = maxHeight,
@ -109,17 +179,11 @@ fun StaticMapView(
}
}
private fun AsyncImagePainter.State.isLoading(): Boolean {
return this is AsyncImagePainter.State.Empty ||
this is AsyncImagePainter.State.Loading
}
@PreviewsDayNight
@Composable
internal fun StaticMapViewPreview() = ElementPreview {
StaticMapView(
lat = 0.0,
lon = 0.0,
location = Location(0.0, 0.0),
zoom = 0.0,
contentDescription = null,
pinVariant = PinVariant.PinnedLocation,

View file

@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -27,14 +28,13 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.location.api.R
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
internal fun StaticMapPlaceholder(
showProgress: Boolean,
painter: Painter,
canReload: Boolean,
contentDescription: String?,
width: Dp,
@ -46,17 +46,15 @@ internal fun StaticMapPlaceholder(
contentAlignment = Alignment.Center,
modifier = modifier
.size(width = width, height = height)
.then(if (showProgress) Modifier else Modifier.clickable(onClick = onLoadMapClick))
.clickable(enabled = canReload, onClick = onLoadMapClick)
) {
Image(
painter = painterResource(id = R.drawable.blurred_map),
painter = painter,
contentDescription = contentDescription,
contentScale = ContentScale.FillBounds,
modifier = Modifier.size(width = width, height = height)
)
if (showProgress) {
CircularProgressIndicator()
} else if (canReload) {
if (canReload) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
@ -77,13 +75,10 @@ internal fun StaticMapPlaceholderPreview() = ElementPreview {
modifier = Modifier.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
listOf(
true to false,
false to true,
false to false,
).forEach { (showProgress, canReload) ->
listOf(false, true)
.forEach { canReload ->
StaticMapPlaceholder(
showProgress = showProgress,
painter = painterResource(R.drawable.blurred_map),
canReload = canReload,
contentDescription = null,
width = 400.dp,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -91,9 +91,9 @@ fun LocationShareRow(
)
}
Text(
text = item.formattedTimestamp,
text = if (item.isLive) stringResource(CommonStrings.screen_room_live_location_banner) else item.formattedTimestamp,
style = ElementTheme.typography.fontBodySmRegular,
color = ElementTheme.colors.textSecondary,
color = if (item.isLive) ElementTheme.colors.textPrimary else ElementTheme.colors.textSecondary,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)

View file

@ -10,12 +10,14 @@ package io.element.android.features.location.impl.common.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.safeDrawing
@ -43,6 +45,7 @@ import androidx.compose.ui.unit.max
import io.element.android.features.location.api.internal.rememberTileStyleUrl
import io.element.android.features.location.impl.common.MapDefaults
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.BottomSheetScaffold
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
@ -112,8 +115,11 @@ fun MapBottomSheetScaffold(
modifier = Modifier,
sheetPeekHeight = sheetPeekHeight,
sheetContent = {
sheetContent(sheetPadding)
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
val maxContentHeight = (layoutHeightPx * 0.5f).roundToInt().toDp()
Column(modifier = Modifier.heightIn(max = maxContentHeight)) {
sheetContent(sheetPadding)
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
}
},
scaffoldState = scaffoldState,
sheetDragHandle = sheetDragHandle,

View file

@ -0,0 +1,20 @@
/*
* 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.features.location.impl.show
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
class LiveLocationShareComparator(private val currentUser: UserId) : Comparator<LiveLocationShare> {
override fun compare(p0: LiveLocationShare, p1: LiveLocationShare): Int {
val p0IsCurrentUser = p0.userId == currentUser
val p1IsCurrentUser = p1.userId == currentUser
if (p0IsCurrentUser != p1IsCurrentUser) return if (p0IsCurrentUser) -1 else 1
return p1.startTimestamp.compareTo(p0.startTimestamp)
}
}

View file

@ -13,11 +13,13 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import io.element.android.features.location.api.Location
import io.element.android.features.location.api.ShowLocationMode
import io.element.android.features.location.impl.common.LocationConstraintsCheck
import io.element.android.features.location.impl.common.MapDefaults
@ -29,14 +31,20 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
import io.element.android.features.location.impl.common.toDialogState
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.api.DateFormatterMode
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.getBestName
import io.element.android.libraries.matrix.api.room.joinedRoomMembers
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.combine
@AssistedInject
class ShowLocationPresenter(
@ -46,6 +54,7 @@ class ShowLocationPresenter(
private val buildMeta: BuildMeta,
private val dateFormatter: DateFormatter,
private val stringProvider: StringProvider,
private val joinedRoom: JoinedRoom,
) : Presenter<ShowLocationState> {
@AssistedFactory
fun interface Factory {
@ -96,9 +105,9 @@ class ShowLocationPresenter(
}
}
val locationShares = remember {
when (mode) {
is ShowLocationMode.Static -> {
val locationShares = when (mode) {
is ShowLocationMode.Static -> {
remember {
val relativeTime = dateFormatter.format(timestamp = mode.timestamp, mode = DateFormatterMode.Full, useRelative = true)
val formattedTimestamp = stringProvider.getString(
CommonStrings.screen_static_location_sheet_timestamp_description,
@ -121,15 +130,59 @@ class ShowLocationPresenter(
)
)
}
ShowLocationMode.Live -> persistentListOf()
}
is ShowLocationMode.Live -> {
produceState(persistentListOf()) {
val comparator = LiveLocationShareComparator(currentUser = joinedRoom.sessionId)
val liveLocationSharesFlow = joinedRoom.subscribeToLiveLocationShares()
val membersStateFlow = joinedRoom.membersStateFlow.mapState { it.joinedRoomMembers() }
combine(liveLocationSharesFlow, membersStateFlow) { liveShares, members ->
liveShares
.sortedWith(comparator)
.mapNotNull { share ->
val lastLocation = share.lastLocation ?: return@mapNotNull null
val location = Location.fromGeoUri(lastLocation.geoUri) ?: return@mapNotNull null
val member = members.find { it.userId == share.userId }
val displayName = member?.getBestName() ?: share.userId.value
val avatarUrl = member?.avatarUrl
val relativeTime = dateFormatter.format(timestamp = lastLocation.timestamp, mode = DateFormatterMode.Full, useRelative = true)
val formattedTimestamp = stringProvider.getString(
CommonStrings.screen_static_location_sheet_timestamp_description,
relativeTime
)
LocationShareItem(
userId = share.userId,
displayName = displayName,
avatarData = AvatarData(
id = share.userId.value,
name = displayName,
url = avatarUrl,
size = AvatarSize.UserListItem,
),
formattedTimestamp = formattedTimestamp,
location = location,
isLive = true,
assetType = lastLocation.assetType,
)
}
.toImmutableList()
}.collect { value = it }
}.value
}
}
val focusedLocation = when (mode) {
is ShowLocationMode.Static -> locationShares.firstOrNull()
is ShowLocationMode.Live -> locationShares.firstOrNull { it.userId == mode.senderId }
}
return ShowLocationState(
dialogState = dialogState,
locationShares = locationShares,
focusedLocation = focusedLocation,
hasLocationPermission = permissionsState.isAnyGranted,
isTrackMyLocation = isTrackMyLocation,
isLive = mode is ShowLocationMode.Live,
appName = appName,
eventSink = ::handleEvent,
)

View file

@ -18,14 +18,16 @@ import io.element.android.libraries.matrix.api.room.location.AssetType
import kotlinx.collections.immutable.ImmutableList
data class ShowLocationState(
val isLive: Boolean,
val dialogState: LocationConstraintsDialogState,
val locationShares: ImmutableList<LocationShareItem>,
val focusedLocation: LocationShareItem?,
val hasLocationPermission: Boolean,
val isTrackMyLocation: Boolean,
val appName: String,
val eventSink: (ShowLocationEvent) -> Unit,
) {
val isSheetDraggable = locationShares.any { item -> item.isLive }
val isSheetDraggable = isLive && locationShares.isNotEmpty()
}
data class LocationShareItem(

View file

@ -21,6 +21,8 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
override val values: Sequence<ShowLocationState>
get() = sequenceOf(
aShowLocationState(),
aShowLocationState(isLive = true),
aShowLocationState(isLive = true, locationShares = emptyList()),
aShowLocationState(
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
),
@ -44,8 +46,10 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
private const val APP_NAME = "ApplicationName"
fun aShowLocationState(
isLive: Boolean = false,
constraintsDialogState: LocationConstraintsDialogState = LocationConstraintsDialogState.None,
locationShares: List<LocationShareItem> = listOf(aLocationShareItem()),
locationShares: List<LocationShareItem> = listOf(aLocationShareItem(isLive = isLive)),
focusedLocation: LocationShareItem? = locationShares.firstOrNull(),
hasLocationPermission: Boolean = false,
isTrackMyLocation: Boolean = false,
appName: String = APP_NAME,
@ -54,9 +58,11 @@ fun aShowLocationState(
return ShowLocationState(
dialogState = constraintsDialogState,
locationShares = locationShares.toImmutableList(),
focusedLocation = focusedLocation,
hasLocationPermission = hasLocationPermission,
isTrackMyLocation = isTrackMyLocation,
appName = appName,
isLive = isLive,
eventSink = eventSink,
)
}
@ -70,10 +76,10 @@ fun aLocationShareItem(
url = null,
size = AvatarSize.UserListItem,
),
formattedTimestamp: String = "Shared 1 min ago",
location: Location = Location(1.23, 2.34, 4f),
isLive: Boolean = false,
assetType: AssetType? = null,
formattedTimestamp: String = "Shared 1 min ago",
location: Location = Location(1.23, 2.34, 4f),
) = LocationShareItem(
userId = userId,
displayName = displayName,

View file

@ -12,8 +12,11 @@ package io.element.android.features.location.impl.show
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SheetValue
@ -21,11 +24,15 @@ import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.material3.rememberStandardBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
@ -65,35 +72,33 @@ fun ShowLocationView(
onDismiss = { state.eventSink(ShowLocationEvent.DismissDialog) },
)
val initialPosition = remember {
if (state.locationShares.isEmpty()) {
MapDefaults.defaultCameraPosition
} else {
val firstLocation = state.locationShares.first().location
CameraPosition(
target = Position(latitude = firstLocation.lat, longitude = firstLocation.lon),
val cameraState = rememberCameraState(firstPosition = MapDefaults.defaultCameraPosition)
var hasAnimatedToFocusedLocation by remember { mutableStateOf(false) }
LaunchedEffect(state.focusedLocation) {
if (state.focusedLocation != null && !hasAnimatedToFocusedLocation) {
hasAnimatedToFocusedLocation = true
val position = CameraPosition(
target = Position(latitude = state.focusedLocation.location.lat, longitude = state.focusedLocation.location.lon),
zoom = MapDefaults.DEFAULT_ZOOM
)
cameraState.position = position
}
}
val cameraState = rememberCameraState(firstPosition = initialPosition)
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
LaunchedEffect(cameraState.isCameraMoving) {
if (cameraState.moveReason == CameraMoveReason.GESTURE) {
state.eventSink(ShowLocationEvent.TrackMyLocation(false))
}
}
val userLocationState = rememberUserLocationState(state.hasLocationPermission)
val scaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = rememberStandardBottomSheetState(
initialValue =
if (state.isSheetDraggable) {
SheetValue.PartiallyExpanded
} else {
SheetValue.Expanded
}
)
bottomSheetState = rememberStandardBottomSheetState(SheetValue.Expanded)
)
LaunchedEffect(state.isSheetDraggable) {
if (!state.isSheetDraggable) {
scaffoldState.bottomSheetState.expand()
}
}
MapBottomSheetScaffold(
sheetDragHandle = if (state.isSheetDraggable) {
{ BottomSheetDefaults.DragHandle() }
@ -116,29 +121,46 @@ fun ShowLocationView(
},
sheetContent = { sheetPaddings ->
val coroutineScope = rememberCoroutineScope()
Spacer(Modifier.height(20.dp))
Text(
text = stringResource(CommonStrings.screen_static_location_sheet_title),
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
state.locationShares.forEach { locationShare ->
LocationShareRow(
item = locationShare,
onShareClick = { state.eventSink(ShowLocationEvent.Share(locationShare.location)) },
modifier = Modifier.clickable {
state.eventSink(ShowLocationEvent.TrackMyLocation(false))
val position = CameraPosition(
padding = sheetPaddings,
target = Position(locationShare.location.lon, locationShare.location.lat),
zoom = MapDefaults.DEFAULT_ZOOM
)
coroutineScope.launch {
cameraState.animateTo(finalPosition = position)
}
}
if (!state.isSheetDraggable) {
// If sheet is draggable the DragHandle has already some padding
Spacer(Modifier.height(20.dp))
}
if (state.locationShares.isEmpty()) {
Text(
text = stringResource(CommonStrings.screen_live_location_sheet_nobody_sharing),
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary,
modifier = Modifier
.fillMaxWidth()
.padding(all = 16.dp),
textAlign = TextAlign.Center,
)
} else {
Text(
text = stringResource(CommonStrings.screen_static_location_sheet_title),
style = ElementTheme.typography.fontBodyLgMedium,
color = ElementTheme.colors.textPrimary,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
LazyColumn {
items(state.locationShares) { locationShare ->
LocationShareRow(
item = locationShare,
onShareClick = { state.eventSink(ShowLocationEvent.Share(locationShare.location)) },
modifier = Modifier.clickable {
state.eventSink(ShowLocationEvent.TrackMyLocation(false))
val position = CameraPosition(
padding = sheetPaddings,
target = Position(locationShare.location.lon, locationShare.location.lat),
zoom = MapDefaults.DEFAULT_ZOOM
)
coroutineScope.launch {
cameraState.animateTo(finalPosition = position)
}
}
)
}
}
}
},
mapContent = {

View file

@ -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_share_location_live_location_duration_picker_title">"Wie lange soll der Live-Standort geteilt werden?"</string>
</resources>

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_share_location_live_location_disclaimer_title">"Reaaliaikainen sijaintihistoriasi tallennetaan huoneeseen ja on jäsenten nähtävissä istunnon päätyttyä."</string>
<string name="screen_share_location_live_location_duration_picker_title">"Valitse, kuinka kauan haluat jakaa reaaliaikaisen sijaintisi."</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_share_location_live_location_disclaimer_title">"Vaša povijest lokacije uživo bit će pohranjena u sobi i vidljiva članovima nakon završetka sesije."</string>
<string name="screen_share_location_live_location_duration_picker_title">"Odaberite koliko dugo želite dijeliti svoju lokaciju uživo."</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_share_location_live_location_disclaimer_title">"Jonli joylashuv tarixingiz chat-xonada saqlanadi va sessiya tugaganidan keyin homiylarga korinadi."</string>
<string name="screen_share_location_live_location_duration_picker_title">"Jonli joylashuvingiz qancha vaqt ulashilishini tanlang."</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<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>
</resources>

View file

@ -19,6 +19,7 @@ import io.element.android.features.location.impl.common.permissions.FakePermissi
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.tests.testutils.node.TestParentNode
@ -43,7 +44,8 @@ class DefaultShowLocationEntryPointTest {
locationActions = FakeLocationActions(),
buildMeta = aBuildMeta(),
dateFormatter = FakeDateFormatter(),
stringProvider = FakeStringProvider()
stringProvider = FakeStringProvider(),
joinedRoom = FakeJoinedRoom(),
)
},
analyticsService = FakeAnalyticsService(),

View file

@ -0,0 +1,69 @@
/*
* 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.features.location.impl.show
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
import org.junit.Test
class LiveLocationShareComparatorTest {
private val currentUser = UserId("@me:matrix.org")
private val comparator = LiveLocationShareComparator(currentUser)
@Test
fun `compare returns zero when comparing the same current user share`() {
val share = aLiveLocationShare(userId = currentUser, startTimestamp = 123L)
val result = comparator.compare(share, share)
assertThat(result).isEqualTo(0)
}
@Test
fun `compare orders current user share before another user share`() {
val otherShare = aLiveLocationShare(userId = UserId("@alice:matrix.org"), startTimestamp = 200L)
val currentUserShare = aLiveLocationShare(userId = currentUser, startTimestamp = 100L)
val sortedShares = listOf(otherShare, currentUserShare).sortedWith(comparator)
assertThat(sortedShares).containsExactly(currentUserShare, otherShare).inOrder()
}
@Test
fun `compare orders current user shares by newest start timestamp first`() {
val newerShare = aLiveLocationShare(userId = currentUser, startTimestamp = 200L)
val olderShare = aLiveLocationShare(userId = currentUser, startTimestamp = 100L)
val sortedShares = listOf(olderShare, newerShare).sortedWith(comparator)
assertThat(sortedShares).containsExactly(newerShare, olderShare).inOrder()
}
@Test
fun `compare orders non current user shares by newest start timestamp first`() {
val newerShare = aLiveLocationShare(userId = UserId("@alice:matrix.org"), startTimestamp = 200L)
val olderShare = aLiveLocationShare(userId = UserId("@bob:matrix.org"), startTimestamp = 100L)
val sortedShares = listOf(olderShare, newerShare).sortedWith(comparator)
assertThat(sortedShares).containsExactly(newerShare, olderShare).inOrder()
}
}
private fun aLiveLocationShare(
userId: UserId,
startTimestamp: Long,
): LiveLocationShare {
return LiveLocationShare(
userId = userId,
lastLocation = null,
startTimestamp = startTimestamp,
endTimestamp = startTimestamp + 1_000L,
)
}

View file

@ -22,15 +22,23 @@ import io.element.android.features.location.impl.common.permissions.PermissionsS
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.location.LastLocation
import io.element.android.libraries.matrix.api.room.location.LiveLocationShare
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
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
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class ShowLocationPresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@ -51,13 +59,15 @@ class ShowLocationPresenterTest {
assetType = null,
),
locationActions: FakeLocationActions = fakeLocationActions,
joinedRoom: JoinedRoom = FakeJoinedRoom(),
) = ShowLocationPresenter(
mode = mode,
permissionsPresenterFactory = { fakePermissionsPresenter },
locationActions = locationActions,
buildMeta = fakeBuildMeta,
dateFormatter = fakeDateFormatter,
stringProvider = FakeStringProvider()
stringProvider = FakeStringProvider(),
joinedRoom = joinedRoom,
)
@Test
@ -318,4 +328,159 @@ class ShowLocationPresenterTest {
assertThat(fakeLocationActions.openLocationSettingsInvocationsCount).isEqualTo(1)
}
}
@Test
fun `live mode emits empty location shares initially`() = runTest {
val presenter = createShowLocationPresenter(
mode = ShowLocationMode.Live(senderId = UserId("@alice:matrix.org")),
joinedRoom = FakeJoinedRoom(),
)
presenter.test {
val initialState = awaitItem()
assertThat(initialState.locationShares).isEmpty()
assertThat(initialState.isSheetDraggable).isFalse()
}
}
@Test
fun `live mode collects live shares from room`() = runTest {
val userId = UserId("@bob:matrix.org")
val liveSharesFlow = MutableStateFlow(
listOf(
aLiveLocationShare(userId = userId)
)
)
val fakeRoom = FakeJoinedRoom(liveLocationSharesFlow = liveSharesFlow)
val presenter = createShowLocationPresenter(
mode = ShowLocationMode.Live(senderId = userId),
joinedRoom = fakeRoom,
)
presenter.test {
// Skip initial empty state from collectAsState(initial = emptyList())
skipItems(1)
val state = awaitItem()
assertThat(state.locationShares).hasSize(1)
val item = state.locationShares.first()
assertThat(item.userId).isEqualTo(userId)
assertThat(item.location.lat).isEqualTo(48.8584)
assertThat(item.location.lon).isEqualTo(2.2945)
assertThat(item.isLive).isTrue()
assertThat(state.isSheetDraggable).isTrue()
}
}
@Test
fun `live mode handles invalid geo uri gracefully`() = runTest {
val validUserId = UserId("@alice:matrix.org")
val invalidUserId = UserId("@bob:matrix.org")
val liveSharesFlow = MutableStateFlow(
listOf(
aLiveLocationShare(userId = validUserId),
aLiveLocationShare(userId = invalidUserId, geoUri = "invalid-geo-uri"),
)
)
val fakeRoom = FakeJoinedRoom(liveLocationSharesFlow = liveSharesFlow)
val presenter = createShowLocationPresenter(
mode = ShowLocationMode.Live(senderId = validUserId),
joinedRoom = fakeRoom,
)
presenter.test {
// Skip initial empty state from collectAsState(initial = emptyList())
skipItems(1)
val state = awaitItem()
// Only the valid location share should be present
assertThat(state.locationShares).hasSize(1)
assertThat(state.locationShares.first().userId).isEqualTo(validUserId)
}
}
@Test
fun `live mode updates when shares change`() = runTest {
val userId = UserId("@bob:matrix.org")
val liveSharesFlow = MutableStateFlow(emptyList<LiveLocationShare>())
val fakeRoom = FakeJoinedRoom(liveLocationSharesFlow = liveSharesFlow)
val presenter = createShowLocationPresenter(
mode = ShowLocationMode.Live(senderId = userId),
joinedRoom = fakeRoom,
)
presenter.test {
// Initial state is empty
val initialState = awaitItem()
assertThat(initialState.locationShares).isEmpty()
// Emit a new live share
liveSharesFlow.value = listOf(
aLiveLocationShare(userId = userId)
)
val updatedState = awaitItem()
assertThat(updatedState.locationShares).hasSize(1)
assertThat(updatedState.locationShares.first().userId).isEqualTo(userId)
}
}
@Test
fun `static mode emits location share with correct data`() = runTest {
val senderId = UserId("@alice:matrix.org")
val senderName = "Alice"
val avatarUrl = "https://example.com/avatar.png"
val mode = ShowLocationMode.Static(
location = location,
senderName = senderName,
senderId = senderId,
senderAvatarUrl = avatarUrl,
timestamp = 0L,
assetType = AssetType.SENDER,
)
val presenter = createShowLocationPresenter(mode = mode)
presenter.test {
val state = awaitItem()
assertThat(state.locationShares).hasSize(1)
val item = state.locationShares.first()
assertThat(item.userId).isEqualTo(senderId)
assertThat(item.displayName).isEqualTo(senderName)
assertThat(item.location).isEqualTo(location)
assertThat(item.isLive).isFalse()
assertThat(item.assetType).isEqualTo(AssetType.SENDER)
assertThat(item.avatarData.id).isEqualTo(senderId.value)
assertThat(item.avatarData.name).isEqualTo(senderName)
assertThat(item.avatarData.url).isEqualTo(avatarUrl)
}
}
@Test
fun `static mode has non-draggable sheet`() = runTest {
val presenter = createShowLocationPresenter()
presenter.test {
val state = awaitItem()
assertThat(state.isSheetDraggable).isFalse()
}
}
}
private fun aLiveLocationShare(
userId: UserId,
geoUri: String = "geo:48.8584,2.2945",
timestamp: Long = 0L,
startTimestamp: Long = 0L,
endTimestamp: Long = Long.MAX_VALUE,
assetType: AssetType = AssetType.SENDER,
): LiveLocationShare {
return LiveLocationShare(
userId = userId,
lastLocation = LastLocation(
geoUri = geoUri,
timestamp = timestamp,
assetType = assetType,
),
startTimestamp = startTimestamp,
endTimestamp = endTimestamp,
)
}

View file

@ -30,6 +30,7 @@ import io.element.android.features.lockscreen.impl.unlock.di.PinUnlockBindings
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.designsystem.theme.ElementThemeApp
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import kotlinx.coroutines.launch
@ -43,6 +44,7 @@ class PinUnlockActivity : AppCompatActivity() {
@Inject lateinit var presenter: PinUnlockPresenter
@Inject lateinit var lockScreenService: LockScreenService
@Inject lateinit var appPreferencesStore: AppPreferencesStore
@Inject lateinit var featureFlagService: FeatureFlagService
@Inject lateinit var enterpriseService: EnterpriseService
@Inject lateinit var buildMeta: BuildMeta
@ -56,6 +58,7 @@ class PinUnlockActivity : AppCompatActivity() {
}.collectAsState(SemanticColorsLightDark.default)
ElementThemeApp(
appPreferencesStore = appPreferencesStore,
featureFlagService = featureFlagService,
compoundLight = colors.light,
compoundDark = colors.dark,
buildMeta = buildMeta,

View file

@ -23,7 +23,7 @@ Wähle eine einprägsame PIN. Wenn du sie vergisst, wirst du aus der App abgemel
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Bitte gib die gleiche PIN wie zuvor ein."</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"Die PINs stimmen nicht überein"</string>
<string name="screen_app_lock_signout_alert_message">"Um fortzufahren, musst du dich erneut anmelden und eine neue PIN erstellen"</string>
<string name="screen_app_lock_signout_alert_title">"Du wirst abgemeldet"</string>
<string name="screen_app_lock_signout_alert_title">"Dieses Gerät wurde entfernt"</string>
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"Du hast %1$d Versuch, um zu entsperren"</item>
<item quantity="other">"Du hast %1$d Versuche, um zu entsperren"</item>
@ -34,5 +34,5 @@ Wähle eine einprägsame PIN. Wenn du sie vergisst, wirst du aus der App abgemel
</plurals>
<string name="screen_app_lock_use_biometric_android">"Biometrie verwenden"</string>
<string name="screen_app_lock_use_pin_android">"PIN verwenden"</string>
<string name="screen_signout_in_progress_dialog_content">"Abmelden…"</string>
<string name="screen_signout_in_progress_dialog_content">"Gerät wird entfernt…"</string>
</resources>

View file

@ -23,7 +23,7 @@ Vali midagi, mis hästi meelde jääb. Kui unustad selle PIN-koodi, siis turvaka
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Palun sisesta sama PIN-kood kaks korda"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN-koodid ei klapi omavahel"</string>
<string name="screen_app_lock_signout_alert_message">"Jätkamaks pead uuesti sisse logima ja looma uue PIN-koodi"</string>
<string name="screen_app_lock_signout_alert_title">"Sa oled logimas välja"</string>
<string name="screen_app_lock_signout_alert_title">"See seade on eemaldamisel"</string>
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"Sul on lukustuse eemaldamiseks jäänud %1$d katse"</item>
<item quantity="other">"Sul on lukustuse eemaldamiseks jäänud %1$d katset"</item>

View file

@ -23,7 +23,7 @@ Odaberite nešto nezaboravno. Ako zaboravite ovaj PIN, bit ćete odjavljeni iz a
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Unesite dvaput isti PIN"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN-ovi se ne podudaraju"</string>
<string name="screen_app_lock_signout_alert_message">"Morat ćete se ponovno prijaviti i izraditi novi PIN da biste mogli nastaviti"</string>
<string name="screen_app_lock_signout_alert_title">"Odjavit ćete se"</string>
<string name="screen_app_lock_signout_alert_title">"Ovaj uređaj se uklanja"</string>
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"Imate %1$d pokušaj otključavanja"</item>
<item quantity="few">"Imate %1$d pokušaja otključavanja"</item>
@ -36,5 +36,5 @@ Odaberite nešto nezaboravno. Ako zaboravite ovaj PIN, bit ćete odjavljeni iz a
</plurals>
<string name="screen_app_lock_use_biometric_android">"Upotrijebi biometriju"</string>
<string name="screen_app_lock_use_pin_android">"Upotrijebi PIN"</string>
<string name="screen_signout_in_progress_dialog_content">"Odjavljivanje…"</string>
<string name="screen_signout_in_progress_dialog_content">"Uklanjanje uređaja…"</string>
</resources>

View file

@ -3,6 +3,7 @@
<string name="screen_app_lock_biometric_authentication">"xác thực sinh trắc học"</string>
<string name="screen_app_lock_biometric_unlock">"mở khóa sinh trắc học"</string>
<string name="screen_app_lock_biometric_unlock_title_android">"Mở khóa bằng sinh trắc học"</string>
<string name="screen_app_lock_confirm_biometric_authentication_android">"Xác nhận sinh trắc học"</string>
<string name="screen_app_lock_forgot_pin">"Quên mã PIN rồi à?"</string>
<string name="screen_app_lock_settings_change_pin">"Thay đổi mã PIN"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Cho phép mở khóa bằng sinh trắc học"</string>

View file

@ -23,7 +23,7 @@
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"請輸入相同的 PIN 碼兩次"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN 碼不一樣"</string>
<string name="screen_app_lock_signout_alert_message">"您需要重新登入並建立新的 PIN 碼才能繼續"</string>
<string name="screen_app_lock_signout_alert_title">"您即將登出"</string>
<string name="screen_app_lock_signout_alert_title">"此裝置已被移除"</string>
<plurals name="screen_app_lock_subtitle">
<item quantity="other">"您有 %1$d 次解鎖的機會"</item>
</plurals>
@ -32,5 +32,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>

View file

@ -23,7 +23,7 @@
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"请输入两次相同的 PIN 码"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN 码不匹配"</string>
<string name="screen_app_lock_signout_alert_message">"您需要重新登录并创建新的 PIN 才能继续"</string>
<string name="screen_app_lock_signout_alert_title">"您正在登出"</string>
<string name="screen_app_lock_signout_alert_title">"正在被移除该设备"</string>
<plurals name="screen_app_lock_subtitle">
<item quantity="other">"还剩 %1$d 次解锁机会"</item>
</plurals>

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