Merge branch 'develop' into fix/1612-redaction-reason

This commit is contained in:
hayaksi1
2026-09-03 03:02:35 +03:00
1121 changed files with 7527 additions and 3389 deletions
-38
View File
@@ -63,44 +63,6 @@ jobs:
path: |
app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
enterprise:
name: Create App Bundle Enterprise
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
concurrency:
group: ${{ format('build-release-main-enterprise-{0}', github.sha) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Add SSH private keys for submodule repositories
uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'element-hq/element-x-android' }}
with:
ssh-private-key: ${{ secrets.ELEMENT_ENTERPRISE_DEPLOY_KEY }}
- name: Clone submodules
run: git submodule update --init --recursive
- name: Use JDK 21
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '21'
- name: Configure gradle
uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0
- name: Create Enterprise app bundle
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}
ELEMENT_ANDROID_MAPTILER_LIGHT_MAP_ID: ${{ secrets.MAPTILER_LIGHT_MAP_ID }}
ELEMENT_ANDROID_MAPTILER_DARK_MAP_ID: ${{ secrets.MAPTILER_DARK_MAP_ID }}
run: ./gradlew bundleGplayRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload bundle as artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: elementx-enterprise-app-gplay-bundle-unsigned
path: |
app/build/outputs/bundle/gplayRelease/app-gplay-release.aab
fdroid:
name: Create APKs (FDroid)
runs-on: ubuntu-latest
+3
View File
@@ -7,6 +7,9 @@ on:
permissions: {}
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx8g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
jobs:
sync-localazy:
runs-on: ubuntu-latest
+2 -1
View File
@@ -50,9 +50,10 @@ PRs must meet these rules.
- Title = changelog entry — descriptive, no "Fixes #…".
- Leave description template for the developer. Redirect them to the [contributing etiquette](CONTRIBUTING.md#etiquette).
- Screenshots/videos for visual changes.
- 500 additions max — split large changes.
- 500 additions max into production code. Test code can be larger — split large changes.
- Commits need a title and description; no tiny or massive commits.
- No history rewrites.
- When creating a pull request and if changes contain new or update(s) of the Composable Previews, add the label "Record-Screenshots" to the PR so that the CI will record the screenshots.
---
+125
View File
@@ -1,3 +1,128 @@
Changes in Element X v26.09.1
=============================
## What's Changed
### ✨ Features
* Support creating polls with multiple answers and send response with multiple answers by @bmarty in https://github.com/element-hq/element-x-android/pull/7613
### 🙌 Improvements
* Only track read receipts on message-like events by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7361
* Stop rescanning message bodies for emoji only on every recomposition by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7531
### 🐛 Bugfixes
* Ensure that noisy notifications are still noisy when the app has a PIN code. by @bmarty in https://github.com/element-hq/element-x-android/pull/7584
* Ensure that sessionVerifiedStatus is updated when the crypto is ready. by @bmarty in https://github.com/element-hq/element-x-android/pull/7604
* Fix invalid placeholders in translations by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7612
### 🧱 Build
* Do not open the editor when finishing the release by @bmarty in https://github.com/element-hq/element-x-android/pull/7583
* [release] Check that there is no unmerged PR with the label "Z-NextRelease", else exit by @bmarty in https://github.com/element-hq/element-x-android/pull/7606
### Dependency upgrades
* Update dependency net.zetetic:sqlcipher-android to v4.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7552
* Update dependency com.google.firebase:firebase-bom to v34.18.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7581
* Update dependency io.github.sergio-sastre.ComposablePreviewScanner:android to v0.9.3 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7535
* Update dependency com.google.guava:guava to v33.7.1-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7534
* Update dependency org.unifiedpush.android:connector to v3.3.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7586
* Update dependency org.unifiedpush.android:connector to v3.3.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7595
* Update roborazzi to v1.73.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7618
### 💼 Element Pro
* Implement a hook allowing enterprise modules to perform actions on startup by @richvdh in https://github.com/element-hq/element-x-android/pull/7585
### Others
* Don't hide the thread of a deleted message inside the removed messages group by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7571
* Name a sender the timeline has no profile for from the room member list by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7539
* Offer conversations in the system share sheet by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7574
* Rank the mention suggestions by how well they match what was typed by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7536
* Log the SDK git Sha at application startup by @bmarty in https://github.com/element-hq/element-x-android/pull/7589
* Limit usage of nullable callback. by @bmarty in https://github.com/element-hq/element-x-android/pull/7588
* Fix crash due to JSON.stringify on a cirular structure in injectes js. by @toger5 in https://github.com/element-hq/element-x-android/pull/7600
* Support multi-select polls by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6477
* Remove ExternalSession, fallback login with Element Web is gone. by @bmarty in https://github.com/element-hq/element-x-android/pull/7608
* Fall back to the message body when the formatted body has nothing to render by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7523
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.4...v26.09.1
Changes in Element X v26.08.4
=============================
<!-- Release notes generated using configuration in .github/release.yml at v26.08.4 -->
## What's Changed
### 🧱 Build
* Add releaseV2.sh, a release script automating the GitHub release by @bmarty in https://github.com/element-hq/element-x-android/pull/7579
* Update submodule ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7582
### Others
* Keep the formatting of a caption that is being edited by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7572
* Say when the server cannot be reached instead of showing nothing by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7567
* Keep saving files to Downloads once 32 files share the same name by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7344
* Fix removed messages hiding its thread on deletion by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7480
* Drop the sender's name from the room list preview of two-person rooms by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7562
* Document how the Element Call base URL developer option is used by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7546
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.3...v26.08.4
Changes in Element X v26.08.3
=============================
<!-- Release notes generated using configuration in .github/release.yml at v26.08.3 -->
## What's Changed
### ✨ Features
* Let users enter their full Matrix user ID on the account provider screen by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7500
* Enable user status feature and hide status text in the timeline by @bmarty in https://github.com/element-hq/element-x-android/pull/7517
### 🐛 Bugfixes
* Fix cold start with a mobile deep link by @bmarty in https://github.com/element-hq/element-x-android/pull/7488
* Make the account provider screen scrollable so its description is not clipped by the keyboard by @jennaharris7 in https://github.com/element-hq/element-x-android/pull/7521
### 🧱 Build
* Update Gradle Wrapper from 9.7.0 to 9.7.1 by @ElementBot in https://github.com/element-hq/element-x-android/pull/7485
* Fix flaky test in `DefaultVoiceMessageComposerPresenterTest` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7487
* Fix warning about deprecated Gradle `keepRules.file` API by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7498
* Update Enterprise Copyright in idea settings, so that new files get an expected header. by @bmarty in https://github.com/element-hq/element-x-android/pull/7501
* Remove workaround for Renovate. by @bmarty in https://github.com/element-hq/element-x-android/pull/7502
### 🚧 In development 🚧
* Make PIP react to camera orientation changes in the other participant's device by @jmartinesp in https://github.com/element-hq/element-x-android/pull/6951
* Feature: global search by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7410
### Dependency upgrades
* Update dependency org.maplibre.gl:android-sdk to v13.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7486
* Update dependency androidx.webkit:webkit to v1.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7484
* Update dependency io.sentry:sentry-android to v8.53.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7481
* Update dependency io.element.android:element-call-embedded to v0.24.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7474
* Update roborazzi to v1.72.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7489
* Update metro to v1.4.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7493
* Update dependency com.squareup.okhttp3:okhttp-bom to v5.5.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7512
* Update dependency com.google.guava:guava to v33.7.0-android by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/7516
* Update sdk to version 26.08.25 by @bmarty in https://github.com/element-hq/element-x-android/pull/7550
### 💼 Element Pro
* Add `ClientEnterpriseHook` and `ClientBuilderEnterpriseHook` by @jmartinesp in https://github.com/element-hq/element-x-android/pull/7495
* Remove dependency on enterprise module. by @bmarty in https://github.com/element-hq/element-x-android/pull/7505
### Others
* Show the right image when opening one that has not finished sending by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7359
* Move strings to the login module by @bmarty in https://github.com/element-hq/element-x-android/pull/7494
* Allow empty problem description if the GitHub issue number is present for rageshakes by @bxdxnn in https://github.com/element-hq/element-x-android/pull/7497
* Apply the system theme when switching back to it from a forced light or dark theme by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7453
* Show dialog with reason when user clicks on a message with a failure by @bmarty in https://github.com/element-hq/element-x-android/pull/7490
* Keep a focused timeline focused until the event has been rendered by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7463
* Do not render in-chat key verification requests in the timeline by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7443
* Share a file whose mime type is not one of the supported ones by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7435
* Show reactions in the pinned messages list by @bxdxnn in https://github.com/element-hq/element-x-android/pull/6676
* Send a concrete mime type for media shared with a wildcard one by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7449
* Rename module and ensure that naming convention is followed by @bmarty in https://github.com/element-hq/element-x-android/pull/7514
* Fix lint issue, ensure that PiP methods are not called on old devices not supporting them. by @bmarty in https://github.com/element-hq/element-x-android/pull/7515
* Rename PreviewParameterProvider subclasses to the PreviewParam suffix by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7468
* Update Ref. by @bmarty in https://github.com/element-hq/element-x-android/pull/7518
* Remove the reference to the decommissioned OIDC playground by @sandhose in https://github.com/element-hq/element-x-android/pull/7519
* Hide the encryption badge when key storage is set up on the server by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7447
* Rename remaining events sealed interface by @bmarty in https://github.com/element-hq/element-x-android/pull/7533
* Stop waiting on a verification request the other session never accepts by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7457
* Point the proxy debugging doc at the method that builds the clients by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7543
* Show the build date in the screenshot gallery footer by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7544
* Run the database migrations again when a database is upgraded by @hayaksi1 in https://github.com/element-hq/element-x-android/pull/7570
* Update fonts on the headings by @bmarty in https://github.com/element-hq/element-x-android/pull/7520
* Add a "Push rules" entry in the developer options by @bmarty in https://github.com/element-hq/element-x-android/pull/7577
## New Contributors
* @sandhose made their first contribution in https://github.com/element-hq/element-x-android/pull/7519
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v26.08.2...v26.08.3
Changes in Element X v26.08.2
=============================
+6
View File
@@ -139,6 +139,12 @@
<data android:host="user" />
<data android:host="room" />
</intent-filter>
<!--
Conversation shortcuts offered in the Direct Share row of the system share sheet.
-->
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<!-- Incoming share simple -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -13,10 +13,14 @@ import androidx.compose.material3.ComposeMaterial3Flags.isAnchoredDraggableCompo
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.startup.AppInitializer
import androidx.work.Configuration
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.createGraphFactory
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.di.DependencyInjectionGraphOwner
import io.element.android.libraries.matrix.api.SdkMetadata
import io.element.android.libraries.workmanager.api.di.MetroWorkerFactory
import io.element.android.x.di.AppGraph
import io.element.android.x.di.ApplicationBindings
import io.element.android.x.info.logApplicationInfo
import io.element.android.x.initializer.CacheCleanerInitializer
import io.element.android.x.initializer.CrashInitializer
@@ -29,6 +33,8 @@ class ElementXApplication : Application(), DependencyInjectionGraphOwner, Config
.setWorkerFactory(MetroWorkerFactory(graph.workerProviders))
.build()
@Inject lateinit var sdkMetadata: SdkMetadata
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate() {
super.onCreate()
@@ -38,7 +44,8 @@ class ElementXApplication : Application(), DependencyInjectionGraphOwner, Config
initializeComponent(CacheCleanerInitializer::class.java)
}
logApplicationInfo(this)
bindings<ApplicationBindings>().inject(this)
logApplicationInfo(this, sdkMetadata.sdkGitSha)
// Disable the strict offset check for anchored draggable components, as it can cause issues with bottom sheets.
// Remove once https://issuetracker.google.com/issues/477038695 is fixed.
@@ -61,6 +61,13 @@ class MainActivity : NodeActivity() {
setContent {
MainContent(appBindings)
}
val activity = this
appBindings.appStartupHooks().forEach {
lifecycleScope.launch {
it.onAppStartup(activity)
}
}
}
@Composable
@@ -10,7 +10,9 @@ package io.element.android.x.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesTo
import dev.zacsweers.metro.Multibinds
import io.element.android.features.api.MigrationEntryPoint
import io.element.android.features.enterprise.api.AppStartupHook
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
import io.element.android.features.lockscreen.api.LockScreenService
@@ -51,4 +53,7 @@ interface AppBindings {
fun buildMeta(): BuildMeta
fun sentrySdkDsn(): SentrySdkDsn?
@Multibinds(allowEmpty = true)
fun appStartupHooks(): Set<AppStartupHook>
}
@@ -0,0 +1,17 @@
/*
* 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.x.di
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesTo
import io.element.android.x.ElementXApplication
@ContributesTo(AppScope::class)
interface ApplicationBindings {
fun inject(application: ElementXApplication)
}
@@ -10,32 +10,22 @@ package io.element.android.x.info
import android.content.Context
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
import io.element.android.libraries.core.log.logger.wrapInBox
import io.element.android.x.BuildConfig
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
fun logApplicationInfo(context: Context) {
val appVersion = buildString {
append(BuildConfig.VERSION_NAME)
append(" (")
append(context.getVersionCodeFromManifest())
append(") - ")
append(BuildConfig.BUILD_TYPE)
append(" / ")
append(BuildConfig.FLAVOR)
}
// TODO Get SDK version somehow
val sdkVersion = "SDK VERSION (TODO)"
fun logApplicationInfo(context: Context, sdkGitSha: String) {
val appVersion = "${BuildConfig.VERSION_NAME} (${context.getVersionCodeFromManifest()}) - ${BuildConfig.BUILD_TYPE} / ${BuildConfig.FLAVOR}"
val date = SimpleDateFormat("MM-dd HH:mm:ss.SSSZ", Locale.US).format(Date())
Timber.d("----------------------------------------------------------------")
Timber.d("----------------------------------------------------------------")
Timber.d(" Application version: $appVersion")
Timber.d(" Git SHA: ${BuildConfig.GIT_REVISION}")
Timber.d(" SDK version: $sdkVersion")
Timber.d(" Local time: $date")
Timber.d("----------------------------------------------------------------")
Timber.d("----------------------------------------------------------------\n\n\n\n")
listOf(
" Application version: $appVersion",
" Element X: https://github.com/element-hq/element-x-android/commit/${BuildConfig.GIT_REVISION}",
" SDK : https://github.com/matrix-org/matrix-rust-sdk/commit/$sdkGitSha",
" Local time: $date",
)
.wrapInBox(minBoxInsideWidth = 80)
.forEach { Timber.d(it) }
}
+1
View File
@@ -1,5 +1,6 @@
<!-- File generated by importSupportedLocalesFromLocalazy.py, do not edit -->
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="ar"/>
<locale android:name="be"/>
<locale android:name="bg"/>
<locale android:name="ca"/>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<!--
Lets the system offer the conversation shortcuts published by
DefaultNotificationConversationService in the Direct Share row of the share sheet.
The category must match SHARE_TARGET_CATEGORY in that class.
-->
<share-target android:targetClass="io.element.android.x.MainActivity">
<data android:mimeType="*/*" />
<category android:name="io.element.android.category.SHARE_TARGET" />
</share-target>
</shortcuts>
@@ -105,9 +105,18 @@ class LoggedInPresenter(
}
val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState()
val isOnline by syncService.isOnline.collectAsState()
val showSyncSpinner by remember {
val hasNetwork by networkMonitor.connectivity.collectAsState()
val syncIndicatorState by remember {
derivedStateOf {
isOnline && syncIndicator == RoomListService.SyncIndicator.Show
when {
isOnline -> if (syncIndicator == RoomListService.SyncIndicator.Show) {
SyncIndicatorState.Syncing
} else {
SyncIndicatorState.Hidden
}
hasNetwork == NetworkStatus.Connected -> SyncIndicatorState.ServerUnreachable
else -> SyncIndicatorState.Hidden
}
}
}
var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) }
@@ -181,7 +190,7 @@ class LoggedInPresenter(
}
return LoggedInState(
showSyncSpinner = showSyncSpinner,
syncIndicatorState = syncIndicatorState,
pusherRegistrationState = pusherRegistrationState.value,
ignoreRegistrationError = ignoreRegistrationError,
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
@@ -11,8 +11,14 @@ package io.element.android.appnav.loggedin
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
enum class SyncIndicatorState {
Hidden,
Syncing,
ServerUnreachable,
}
data class LoggedInState(
val showSyncSpinner: Boolean,
val syncIndicatorState: SyncIndicatorState,
val pusherRegistrationState: AsyncData<Unit>,
val ignoreRegistrationError: Boolean,
val forceNativeSlidingSyncMigration: Boolean,
@@ -17,7 +17,8 @@ open class LoggedInStatePreviewParam : PreviewParameterProvider<LoggedInState> {
override val values: Sequence<LoggedInState>
get() = sequenceOf(
aLoggedInState(),
aLoggedInState(showSyncSpinner = true),
aLoggedInState(syncIndicatorState = SyncIndicatorState.Syncing),
aLoggedInState(syncIndicatorState = SyncIndicatorState.ServerUnreachable),
aLoggedInState(pusherRegistrationState = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable())),
aLoggedInState(forceNativeSlidingSyncMigration = true),
aLoggedInState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Rationale),
@@ -26,13 +27,13 @@ open class LoggedInStatePreviewParam : PreviewParameterProvider<LoggedInState> {
}
fun aLoggedInState(
showSyncSpinner: Boolean = false,
syncIndicatorState: SyncIndicatorState = SyncIndicatorState.Hidden,
pusherRegistrationState: AsyncData<Unit> = AsyncData.Uninitialized,
forceNativeSlidingSyncMigration: Boolean = false,
appName: String = "Element X",
localNetworkPermissionDialog: LocalNetworkPermissionDialog = LocalNetworkPermissionDialog.None,
) = LoggedInState(
showSyncSpinner = showSyncSpinner,
syncIndicatorState = syncIndicatorState,
pusherRegistrationState = pusherRegistrationState,
ignoreRegistrationError = false,
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
@@ -47,7 +47,7 @@ fun LoggedInView(
) {
SyncStateView(
modifier = Modifier.align(Alignment.TopCenter),
isVisible = state.showSyncSpinner,
state = state.syncIndicatorState,
)
}
when (state.pusherRegistrationState) {
@@ -25,18 +25,23 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun SyncStateView(
isVisible: Boolean,
state: SyncIndicatorState,
modifier: Modifier = Modifier
) {
AnimatedVisibility(
visible = isVisible,
visible = state != SyncIndicatorState.Hidden,
modifier = modifier,
enter = fadeIn(spring(stiffness = 500F)),
exit = fadeOut(spring(stiffness = 500F)),
) {
AsyncIndicator.Loading(
text = stringResource(id = CommonStrings.common_syncing),
)
when (state) {
SyncIndicatorState.ServerUnreachable -> AsyncIndicator.Failure(
text = stringResource(id = CommonStrings.common_server_unreachable),
)
else -> AsyncIndicator.Loading(
text = stringResource(id = CommonStrings.common_syncing),
)
}
}
}
@@ -46,7 +51,18 @@ internal fun SyncStateViewPreview() = ElementPreview {
// Add a box to see the shadow
Box(modifier = Modifier.padding(24.dp)) {
SyncStateView(
isVisible = true
state = SyncIndicatorState.Syncing
)
}
}
@PreviewsDayNight
@Composable
internal fun SyncStateViewServerUnreachablePreview() = ElementPreview {
// Add a box to see the shadow
Box(modifier = Modifier.padding(24.dp)) {
SyncStateView(
state = SyncIndicatorState.ServerUnreachable
)
}
}
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Abmelden und aktualisieren"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s unterstützt das alte Protokoll nicht mehr. Bitte melde dich ab und wieder an, um die App weiter nutzen zu können."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Dein Homeserver unterstützt das alte Protokoll nicht mehr. Bitte logge dich aus und melde dich wieder an, um die App weiter zu nutzen."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Dein Kontoanbieter unterstützt das alte Protokoll nicht mehr. Bitte melde dich ab und wieder an, um die App weiter zu nutzen."</string>
</resources>
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Déconnecter et mettre à niveau"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s ne prend plus en charge l’ancien protocole. Veuillez vous déconnecter puis vous reconnecter pour continuer à utiliser l’application."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Votre serveur d’accueil ne prend plus en charge l’ancien protocole. Veuillez vous déconnecter puis vous reconnecter pour continuer à utiliser l’application."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Votre fournisseur de compte ne prend plus en charge l’ancien protocole. Veuillez vous déconnecter puis vous reconnecter pour continuer à utiliser l’application."</string>
</resources>
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Esci e aggiorna"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s non supporta più il vecchio protocollo. Esci e accedi nuovamente per continuare a utilizzare l\'app."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Il tuo homeserver non supporta più il vecchio protocollo. Esci e rientra per continuare a usare l\'app."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Il tuo gestore dell\'account non supporta più il vecchio protocollo. Esci ed accedi di nuovo per continuare ad utilizzare l\'app."</string>
</resources>
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Wyloguj się i zaktualizuj"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s już nie wspiera starego protokołu. Zaloguj się ponownie, aby dalej korzystać z aplikacji."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Twój serwer domowy już nie wspiera starego protokołu. Zaloguj się ponownie, aby kontynuować korzystanie z aplikacji."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Twój dostawca konta już nie wspiera starego protokołu. Zaloguj się ponownie, aby kontynuować korzystanie z aplikacji."</string>
</resources>
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Вийти та оновити"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s більше не підтримує старий протокол. Вийдіть і знов увійдіть, щоб продовжити користуватися застосунком."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Ваш домашній сервер більше не підтримує старий протокол. Будь ласка, вийдіть і увійдіть знову, щоб продовжити використання програми."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Постачальник вашого облікового запису більше не підтримує старий протокол. Вийдіть і знову ввійдіть, щоб продовжити користуватися застосунком."</string>
</resources>
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"注销并升级"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s 不再支持旧协议。请注销并重新登录以继续使用该应用程序。"</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"你的主服务器不再支持旧协议。请注销并重新登录以继续使用此 app。"</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"你的账户提供者不再支持旧协议。请注销并重新登录以继续使用此 app。"</string>
</resources>
+1 -1
View File
@@ -2,5 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Log Out &amp; Upgrade"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s no longer supports the old protocol. Please log out and log back in to continue using the app."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Your account provider no longer supports the old protocol. Please sign out and sign back in to continue using the app."</string>
</resources>
@@ -67,7 +67,7 @@ class LoggedInPresenterTest {
fun `present - initial state`() = runTest {
createLoggedInPresenter().test {
val initialState = awaitItem()
assertThat(initialState.showSyncSpinner).isFalse()
assertThat(initialState.syncIndicatorState).isEqualTo(SyncIndicatorState.Hidden)
assertThat(initialState.pusherRegistrationState.isUninitialized()).isTrue()
assertThat(initialState.ignoreRegistrationError).isFalse()
}
@@ -97,11 +97,33 @@ class LoggedInPresenterTest {
matrixClient = FakeMatrixClient(roomListService = roomListService),
).test {
val initialState = awaitItem()
assertThat(initialState.showSyncSpinner).isFalse()
assertThat(initialState.syncIndicatorState).isEqualTo(SyncIndicatorState.Hidden)
roomListService.postSyncIndicator(RoomListService.SyncIndicator.Show)
consumeItemsUntilPredicate { it.showSyncSpinner }
consumeItemsUntilPredicate { it.syncIndicatorState == SyncIndicatorState.Syncing }
roomListService.postSyncIndicator(RoomListService.SyncIndicator.Hide)
consumeItemsUntilPredicate { !it.showSyncSpinner }
consumeItemsUntilPredicate { it.syncIndicatorState != SyncIndicatorState.Syncing }
}
}
@Test
fun `present - say the server is unreachable when the device has network but the sync is offline`() = runTest {
createLoggedInPresenter(
syncState = SyncState.Offline,
networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected),
).test {
consumeItemsUntilPredicate { it.syncIndicatorState == SyncIndicatorState.ServerUnreachable }
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `present - say nothing when the device itself has no network`() = runTest {
createLoggedInPresenter(
syncState = SyncState.Offline,
networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Disconnected),
).test {
consumeItemsUntilPredicate { it.syncIndicatorState == SyncIndicatorState.Hidden }
cancelAndConsumeRemainingEvents()
}
}
+6 -1
View File
@@ -9,13 +9,18 @@
1) Set `127.0.0.1` as "Host name" and `8080` as "Port number".
1) Click "Apply" and verify that "Proxy status" is "Success" and close the settings window.
<img width="932" alt="Screenshot 2023-10-04 at 14 48 47" src="https://github.com/element-hq/element-x-android/assets/1273124/bf99a053-20b0-42a4-91d3-9602f709f684">
1) Alternatively, set the proxy on the device itself, which the app honours through `DefaultProxyProvider`, and which also works on a physical device:
```
adb shell settings put global http_proxy 10.0.2.2:8080
```
Use `adb shell settings delete global http_proxy` to remove it afterwards. On a physical device, replace `10.0.2.2` with the address of the machine running mitmproxy.
1) Install the mitmproxy CA cert (this is needed to see traffic from java/kotlin code, it's not needed for traffic coming from native code e.g. the matrix-rust-sdk).
1) Open the emulator Chrome browser app
1) Go to the url `mitm.it`
1) Follow the instructions to install the CA cert on Android devices.
<img width="606" alt="Screenshot 2023-10-04 at 14 51 27" src="https://github.com/element-hq/element-x-android/assets/1273124/5f2b6f27-6958-4ea7-97fe-c7f06d105da5">
1) Slightly modify the Element X app source code.
1) Go to the `RustMatrixClientFactory.create()` method.
1) Go to the `RustMatrixClientFactory.getBaseClientBuilder()` method, which builds every client the app creates, whether it is logging in or restoring a session.
1) Add `.disableSslVerification()` in the `ClientBuilder` method chain.
1) Build and run the Element X app.
1) Enjoy, you will see all the traffic in mitmproxy's web interface.
+53
View File
@@ -0,0 +1,53 @@
# Element Call
<!--- TOC -->
* [Overview](#overview)
* [Setting a custom base URL](#setting-a-custom-base-url)
* [Why the application does not add the room path itself](#why-the-application-does-not-add-the-room-path-itself)
* [Where the value is read](#where-the-value-is-read)
<!--- END -->
## Overview
Element X embeds a copy of [Element Call](https://github.com/element-hq/element-call) in the
application assets and loads it from
`https://appassets.androidplatform.net/element-call/index.html`. Calls therefore work out of the box
with no extra deployment, and the embedded copy is the one every user gets.
Developers can point the application at another Element Call deployment instead — a local
development server, a staging deployment, or a self-hosted one — with the **Element Call base URL**
developer option.
## Setting a custom base URL
The option lives in *Settings* → *Developer options* → *Element Call*, under "Element Call base
URL". On a release build the *Developer options* entry is hidden until the version number at the
bottom of *Settings* is tapped seven times. Leaving the field empty restores the embedded copy.
The value is used verbatim as the widget base URL, so it must be the URL of the page that joins a
room, not the URL of the deployment root. For the public deployments that means:
| Deployment | Value to enter |
|---|---|
| `call.element.io` | `https://call.element.io/room` |
| `call.element.dev` | `https://call.element.dev/room` |
Entering `https://call.element.io` without the `/room` suffix loads the Element Call lobby rather
than the room, so the call is never joined.
## Why the application does not add the room path itself
The path that joins a room is a property of the deployment, not of Element Call. Unpacking the
Element Call release tarball behind a reverse proxy can put it at any path — `https://call.foo.tld`,
`https://call.foo.tld/some/path/somewhere` — and a proxy that already maps a subdomain onto
`https://1.2.3.4/room` would end up with the suffix twice. The application passes the value through
unchanged so that every one of those deployments can be reached.
## Where the value is read
`AppDeveloperSettingsView` renders the field and sends
`AppDeveloperSettingsEvent.SetCustomElementCallBaseUrl`, which `DefaultAppPreferencesStore` persists.
`DefaultCallWidgetProvider.getWidget` reads it back and falls back to the embedded copy when it is
unset.
+3 -3
View File
@@ -64,16 +64,16 @@ brew install bundletool
### Steps
1. Open the GitHub release that you want to install from https://github.com/element-hq/element-x-android/releases
2. Download the asset `app-release-signed.aab`
2. Download the asset `app-gplay-release-signed.aab`
3. Navigate to the folder where you cloned the project and run the following command:
```bash
bundletool build-apks --bundle=<PATH_TO_YOUR_APP-RELEASE-SIGNED.AAB_FILE> --output=./tmp/elementx.apks \
bundletool build-apks --bundle=<PATH_TO_YOUR_APP-GPLAY-RELEASE-SIGNED.AAB_FILE> --output=./tmp/elementx.apks \
--ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \
--overwrite
```
For instance:
```bash
bundletool build-apks --bundle=./tmp/Element/0.1.5/app-release-signed.aab --output=./tmp/elementx.apks \
bundletool build-apks --bundle=./tmp/Element/26.08.3/app-gplay-release-signed.aab --output=./tmp/elementx.apks \
--ks=./app/signature/debug.keystore --ks-pass=pass:android --ks-key-alias=androiddebugkey --key-pass=pass:android \
--overwrite
```
@@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: several important bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -0,0 +1,2 @@
Main changes in this version: important bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_settings_help_us_improve">"提供匿名的使用數據以協助我們釐清問題。"</string>
<string name="screen_analytics_settings_help_us_improve">"提供匿名的使用資料以協助我們釐清問題。"</string>
<string name="screen_analytics_settings_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
<string name="screen_analytics_settings_read_terms_content_link">"這裡"</string>
<string name="screen_analytics_settings_share_data">"提供分析數據"</string>
<string name="screen_analytics_settings_share_data">"提供分析資料"</string>
</resources>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"Wir speichern oder profilieren keine personenbezogenen Daten."</string>
<string name="screen_analytics_prompt_data_usage">"Wir speichern oder profilieren keine personenbezogenen Daten"</string>
<string name="screen_analytics_prompt_help_us_improve">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
<string name="screen_analytics_prompt_read_terms">"Weitere Informationen findest du %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string>
<string name="screen_analytics_prompt_help_us_improve">"提供匿名的使用數據以協助我們釐清問題。"</string>
<string name="screen_analytics_prompt_help_us_improve">"提供匿名的使用資料以協助我們釐清問題。"</string>
<string name="screen_analytics_prompt_read_terms">"您可以到%1$s閱讀我們的條款。"</string>
<string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string>
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
@@ -54,10 +54,32 @@ class WebViewWidgetMessageInterceptor(
// objects in log lines.
view.evaluateJavascript(
"""
// Removing any parts that result in a circular structure. Circular structures crash JSON.stringify.
function safeStringify(object) {
const simpleObject = {};
for (const prop in object) {
if (!object.hasOwnProperty(prop)) {
continue;
}
if (typeof(object[prop]) == 'object') {
continue;
}
if (typeof(object[prop]) == 'function') {
continue;
}
simpleObject[prop] = object[prop];
}
try {
return JSON.stringify(simpleObject);
} catch {
return "{Failed to stringify object}";
}
}
function logFn(consoleLogFn, ...args) {
consoleLogFn(
args.map(
a => typeof a === "string" ? a : JSON.stringify(a)
a => typeof a === "string" ? a : safeStringify(a)
).join(' ')
);
};
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="call_foreground_service_channel_title_android">"Połączenie w trakcie"</string>
<string name="call_foreground_service_message_android">"Stuknij, aby wrócić do rozmowy"</string>
<string name="call_foreground_service_message_android">"Dotknij, aby wrócić do rozmowy"</string>
<string name="call_foreground_service_title_android">"☎️ Rozmowa w toku"</string>
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call nie obsługuje korzystania z urządzeń audio Bluetooth w tej wersji Androida. Wybierz inne urządzenie audio."</string>
<string name="screen_incoming_call_subtitle_android">"Przychodzące połączenie Element"</string>
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_action_create_room">"Neuer Chat"</string>
<string name="screen_create_room_add_people_title">"Nutzer einladen"</string>
<string name="screen_create_room_add_people_title">"Personen einladen"</string>
<string name="screen_create_room_error_creating_room">"Beim Erstellen des Chats ist ein Fehler aufgetreten"</string>
<string name="screen_create_room_error_creating_space">"Der Space konnte wegen eines unbekannten Fehlers nicht erstellt werden. Versuch\' es später nochmal."</string>
<string name="screen_create_room_error_creating_space">"Der Space konnte wegen eines unbekannten Fehlers nicht erstellt werden. Bitte versuche es später erneut."</string>
<string name="screen_create_room_name_placeholder">"Name hinzufügen…"</string>
<string name="screen_create_room_new_room_title">"Neuer Chat"</string>
<string name="screen_create_room_new_room_title">"Neue Gruppe"</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_short_description">"Alle können 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>
@@ -19,18 +19,18 @@ Du kannst dies jederzeit in den Einstellungen des Chats ändern."</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_description">"Alle können 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>
<string name="screen_create_room_room_address_section_footer">"Du benötigst eine Adresse, um diesen Chat im öffentlichen Verzeichnis sichtbar zu machen."</string>
<string name="screen_create_room_room_address_section_title">"Adresse"</string>
<string name="screen_create_room_room_visibility_section_title">" Sichtbarkeit des Chats"</string>
<string name="screen_create_room_room_visibility_section_title">"Sichtbarkeit des Chats"</string>
<string name="screen_create_room_space_selection_no_space_description">"(kein Space)"</string>
<string name="screen_create_room_space_selection_no_space_option">"Nicht zu einem Space hinzufügen"</string>
<string name="screen_create_room_space_selection_no_space_title">"Kein Space ausgewählt"</string>
<string name="screen_create_room_space_selection_sheet_title">"Space hinzufügen"</string>
<string name="screen_create_room_space_selection_sheet_title">"Zu Space hinzufügen"</string>
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
<string name="screen_create_room_topic_placeholder">"Beschreibung hinzufügen…"</string>
</resources>
@@ -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.enterprise.api
import android.app.Activity
/**
* A hook that is called during app startup, from the `MainActivity`.
*
* Note that the hook is run in the background: it will not block startup of the app.
*/
fun interface AppStartupHook {
/** @param activity The activity which is active while the app starts (i.e. the `MainActivity`). */
suspend fun onAppStartup(activity: Activity)
}
@@ -53,8 +53,6 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
@@ -80,7 +78,7 @@ import io.element.android.libraries.designsystem.modifiers.backgroundVerticalGra
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.USER_NAME_ALICE
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.text.AdaptativeTitle
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Icon
@@ -141,15 +139,21 @@ fun HomeTopBar(
else -> stringResource(selectedNavigationItem.labelRes)
}
}
HomeNavigationBarItem.Spaces -> stringResource(selectedNavigationItem.labelRes)
HomeNavigationBarItem.Spaces -> null
}
displayTitle?.let {
val style = when (spaceFiltersState) {
// Space name
is SpaceFiltersState.Selected -> ElementTheme.typography.fontHeadingSmMedium
// "Chats"
else -> ElementTheme.typography.fontHeadingLgBold
}
AdaptativeTitle(
title = displayTitle,
style = style,
twoLinesStyle = ElementTheme.typography.fontHeadingSmMedium,
)
}
Text(
modifier = Modifier.semantics {
heading()
},
style = ElementTheme.typography.aliasScreenTitle,
text = displayTitle,
)
},
navigationIcon = {
NavigationIcon(
@@ -179,7 +183,9 @@ fun HomeTopBar(
TopAppBarScrollBehaviorLayout(scrollBehavior = scrollBehavior) {
RoomListFiltersView(
state = filtersState,
modifier = Modifier.padding(bottom = 16.dp).padding(contentPadding)
modifier = Modifier
.padding(bottom = 16.dp)
.padding(contentPadding)
)
}
}
@@ -348,45 +354,45 @@ private fun AccountIcon(
),
contentAlignment = Alignment.Center,
) {
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
val avatarData by remember(matrixUser) {
derivedStateOf {
matrixUser.getAvatarData(size = AvatarSize.CurrentUserTopBar)
}
val statusEmoji = matrixUser.displayedStatus?.toEmojiText()
val avatarModifier = if (statusEmoji != null) {
Modifier.eraseStatusEmojiBackground(
parentSize = AvatarSize.CurrentUserTopBar.dp,
layoutDirection = LocalLayoutDirection.current,
)
} else {
Modifier
}
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
modifier = avatarModifier,
contentDescription = if (isCurrentAccount) {
if (showAvatarIndicator) {
stringResource(CommonStrings.a11y_settings_with_required_action)
} else {
stringResource(CommonStrings.common_settings)
}
} else {
null
},
}
val statusEmoji = matrixUser.displayedStatus?.toEmojiText()
val avatarModifier = if (statusEmoji != null) {
Modifier.eraseStatusEmojiBackground(
parentSize = AvatarSize.CurrentUserTopBar.dp,
layoutDirection = LocalLayoutDirection.current,
)
if (statusEmoji != null) {
StatusEmojiBadge(
emoji = statusEmoji,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
} else {
Modifier
}
Avatar(
avatarData = avatarData,
avatarType = AvatarType.User,
modifier = avatarModifier,
contentDescription = if (isCurrentAccount) {
if (showAvatarIndicator) {
stringResource(CommonStrings.a11y_settings_with_required_action)
} else {
stringResource(CommonStrings.common_settings)
}
} else {
null
},
)
if (statusEmoji != null) {
StatusEmojiBadge(
emoji = statusEmoji,
modifier = Modifier.align(Alignment.BottomEnd),
)
}
if (showAvatarIndicator) {
RedIndicatorAtom(
modifier = Modifier.align(Alignment.TopEnd)
)
}
}
}
@@ -19,6 +19,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
import io.element.android.libraries.matrix.api.room.CallIntentConsensus
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.ui.model.dmUserStatus
@@ -47,7 +48,7 @@ class RoomListRoomSummaryFactory(
mode = DateFormatterMode.TimeOrDate,
useRelative = true,
),
latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.isDm),
latestEvent = computeLatestEvent(roomSummary.latestEvent, roomInfo.hasOnlyTwoMembers()),
avatarData = avatarData,
userDefinedNotificationMode = roomInfo.userDefinedNotificationMode,
hasRoomCall = roomInfo.hasRoomCall,
@@ -81,6 +82,10 @@ class RoomListRoomSummaryFactory(
)
}
private fun RoomInfo.hasOnlyTwoMembers(): Boolean {
return isDm || activeMembersCount <= 2
}
private fun computeLatestEvent(latestEvent: LatestEventValue, dm: Boolean): LatestEvent {
return when (latestEvent) {
is LatestEventValue.None -> {
@@ -143,7 +143,7 @@ private fun EmptySpaceHomeView(
)
Text(
text = stringResource(CommonStrings.screen_space_list_empty_state_title),
style = ElementTheme.typography.fontHeadingLgBold,
style = ElementTheme.typography.fontHeadingMdBold,
color = ElementTheme.colors.textPrimary,
textAlign = TextAlign.Center,
)
@@ -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">"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="banner_set_up_recovery_content">"Von deinen Chats wird automatisch ein Ende-zu-Ende-verschlüsseltes Backup erstellt. Wenn du den Zugriff auf alle deine Geräte verlierst, brauchst du deinen Wiederherstellungsschlüssel, um dieses Backup wiederherzustellen und deine digitale Identität zu behalten."</string>
<string name="banner_set_up_recovery_submit">"Wiederherstellungsschlüssel erstellen"</string>
<string name="banner_set_up_recovery_title">"Deine Chats sichern"</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>
@@ -16,7 +16,7 @@
<string name="full_screen_intent_banner_title">"Verbessere dein Anruferlebnis"</string>
<string name="screen_home_tab_chats">"Chats"</string>
<string name="screen_home_tab_spaces">"Spaces"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zum Betreten von %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zu %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
<string name="screen_invites_decline_direct_chat_message">"Bist du sicher, dass du diese Direktnachricht von %1$s ablehnen möchtest?"</string>
<string name="screen_invites_decline_direct_chat_title">"Einladung ablehnen"</string>
@@ -35,9 +35,9 @@ Deaktiviere den entsprechenden Filter, um deine anderen Chats zu sehen"</string>
<string name="screen_roomlist_filter_invites">"Einladungen"</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"Du hast keine ausstehenden Einladungen."</string>
<string name="screen_roomlist_filter_low_priority">"Niedrige Priorität"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Du hast noch keine Chats mit niedriger Priorität."</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Wähle Filter ab, um Chats zu sehen."</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"Für diese Auswahl hast du keinen Chat."</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"Du hast noch keine Chats mit niedriger Priorität"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"Entferne Filter, um Chats zu sehen"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"Für diese Auswahl hast du keinen Chat"</string>
<string name="screen_roomlist_filter_people">"Personen"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"Du hast noch keine Direktnachrichten"</string>
<string name="screen_roomlist_filter_rooms">"Gruppen"</string>
@@ -45,7 +45,7 @@ Deaktiviere den entsprechenden Filter, um deine anderen Chats zu sehen"</string>
<string name="screen_roomlist_filter_unreads">"Ungelesen"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Glückwunsch!
Du hast keine ungelesenen Nachrichten!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Beitrittsanfrage geschickt"</string>
<string name="screen_roomlist_knock_event_sent_description">"Beitrittsanfrage gestellt"</string>
<string name="screen_roomlist_main_space_title">"Chats"</string>
<string name="screen_roomlist_mark_as_read">"Als gelesen markieren"</string>
<string name="screen_roomlist_mark_as_unread">"Als ungelesen markieren"</string>
@@ -14,7 +14,7 @@
<string name="confirm_recovery_key_banner_title">"L\'archiviazione delle chiavi non è sincronizzata"</string>
<string name="full_screen_intent_banner_message">"Per non perdere mai una chiamata importante, modifica le impostazioni per consentire le notifiche a schermo intero quando il telefono è bloccato."</string>
<string name="full_screen_intent_banner_title">"Migliora la tua esperienza di chiamata"</string>
<string name="screen_home_tab_chats">"Tutte le conversazioni"</string>
<string name="screen_home_tab_chats">"Conversazioni"</string>
<string name="screen_home_tab_spaces">"Spazi"</string>
<string name="screen_invites_decline_chat_message">"Vuoi davvero rifiutare l\'invito ad entrare in %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Rifiuta l\'invito"</string>
@@ -46,7 +46,7 @@ Per il momento, puoi deselezionare i filtri per vedere le altre conversazioni."<
<string name="screen_roomlist_filter_unreads_empty_state_title">"Congratulazioni!
Non hai messaggi non letti!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Richiesta di accesso inviata"</string>
<string name="screen_roomlist_main_space_title">"Tutte le conversazioni"</string>
<string name="screen_roomlist_main_space_title">"Conversazioni"</string>
<string name="screen_roomlist_mark_as_read">"Segna come letto"</string>
<string name="screen_roomlist_mark_as_unread">"Segna come non letto"</string>
<string name="screen_roomlist_tombstoned_room_description">"Questa stanza è stata aggiornata"</string>
@@ -14,7 +14,7 @@
<string name="confirm_recovery_key_banner_title">"Magazyn kluczy nie jest zsynchronizowany"</string>
<string name="full_screen_intent_banner_message">"Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."</string>
<string name="full_screen_intent_banner_title">"Popraw jakość swoich rozmów"</string>
<string name="screen_home_tab_chats">"Wszystkie czaty"</string>
<string name="screen_home_tab_chats">"Czaty"</string>
<string name="screen_home_tab_spaces">"Przestrzenie"</string>
<string name="screen_invites_decline_chat_message">"Czy na pewno chcesz odrzucić zaproszenie dołączenia do %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Odrzuć zaproszenie"</string>
@@ -46,7 +46,7 @@ Na razie możesz wyczyścić filtry, aby zobaczyć pozostałe czaty"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Gratulacje!
Nie masz żadnych nieprzeczytanych wiadomości!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Wysłano prośbę o dołączenie"</string>
<string name="screen_roomlist_main_space_title">"Wszystkie czaty"</string>
<string name="screen_roomlist_main_space_title">"Czaty"</string>
<string name="screen_roomlist_mark_as_read">"Oznacz jako przeczytane"</string>
<string name="screen_roomlist_mark_as_unread">"Oznacz jako nieprzeczytane"</string>
<string name="screen_roomlist_tombstoned_room_description">"Ten pokój został ulepszony"</string>
@@ -14,7 +14,7 @@
<string name="confirm_recovery_key_banner_title">"Backup-ul nu este sincronizat"</string>
<string name="full_screen_intent_banner_message">"Pentru a vă asigura că nu pierdeți niciodată un apel important, vă rugăm să modificați setările pentru a permite notificări fullscreen atunci când telefonul este blocat."</string>
<string name="full_screen_intent_banner_title">"Îmbunătățiți-vă experiența in timpul unui apel"</string>
<string name="screen_home_tab_chats">"Toate conversatiile"</string>
<string name="screen_home_tab_chats">"Conversații"</string>
<string name="screen_home_tab_spaces">"Spații"</string>
<string name="screen_invites_decline_chat_message">"Sigur doriți să refuzați alăturarea la %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Refuzați invitația"</string>
@@ -46,7 +46,7 @@ Deocamdată, puteți deselecta filtrele pentru a vedea celelalte chat-uri"</stri
<string name="screen_roomlist_filter_unreads_empty_state_title">"Felicitari!
Nu aveți mesaje necitite!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Cererea de alăturare a fost trimisă"</string>
<string name="screen_roomlist_main_space_title">"Toate conversatiile"</string>
<string name="screen_roomlist_main_space_title">"Conversații"</string>
<string name="screen_roomlist_mark_as_read">"Marcați ca citită"</string>
<string name="screen_roomlist_mark_as_unread">"Marcați ca necitită"</string>
<string name="screen_roomlist_tombstoned_room_description">"Această cameră a fost modernizată."</string>
@@ -36,6 +36,8 @@
<string name="screen_roomlist_filter_invites_empty_state_title">"У вас немає запрошень, що очікують на розгляд."</string>
<string name="screen_roomlist_filter_low_priority">"Низький пріоритет"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"У вас ще немає неважливих бесід"</string>
<string name="screen_roomlist_filter_mention">"Згадка"</string>
<string name="screen_roomlist_filter_mention_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>
@@ -36,6 +36,8 @@
<string name="screen_roomlist_filter_invites_empty_state_title">"没有待处理的邀请。"</string>
<string name="screen_roomlist_filter_low_priority">"低优先级"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"你暂无任何低优先级聊天"</string>
<string name="screen_roomlist_filter_mention">"提及"</string>
<string name="screen_roomlist_filter_mention_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>
@@ -36,13 +36,15 @@ For now, you can deselect filters in order to see your other chats"</string>
<string name="screen_roomlist_filter_invites_empty_state_title">"You don\'t have any pending invites."</string>
<string name="screen_roomlist_filter_low_priority">"Low Priority"</string>
<string name="screen_roomlist_filter_low_priority_empty_state_title">"You don’t have any low priority chats yet"</string>
<string name="screen_roomlist_filter_mention">"Mention"</string>
<string name="screen_roomlist_filter_mention_empty_state_title">"You don’t have any unread mentions"</string>
<string name="screen_roomlist_filter_mixed_empty_state_subtitle">"You can deselect filters in order to see your other chats"</string>
<string name="screen_roomlist_filter_mixed_empty_state_title">"You don’t have chats for this selection"</string>
<string name="screen_roomlist_filter_people">"People"</string>
<string name="screen_roomlist_filter_people_empty_state_title">"You don’t have any DMs yet"</string>
<string name="screen_roomlist_filter_rooms">"Rooms"</string>
<string name="screen_roomlist_filter_rooms_empty_state_title">"You’re not in any room yet"</string>
<string name="screen_roomlist_filter_unreads">"Unreads"</string>
<string name="screen_roomlist_filter_unreads">"Unread"</string>
<string name="screen_roomlist_filter_unreads_empty_state_title">"Congrats!
You don’t have any unread messages!"</string>
<string name="screen_roomlist_knock_event_sent_description">"Request to join sent"</string>
@@ -8,10 +8,36 @@
package io.element.android.features.home.impl.datasource
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.matrix.test.room.aRoomSummary
import org.junit.Test
class RoomListRoomSummaryFactoryTest {
@Test
fun `a room with two members does not prefix its preview with the sender`() {
val formatter = FakeRoomLatestEventFormatter()
val factory = aRoomListRoomSummaryFactory(roomLatestEventFormatter = formatter)
factory.create(aRoomSummary(info = aRoomInfo(isDm = false, activeMembersCount = 2)))
assertThat(formatter.lastIsDmRoom).isTrue()
}
@Test
fun `a room with more than two members prefixes its preview with the sender`() {
val formatter = FakeRoomLatestEventFormatter()
val factory = aRoomListRoomSummaryFactory(roomLatestEventFormatter = formatter)
factory.create(aRoomSummary(info = aRoomInfo(isDm = false, activeMembersCount = 3)))
assertThat(formatter.lastIsDmRoom).isFalse()
}
}
fun aRoomListRoomSummaryFactory(
dateFormatter: DateFormatter = FakeDateFormatter { _, _, _ -> "Today" },
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_decline_and_block_block_user_option_description">"Du wirst keine Nachrichten oder Chat-Einladungen von diesem Nutzer sehen."</string>
<string name="screen_decline_and_block_block_user_option_description">"Du wirst keine Nachrichten oder Chat-Einladungen von diesem Nutzer sehen"</string>
<string name="screen_decline_and_block_block_user_option_title">"Nutzer blockieren"</string>
<string name="screen_decline_and_block_report_user_option_description">"Melde diesen Chat deinem Konto-Anbieter."</string>
<string name="screen_decline_and_block_report_user_option_description">"Melde diesen Chat deinem Kontoanbieter."</string>
<string name="screen_decline_and_block_report_user_reason_placeholder">"Beschreibe den Grund für die Meldung…"</string>
<string name="screen_decline_and_block_title">"Ablehnen und blockieren"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zum Betreten von %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_message">"Möchtest du die Einladung zu %1$s wirklich ablehnen?"</string>
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
<string name="screen_invites_decline_direct_chat_message">"Bist du sicher, dass du diese Direktnachricht von %1$s ablehnen möchtest?"</string>
<string name="screen_invites_decline_direct_chat_title">"Einladung ablehnen"</string>
@@ -2,4 +2,8 @@
<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>
<string name="screen_invite_users_confirm_dialog_subtitle_multiple_users">"Προς το παρόν δεν έχετε καμία συνομιλία με αυτές τις επαφές. Επιβεβαιώστε την πρόσκλησή τους σε αυτήν την αίθουσα πριν συνεχίσετε."</string>
<string name="screen_invite_users_confirm_dialog_subtitle_one_user">"Προς το παρόν δεν έχετε καμία συνομιλία με αυτήν την επαφή. Επιβεβαιώστε την πρόσκληση σε αυτήν την αίθουσα συνομιλίας πριν συνεχίσετε."</string>
<string name="screen_invite_users_confirm_dialog_title_mutiple_users">"Πρόσκληση νέων επαφών σε αυτη την αίθουσα;"</string>
<string name="screen_invite_users_confirm_dialog_title_one_user">"Θέλετε να προσκαλέσετε νέα επαφή σε αυτήν την αίθουσα;"</string>
</resources>
@@ -22,12 +22,12 @@
<string name="screen_join_room_knock_message_characters_count">"%1$d von %2$d erlaubte Zeichen"</string>
<string name="screen_join_room_knock_message_description">"Nachricht (optional)"</string>
<string name="screen_join_room_knock_sent_description">"Sollte deine Anfrage akzeptiert werden, erhältst du eine Einladung, dem Chat beizutreten."</string>
<string name="screen_join_room_knock_sent_title">"Beitrittsanfrage geschickt"</string>
<string name="screen_join_room_knock_sent_title">"Beitrittsanfrage gestellt"</string>
<string name="screen_join_room_loading_alert_message">"Wir konnten die Chat Vorschau nicht anzeigen. Dies kann an Netzwerk- oder Serverproblemen liegen."</string>
<string name="screen_join_room_loading_alert_title">"Wir konnten diese Chat-Vorschau nicht anzeigen"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s unterstützt noch keine Spaces. Du kannst auf Spaces im Web zugreifen."</string>
<string name="screen_join_room_space_not_supported_title">"Spaces werden noch nicht unterstützt"</string>
<string name="screen_join_room_subtitle_knock">"Klopfe an um einen Admin zu benachrichtigen. Nach der Freigabe kannst du dich an der Unterhaltung beteiligen."</string>
<string name="screen_join_room_subtitle_knock">"Wähle die Schaltfläche unten – eine Chat-Administration wird benachrichtigt. Nach der Freigabe kannst du dich an der Unterhaltung beteiligen."</string>
<string name="screen_join_room_subtitle_no_preview">"Du musst Mitglied in diesem Chat sein, um den Nachrichtenverlauf zu sehen."</string>
<string name="screen_join_room_title_knock">"Willst du diesem Chat beitreten?"</string>
<string name="screen_join_room_title_no_preview">"Vorschau nicht verfügbar"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"Nie udało nam się wyświetlić podglądu tego pokoju"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s jeszcze nie obsługuje przestrzeni. Uzyskaj dostęp do przestrzeni w wersji web."</string>
<string name="screen_join_room_space_not_supported_title">"Przestrzenie nie są jeszcze obsługiwane"</string>
<string name="screen_join_room_subtitle_knock">"Kliknij przycisk poniżej, aby powiadomić administratora pokoju. Po zatwierdzeniu będziesz mógł dołączyć do rozmowy."</string>
<string name="screen_join_room_subtitle_knock">"Dotknij przycisku poniżej, aby powiadomić administratora czatu. Po zatwierdzeniu będziesz mógł dołączyć do rozmowy."</string>
<string name="screen_join_room_subtitle_no_preview">"Musisz być członkiem tego pokoju, aby wyświetlić historię wiadomości."</string>
<string name="screen_join_room_title_knock">"Chcesz dołączyć do tego pokoju?"</string>
<string name="screen_join_room_title_no_preview">"Podgląd nie jest dostępny"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"Ми не можемо показати попередній перегляд цієї кімнати"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s ще не підтримує простори. Ви можете отримати доступ до них у вебверсії."</string>
<string name="screen_join_room_space_not_supported_title">"Простори поки що не підтримуються"</string>
<string name="screen_join_room_subtitle_knock">"Натисніть кнопку нижче, і адміністратор кімнати отримає сповіщення. Ви зможете приєднатися до розмови після схвалення."</string>
<string name="screen_join_room_subtitle_knock">"Торкніться кнопки нижче і адміністратор бесіди отримає сповіщення. Ви зможете приєднатися до розмови після схвалення."</string>
<string name="screen_join_room_subtitle_no_preview">"Ви мусите бути учасником цієї кімнати, щоб переглядати історію повідомлень."</string>
<string name="screen_join_room_title_knock">"Хочете приєднатися до цієї кімнати?"</string>
<string name="screen_join_room_title_no_preview">"Попередній перегляд недоступний"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"无法显示此房间预览"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s 暂不支持空间。你可以通过 Web 客户端访问空间。"</string>
<string name="screen_join_room_space_not_supported_title">"空间尚未受到支持"</string>
<string name="screen_join_room_subtitle_knock">"点击以下按钮以通知房间管理员。获得批准后你将能加入对话。"</string>
<string name="screen_join_room_subtitle_knock">"点击以下按钮聊天管理员将收到通知。获得批准后即可加入对话。"</string>
<string name="screen_join_room_subtitle_no_preview">"只有房间成员才能查看消息历史。"</string>
<string name="screen_join_room_title_knock">"想加入此房间吗?"</string>
<string name="screen_join_room_title_no_preview">"预览不可用"</string>
@@ -27,7 +27,7 @@
<string name="screen_join_room_loading_alert_title">"We couldn’t display this room preview"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s does not support spaces yet. You can access spaces on web."</string>
<string name="screen_join_room_space_not_supported_title">"Spaces are not supported yet"</string>
<string name="screen_join_room_subtitle_knock">"Click the button below and a room administrator will be notified. You’ll be able to join the conversation once approved."</string>
<string name="screen_join_room_subtitle_knock">"Tap the button below and a chat administrator will be notified. You\'ll be able to join the conversation once approved."</string>
<string name="screen_join_room_subtitle_no_preview">"You must be a member of this room to view the message history."</string>
<string name="screen_join_room_title_knock">"Want to join this room?"</string>
<string name="screen_join_room_title_no_preview">"Preview is not available"</string>
@@ -9,7 +9,7 @@
<string name="screen_knock_requests_list_accept_all_loading_title">"Alle Beitrittsanfragen werden angenommen"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Wir konnten diese Beitrittsanfrage nicht annehmen. Möchtest du es noch mal versuchen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Die Beitrittsanfrage konnte nicht akzeptiert werden"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Beitrittsanfrage annehmen"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Beitrittsanfrage wird angenommen"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, ablehnen und sperren"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Bist du sicher, dass du %1$s ablehnen und sperren möchtest? Dieser Nutzer kann dann keinen erneuten Beitritt zu diesem Chat anfragen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Ablehnen und Zugriff sperren"</string>
@@ -29,7 +29,7 @@
<item quantity="one">"%1$s +%2$d weiterer möchten diesem Chat beitreten"</item>
<item quantity="other">"%1$s +%2$d weitere möchten diesem Chat beitreten"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Alles ansehen"</string>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Alle anzeigen"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Akzeptieren"</string>
<string name="screen_room_single_knock_request_title">"%1$s möchte diesem Chat beitreten"</string>
<string name="screen_room_single_knock_request_view_button_title">"Ansicht"</string>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Bist du sicher, dass du diese Unterhaltung verlassen willst? Diese Unterhaltung ist nicht öffentlich und du kannst ihr ohne Einladung nicht wieder beitreten."</string>
<string name="leave_room_alert_empty_subtitle">"Bist du sicher, dass du diesen Chat verlassen möchtest? Du bist die einzige Person hier. Wenn du gehst, kann in Zukunft niemand mehr eintreten, auch du nicht."</string>
<string name="leave_room_alert_empty_subtitle">"Bist du sicher, dass du diesen Chat verlassen möchtest? Du bist die einzige Person hier. Wenn du gehst, kann in Zukunft niemand mehr beitreten, auch du nicht."</string>
<string name="leave_room_alert_private_subtitle">"Bist du sicher, dass du diesen Chat verlassen möchtest? Dieser Chat ist nicht öffentlich und du kannst ihm ohne Einladung nicht erneut beitreten."</string>
<string name="leave_room_alert_select_new_owner_action">"Wähle Eigentümer"</string>
<string name="leave_room_alert_select_new_owner_subtitle">"Du bist der einzige Eigentümer dieses Chats. Du musst die Eigentumsrechte an jemand anderen übertragen, bevor du den Chat verlässt."</string>
@@ -5,6 +5,7 @@
<string name="screen_link_new_device_desktop_step3">"Scanne den QR-Code mit diesem Gerät"</string>
<string name="screen_link_new_device_desktop_submit">"Bereit zum Scannen"</string>
<string name="screen_link_new_device_desktop_title">"Öffne %1$s auf einem Desktop-Computer, um den QR-Code zu erhalten"</string>
<string name="screen_link_new_device_desktop_warning">"Scanne einen QR-Code nur, nachdem du die obigen Schritte auf deinem eigenen Gerät ausgeführt hast. Scanne niemals einen Code, den dir eine andere Person geschickt hat."</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"Die Zahlen stimmen nicht überein"</string>
<string name="screen_link_new_device_enter_number_notice">"Gib den 2-stelligen Code ein"</string>
<string name="screen_link_new_device_enter_number_subtitle">"Dadurch wird überprüft, ob die Verbindung zu deinem anderen Gerät sicher ist."</string>
@@ -19,19 +20,19 @@
<string name="screen_link_new_device_error_request_timeout_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
<string name="screen_link_new_device_mobile_step1">"Öffne %1$s auf dem anderen Gerät"</string>
<string name="screen_link_new_device_mobile_step2">"Wähle %1$s"</string>
<string name="screen_link_new_device_mobile_step2_action">"„Mit QR-Code anmelden”"</string>
<string name="screen_link_new_device_mobile_step3">"Scanne den hier gezeigten QR-Code mit dem anderen Gerät."</string>
<string name="screen_link_new_device_mobile_step2_action">"„Mit QR-Code anmelden“"</string>
<string name="screen_link_new_device_mobile_step3">"Scanne den hier gezeigten QR-Code mit dem anderen Gerät"</string>
<string name="screen_link_new_device_mobile_title">"Öffne %1$s auf dem anderen Gerät"</string>
<string name="screen_link_new_device_root_desktop_computer">"Desktop-Computer"</string>
<string name="screen_link_new_device_root_loading_qr_code">"QR-Code wird geladen…"</string>
<string name="screen_link_new_device_root_loading_qr_code">"QR-Code wird geladen …"</string>
<string name="screen_link_new_device_root_mobile_device">"Mobilgerät"</string>
<string name="screen_link_new_device_root_title">"Welchen Gerätetyp möchtest du verknüpfen?"</string>
<string name="screen_link_new_device_wrong_number_subtitle">"Versuch\' es bitte noch mal und stell sicher, dass du den zweistelligen Code richtig eingegeben hast. Wenn die Zahlen immer noch nicht übereinstimmen, wende dich an deinen Kontoanbieter."</string>
<string name="screen_link_new_device_wrong_number_subtitle">"Bitte versuche es erneut und stelle sicher, dass du den zweistelligen Code richtig eingegeben hast. Wenn die Zahlen weiterhin nicht übereinstimmen, wende dich an deinen Kontoanbieter."</string>
<string name="screen_link_new_device_wrong_number_title">"Die Zahlen stimmen nicht überein"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Und jetzt?"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Wenn das nicht funktioniert, melde dich manuell an"</string>
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
@@ -41,7 +42,7 @@
<string name="screen_qr_code_login_error_declined_subtitle">"Die Anmeldung auf dem anderen Gerät wurde abgelehnt."</string>
<string name="screen_qr_code_login_error_declined_title">"Anmelden abgelehnt"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Du musst nichts weiter tun."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Die Anmeldung ist abgelaufen. Bitte versuche es erneut."</string>
<string name="screen_qr_code_login_error_expired_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Dein anderes Gerät unterstützt die Anmeldung bei %s mit einem QR-Code nicht.
@@ -5,6 +5,7 @@
<string name="screen_link_new_device_desktop_step3">"Scansiona il codice QR con questo dispositivo"</string>
<string name="screen_link_new_device_desktop_submit">"Pronto per la scansione"</string>
<string name="screen_link_new_device_desktop_title">"Apri %1$s su un computer desktop per ottenere il codice QR"</string>
<string name="screen_link_new_device_desktop_warning">"Scansiona un codice QR solo dopo aver seguito i passaggi sopra descritti sul tuo dispositivo. Non scansionare mai un codice inviato da un\'altra persona."</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"I numeri non corrispondono"</string>
<string name="screen_link_new_device_enter_number_notice">"Inserisci il codice a 2 cifre"</string>
<string name="screen_link_new_device_enter_number_subtitle">"Questo verificherà che la connessione con l\'altro dispositivo sia sicura."</string>
@@ -5,6 +5,7 @@
<string name="screen_link_new_device_desktop_step3">"この端末でQRコードを読み取る"</string>
<string name="screen_link_new_device_desktop_submit">"読み取る"</string>
<string name="screen_link_new_device_desktop_title">"%1$s をコンピュータで開き、QRコードを表示してください"</string>
<string name="screen_link_new_device_desktop_warning">"あなた自身の端末においてのみ、以上の手順にならってQRコードを読み取ってください。他者から送られたコードは絶対に読み取らないでください。"</string>
<string name="screen_link_new_device_enter_number_error_numbers_do_not_match">"数字が一致しません"</string>
<string name="screen_link_new_device_enter_number_notice">"2桁の数字を入力してください"</string>
<string name="screen_link_new_device_enter_number_subtitle">"他の端末との接続が安全であることを確認します。"</string>
@@ -2,4 +2,5 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_share_location_live_location_disclaimer_title">"Der Verlauf deines Live-Standorts wird im Chat gespeichert und bleibt für die Mitglieder sichtbar, auch nachdem du die Freigabe beendet hast."</string>
<string name="screen_share_location_live_location_duration_picker_title">"Wie lange soll der Live-Standort geteilt werden?"</string>
<string name="screen_share_location_live_location_missing_permissions">"Du hast keine Berechtigung, deinen Live-Standort in diesem Chat zu teilen"</string>
</resources>
@@ -7,20 +7,20 @@
<string name="screen_app_lock_forgot_pin">"PIN vergessen?"</string>
<string name="screen_app_lock_settings_change_pin">"PIN-Code ändern"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Biometrisches Entsperren zulassen"</string>
<string name="screen_app_lock_settings_remove_pin">"Pin entfernen"</string>
<string name="screen_app_lock_settings_remove_pin">"PIN entfernen"</string>
<string name="screen_app_lock_settings_remove_pin_alert_message">"Bist du sicher, dass du die PIN entfernen willst?"</string>
<string name="screen_app_lock_settings_remove_pin_alert_title">"PIN entfernen?"</string>
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"%1$s zulassen"</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Ich möchte diese PIN verwenden."</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Ich würde lieber eine PIN verwenden"</string>
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Spare dir etwas Zeit und benutze %1$s, um die App zu entsperren"</string>
<string name="screen_app_lock_setup_choose_pin">"PIN wählen"</string>
<string name="screen_app_lock_setup_confirm_pin">"PIN bestätigen"</string>
<string name="screen_app_lock_setup_pin_context">"Sperre %1$s um deine Chats zusätzlich abzusichern.
Wähle eine einprägsame PIN. Wenn du sie vergisst, wirst du aus der App abgemeldet."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_content">"Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden."</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Bitte eine andere PIN verwenden."</string>
<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_forbidden_dialog_content">"Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden"</string>
<string name="screen_app_lock_setup_pin_forbidden_dialog_title">"Bitte eine andere PIN verwenden"</string>
<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">"Dieses Gerät wurde entfernt"</string>
@@ -115,6 +115,9 @@ fun ConfirmAccountProviderView(
HeaderFooterPage(
modifier = modifier,
// Let the header and content scroll so the supporting text is not clipped by the footer when the
// keyboard is visible and vertical space is reduced.
isScrollable = true,
header = {
IconTitleSubtitleMolecule(
modifier = Modifier.padding(top = 60.dp),
@@ -5,9 +5,9 @@
<string name="screen_account_provider_form_notice">"Gib einen Suchbegriff oder eine Domainadresse ein."</string>
<string name="screen_account_provider_form_subtitle">"Suche nach einem Unternehmen, einer Community oder einem privaten Server."</string>
<string name="screen_account_provider_form_title">"Kontoanbieter finden"</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Gespräche gespeichert – so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signin_title">"Du bist dabei, dich bei %s anzumelden"</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Gespräche gespeichert – so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_account_provider_signup_title">"Du bist dabei, ein Konto bei %s zu erstellen"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org ist ein großer, kostenloser Server im öffentlichen Matrix-Netzwerk für eine sichere, dezentralisierte Kommunikation, der von der Matrix.org Foundation betrieben wird."</string>
<string name="screen_change_account_provider_other">"Sonstige"</string>
@@ -16,23 +16,31 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Auf %1$s ist die Element Pro App erforderlich. Bitte lade diese aus dem Store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro erforderlich"</string>
<string name="screen_change_server_error_invalid_homeserver">"Wir konnten diesen Homeserver nicht erreichen. Bitte überprüfe, ob du die Homeserver-URL korrekt eingegeben hast. Wenn die URL korrekt ist, wende dich an deinen Homeserver-Administrator, um weitere Hilfe zu erhalten."</string>
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems in der \".well-known\" Datei nicht verfügbar:
<string name="screen_change_server_error_invalid_homeserver">"Wir konnten diesen Kontoanbieter nicht erreichen. Bitte überprüfe, ob du die Homeserver-URL korrekt eingegeben hast. Wenn die URL korrekt ist, wende dich an deinen Kontoanbieter, um weitere Hilfe zu erhalten."</string>
<string name="screen_change_server_error_invalid_well_known">"Der Server ist aufgrund eines Problems in der Datei .well-known nicht verfügbar:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding-Sync nicht. Für die Verwendung von %1$s ist eine Aktualisierung des Servers erforderlich."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Der gewählte Kontoanbieter unterstützt Sliding-Sync nicht. Für die Verwendung von %1$s ist eine Aktualisierung erforderlich."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s darf keine Verbindung zu %2$s herstellen."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Die App wurde so konfiguriert, dass sie %1$s zulässt."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Kontoanbieter %1$s ist nicht zulässig."</string>
<string name="screen_change_server_form_header">"Homeserver-URL"</string>
<string name="screen_change_server_form_notice">"Gib eine Domain-Adresse ein."</string>
<string name="screen_change_server_navigation_title_login">"Anmelden"</string>
<string name="screen_change_server_navigation_title_register">"Konto erstellen"</string>
<string name="screen_change_server_subtitle">"Wie lautet die Adresse deines Servers?"</string>
<string name="screen_change_server_textfield_footer_login">"Wenn dein Konto von deinem Unternehmen oder einer Community bereitgestellt wird, ändere den Kontoanbieter (Beispiel: firmenname.com)."</string>
<string name="screen_change_server_textfield_footer_register">"Erstelle kostenlos ein Konto auf matrix.org. Wenn du ein Konto bei einem anderen Anbieter erstellen möchtest, passe die Eingabe entsprechend an (Beispiel: communityname.org)."</string>
<string name="screen_change_server_textfield_header">"Kontoanbieter"</string>
<string name="screen_change_server_textfield_placeholder">"example.com"</string>
<string name="screen_change_server_title">"Wähle deinen Server aus"</string>
<string name="screen_change_server_title_login">"Gib deinen Kontoanbieter ein"</string>
<string name="screen_change_server_title_register">"Wähle einen Kontoanbieter"</string>
<string name="screen_create_account_title">"Konto erstellen"</string>
<string name="screen_login_error_deactivated_account">"Dieses Konto wurde gelöscht."</string>
<string name="screen_login_error_invalid_credentials">"Falscher Nutzername und/oder Passwort"</string>
<string name="screen_login_error_invalid_user_id">"Dies ist keine gültige Nutzerkennung. Erwartetes Format: \'@nutzer:homeserver.org\'"</string>
<string name="screen_login_error_invalid_user_id">"Dies ist keine gültige Nutzerkennung. Erwartetes Format: ‚@nutzer:homeserver.org‘"</string>
<string name="screen_login_error_refresh_tokens">"Dieser Server ist so konfiguriert, dass er Refresh-Tokens verwendet. Diese werden für die passwortbasierte Anmeldung nicht unterstützt."</string>
<string name="screen_login_error_unsupported_authentication">"Der ausgewählte Homeserver unterstützt weder den Login per Passwort noch per OAuth. Bitte kontaktiere deinen Administrator oder wähle einen anderen Homeserver."</string>
<string name="screen_login_error_unsupported_authentication">"Der ausgewählte Kontoanbieter unterstützt weder die Anmeldung per Passwort noch per OAuth. Wende dich an deinen Kontoanbieter oder wähle einen anderen."</string>
<string name="screen_login_form_header">"Gib deine Daten ein"</string>
<string name="screen_login_subtitle">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
<string name="screen_login_title">"Willkommen zurück!"</string>
@@ -40,9 +48,10 @@
<string name="screen_missing_key_backup_open_element_classic">"Element Classic öffnen"</string>
<string name="screen_missing_key_backup_step_1">"Öffne Element Classic auf deinem Gerät"</string>
<string name="screen_missing_key_backup_step_2_android">"Geh zu Einstellungen &gt; Sicherheit &amp; Datenschutz"</string>
<string name="screen_missing_key_backup_step_4">"Folge den Anweisungen, um den Schlüsselspeicher zu aktivieren."</string>
<string name="screen_missing_key_backup_step_3_android">"Wähle unter „Verwaltung der Verschlüsselungs-Schlüssel“ den Eintrag „Wiederherstellung verschlüsselter Nachrichten“"</string>
<string name="screen_missing_key_backup_step_4">"Folge den Anweisungen, um deinen Schlüsselspeicher zu aktivieren"</string>
<string name="screen_missing_key_backup_step_5">"Zurück zu %1$s"</string>
<string name="screen_missing_key_backup_title">"Aktiviere deinen Schlüsselspeicher, bevor du mit %1$s fortfährst."</string>
<string name="screen_missing_key_backup_title">"Aktiviere deinen Schlüsselspeicher, bevor du mit %1$s fortfährst"</string>
<string name="screen_onboarding_app_version">"Version %1$s"</string>
<string name="screen_onboarding_checking_account">"Konto wird geprüft"</string>
<string name="screen_onboarding_sign_in_manually">"Manuell anmelden"</string>
@@ -52,12 +61,12 @@
<string name="screen_onboarding_welcome_back">"Willkommen zurück"</string>
<string name="screen_onboarding_welcome_message">"Willkommen beim schnellsten %1$s aller Zeiten. Optimiert für Geschwindigkeit und Einfachheit."</string>
<string name="screen_onboarding_welcome_subtitle">"Willkommen zu %1$s. Aufgeladen, für Geschwindigkeit und Einfachheit."</string>
<string name="screen_onboarding_welcome_title">"Sei in Deinem Element"</string>
<string name="screen_qr_code_login_connecting_subtitle">"Sichere Verbindung aufbauen"</string>
<string name="screen_onboarding_welcome_title">"Sei in deinem Element"</string>
<string name="screen_qr_code_login_connecting_subtitle">"Sichere Verbindung wird aufgebaut"</string>
<string name="screen_qr_code_login_connection_note_secure_state_description">"Es konnte keine sichere Verbindung zu dem neuen Gerät hergestellt werden."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_header">"Und jetzt?"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN."</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_1">"Versuche, dich erneut mit einem QR-Code anzumelden, falls dies ein Netzwerkproblem war"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_2">"Wenn das Problem bestehen bleibt, versuche es mit einem anderen WLAN-Netzwerk oder verwende deine mobilen Daten statt WLAN"</string>
<string name="screen_qr_code_login_connection_note_secure_state_list_item_3">"Wenn das nicht funktioniert, melde dich manuell an"</string>
<string name="screen_qr_code_login_connection_note_secure_state_title">"Die Verbindung ist nicht sicher"</string>
<string name="screen_qr_code_login_device_code_subtitle">"Du wirst aufgefordert, die beiden unten abgebildeten Ziffern einzugeben."</string>
@@ -69,7 +78,7 @@
<string name="screen_qr_code_login_error_declined_subtitle">"Die Anmeldung auf dem anderen Gerät wurde abgelehnt."</string>
<string name="screen_qr_code_login_error_declined_title">"Anmelden abgelehnt"</string>
<string name="screen_qr_code_login_error_device_already_signed_in_subtitle">"Du musst nichts weiter tun."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet."</string>
<string name="screen_qr_code_login_error_device_already_signed_in_title">"Dein anderes Gerät ist schon angemeldet"</string>
<string name="screen_qr_code_login_error_expired_subtitle">"Die Anmeldung ist abgelaufen. Bitte versuche es erneut."</string>
<string name="screen_qr_code_login_error_expired_title">"Die Anmeldung wurde nicht rechtzeitig abgeschlossen"</string>
<string name="screen_qr_code_login_error_linking_not_suported_subtitle">"Dein anderes Gerät unterstützt die Anmeldung bei %s mit einem QR-Code nicht.
@@ -80,9 +89,9 @@ Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Ger
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s wird nicht unterstützt"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Bereit zum Scannen"</string>
<string name="screen_qr_code_login_initial_state_item_1">"%1$s auf einem Desktop-Gerät öffnen"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Wähle dein Profilbild"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Wähle %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Neues Gerät verknüpfen\""</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"„Neues Gerät verknüpfen“"</string>
<string name="screen_qr_code_login_initial_state_item_4">"Scanne den QR-Code mit diesem Gerät"</string>
<string name="screen_qr_code_login_initial_state_subtitle">"Nur verfügbar falls dein Kontoanbieter dies unterstützt."</string>
<string name="screen_qr_code_login_initial_state_title">"Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten"</string>
@@ -96,12 +105,12 @@ Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Ger
<string name="screen_qr_code_login_start_over_button">"Neu beginnen"</string>
<string name="screen_qr_code_login_unknown_error_description">"Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es erneut."</string>
<string name="screen_qr_code_login_verify_code_loading">"Warten auf dein anderes Gerät"</string>
<string name="screen_qr_code_login_verify_code_subtitle">"Dein Konto-Provider kann nach dem folgenden Code fragen, um die Anmeldung zu bestätigen."</string>
<string name="screen_qr_code_login_verify_code_subtitle">"Dein Kontoanbieter fragt möglicherweise nach dem folgenden Code, um die Anmeldung zu bestätigen."</string>
<string name="screen_qr_code_login_verify_code_title">"Dein Verifizierungscode"</string>
<string name="screen_server_confirmation_change_server">"Kontoanbieter wechseln"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Ein privater Server für die Mitarbeiter von Element."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
<string name="screen_server_confirmation_message_register">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_server_confirmation_message_register">"Hier werden deine Gespräche gespeichert – so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würdest."</string>
<string name="screen_server_confirmation_title_login">"Du bist dabei, dich bei %1$s anzumelden"</string>
<string name="screen_server_confirmation_title_picker_mode">"Kontoanbieter auswählen"</string>
<string name="screen_server_confirmation_title_register">"Du bist dabei, auf %1$s ein Konto zu erstellen"</string>
@@ -16,7 +16,7 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Η εφαρμογή Element Pro απαιτείται στο %1$s. Παρακαλώ κατεβάστε την από το κατάστημα."</string>
<string name="screen_change_server_error_element_pro_required_title">"Απαιτείται το Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"Δεν μπορούσαμε να επικοινωνήσουμε με αυτόν τον οικιακό διακομιστή. Βεβαιώσου ότι έχεις εισαγάγει σωστά τη διεύθυνση URL του αρχικού διακομιστή. Εάν η διεύθυνση URL είναι σωστή, επικοινώνησε με τον διαχειριστή του κεντρικού διακομιστή για περαιτέρω βοήθεια."</string>
<string name="screen_change_server_error_invalid_homeserver">"Δεν ήταν δυνατή η επικοινωνία με αυτόν τον πάροχο λογαριασμού. Ελέγξτε ότι έχετε εισαγάγει σωστά τη διεύθυνση URL του παρόχου λογαριασμού. Εάν η διεύθυνση URL είναι σωστή, επικοινωνήστε με τον διαχειριστή του παρόχου λογαριασμού σας για περαιτέρω βοήθεια."</string>
<string name="screen_change_server_error_invalid_well_known">"Ο διακομιστής δεν είναι διαθέσιμος λόγω προβλήματος στο αρχείο .well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Ο επιλεγμένος πάροχος λογαριασμού δεν υποστηρίζει sliding sync. Απαιτείται αναβάθμιση στο διακομιστή για χρήση του %1$s."</string>
@@ -32,7 +32,7 @@
<string name="screen_login_error_invalid_credentials">"Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης"</string>
<string name="screen_login_error_invalid_user_id">"Αυτό δεν είναι έγκυρο αναγνωριστικό χρήστη. Αναμενόμενη μορφή: \'@χρήστης:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Αυτός ο διακομιστής έχει ρυθμιστεί ώστε να χρησιμοποιεί διακριτικά ανανέωσης. Αυτά δεν υποστηρίζονται όταν χρησιμοποιείς σύνδεση μέσω κωδικού πρόσβασης."</string>
<string name="screen_login_error_unsupported_authentication">"Ο επιλεγμένος οικιακός διακομιστής δεν υποστηρίζει κωδικό πρόσβασης ή σύνδεση OAuth. Επικοινωνήστε με τον διαχειριστή σου ή επέλεξε άλλο οικιακό διακομιστή."</string>
<string name="screen_login_error_unsupported_authentication">"Ο επιλεγμένος πάροχος λογαριασμού δεν υποστηρίζει σύνδεση με κωδικό πρόσβασης ή OAuth. Παρακαλούμε επικοινωνήστε με τον πάροχο του λογαριασμού σας ή επιλέξτε διαφορετικό."</string>
<string name="screen_login_form_header">"Εισήγαγε τα στοιχεία σου"</string>
<string name="screen_login_subtitle">"Το Matrix είναι ένα ανοιχτό δίκτυο για ασφαλή, αποκεντρωμένη επικοινωνία."</string>
<string name="screen_login_title">"Καλωσόρισες ξανά!"</string>
@@ -89,7 +89,7 @@ Proovi käsitsi sisselogimist või skaneeri QR-koodi mõne muu seadmega."</strin
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s pole toetatud"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Skaneerimiseks valmis"</string>
<string name="screen_qr_code_login_initial_state_item_1">"Ava %1$s töölauarakenduses"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klõpsi oma tunnuspilti"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klõpsa oma tunnuspilti"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Vali %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"„Seo uus seade“"</string>
<string name="screen_qr_code_login_initial_state_item_4">"Skaneeri QR-koodi selle seadmega"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"L’application Element Pro est requise sur %1$s. Veuillez la télécharger depuis le store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro est requis"</string>
<string name="screen_change_server_error_invalid_homeserver">"Nous n’avons pas pu atteindre ce serveur d’accueil. Vérifiez que vous avez correctement saisi l’URL du serveur d’accueil. Si l’URL est correcte, contactez l’administrateur de votre serveur d’accueil pour obtenir de l’aide."</string>
<string name="screen_change_server_error_invalid_homeserver">"Impossible de se connecter à ce fournisseur de compte. Vérifiez que l’URL saisie est correcte. Si c’est le cas, contactez l’administrateur de votre fournisseur de compte pour obtenir de l’aide."</string>
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte n’est pas disponible en raison d’un problème dans le fichier .well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Une mise à jour du serveur est nécessaire pour pouvoir utiliser %1$s."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Le fournisseur de compte sélectionné ne prend pas en charge le sliding sync. Le serveur doit être mis à jour pour pouvoir utiliser %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s n’est pas autorisé à se connecter à %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Cette application a été configurée pour autoriser: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Le fournisseur de compte %1$s n’est pas autorisé."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Nom d’utilisateur et/ou mot de passe incorrects"</string>
<string name="screen_login_error_invalid_user_id">"Il ne s’agit pas d’un identifiant utilisateur valide. Format attendu : « @user:homeserver.org »"</string>
<string name="screen_login_error_refresh_tokens">"Ce serveur est configuré pour utiliser des tokens d’actualisation. Ils ne sont pas pris en charge lors de l’utilisation d’une connexion basée sur un mot de passe."</string>
<string name="screen_login_error_unsupported_authentication">"Le serveur d’accueil sélectionné ne prend pas en charge le mot de passe ou la connexion OAuth. Contactez votre administrateur ou choisissez un autre serveur d’accueil."</string>
<string name="screen_login_error_unsupported_authentication">"Le fournisseur de compte sélectionné ne prend pas en charge la connexion avec mot de passe ni OAuth. Contactez votre administrateur ou choisissez un autre fournisseur de compte."</string>
<string name="screen_login_form_header">"Saisissez vos identifiants"</string>
<string name="screen_login_subtitle">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
<string name="screen_login_title">"Content de vous revoir !"</string>
@@ -25,8 +25,16 @@
<string name="screen_change_server_error_unauthorized_homeserver_title">"Davatelj usluga računa %1$s nije dopušten."</string>
<string name="screen_change_server_form_header">"URL matičnog poslužitelja"</string>
<string name="screen_change_server_form_notice">"Unesite adresu domene."</string>
<string name="screen_change_server_navigation_title_login">"Prijava"</string>
<string name="screen_change_server_navigation_title_register">"Izradi račun"</string>
<string name="screen_change_server_subtitle">"Koja je adresa vašeg poslužitelja?"</string>
<string name="screen_change_server_textfield_footer_login">"Ako vam je račun omogućila vaša tvrtka ili zajednica, promijenite pružatelja računa (primjer: nazivtvrtke.com)."</string>
<string name="screen_change_server_textfield_footer_register">"Besplatno kreirajte račun na matrix.org. Ako želite kreirati račun kod drugog pružatelja usluga, promijenite ga u skladu s tim (primjer: communityname.org)."</string>
<string name="screen_change_server_textfield_header">"Pružatelj usluga računa"</string>
<string name="screen_change_server_textfield_placeholder">"primjer.com"</string>
<string name="screen_change_server_title">"Odaberite svoj poslužitelj"</string>
<string name="screen_change_server_title_login">"Unesite davatelja usluge računa"</string>
<string name="screen_change_server_title_register">"Odaberite pružatelja usluga računa"</string>
<string name="screen_create_account_title">"Izradi račun"</string>
<string name="screen_login_error_deactivated_account">"Ovaj je račun deaktiviran."</string>
<string name="screen_login_error_invalid_credentials">"Netočno korisničko ime i/ili zaporka"</string>
@@ -16,23 +16,31 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"L\'app Element Pro è necessaria su %1$s. Scaricala dallo store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro è richiesto"</string>
<string name="screen_change_server_error_invalid_homeserver">"Non siamo riusciti a raggiungere questo homeserver. Verifica di aver inserito correttamente l\'URL. Se l\'URL è corretto, contatta l\'amministratore del homeserver per ulteriore assistenza."</string>
<string name="screen_change_server_error_invalid_homeserver">"Non è stato possibile contattare il gestorehomeserver di questo account. Verifica di aver inserito correttamente l\'URL dell\'homeserver. Se l\'URL è corretto, contatta il gestore del tuo account per ulteriore assistenza."</string>
<string name="screen_change_server_error_invalid_well_known">"Il server non è disponibile per un problema nel file well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Il fornitore di account selezionato non supporta la Sliding sync. Per utilizzare %1$s è necessario un aggiornamento del server."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Il gestore dell\'account selezionato non supporta la sliding sync. È necessario un aggiornamento per utilizzare %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s non è autorizzato a connettersi a %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Questa app è stata configurata per consentire: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Fornitore dell\'account %1$s non consentito."</string>
<string name="screen_change_server_form_header">"URL dell\'homeserver"</string>
<string name="screen_change_server_form_notice">"Inserisci un indirizzo di dominio."</string>
<string name="screen_change_server_navigation_title_login">"Accedi"</string>
<string name="screen_change_server_navigation_title_register">"Crea account"</string>
<string name="screen_change_server_subtitle">"Qual è l\'indirizzo del tuo server?"</string>
<string name="screen_change_server_textfield_footer_login">"Se il tuo account è fornito dalla tua azienda o da una comunità, cambia il gestore dell\'account (esempio: companyname.com)."</string>
<string name="screen_change_server_textfield_footer_register">"Crea un account gratuito su matrix.org. Se desideri creare un account con un provider diverso, modificalo di conseguenza (esempio: communityname.org)."</string>
<string name="screen_change_server_textfield_header">"Gestore dell\'account"</string>
<string name="screen_change_server_textfield_placeholder">"esempio.com"</string>
<string name="screen_change_server_title">"Seleziona il tuo server"</string>
<string name="screen_change_server_title_login">"Inserisci il tuo gestore dell\'account"</string>
<string name="screen_change_server_title_register">"Scegli un gestore dell\'account"</string>
<string name="screen_create_account_title">"Crea account"</string>
<string name="screen_login_error_deactivated_account">"Questo account è stato eliminato."</string>
<string name="screen_login_error_invalid_credentials">"Nome utente e/o password errati"</string>
<string name="screen_login_error_invalid_user_id">"Questo non è un identità utente valida. il formato atteso é: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Questo server è configurato per usare i token di aggiornamento. Non sono supportati quando si usa l\'accesso basato su password."</string>
<string name="screen_login_error_unsupported_authentication">"L\'homeserver selezionato non supporta la password o l\'accesso OAuth. Contatta il tuo amministratore o scegli un altro homeserver."</string>
<string name="screen_login_error_unsupported_authentication">"Il gestore dell\'account selezionato non supporta l\'accesso tramite password o OAuth. Si prega di contattare il proprio gestore dell\'account o di sceglierne uno diverso."</string>
<string name="screen_login_form_header">"Inserisci i tuoi dati"</string>
<string name="screen_login_subtitle">"Matrix è una rete aperta per comunicazioni sicure e decentralizzate."</string>
<string name="screen_login_title">"Bentornato!"</string>
@@ -53,7 +53,7 @@
<string name="screen_onboarding_welcome_back">"다시 오신 것을 환영합니다"</string>
<string name="screen_onboarding_welcome_message">"%1$s 에 오신 것을 환영합니다. 속도와 단순성을 극대화한 가장 빠른 버전입니다."</string>
<string name="screen_onboarding_welcome_subtitle">"%1$s 에 오신 것을 환영합니다. 속도와 단순성을 위해 최적화된 앱입니다."</string>
<string name="screen_onboarding_welcome_title">"당신의 엘리먼트에 있어"</string>
<string name="screen_onboarding_welcome_title">"가장 나다운 공간, Element에서"</string>
<string name="screen_qr_code_login_connecting_subtitle">"안전한 연결 설정"</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>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Wymagana jest aplikacja Element Pro na %1$s. Znajdziesz ją w sklepie z aplikacjami."</string>
<string name="screen_change_server_error_element_pro_required_title">"Wymagany jest Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z dostawcą konta. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem dostawcy konta w celu uzyskania dalszej pomocy."</string>
<string name="screen_change_server_error_invalid_well_known">"Serwer nie jest dostępny z powodu problemu pliku .well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera synchronizacji przesuwnej. Wymagana jest aktualizacja serwera %1$s."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera funkcji sliding sync. Wymagana jest aktualizacja serwera, aby korzystać z %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nie posiada zezwolenia na dołączenie do %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Aplikacja została skonfigurowana tak, aby zezwalała na: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Dostawca konta %1$s jest niedozwolony."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Nieprawidłowa nazwa użytkownika i/lub hasło"</string>
<string name="screen_login_error_invalid_user_id">"To nie jest prawidłowy identyfikator użytkownika. Oczekiwany format: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Ten serwer został skonfigurowany do korzystania z tokenów odświeżania. Nie są one obsługiwane, gdy korzystasz z hasła."</string>
<string name="screen_login_error_unsupported_authentication">"Wybrany serwer domowy nie obsługuje uwierzytelniania hasłem, ani OAuth. Skontaktuj się z jego administratorem lub wybierz inny serwer domowy."</string>
<string name="screen_login_error_unsupported_authentication">"Wybrany dostawca konta nie obsługuje uwierzytelniania hasłem, ani OAuth. Spróbuj się z nim skontaktować lub wybierz innego."</string>
<string name="screen_login_form_header">"Wprowadź swoje dane"</string>
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
<string name="screen_login_title">"Witaj ponownie!"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"Необхідно встановити застосунок Element Pro на %1$s. Завантажте його з магазину."</string>
<string name="screen_change_server_error_element_pro_required_title">"Потрібен Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"Не вдалося під\'єднатися до цього домашнього сервера. Перевірте правильність введеної URL-адреси домашнього сервера. Якщо URL-адреса правильна, зверніться по додаткову допомогу до адміністратора домашнього сервера."</string>
<string name="screen_change_server_error_invalid_homeserver">"Нам не вдалося зв’язатися з цим постачальником облікового запису. Перевірте, чи правильно ви ввели URL-адресу постачальника облікового запису. Якщо URL-адреса правильна, зверніться до адміністратора постачальника вашого облікового запису по додаткову допомогу."</string>
<string name="screen_change_server_error_invalid_well_known">"Сервер недоступний через помилку у файлі well-known:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Вибраний постачальник облікових записів не підтримує sliding sync. Щоб користуватися %1$s необхідно оновити сервер."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Вибраний постачальник облікового запису не підтримує Sliding sync. Для використання функції %1$s потрібно оновити сервер."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s не дозволено під\'єднуватися до %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"Цей застосунок налаштовано так, щоб дозволити: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Постачальник облікового запису %1$s не дозволений."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Неправильне ім\'я користувача та/або пароль"</string>
<string name="screen_login_error_invalid_user_id">"Це недійсний ідентифікатор користувача. Очікуваний формат: \'@user:homeserver.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Цей сервер налаштований на використання оновлюваних токенів. Вони не підтримуються, якщо використовується вхід за допомогою основі пароля."</string>
<string name="screen_login_error_unsupported_authentication">"Обраний домашній сервер не підтримує вхід за допомогою пароля або OAuth. Зверніться до адміністратора або виберіть інший домашній сервер."</string>
<string name="screen_login_error_unsupported_authentication">"Вибраний постачальник облікового запису не підтримує пароль або вхід через OAuth. Зверніться до постачальника облікового запису або виберіть іншого."</string>
<string name="screen_login_form_header">"Введіть свої дані"</string>
<string name="screen_login_subtitle">"Matrix — це відкрита мережа для безпечної, децентралізованої комунікації."</string>
<string name="screen_login_title">"З поверненням!"</string>
@@ -89,7 +89,7 @@
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s не підтримується"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Готовий до сканування"</string>
<string name="screen_qr_code_login_initial_state_item_1">"Відкрийте %1$s на комп\'ютері"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Натисніть на свою аватарку"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Натисніть на свій аватар"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Виберіть %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"“Під\'єднати новий пристрій”"</string>
<string name="screen_qr_code_login_initial_state_item_4">"Зіскануйте QR-код цим пристроєм"</string>
@@ -25,8 +25,16 @@
<string name="screen_change_server_error_unauthorized_homeserver_title">"不允許帳號提供者 %1$s。"</string>
<string name="screen_change_server_form_header">"家伺服器 URL"</string>
<string name="screen_change_server_form_notice">"輸入網域位址。"</string>
<string name="screen_change_server_navigation_title_login">"登入"</string>
<string name="screen_change_server_navigation_title_register">"建立帳號"</string>
<string name="screen_change_server_subtitle">"您的伺服器地址?"</string>
<string name="screen_change_server_textfield_footer_login">"若您的帳號是由您的公司或社群提供的,請變更帳號提供者(範例:companyname.com)。"</string>
<string name="screen_change_server_textfield_footer_register">"在 matrix.org 上建立免費帳號。若您想要使用其他提供者建立帳號,請據此調整(範例:communityname.org)。"</string>
<string name="screen_change_server_textfield_header">"帳號提供者"</string>
<string name="screen_change_server_textfield_placeholder">"example.com"</string>
<string name="screen_change_server_title">"選擇您的伺服器"</string>
<string name="screen_change_server_title_login">"輸入您的帳號提供者"</string>
<string name="screen_change_server_title_register">"選擇帳號提供者"</string>
<string name="screen_create_account_title">"建立帳號"</string>
<string name="screen_login_error_deactivated_account">"此帳號已被刪除。"</string>
<string name="screen_login_error_invalid_credentials">"不正確的使用者名稱或密碼"</string>
@@ -16,7 +16,7 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"%1$s 要求 Element Pro。请从应用商店下载。"</string>
<string name="screen_change_server_error_element_pro_required_title">"需要 Element Pro"</string>
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此服务器。请检查输入的服务器 URL 是否正确。如果 URL 正确,请联系服务器管理员寻求进一步帮助。"</string>
<string name="screen_change_server_error_invalid_homeserver">"我们无法访问此账户提供者。请检查输入的账户提供者 URL 是否正确。如果 URL 正确,请联系账户提供者管理员寻求进一步帮助。"</string>
<string name="screen_change_server_error_invalid_well_known">"由于 .well-known 文件存在问题,服务器不可用:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"所选账户提供者不支持滑动同步。需要升级服务器才能使用 %1$s。"</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"用户名与(或)密码不正确"</string>
<string name="screen_login_error_invalid_user_id">"这不是合法的用户 ID。预期格式:“@user:homeserver.org”。"</string>
<string name="screen_login_error_refresh_tokens">"此服务器使用刷新令牌。使用密码登录时不支持这些功能。"</string>
<string name="screen_login_error_unsupported_authentication">"该服务器不支持密码登录与 OAuth 登录。请联系服务器管理员或选择另一服务器。"</string>
<string name="screen_login_error_unsupported_authentication">"所选账户提供者不支持密码或 OAuth 登录。请联系账户提供者或选择另一账户提供者。"</string>
<string name="screen_login_form_header">"输入详细信息"</string>
<string name="screen_login_subtitle">"Matrix 是一个用于安全、去中心化通信的开放网络。"</string>
<string name="screen_login_title">"欢迎回来!"</string>
@@ -16,10 +16,10 @@
<string name="screen_change_server_error_element_pro_required_action_android">"Google Play"</string>
<string name="screen_change_server_error_element_pro_required_message">"The Element Pro app is required on %1$s. Please download it from the store."</string>
<string name="screen_change_server_error_element_pro_required_title">"Element Pro required"</string>
<string name="screen_change_server_error_invalid_homeserver">"We couldn\'t reach this homeserver. Please check that you have entered the homeserver URL correctly. If the URL is correct, contact your homeserver administrator for further help."</string>
<string name="screen_change_server_error_invalid_homeserver">"We couldn\'t reach this account provider. Please check that you have entered the account provider URL correctly. If the URL is correct, contact your account provider administrator for further help."</string>
<string name="screen_change_server_error_invalid_well_known">"Server isn\'t available due to an issue in the .well-known file:
%1$s"</string>
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. An upgrade to the server is needed to use %1$s."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"The selected account provider does not support sliding sync. The server needs to be upgraded to use %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s is not allowed to connect to %2$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_content">"This app has been configured to allow: %1$s."</string>
<string name="screen_change_server_error_unauthorized_homeserver_title">"Account provider %1$s not allowed."</string>
@@ -40,7 +40,7 @@
<string name="screen_login_error_invalid_credentials">"Incorrect username and/or password"</string>
<string name="screen_login_error_invalid_user_id">"This is not a valid user identifier. Expected format: ‘@user:homeserver.org’"</string>
<string name="screen_login_error_refresh_tokens">"This server is configured to use refresh tokens. These aren\'t supported when using password based login."</string>
<string name="screen_login_error_unsupported_authentication">"The selected homeserver doesn\'t support password or OAuth login. Please contact your admin or choose another homeserver."</string>
<string name="screen_login_error_unsupported_authentication">"The selected account provider doesn\'t support password or OAuth login. Please contact your account provider or choose a different one."</string>
<string name="screen_login_form_header">"Enter your details"</string>
<string name="screen_login_subtitle">"Matrix is an open network for secure, decentralised communication."</string>
<string name="screen_login_title">"Welcome back!"</string>
@@ -51,6 +51,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.event.captionOrNull
import io.element.android.features.messages.impl.timeline.model.event.htmlCaptionOrNull
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
import io.element.android.features.messages.impl.voicemessages.composer.DefaultVoiceMessageComposerPresenter
import io.element.android.features.roomcall.api.RoomCallState
@@ -396,7 +397,7 @@ class MessagesPresenter(
TimelineItemAction.Edit,
TimelineItemAction.EditPoll -> handleActionEdit(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.AddCaption -> handleActionAddCaption(targetEvent, composerState)
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState)
TimelineItemAction.EditCaption -> handleActionEditCaption(targetEvent, composerState, enableTextFormatting)
TimelineItemAction.RemoveCaption -> handleRemoveCaption(targetEvent)
TimelineItemAction.Reply -> handleActionReply(targetEvent, composerState, timelineProtectionState)
TimelineItemAction.ReplyInThread -> {
@@ -575,10 +576,15 @@ class MessagesPresenter(
private suspend fun handleActionEditCaption(
targetEvent: TimelineItem.Event,
composerState: MessageComposerState,
enableTextFormatting: Boolean,
) {
val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId,
content = targetEvent.content.captionOrNull().orEmpty(),
content = if (enableTextFormatting) {
targetEvent.content.htmlCaptionOrNull() ?: targetEvent.content.captionOrNull()
} else {
targetEvent.content.captionOrNull()
}.orEmpty(),
)
composerState.eventSink(
MessageComposerEvent.SetMode(composerMode)
@@ -115,9 +115,10 @@ class SuggestionsProcessor(
.filterUpTo(MAX_BATCH_ITEMS) { member ->
isJoinedMemberAndNotSelf(member) && memberMatchesQuery(member, query)
}
.sortedBy { member -> matchRank(member, query) }
.map(ResolvedSuggestion::Member)
if ("room".contains(query) && canSendRoomMention) {
if ("room".startsWith(query, ignoreCase = true) && canSendRoomMention) {
listOf(ResolvedSuggestion.AtRoom) + matchingMembers
} else {
matchingMembers
@@ -125,6 +126,18 @@ class SuggestionsProcessor(
}
}
/**
* Members whose display name starts with the query come first, then members whose user id
* localpart starts with it, then the remaining matches on any other part of the name or id.
*/
private fun matchRank(member: RoomMember, query: String): Int {
return when {
member.displayName?.startsWith(query, ignoreCase = true) == true -> 0
member.userId.extractedDisplayName.startsWith(query, ignoreCase = true) -> 1
else -> 2
}
}
companion object {
// We don't want to retrieve thousands of members
private const val MAX_BATCH_ITEMS = 100
@@ -52,9 +52,9 @@ sealed interface TimelineEvent {
*/
sealed interface TimelineItemPollEvent : TimelineItemEvent
data class SelectPollAnswer(
data class SendPollResponse(
val pollStartId: EventId,
val answerId: String
val answerIds: List<String>
) : TimelineItemPollEvent
data class EndPoll(
@@ -201,13 +201,15 @@ class TimelinePresenter(
newEventState.value = NewEventState.None
}
}
is TimelineEvent.SelectPollAnswer -> sessionCoroutineScope.launch {
is TimelineEvent.SendPollResponse -> sessionCoroutineScope.launch {
timelineController.invokeOnCurrentTimeline {
sendPollResponseAction.execute(
timeline = this,
pollStartId = event.pollStartId,
answerId = event.answerId
)
answerIds = event.answerIds,
).onFailure { error ->
Timber.tag(tag).e(error, "Failed to send poll response")
}
}
}
is TimelineEvent.EndPoll -> sessionCoroutineScope.launch {
@@ -49,7 +49,9 @@ import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
@@ -217,13 +219,23 @@ fun TimelineItemEventRow(
inReplyToClick(inReplyToEventId)
}
Column(modifier = modifier.fillMaxWidth()) {
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
val accessibilityActions = rememberTimelineItemAccessibilityActions(
canReply = canReply,
onLongClick = onLongClick,
onSwipeToReply = onSwipeToReply,
)
Column(
modifier = modifier
.fillMaxWidth()
.semantics { customActions = accessibilityActions }
) {
if (event.groupPosition.isNew()) {
Spacer(modifier = Modifier.height(16.dp))
} else {
Spacer(modifier = Modifier.height(2.dp))
}
val canReply = timelineRoomInfo.userHasPermissionToSendMessage && event.canBeRepliedTo
if (canReply) {
val state: SwipeableActionsState = rememberSwipeableActionsState()
val offset = state.offset.floatValue
@@ -319,6 +331,40 @@ fun TimelineItemEventRow(
}
}
/**
* Exposes the gestures of a timeline item — long press for the action list, swipe for a reply — as TalkBack actions, since neither gesture is reachable
* with a screen reader enabled.
*/
@Composable
private fun rememberTimelineItemAccessibilityActions(
canReply: Boolean,
onLongClick: () -> Unit,
onSwipeToReply: () -> Unit,
): List<CustomAccessibilityAction> {
val messageActionsLabel = stringResource(CommonStrings.common_message_actions)
val replyLabel = stringResource(CommonStrings.action_reply)
val latestOnLongClick by rememberUpdatedState(onLongClick)
val latestOnSwipeToReply by rememberUpdatedState(onSwipeToReply)
return remember(canReply, messageActionsLabel, replyLabel) {
buildList {
add(
CustomAccessibilityAction(messageActionsLabel) {
latestOnLongClick()
true
}
)
if (canReply) {
add(
CustomAccessibilityAction(replyLabel) {
latestOnSwipeToReply()
true
}
)
}
}
}
}
@Composable
private fun ThreadSummaryView(
threadSummary: ThreadSummary,
@@ -26,8 +26,8 @@ fun TimelineItemPollView(
eventSink: (TimelineEvent.TimelineItemPollEvent) -> Unit,
modifier: Modifier = Modifier,
) {
fun onSelectAnswer(pollStartId: EventId, answerId: String) {
eventSink(TimelineEvent.SelectPollAnswer(pollStartId, answerId))
fun onSendPollResponse(pollStartId: EventId, answerIds: List<String>) {
eventSink(TimelineEvent.SendPollResponse(pollStartId, answerIds))
}
fun onEndPoll(pollStartId: EventId) {
@@ -43,10 +43,12 @@ fun TimelineItemPollView(
question = content.question,
answerItems = content.answerItems.toImmutableList(),
pollKind = content.pollKind,
isMultipleSelection = content.maxSelections > 1u,
maxSelections = content.maxSelections,
isPollEnded = content.isEnded,
isPollEditable = content.isEditable,
isMine = content.isMine,
onSelectAnswer = ::onSelectAnswer,
onSendPollResponse = ::onSendPollResponse,
onEditPoll = ::onEditPoll,
onEndPoll = ::onEndPoll,
modifier = modifier,
@@ -15,7 +15,9 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
@@ -23,6 +25,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayout
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.event.AN_EMOJI_ONLY_TEXT
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContentPreviewParam
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
@@ -47,8 +50,13 @@ fun TimelineItemTextView(
// The View <-> Compose interop is not working well with Compose UI tests (it loops indefinitely), so we skip it in the UI test mode.
if (LocalUiTestMode.current) return
val emojiOnly = content.formattedBody.toString() == content.body &&
content.body.replace(" ", "").containsOnlyEmojis()
val isInPreview = LocalInspectionMode.current
val emojiOnly = remember(content.body, content.formattedBody, isInPreview) {
content.formattedBody.toString() == content.body &&
content.body.replace(" ", "").let { body ->
if (isInPreview) body == AN_EMOJI_ONLY_TEXT else body.containsOnlyEmojis()
}
}
val textStyle = when {
emojiOnly -> ElementTheme.typography.fontHeadingXlRegular
else -> ElementTheme.typography.fontBodyLgRegular
@@ -108,6 +108,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
thumbnailSource = messageType.info?.thumbnailSource,
@@ -132,6 +133,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
thumbnailSource = messageType.info?.thumbnailSource,
@@ -174,6 +176,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
thumbnailSource = messageType.info?.thumbnailSource,
mediaSource = messageType.source,
@@ -198,6 +201,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
@@ -216,6 +220,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
@@ -235,6 +240,7 @@ class TimelineItemContentMessageFactory(
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = formattedCaption,
htmlCaption = messageType.formattedCaption?.body,
isEdited = content.isEdited,
thumbnailSource = messageType.info?.thumbnailSource,
mediaSource = messageType.source,
@@ -345,6 +351,7 @@ class TimelineItemContentMessageFactory(
body = messageType.body,
caption = messageType.body.trimEnd().takeIf { it.isNotEmpty() },
formattedCaption = formattedCaption,
htmlCaption = messageType.formatted?.body,
isEdited = content.isEdited,
items = galleryItems.toImmutableList(),
)
@@ -402,6 +409,7 @@ class TimelineItemContentMessageFactory(
body = messageType.body,
caption = messageType.body.trimEnd().takeIf { it.isNotEmpty() },
formattedCaption = formattedCaption,
htmlCaption = messageType.formatted?.body,
isEdited = content.isEdited,
attachments = attachments.toImmutableList(),
)
@@ -33,6 +33,7 @@ class TimelineItemContentPollFactory(
question = pollContentState.question,
answerItems = pollContentState.answerItems,
pollKind = pollContentState.pollKind,
maxSelections = pollContentState.maxSelections,
isEnded = pollContentState.isPollEnded,
isEdited = content.isEdited
)
@@ -28,10 +28,12 @@ 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.MatrixClient
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
import io.element.android.libraries.matrix.api.timeline.item.event.getDisambiguatedDisplayName
import io.element.android.libraries.matrix.ui.messages.reply.map
@@ -64,7 +66,7 @@ class TimelineItemEventFactory(
val currentSender = currentTimelineItem.event.sender
val groupPosition =
computeGroupPosition(currentTimelineItem, timelineItems, index)
val senderProfile = currentTimelineItem.event.senderProfile
val senderProfile = currentTimelineItem.event.senderProfile.orFallbackTo(roomMembers, currentSender)
val sentTime = dateFormatter.format(
timestamp = currentTimelineItem.event.timestamp,
mode = DateFormatterMode.TimeOnly,
@@ -138,11 +140,32 @@ class TimelineItemEventFactory(
roomMembers: List<RoomMember>,
renderReadReceipts: Boolean,
): TimelineItem.Event {
val senderProfile = receivedMatrixTimelineItem.event.senderProfile.orFallbackTo(roomMembers, timelineItem.senderId)
return timelineItem.copy(
senderProfile = senderProfile,
senderAvatar = timelineItem.senderAvatar.copy(
name = senderProfile.getDisambiguatedDisplayName(timelineItem.senderId),
url = senderProfile.getAvatarUrl(),
),
readReceiptState = receivedMatrixTimelineItem.computeReadReceiptState(roomMembers, renderReadReceipts)
)
}
/**
* The timeline only knows the profiles the SDK has resolved, so a sender it has nothing for is rendered as a raw
* user ID. The room member list usually knows that user, so use it rather than showing the ID.
*/
private fun ProfileDetails.orFallbackTo(roomMembers: List<RoomMember>, senderId: UserId): ProfileDetails {
if (this is ProfileDetails.Ready) return this
val member = roomMembers.find { it.userId == senderId } ?: return this
return ProfileDetails.Ready(
displayName = member.displayName,
displayNameAmbiguous = member.isNameAmbiguous,
avatarUrl = member.avatarUrl,
displayedStatus = member.displayedStatus,
)
}
private fun MatrixTimelineItem.Event.computeReactionsState(): TimelineItemReactions {
if (!config.computeReactions) {
return TimelineItemReactions(reactions = persistentListOf())
@@ -91,7 +91,7 @@ internal fun MatrixTimelineItem.Event.canBeDisplayedInBubbleBlock(): Boolean {
// Can be grouped
is FailedToParseMessageLikeContent,
is MessageContent,
RedactedContent,
is RedactedContent,
is StickerContent,
is PollContent,
is UnableToDecryptContent,
@@ -12,6 +12,7 @@ import androidx.annotation.VisibleForTesting
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.core.UniqueId
@@ -99,7 +100,8 @@ internal const val MIN_REDACTED_RUN_SIZE = 3
* Fold runs of [MIN_REDACTED_RUN_SIZE] or more consecutive deleted (redacted) messages into a single
* [TimelineItem.GroupedEvents], so a long stretch of "Message removed" tiles shows as one expandable
* "N removed messages" line, like element-web. Shorter runs, and anything that is not a redacted event
* (day dividers included), are left untouched.
* (day dividers included), are left untouched. A deleted message that still heads a thread is left
* untouched too, so that its thread summary stays reachable.
*
* This runs as the final step of [TimelineItemGrouper.group], after the state and membership events
* have been grouped. It is deliberately kept out of the normal grouping: a run needs a higher minimum
@@ -134,7 +136,7 @@ internal fun List<TimelineItem>.collapseRedactedRuns(groupIds: MutableMap<String
}
for (item in this) {
if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent) {
if (item is TimelineItem.Event && item.content is TimelineItemRedactedContent && item.threadInfo !is TimelineItemThreadInfo.ThreadRoot) {
run.add(item)
} else {
flushRun()
@@ -14,6 +14,7 @@ data class TimelineItemAttachmentsContent(
val body: String,
val caption: String?,
val formattedCaption: CharSequence?,
val htmlCaption: String? = null,
override val isEdited: Boolean,
val attachments: ImmutableList<AttachmentItem>,
) : TimelineItemEventContent, TimelineItemEventMutableContent {
@@ -17,6 +17,7 @@ data class TimelineItemAudioContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
val duration: Duration,
override val mediaSource: MediaSource,
@@ -33,6 +33,9 @@ sealed interface TimelineItemEventContentWithAttachment :
val fileSize: Long?
val caption: String?
val formattedCaption: CharSequence?
/** The caption as it was written, in HTML, so that editing it can start from the formatted text. */
val htmlCaption: String?
val mediaSource: MediaSource
val mimeType: String
val formattedFileSize: String
@@ -127,6 +130,13 @@ fun TimelineItemEventContent.formattedCaptionOrNull(): CharSequence? = when (thi
else -> null
}
fun TimelineItemEventContent.htmlCaptionOrNull(): String? = when (this) {
is TimelineItemEventContentWithAttachment -> htmlCaption
is TimelineItemGalleryContent -> htmlCaption
is TimelineItemAttachmentsContent -> htmlCaption
else -> null
}
fun TimelineItemEventContentWithAttachment.duration(): Duration? {
return when (this) {
is TimelineItemAudioContent -> duration
@@ -16,6 +16,7 @@ data class TimelineItemFileContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
override val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,
@@ -16,6 +16,7 @@ data class TimelineItemGalleryContent(
val body: String,
val caption: String?,
val formattedCaption: CharSequence?,
val htmlCaption: String? = null,
override val isEdited: Boolean,
val items: ImmutableList<GalleryItem>,
) : TimelineItemEventContent, TimelineItemEventMutableContent {
@@ -19,6 +19,7 @@ data class TimelineItemImageContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
override val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,
@@ -28,6 +28,7 @@ fun aTimelineItemImageContent(
blurhash: String? = A_BLUR_HASH,
filename: String = "A picture.jpg",
caption: String? = null,
htmlCaption: String? = null,
width: Int? = null,
height: Int? = 300,
) = TimelineItemImageContent(
@@ -35,6 +36,7 @@ fun aTimelineItemImageContent(
fileSize = 4 * 1024 * 1024L,
caption = caption,
formattedCaption = null,
htmlCaption = htmlCaption,
isEdited = false,
mediaSource = MediaSource(""),
thumbnailSource = null,
@@ -19,6 +19,7 @@ data class TimelineItemPollContent(
val question: String,
val answerItems: List<PollAnswerItem>,
val pollKind: PollKind,
val maxSelections: ULong,
val isEnded: Boolean,
override val isEdited: Boolean,
) : TimelineItemEventContent,
@@ -32,12 +32,14 @@ fun aTimelineItemPollContent(
isEditable: Boolean = false,
isEnded: Boolean = false,
isEdited: Boolean = false,
maxSelections: ULong = 1u,
): TimelineItemPollContent {
return TimelineItemPollContent(
eventId = EventId("\$anEventId"),
pollKind = PollKind.Disclosed,
question = question,
answerItems = answerItems,
maxSelections = maxSelections,
isMine = isMine,
isEditable = isEditable,
isEnded = isEnded,
@@ -15,6 +15,7 @@ data class TimelineItemStickerContent(
override val fileSize: Long?,
override val caption: String?,
override val formattedCaption: CharSequence?,
override val htmlCaption: String? = null,
override val isEdited: Boolean,
override val mediaSource: MediaSource,
val thumbnailSource: MediaSource?,

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