Merge branch 'release/25.04.3' into main

This commit is contained in:
ganfra 2025-04-23 11:05:37 +02:00
commit 6a6e4e951e
414 changed files with 4326 additions and 1704 deletions

View file

@ -20,7 +20,7 @@ jobs:
- run: | - run: |
npm install --save-dev @babel/plugin-transform-flow-strip-types npm install --save-dev @babel/plugin-transform-flow-strip-types
- name: Danger - name: Danger
uses: danger/danger-js@12.3.4 uses: danger/danger-js@13.0.4
with: with:
args: "--dangerfile ./tools/danger/dangerfile.js" args: "--dangerfile ./tools/danger/dangerfile.js"
env: env:

View file

@ -294,7 +294,7 @@ jobs:
yarn add danger-plugin-lint-report --dev yarn add danger-plugin-lint-report --dev
- name: Danger lint - name: Danger lint
if: always() if: always()
uses: danger/danger-js@12.3.4 uses: danger/danger-js@13.0.4
with: with:
args: "--dangerfile ./tools/danger/dangerfile-lint.js" args: "--dangerfile ./tools/danger/dangerfile-lint.js"
env: env:

View file

@ -2,8 +2,8 @@
<!--- TOC --> <!--- TOC -->
* [Contributing code to Matrix](#contributing-code-to-matrix)
* [Developer onboarding](#developer-onboarding) * [Developer onboarding](#developer-onboarding)
* [Contributing code to Matrix](#contributing-code-to-matrix)
* [Android Studio settings](#android-studio-settings) * [Android Studio settings](#android-studio-settings)
* [Compilation](#compilation) * [Compilation](#compilation)
* [Strings](#strings) * [Strings](#strings)
@ -28,18 +28,18 @@
<!--- END --> <!--- END -->
## Contributing code to Matrix
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
Element X Android support can be found in this room: [![Element X Android Matrix room #element-x-android:matrix.org](https://img.shields.io/matrix/element-x-android:matrix.org.svg?label=%23element-x-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-x-android:matrix.org).
The rest of the document contains specific rules for Matrix Android projects
## Developer onboarding ## Developer onboarding
For a detailed overview of the project, see [Developer Onboarding](./docs/_developer_onboarding.md). For a detailed overview of the project, see [Developer Onboarding](./docs/_developer_onboarding.md).
## Contributing code to Matrix
If instead of contributing to the Element X Android project, you want to contribute to Synapse, the homeserver implementation, please read the [Synapse contribution guide](https://element-hq.github.io/synapse/latest/development/contributing_guide.html).
Element X Android support can be found in this room: [![Element X Android Matrix room #element-x-android:matrix.org](https://img.shields.io/matrix/element-x-android:matrix.org.svg?label=%23element-x-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-x-android:matrix.org).
The rest of the document contains specific rules for Matrix Android projects.
## Android Studio settings ## Android Studio settings
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`). Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).

View file

@ -300,6 +300,7 @@ licensee {
allow("Apache-2.0") allow("Apache-2.0")
allow("MIT") allow("MIT")
allow("BSD-2-Clause") allow("BSD-2-Clause")
allow("BSD-3-Clause")
allowUrl("https://opensource.org/licenses/MIT") allowUrl("https://opensource.org/licenses/MIT")
allowUrl("https://developer.android.com/studio/terms.html") allowUrl("https://developer.android.com/studio/terms.html")
allowUrl("https://www.zetetic.net/sqlcipher/license/") allowUrl("https://www.zetetic.net/sqlcipher/license/")

View file

@ -3,6 +3,7 @@
<locale android:name="be"/> <locale android:name="be"/>
<locale android:name="bg"/> <locale android:name="bg"/>
<locale android:name="cs"/> <locale android:name="cs"/>
<locale android:name="cy"/>
<locale android:name="de"/> <locale android:name="de"/>
<locale android:name="el"/> <locale android:name="el"/>
<locale android:name="en"/> <locale android:name="en"/>
@ -17,6 +18,7 @@
<locale android:name="in"/> <locale android:name="in"/>
<locale android:name="it"/> <locale android:name="it"/>
<locale android:name="ka"/> <locale android:name="ka"/>
<locale android:name="lt"/>
<locale android:name="nb"/> <locale android:name="nb"/>
<locale android:name="nl"/> <locale android:name="nl"/>
<locale android:name="pl"/> <locale android:name="pl"/>

View file

@ -78,15 +78,21 @@ class SyncOrchestrator @AssistedInject constructor(
internal fun observeStates() = coroutineScope.launch { internal fun observeStates() = coroutineScope.launch {
Timber.tag(tag).d("start observing the app and network state") Timber.tag(tag).d("start observing the app and network state")
val isAppActiveFlow = combine(
appForegroundStateService.isInForeground,
appForegroundStateService.isInCall,
appForegroundStateService.isSyncingNotificationEvent,
appForegroundStateService.hasRingingCall,
) { isInForeground, isInCall, isSyncingNotificationEvent, hasRingingCall ->
isInForeground || isInCall || isSyncingNotificationEvent || hasRingingCall
}
combine( combine(
// small debounce to avoid spamming startSync when the state is changing quickly in case of error. // small debounce to avoid spamming startSync when the state is changing quickly in case of error.
syncService.syncState.debounce(100.milliseconds), syncService.syncState.debounce(100.milliseconds),
networkMonitor.connectivity, networkMonitor.connectivity,
appForegroundStateService.isInForeground, isAppActiveFlow,
appForegroundStateService.isInCall, ) { syncState, networkState, isAppActive ->
appForegroundStateService.isSyncingNotificationEvent,
) { syncState, networkState, isInForeground, isInCall, isSyncingNotificationEvent ->
val isAppActive = isInForeground || isInCall || isSyncingNotificationEvent
val isNetworkAvailable = networkState == NetworkStatus.Connected val isNetworkAvailable = networkState == NetworkStatus.Connected
Timber.tag(tag).d("isAppActive=$isAppActive, isNetworkAvailable=$isNetworkAvailable") Timber.tag(tag).d("isAppActive=$isAppActive, isNetworkAvailable=$isNetworkAvailable")

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Allgofnodi ac Uwchraddio"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"Nid yw %1$s bellach yn cefnogi\'r hen brotocol. Allgofnodwch a mewngofnodi\'n ôl i barhau i ddefnyddio\'r ap."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Nid yw eich gweinydd cartref yn cefnogi\'r hen brotocol mwyach. Allgofnodwch a mewngofnodwch yn ôl i barhau i ddefnyddio\'r ap."</string>
</resources>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Logg ut og oppgrader"</string> <string name="banner_migrate_to_native_sliding_sync_action">"Logg ut og oppgrader"</string>
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s støtter ikke lenger den gamle protokollen. Logg ut og logg inn igjen for å fortsette å bruke appen."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Hjemmeserveren din støtter ikke lenger den gamle protokollen. Vennligst logg ut og inn igjen for å fortsette å bruke appen."</string> <string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Hjemmeserveren din støtter ikke lenger den gamle protokollen. Vennligst logg ut og inn igjen for å fortsette å bruke appen."</string>
</resources> </resources>

View file

@ -236,6 +236,50 @@ class SyncOrchestratorTest {
stopSyncRecorder.assertions().isCalledOnce() stopSyncRecorder.assertions().isCalledOnce()
} }
@Test
fun `when the app was in background and we have an incoming ringing call, a sync will be started`() = runTest {
val startSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val stopSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
val syncService = FakeSyncService(initialSyncState = SyncState.Idle).apply {
startSyncLambda = startSyncRecorder
stopSyncLambda = stopSyncRecorder
}
val networkMonitor = FakeNetworkMonitor(initialStatus = NetworkStatus.Connected)
val appForegroundStateService = FakeAppForegroundStateService(
initialForegroundValue = false,
initialIsSyncingNotificationEventValue = false,
initialHasRingingCall = false,
)
val syncOrchestrator = createSyncOrchestrator(
syncService = syncService,
networkMonitor = networkMonitor,
appForegroundStateService = appForegroundStateService,
)
// We start observing
syncOrchestrator.observeStates()
// Advance the time to make sure the orchestrator has had time to start processing the inputs
advanceTimeBy(100.milliseconds)
// Start sync was never called
startSyncRecorder.assertions().isNeverCalled()
// Now we receive a ringing call
appForegroundStateService.updateHasRingingCall(true)
// Start sync will be called shortly after
advanceTimeBy(1.milliseconds)
startSyncRecorder.assertions().isCalledOnce()
// If the sync is running and the ringing call notification is now over, the sync stops after a delay
syncService.emitSyncState(SyncState.Running)
appForegroundStateService.updateHasRingingCall(false)
advanceTimeBy(10.seconds)
stopSyncRecorder.assertions().isCalledOnce()
}
@Test @Test
fun `when the app is in foreground, we sync for a notification and a call is ongoing, the sync will only stop when all conditions are false`() = runTest { fun `when the app is in foreground, we sync for a notification and a call is ongoing, the sync will only stop when all conditions are false`() = runTest {
val startSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) } val startSyncRecorder = lambdaRecorder<Result<Unit>> { Result.success(Unit) }

View file

@ -10,7 +10,7 @@
* [Sync](#sync) * [Sync](#sync)
* [Rust SDK](#rust-sdk) * [Rust SDK](#rust-sdk)
* [Matrix Rust Component Kotlin](#matrix-rust-component-kotlin) * [Matrix Rust Component Kotlin](#matrix-rust-component-kotlin)
* [Build the SDK locally](#build-the-sdk-locally) * [Building the SDK locally](#building-the-sdk-locally)
* [The Android project](#the-android-project) * [The Android project](#the-android-project)
* [Application](#application) * [Application](#application)
* [Jetpack Compose](#jetpack-compose) * [Jetpack Compose](#jetpack-compose)
@ -107,7 +107,7 @@ This is the goal of https://github.com/matrix-org/matrix-rust-components-kotlin.
This repository is used for distributing kotlin releases of the Matrix Rust SDK. This repository is used for distributing kotlin releases of the Matrix Rust SDK.
It'll provide the corresponding aar and also publish them on maven. It'll provide the corresponding aar and also publish them on maven.
Most of the time you want to use the releases made on maven with gradle: Most of the time **you want to use the releases made on maven with gradle**:
```groovy ```groovy
implementation("org.matrix.rustcomponents:sdk-android:latest-version") implementation("org.matrix.rustcomponents:sdk-android:latest-version")
@ -115,7 +115,9 @@ implementation("org.matrix.rustcomponents:sdk-android:latest-version")
You can also have access to the aars through the [release](https://github.com/matrix-org/matrix-rust-components-kotlin/releases) page. You can also have access to the aars through the [release](https://github.com/matrix-org/matrix-rust-components-kotlin/releases) page.
#### Build the SDK locally #### Building the SDK locally
If you want to make changes to the SDK or test them before integrating it with your codebase, you can build the SDK locally too.
Prerequisites: Prerequisites:
* Install the Android NDK (Native Development Kit). To do this from within * Install the Android NDK (Native Development Kit). To do this from within

@ -1 +1 @@
Subproject commit 665a15a1907a116816ffd1653bccfeaeeb7a2968 Subproject commit 0299b8ec4f4233a39230d4c35b97f89728c35fd1

View file

@ -0,0 +1,2 @@
Main changes in this version: bug fixes and improvements.
Full changelog: https://github.com/element-hq/element-x-android/releases

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_settings_read_terms_content_link">"yma"</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_settings_help_us_improve">"Dalinkitės anoniminiais naudojimo duomenimis ir padėkite mums nustatyti problemas."</string>
<string name="screen_analytics_settings_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s ."</string>
<string name="screen_analytics_settings_read_terms_content_link">"čia"</string>
<string name="screen_analytics_settings_share_data">"Dalytis analitiniais duomenimis"</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_read_terms_content_link">"yma"</string>
</resources>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_analytics_prompt_data_usage">"Mes nekaupsime ir neprofiliuosime jokių asmens duomenų"</string>
<string name="screen_analytics_prompt_help_us_improve">"Dalinkitės anoniminiais naudojimo duomenimis ir padėkite mums nustatyti problemas."</string>
<string name="screen_analytics_prompt_read_terms">"Galite perskaityti visas mūsų sąlygas %1$s ."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"čia"</string>
<string name="screen_analytics_prompt_settings">"Tai galite bet kada išjungti"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Mes nesidalinsime Jūsų duomenimis su trečiosiomis šalimis"</string>
<string name="screen_analytics_prompt_title">"Padėkite pagerinti %1$s"</string>
</resources>

View file

@ -66,6 +66,7 @@ dependencies {
implementation(projects.appconfig) implementation(projects.appconfig)
implementation(projects.features.enterprise.api) implementation(projects.features.enterprise.api)
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.androidutils)
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.featureflag.api) implementation(projects.libraries.featureflag.api)

View file

@ -8,6 +8,10 @@
package io.element.android.features.call.impl.ui package io.element.android.features.call.impl.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.util.Log import android.util.Log
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.ConsoleMessage import android.webkit.ConsoleMessage
@ -21,12 +25,17 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.getSystemService
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.call.impl.R import io.element.android.features.call.impl.R
import io.element.android.features.call.impl.pip.PictureInPictureEvents import io.element.android.features.call.impl.pip.PictureInPictureEvents
@ -35,6 +44,8 @@ import io.element.android.features.call.impl.pip.PictureInPictureStateProvider
import io.element.android.features.call.impl.pip.aPictureInPictureState import io.element.android.features.call.impl.pip.aPictureInPictureState
import io.element.android.features.call.impl.utils.WebViewPipController import io.element.android.features.call.impl.utils.WebViewPipController
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
import io.element.android.libraries.androidutils.compat.disableExternalAudioDevice
import io.element.android.libraries.androidutils.compat.enableExternalAudioDevice
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.ProgressDialog
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
@ -147,9 +158,11 @@ private fun CallWebView(
Text("WebView - can't be previewed") Text("WebView - can't be previewed")
} }
} else { } else {
var audioDeviceCallback: AudioDeviceCallback? by remember { mutableStateOf(null) }
AndroidView( AndroidView(
modifier = modifier, modifier = modifier,
factory = { context -> factory = { context ->
audioDeviceCallback = context.setupAudioConfiguration()
WebView(context).apply { WebView(context).apply {
onWebViewCreate(this) onWebViewCreate(this)
setup(userAgent, onPermissionsRequest) setup(userAgent, onPermissionsRequest)
@ -161,12 +174,41 @@ private fun CallWebView(
} }
}, },
onRelease = { webView -> onRelease = { webView ->
// Reset audio mode
webView.context.releaseAudioConfiguration(audioDeviceCallback)
webView.destroy() webView.destroy()
} }
) )
} }
} }
private fun Context.setupAudioConfiguration(): AudioDeviceCallback? {
val audioManager = getSystemService<AudioManager>() ?: return null
// Set 'voice call' mode so volume keys actually control the call volume
audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
audioManager.enableExternalAudioDevice()
return object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
Timber.d("Audio devices added")
audioManager.enableExternalAudioDevice()
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
Timber.d("Audio devices removed")
audioManager.enableExternalAudioDevice()
}
}.also {
audioManager.registerAudioDeviceCallback(it, null)
}
}
private fun Context.releaseAudioConfiguration(audioDeviceCallback: AudioDeviceCallback?) {
val audioManager = getSystemService<AudioManager>() ?: return
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
audioManager.disableExternalAudioDevice()
audioManager.mode = AudioManager.MODE_NORMAL
}
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
private fun WebView.setup( private fun WebView.setup(
userAgent: String, userAgent: String,

View file

@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.push.api.notifications.ForegroundServiceType import io.element.android.libraries.push.api.notifications.ForegroundServiceType
import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
import io.element.android.services.appnavstate.api.AppForegroundStateService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -87,7 +88,9 @@ class DefaultActiveCallManager @Inject constructor(
private val notificationManagerCompat: NotificationManagerCompat, private val notificationManagerCompat: NotificationManagerCompat,
private val matrixClientProvider: MatrixClientProvider, private val matrixClientProvider: MatrixClientProvider,
private val defaultCurrentCallService: DefaultCurrentCallService, private val defaultCurrentCallService: DefaultCurrentCallService,
private val appForegroundStateService: AppForegroundStateService,
) : ActiveCallManager { ) : ActiveCallManager {
private val tag = "DefaultActiveCallManager"
private var timedOutCallJob: Job? = null private var timedOutCallJob: Job? = null
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
@ -106,9 +109,11 @@ class DefaultActiveCallManager @Inject constructor(
override suspend fun registerIncomingCall(notificationData: CallNotificationData) { override suspend fun registerIncomingCall(notificationData: CallNotificationData) {
mutex.withLock { mutex.withLock {
appForegroundStateService.updateHasRingingCall(true)
Timber.tag(tag).d("Received incoming call for room id: ${notificationData.roomId}")
if (activeCall.value != null) { if (activeCall.value != null) {
displayMissedCallNotification(notificationData) displayMissedCallNotification(notificationData)
Timber.w("Already have an active call, ignoring incoming call: $notificationData") Timber.tag(tag).w("Already have an active call, ignoring incoming call: $notificationData")
return return
} }
activeCall.value = ActiveCall( activeCall.value = ActiveCall(
@ -129,6 +134,7 @@ class DefaultActiveCallManager @Inject constructor(
// Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data // Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data
if (activeWakeLock?.isHeld == false) { if (activeWakeLock?.isHeld == false) {
Timber.tag(tag).d("Acquiring partial wakelock")
activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L) activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L)
} }
} }
@ -139,10 +145,13 @@ class DefaultActiveCallManager @Inject constructor(
*/ */
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
suspend fun incomingCallTimedOut(displayMissedCallNotification: Boolean) = mutex.withLock { suspend fun incomingCallTimedOut(displayMissedCallNotification: Boolean) = mutex.withLock {
Timber.tag(tag).d("Incoming call timed out")
val previousActiveCall = activeCall.value ?: return val previousActiveCall = activeCall.value ?: return
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
activeCall.value = null activeCall.value = null
if (activeWakeLock?.isHeld == true) { if (activeWakeLock?.isHeld == true) {
Timber.tag(tag).d("Releasing partial wakelock after timeout")
activeWakeLock.release() activeWakeLock.release()
} }
@ -155,11 +164,12 @@ class DefaultActiveCallManager @Inject constructor(
override suspend fun hungUpCall(callType: CallType) = mutex.withLock { override suspend fun hungUpCall(callType: CallType) = mutex.withLock {
if (activeCall.value?.callType != callType) { if (activeCall.value?.callType != callType) {
Timber.w("Call type $callType does not match the active call type, ignoring") Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring")
return return
} }
cancelIncomingCallNotification() cancelIncomingCallNotification()
if (activeWakeLock?.isHeld == true) { if (activeWakeLock?.isHeld == true) {
Timber.tag(tag).d("Releasing partial wakelock after hang up")
activeWakeLock.release() activeWakeLock.release()
} }
timedOutCallJob?.cancel() timedOutCallJob?.cancel()
@ -169,6 +179,7 @@ class DefaultActiveCallManager @Inject constructor(
override suspend fun joinedCall(callType: CallType) = mutex.withLock { override suspend fun joinedCall(callType: CallType) = mutex.withLock {
cancelIncomingCallNotification() cancelIncomingCallNotification()
if (activeWakeLock?.isHeld == true) { if (activeWakeLock?.isHeld == true) {
Timber.tag(tag).d("Releasing partial wakelock after joining call")
activeWakeLock.release() activeWakeLock.release()
} }
timedOutCallJob?.cancel() timedOutCallJob?.cancel()
@ -181,6 +192,7 @@ class DefaultActiveCallManager @Inject constructor(
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private suspend fun showIncomingCallNotification(notificationData: CallNotificationData) { private suspend fun showIncomingCallNotification(notificationData: CallNotificationData) {
Timber.tag(tag).d("Displaying ringing call notification")
val notification = ringingCallNotificationCreator.createNotification( val notification = ringingCallNotificationCreator.createNotification(
sessionId = notificationData.sessionId, sessionId = notificationData.sessionId,
roomId = notificationData.roomId, roomId = notificationData.roomId,
@ -204,10 +216,13 @@ class DefaultActiveCallManager @Inject constructor(
} }
private fun cancelIncomingCallNotification() { private fun cancelIncomingCallNotification() {
appForegroundStateService.updateHasRingingCall(false)
Timber.tag(tag).d("Ringing call notification cancelled")
notificationManagerCompat.cancel(NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL)) notificationManagerCompat.cancel(NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL))
} }
private fun displayMissedCallNotification(notificationData: CallNotificationData) { private fun displayMissedCallNotification(notificationData: CallNotificationData) {
Timber.tag(tag).d("Displaying missed call notification")
coroutineScope.launch { coroutineScope.launch {
onMissedCallNotificationHandler.addMissedCallNotification( onMissedCallNotificationHandler.addMissedCallNotification(
sessionId = notificationData.sessionId, sessionId = notificationData.sessionId,
@ -227,14 +242,14 @@ class DefaultActiveCallManager @Inject constructor(
.flatMapLatest { activeCall -> .flatMapLatest { activeCall ->
val callType = activeCall.callType as CallType.RoomCall val callType = activeCall.callType as CallType.RoomCall
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room // Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
?.getRoom(callType.roomId) Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
?.roomInfoFlow return@flatMapLatest flowOf()
?.map { }
Timber.d("Has room call status changed for ringing call: ${it.hasRoomCall}") room.roomInfoFlow.map {
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants) Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
} it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
?: flowOf() }
} }
// We only want to check if the room active call status changes // We only want to check if the room active call status changes
.distinctUntilChanged() .distinctUntilChanged()

View file

@ -8,6 +8,7 @@
package io.element.android.features.call.impl.utils package io.element.android.features.call.impl.utils
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri
import javax.inject.Inject import javax.inject.Inject
class CallIntentDataParser @Inject constructor() { class CallIntentDataParser @Inject constructor() {
@ -17,7 +18,7 @@ class CallIntentDataParser @Inject constructor() {
) )
fun parse(data: String?): String? { fun parse(data: String?): String? {
val parsedUrl = data?.let { Uri.parse(data) } ?: return null val parsedUrl = data?.toUri() ?: return null
val scheme = parsedUrl.scheme val scheme = parsedUrl.scheme
return when { return when {
scheme in validHttpSchemes -> parsedUrl scheme in validHttpSchemes -> parsedUrl
@ -37,7 +38,7 @@ class CallIntentDataParser @Inject constructor() {
private fun Uri.getUrlParameter(): Uri? { private fun Uri.getUrlParameter(): Uri? {
return getQueryParameter("url") return getQueryParameter("url")
?.let { urlParameter -> ?.let { urlParameter ->
Uri.parse(urlParameter).takeIf { uri -> urlParameter.toUri().takeIf { uri ->
uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank() uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank()
} }
} }

View file

@ -0,0 +1,6 @@
<?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">"Galwad cyfredol"</string>
<string name="call_foreground_service_message_android">"Tapio i ddychwelyd i\'r alwad"</string>
<string name="call_foreground_service_title_android">"☎️ Galwad ar y gweill"</string>
</resources>

View file

@ -35,6 +35,7 @@ import io.element.android.libraries.push.api.notifications.NotificationIdProvide
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import io.mockk.mockk import io.mockk.mockk
@ -323,5 +324,6 @@ class DefaultActiveCallManagerTest {
notificationManagerCompat = notificationManagerCompat, notificationManagerCompat = notificationManagerCompat,
matrixClientProvider = matrixClientProvider, matrixClientProvider = matrixClientProvider,
defaultCurrentCallService = DefaultCurrentCallService(), defaultCurrentCallService = DefaultCurrentCallService(),
appForegroundStateService = FakeAppForegroundStateService(),
) )
} }

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_room_add_people_title">"Gwahodd pobl"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Gall unrhyw un ymuno â\'r ystafell hon"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Unrhyw un"</string>
<string name="screen_create_room_room_access_section_header">"Mynediad i\'r Ystafell"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Gall unrhyw un ofyn am gael ymuno â\'r ystafell ond bydd rhaid i weinyddwr neu gymedrolwr dderbyn y cais"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Gofyn i gael ymuno"</string>
<string name="screen_create_room_room_address_section_footer">"Er mwyn i\'r ystafell hon fod yn weladwy yn y cyfeiriadur ystafelloedd cyhoeddus, bydd angen cyfeiriad ystafell arnoch."</string>
<string name="screen_create_room_room_address_section_title">"Cyfeiriad yr ystafell"</string>
<string name="screen_create_room_room_name_label">"Enw\'r ystafell"</string>
<string name="screen_create_room_room_visibility_section_title">"Gwelededd yr ystafell"</string>
<string name="screen_create_room_title">"Creu ystafell"</string>
</resources>

View file

@ -0,0 +1,14 @@
<?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">"Naujas kambarys"</string>
<string name="screen_create_room_add_people_title">"Pakviesti žmonių"</string>
<string name="screen_create_room_error_creating_room">"Kuriant kambarį įvyko klaida"</string>
<string name="screen_create_room_private_option_description">"Į šį kambarį gali patekti tik pakviesti žmonės. Visi pranešimai yra užšifruoti nuo pradžios iki galo."</string>
<string name="screen_create_room_private_option_title">"Privatus kambarys"</string>
<string name="screen_create_room_public_option_description">"Bet kas gali rasti šį kambarį.
Tai galite bet kada pakeisti kambario nustatymuose."</string>
<string name="screen_create_room_room_name_label">"Kambario pavadinimas"</string>
<string name="screen_create_room_title">"Kurti kambarį"</string>
<string name="screen_create_room_topic_label">"Tema (nebūtina)"</string>
<string name="screen_start_chat_error_starting_chat">"Bandant pradėti pokalbį įvyko klaida"</string>
</resources>

View file

@ -9,8 +9,11 @@
Du kan endre dette når som helst i rominnstillingene."</string> Du kan endre dette når som helst i rominnstillingene."</string>
<string name="screen_create_room_public_option_title">"Offentlig rom"</string> <string name="screen_create_room_public_option_title">"Offentlig rom"</string>
<string name="screen_create_room_room_access_section_anyone_option_description">"Alle kan bli med i dette rommet"</string> <string name="screen_create_room_room_access_section_anyone_option_description">"Alle kan bli med i dette rommet"</string>
<string name="screen_create_room_room_access_section_anyone_option_title">"Alle"</string>
<string name="screen_create_room_room_access_section_header">"Tilgang til rom"</string>
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan be om å få bli med i rommet, men en administrator eller moderator må godta forespørselen"</string> <string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan be om å få bli med i rommet, men en administrator eller moderator må godta forespørselen"</string>
<string name="screen_create_room_room_access_section_knocking_option_title">"Be om å bli med"</string> <string name="screen_create_room_room_access_section_knocking_option_title">"Be om å bli med"</string>
<string name="screen_create_room_room_address_section_footer">"For at dette rommet skal være synlig i den offentlige romkatalogen, trenger du en romadresse."</string>
<string name="screen_create_room_room_address_section_title">"Romadresse"</string> <string name="screen_create_room_room_address_section_title">"Romadresse"</string>
<string name="screen_create_room_room_name_label">"Romnavn"</string> <string name="screen_create_room_room_name_label">"Romnavn"</string>
<string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string> <string name="screen_create_room_room_visibility_section_title">"Romsynlighet"</string>
@ -18,7 +21,10 @@ Du kan endre dette når som helst i rominnstillingene."</string>
<string name="screen_create_room_topic_label">"Emne (valgfritt)"</string> <string name="screen_create_room_topic_label">"Emne (valgfritt)"</string>
<string name="screen_room_directory_search_title">"Romkatalog"</string> <string name="screen_room_directory_search_title">"Romkatalog"</string>
<string name="screen_start_chat_error_starting_chat">"Det oppstod en feil når du prøvde å starte en chat"</string> <string name="screen_start_chat_error_starting_chat">"Det oppstod en feil når du prøvde å starte en chat"</string>
<string name="screen_start_chat_join_room_by_address_action">"Bli med i rommet med adresse"</string>
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ikke en gyldig adresse"</string> <string name="screen_start_chat_join_room_by_address_invalid_address">"Ikke en gyldig adresse"</string>
<string name="screen_start_chat_join_room_by_address_placeholder">"Gå inn…"</string>
<string name="screen_start_chat_join_room_by_address_room_found">"Matchende rom funnet"</string>
<string name="screen_start_chat_join_room_by_address_room_not_found">"Rom ikke funnet"</string> <string name="screen_start_chat_join_room_by_address_room_not_found">"Rom ikke funnet"</string>
<string name="screen_start_chat_join_room_by_address_supporting_text">"f.eks. #rom-navn:matrix.org"</string> <string name="screen_start_chat_join_room_by_address_supporting_text">"f.eks. #rom-navn:matrix.org"</string>
</resources> </resources>

View file

@ -3,7 +3,7 @@
<string name="screen_create_room_action_create_room">"Nieuwe kamer"</string> <string name="screen_create_room_action_create_room">"Nieuwe kamer"</string>
<string name="screen_create_room_add_people_title">"Mensen uitnodigen"</string> <string name="screen_create_room_add_people_title">"Mensen uitnodigen"</string>
<string name="screen_create_room_error_creating_room">"Er is een fout opgetreden bij het aanmaken van de kamer"</string> <string name="screen_create_room_error_creating_room">"Er is een fout opgetreden bij het aanmaken van de kamer"</string>
<string name="screen_create_room_private_option_description">"Berichten in deze kamer zijn versleuteld. Versleuteling kan achteraf niet worden uitgeschakeld."</string> <string name="screen_create_room_private_option_description">"Alleen uitgenodigde personen hebben toegang tot deze kamer. Alle berichten zijn end-to-end versleuteld."</string>
<string name="screen_create_room_private_option_title">"Privé kamer"</string> <string name="screen_create_room_private_option_title">"Privé kamer"</string>
<string name="screen_create_room_public_option_description">"Iedereen kan deze kamer vinden. <string name="screen_create_room_public_option_description">"Iedereen kan deze kamer vinden.
Je kunt dit op elk gewenst moment wijzigen in de kamerinstellingen."</string> Je kunt dit op elk gewenst moment wijzigen in de kamerinstellingen."</string>

View file

@ -4,7 +4,7 @@
<string name="screen_create_room_add_people_title">"Convidar pessoas"</string> <string name="screen_create_room_add_people_title">"Convidar pessoas"</string>
<string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string> <string name="screen_create_room_error_creating_room">"Ocorreu um erro ao criar a sala"</string>
<string name="screen_create_room_private_option_description">"Apenas as pessoas convidadas podem aceder a esta sala. Todas as mensagens são criptografadas de ponta a ponta."</string> <string name="screen_create_room_private_option_description">"Apenas as pessoas convidadas podem aceder a esta sala. Todas as mensagens são criptografadas de ponta a ponta."</string>
<string name="screen_create_room_private_option_title">"Sala privativa (somente por convite)"</string> <string name="screen_create_room_private_option_title">"Sala privada"</string>
<string name="screen_create_room_public_option_description">"Qualquer um pode encontrar esta sala. <string name="screen_create_room_public_option_description">"Qualquer um pode encontrar esta sala.
Você pode mudar isso a qualquer momento nas configurações da sala."</string> Você pode mudar isso a qualquer momento nas configurações da sala."</string>
<string name="screen_create_room_room_name_label">"Nome da sala"</string> <string name="screen_create_room_room_name_label">"Nome da sala"</string>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_deactivate_account_title">"Cau cyfrif"</string>
</resources>

View file

@ -1,6 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_deactivate_account_delete_all_messages">"Ezabatu nire mezu guztiak"</string> <string name="screen_deactivate_account_delete_all_messages">"Ezabatu nire mezu guztiak"</string>
<string name="screen_deactivate_account_description">"Kontuaren desaktibazioa %1$s, honakoa eragingo du:"</string>
<string name="screen_deactivate_account_description_bold_part">"ezin da desegin"</string>
<string name="screen_deactivate_account_list_item_1_bold_part">"Ezgaitu betiko"</string>
<string name="screen_deactivate_account_list_item_2">"Kendu zure burua txat gela guztietatik."</string> <string name="screen_deactivate_account_list_item_2">"Kendu zure burua txat gela guztietatik."</string>
<string name="screen_deactivate_account_title">"Desaktibatu kontua"</string> <string name="screen_deactivate_account_title">"Desaktibatu kontua"</string>
</resources> </resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_welcome_button">"Ffwrdd â ni!"</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_decline_and_block_alert_confirmation">"Iawn, gwrthod a rhwystro"</string>
<string name="screen_join_room_decline_and_block_alert_message">"Ydych chi\'n siŵr eich bod am wrthod y gwahoddiad i ymuno â\'r ystafell hon? Bydd hyn hefyd yn atal %1$s rhag cysylltu â chi neu eich gwahodd i ystafelloedd."</string>
<string name="screen_join_room_decline_and_block_alert_title">"Gwrthod gwahoddiad a rhwystro"</string>
<string name="screen_join_room_decline_and_block_button_title">"Gwrthod a rhwystro"</string>
</resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_invites_decline_chat_message">"Ar tikrai norite atmesti kvietimą prisijungti prie %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Atmesti kvietimą"</string>
<string name="screen_invites_decline_direct_chat_message">"Ar tikrai norite atmesti šį privatų pokalbį su %1$s ?"</string>
<string name="screen_invites_decline_direct_chat_title">"Atmesti pokalbį"</string>
<string name="screen_invites_empty_list">"Jokių kvietimų"</string>
<string name="screen_invites_invited_you">"%1$s(%2$s) pakvietė Jus"</string>
</resources>

View file

@ -7,6 +7,7 @@
<string name="screen_invites_empty_list">"Ingen invitasjoner"</string> <string name="screen_invites_empty_list">"Ingen invitasjoner"</string>
<string name="screen_invites_invited_you">"%1$s(%2$s) inviterte deg"</string> <string name="screen_invites_invited_you">"%1$s(%2$s) inviterte deg"</string>
<string name="screen_join_room_decline_and_block_alert_confirmation">"Ja, avslå og blokker"</string> <string name="screen_join_room_decline_and_block_alert_confirmation">"Ja, avslå og blokker"</string>
<string name="screen_join_room_decline_and_block_alert_message">"Er du sikker på at du vil avslå invitasjonen til å bli med i dette rommet? Dette vil også forhindre %1$s fra å kontakte deg eller invitere deg til rom."</string>
<string name="screen_join_room_decline_and_block_alert_title">"Avslå invitasjon og blokker"</string> <string name="screen_join_room_decline_and_block_alert_title">"Avslå invitasjon og blokker"</string>
<string name="screen_join_room_decline_and_block_button_title">"Avslå og blokker"</string> <string name="screen_join_room_decline_and_block_button_title">"Avslå og blokker"</string>
</resources> </resources>

View file

@ -47,7 +47,7 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
), ),
aJoinRoomState( aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin), contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
joinAction = AsyncAction.Failure(ClientException.Generic("Something went wrong")) joinAction = AsyncAction.Failure(ClientException.Generic("Something went wrong", null))
), ),
aJoinRoomState( aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null)) contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(null))

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_cancel_knock_action">"Diddymu cais"</string>
<string name="screen_join_room_cancel_knock_alert_confirmation">"Iawn, diddymu"</string>
<string name="screen_join_room_cancel_knock_alert_description">"Ydych chi\'n siŵr eich bod am ddiddymu\'ch cais i ymuno â\'r ystafell hon?"</string>
<string name="screen_join_room_cancel_knock_alert_title">"Diddymu cais i ymuno"</string>
<string name="screen_join_room_decline_and_block_alert_confirmation">"Iawn, gwrthod a rhwystro"</string>
<string name="screen_join_room_decline_and_block_alert_message">"Ydych chi\'n siŵr eich bod am wrthod y gwahoddiad i ymuno â\'r ystafell hon? Bydd hyn hefyd yn atal %1$s rhag cysylltu â chi neu eich gwahodd i ystafelloedd."</string>
<string name="screen_join_room_decline_and_block_alert_title">"Gwrthod gwahoddiad a rhwystro"</string>
<string name="screen_join_room_decline_and_block_button_title">"Gwrthod a rhwystro"</string>
<string name="screen_join_room_knock_message_description">"Neges (dewisol)"</string>
<string name="screen_join_room_knock_sent_description">"Byddwch yn derbyn gwahoddiad i ymuno â\'r ystafell os caiff eich cais ei dderbyn."</string>
<string name="screen_join_room_knock_sent_title">"Anfonwyd y cais i ymuno"</string>
<string name="screen_join_room_loading_alert_message">"Doedd dim modd dangos rhagolwg yr ystafell. Gall hyn fod oherwydd problemau rhwydwaith neu weinydd."</string>
<string name="screen_join_room_loading_alert_title">"Doedd dim modd dangos rhagolwg yr ystafell hon"</string>
</resources>

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_ban_reason">"Arrazoia: %1$s."</string>
<string name="screen_join_room_cancel_knock_action">"Utzi eskaera bertan behera"</string> <string name="screen_join_room_cancel_knock_action">"Utzi eskaera bertan behera"</string>
<string name="screen_join_room_cancel_knock_alert_confirmation">"Bai, utzi bertan behera"</string> <string name="screen_join_room_cancel_knock_alert_confirmation">"Bai, utzi bertan behera"</string>
<string name="screen_join_room_forget_action">"Ahaztu gela hau"</string>
<string name="screen_join_room_join_action">"Sartu gelan"</string> <string name="screen_join_room_join_action">"Sartu gelan"</string>
<string name="screen_join_room_knock_action">"Bidali batzeko eskaera"</string> <string name="screen_join_room_knock_action">"Bidali batzeko eskaera"</string>
<string name="screen_join_room_knock_message_description">"Mezua (aukerakoa)"</string> <string name="screen_join_room_knock_message_description">"Mezua (aukerakoa)"</string>

View file

@ -8,6 +8,7 @@
<string name="screen_join_room_cancel_knock_alert_description">"Er du sikker på at du vil kansellere forespørselen din om å bli med i dette rommet?"</string> <string name="screen_join_room_cancel_knock_alert_description">"Er du sikker på at du vil kansellere forespørselen din om å bli med i dette rommet?"</string>
<string name="screen_join_room_cancel_knock_alert_title">"Avbryt forespørsel om å bli med"</string> <string name="screen_join_room_cancel_knock_alert_title">"Avbryt forespørsel om å bli med"</string>
<string name="screen_join_room_decline_and_block_alert_confirmation">"Ja, avslå og blokker"</string> <string name="screen_join_room_decline_and_block_alert_confirmation">"Ja, avslå og blokker"</string>
<string name="screen_join_room_decline_and_block_alert_message">"Er du sikker på at du vil avslå invitasjonen til å bli med i dette rommet? Dette vil også forhindre %1$s fra å kontakte deg eller invitere deg til rom."</string>
<string name="screen_join_room_decline_and_block_alert_title">"Avslå invitasjon og blokker"</string> <string name="screen_join_room_decline_and_block_alert_title">"Avslå invitasjon og blokker"</string>
<string name="screen_join_room_decline_and_block_button_title">"Avslå og blokker"</string> <string name="screen_join_room_decline_and_block_button_title">"Avslå og blokker"</string>
<string name="screen_join_room_fail_message">"Å bli med i rommet mislyktes."</string> <string name="screen_join_room_fail_message">"Å bli med i rommet mislyktes."</string>
@ -20,6 +21,8 @@
<string name="screen_join_room_knock_message_description">"Melding (valgfritt)"</string> <string name="screen_join_room_knock_message_description">"Melding (valgfritt)"</string>
<string name="screen_join_room_knock_sent_description">"Du vil motta en invitasjon til å bli med i rommet hvis forespørselen din blir akseptert."</string> <string name="screen_join_room_knock_sent_description">"Du vil motta en invitasjon til å bli med i rommet hvis forespørselen din blir akseptert."</string>
<string name="screen_join_room_knock_sent_title">"Forespørsel om å bli med sendt"</string> <string name="screen_join_room_knock_sent_title">"Forespørsel om å bli med sendt"</string>
<string name="screen_join_room_loading_alert_message">"Vi kunne ikke vise forhåndsvisningen av rommet. Dette kan skyldes nettverks- eller serverproblemer."</string>
<string name="screen_join_room_loading_alert_title">"Vi kunne ikke vise forhåndsvisning av dette rommet"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s støtter ikke områder ennå. Du kan få tilgang til områder på nett."</string> <string name="screen_join_room_space_not_supported_description">"%1$s støtter ikke områder ennå. Du kan få tilgang til områder på nett."</string>
<string name="screen_join_room_space_not_supported_title">"Områder støttes ikke ennå"</string> <string name="screen_join_room_space_not_supported_title">"Områder støttes ikke ennå"</string>
<string name="screen_join_room_subtitle_knock">"Klikk på knappen nedenfor, så vil en romadministrator bli varslet. Du vil kunne delta i samtalen når den er godkjent."</string> <string name="screen_join_room_subtitle_knock">"Klikk på knappen nedenfor, så vil en romadministrator bli varslet. Du vil kunne delta i samtalen når den er godkjent."</string>

View file

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_join_room_cancel_knock_action">"Verzoek annuleren"</string>
<string name="screen_join_room_join_action">"Toetreden tot de kamer"</string> <string name="screen_join_room_join_action">"Toetreden tot de kamer"</string>
<string name="screen_join_room_knock_action">"Klop om deel te nemen"</string> <string name="screen_join_room_knock_action">"Klop om deel te nemen"</string>
<string name="screen_join_room_knock_message_description">"Bericht (optioneel)"</string> <string name="screen_join_room_knock_message_description">"Bericht (optioneel)"</string>
<string name="screen_join_room_knock_sent_description">"Je ontvangt een uitnodiging om deel te nemen aan de kamer als je aanvraag wordt geaccepteerd."</string>
<string name="screen_join_room_knock_sent_title">"Verzoek om toe te treden verzonden"</string> <string name="screen_join_room_knock_sent_title">"Verzoek om toe te treden verzonden"</string>
<string name="screen_join_room_space_not_supported_description">"%1$s ondersteunt nog geen spaces. Je kunt spaces benaderen via de webbrowser."</string> <string name="screen_join_room_space_not_supported_description">"%1$s ondersteunt nog geen spaces. Je kunt spaces benaderen via de webbrowser."</string>
<string name="screen_join_room_space_not_supported_title">"Spaces worden nog niet ondersteund"</string> <string name="screen_join_room_space_not_supported_title">"Spaces worden nog niet ondersteund"</string>

View file

@ -250,7 +250,7 @@ class JoinRoomPresenterTest {
val presenter = createJoinRoomPresenter( val presenter = createJoinRoomPresenter(
roomDescription = Optional.of(roomDescription), roomDescription = Optional.of(roomDescription),
joinRoomLambda = { _, _, _ -> joinRoomLambda = { _, _, _ ->
Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden")) Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden", null))
}, },
) )
presenter.test { presenter.test {
@ -742,7 +742,7 @@ class JoinRoomPresenterTest {
fun `present - when room is not known RoomPreview is loaded with error Forbidden`() = runTest { fun `present - when room is not known RoomPreview is loaded with error Forbidden`() = runTest {
val client = FakeMatrixClient( val client = FakeMatrixClient(
getRoomPreviewResult = { _, _ -> getRoomPreviewResult = { _, _ ->
Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden")) Result.failure(ClientException.MatrixApi(ErrorKind.Forbidden, "403", "Forbidden", null))
} }
) )
val presenter = createJoinRoomPresenter( val presenter = createJoinRoomPresenter(

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Iawn, derbyn y cyfan"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Ydych chi\'n siŵr eich bod am dderbyn pob cais i ymuno?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Derbyn pob cais"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Derbyn y cyfan"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Doedd dim modd derbyn pob cais. Hoffech chi roi cynnig arall arni?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Wedi methu derbyn pob cais"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Yn derbyn pob cais i ymuno"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Doedd dim modd derbyn y cais hwn. Hoffech chi roi cynnig arall arni?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Wedi methu â derbyn y cais"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Yn derbyn cais i ymuno"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Iawn, gwrthod a gwahardd"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Ydych chi\'n siŵr eich bod am wrthod a gwahardd %1$s? Bydd y defnyddiwr hwn ddim yn gallu gofyn am fynediad i ymuno â\'r ystafell hon eto."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Gwrthod a gwahardd mynediad"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Yn gwrthod a gwahardd mynediad"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Iawn, gwrthod"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Ydych chi\'n siŵr eich bod am wrthod %1$s cais i ymuno â\'r ystafell hon?"</string>
<string name="screen_knock_requests_list_decline_alert_title">"Gwrthod mynediad"</string>
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Gwrthod a gwahardd"</string>
<string name="screen_knock_requests_list_decline_failed_alert_description">"Doedd dim modd i ni wrthod y cais hwn. Hoffech chi roi cynnig arall arni?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Wedi methu â gwrthod y cais"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Yn gwrthod cais i ymuno"</string>
<string name="screen_knock_requests_list_empty_state_description">"Pan fydd rhywun yn gofyn am gael ymuno â\'r ystafell, byddwch yn gallu gweld eu cais yma."</string>
<string name="screen_knock_requests_list_empty_state_title">"Dim cais i ymuno yn disgwyl"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Yn llwytho ceisiadau i ymuno…"</string>
<string name="screen_knock_requests_list_title">"Ceisiadau i ymuno"</string>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Gweld y cyfan"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Derbyn"</string>
<string name="screen_room_single_knock_request_title">"Mae %1$s eisiau ymuno â\'r ystafell hon"</string>
<string name="screen_room_single_knock_request_view_button_title">"Golwg"</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_single_knock_request_accept_button_title">"Priimti"</string>
</resources>

View file

@ -1,9 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, godta alle"</string> <string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, godta alle"</string>
<string name="screen_knock_requests_list_accept_all_alert_description">"Er du sikker på at du vil godta alle forespørsler om å bli med?"</string>
<string name="screen_knock_requests_list_accept_all_alert_title">"Godta alle forespørsler"</string> <string name="screen_knock_requests_list_accept_all_alert_title">"Godta alle forespørsler"</string>
<string name="screen_knock_requests_list_accept_all_button_title">"Godta alle"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunne ikke godta alle forespørsler. Vil du prøve igjen?"</string>
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kunne ikke godta alle forespørsler"</string>
<string name="screen_knock_requests_list_accept_all_loading_title">"Godtar alle forespørsler om å bli med"</string> <string name="screen_knock_requests_list_accept_all_loading_title">"Godtar alle forespørsler om å bli med"</string>
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunne ikke godta denne forespørselen. Vil du prøve igjen?"</string>
<string name="screen_knock_requests_list_accept_failed_alert_title">"Kunne ikke godta forespørselen"</string>
<string name="screen_knock_requests_list_accept_loading_title">"Godtar forespørsel om å bli med"</string> <string name="screen_knock_requests_list_accept_loading_title">"Godtar forespørsel om å bli med"</string>
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, avslå og utesteng"</string>
<string name="screen_knock_requests_list_ban_alert_description">"Er du sikker på at du vil avvise og utestenge %1$s? Denne brukeren vil ikke kunne be om tilgang til dette rommet igjen."</string>
<string name="screen_knock_requests_list_ban_alert_title">"Avslå og forby tilgang"</string>
<string name="screen_knock_requests_list_ban_loading_title">"Avslår og forbyr tilgang"</string> <string name="screen_knock_requests_list_ban_loading_title">"Avslår og forbyr tilgang"</string>
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, avslå"</string> <string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, avslå"</string>
<string name="screen_knock_requests_list_decline_alert_description">"Er du sikker på at du vil avslå %1$ss forespørsel om å bli med i dette rommet?"</string> <string name="screen_knock_requests_list_decline_alert_description">"Er du sikker på at du vil avslå %1$ss forespørsel om å bli med i dette rommet?"</string>
@ -12,8 +21,14 @@
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunne ikke avslå denne forespørselen. Vil du prøve igjen?"</string> <string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunne ikke avslå denne forespørselen. Vil du prøve igjen?"</string>
<string name="screen_knock_requests_list_decline_failed_alert_title">"Kunne ikke avslå forespørselen"</string> <string name="screen_knock_requests_list_decline_failed_alert_title">"Kunne ikke avslå forespørselen"</string>
<string name="screen_knock_requests_list_decline_loading_title">"Avslår forespørsel om å bli med"</string> <string name="screen_knock_requests_list_decline_loading_title">"Avslår forespørsel om å bli med"</string>
<string name="screen_knock_requests_list_empty_state_description">"Når noen ber om å bli med i rommet, vil du kunne se forespørselen deres her."</string>
<string name="screen_knock_requests_list_empty_state_title">"Ingen ventende forespørsel om å bli med"</string> <string name="screen_knock_requests_list_empty_state_title">"Ingen ventende forespørsel om å bli med"</string>
<string name="screen_knock_requests_list_initial_loading_title">"Laster inn forespørsler om å bli med…"</string>
<string name="screen_knock_requests_list_title">"Forespørsler om å bli med"</string> <string name="screen_knock_requests_list_title">"Forespørsler om å bli med"</string>
<plurals name="screen_room_multiple_knock_requests_title">
<item quantity="one">"%1$s +%2$d andre ønsker å bli med i dette rommet"</item>
<item quantity="other">"%1$s +%2$d andre ønsker å bli med i dette rommet"</item>
</plurals>
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vis alle"</string> <string name="screen_room_multiple_knock_requests_view_all_button_title">"Vis alle"</string>
<string name="screen_room_single_knock_request_accept_button_title">"Godta"</string> <string name="screen_room_single_knock_request_accept_button_title">"Godta"</string>
<string name="screen_room_single_knock_request_title">"%1$s ønsker å bli med i dette rommet"</string> <string name="screen_room_single_knock_request_title">"%1$s ønsker å bli med i dette rommet"</string>

View file

@ -0,0 +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">"Ydych chi\'n siŵr eich bod am adael y sgwrs hon? Dyw\'r sgwrs hon ddim yn gyhoeddus a fyddwch chi ddim yn gallu ailymuno heb wahoddiad."</string>
<string name="leave_room_alert_empty_subtitle">"Ydych chi\'n siŵr eich bod am adael yr ystafell hon? Chi yw\'r unig berson yma. Os byddwch yn gadael, fydd neb yn gallu ymuno yn y dyfodol, gan gynnwys chi."</string>
<string name="leave_room_alert_private_subtitle">"Ydych chi\'n siŵr eich bod am adael yr ystafell hon? Dyw\'r ystafell hon ddim yn gyhoeddus a fyddwch chi ddim yn gallu ailymuno heb wahoddiad."</string>
<string name="leave_room_alert_subtitle">"Ydych chi\'n siŵr eich bod am adael yr ystafell?"</string>
</resources>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_room_alert_empty_subtitle">"Ar tikrai norite išeiti iš šio kambario? Jūs esate vienintelis žmogus jame. Jei išeisite, niekas negalės prisijungti ateityje, įskaitant Jus."</string>
<string name="leave_room_alert_private_subtitle">"Ar tikrai norite išeiti iš šio kambario? Šis kambarys nėra viešas ir negalėsite vėl prisijungti be kvietimo."</string>
<string name="leave_room_alert_subtitle">"Ar tikrai norite išeiti iš kambario?"</string>
</resources>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Tem certeza de que deseja sair dessa conversa? Essa conversa não é pública e você não poderá entrar novamente sem um convite."</string>
<string name="leave_room_alert_empty_subtitle">"Tem certeza de que deseja sair desta sala? Você é a única pessoa aqui. Se você sair, ninguém poderá ingressar no futuro, inclusive você."</string> <string name="leave_room_alert_empty_subtitle">"Tem certeza de que deseja sair desta sala? Você é a única pessoa aqui. Se você sair, ninguém poderá ingressar no futuro, inclusive você."</string>
<string name="leave_room_alert_private_subtitle">"Tem certeza de que deseja sair desta sala? Esta sala não é pública e você não poderá entrar novamente sem um convite."</string> <string name="leave_room_alert_private_subtitle">"Tem certeza de que deseja sair desta sala? Esta sala não é pública e você não poderá entrar novamente sem um convite."</string>
<string name="leave_room_alert_subtitle">"Tem certeza de que deseja sair da sala?"</string> <string name="leave_room_alert_subtitle">"Tem certeza de que deseja sair da sala?"</string>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_app_lock_forgot_pin">"Wedi anghofio\'ch PIN?"</string>
<string name="screen_app_lock_setup_confirm_pin">"Cadarnhau eich PIN"</string>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_in_progress_dialog_content">"Atsijungiama…"</string>
</resources>

View file

@ -9,11 +9,11 @@ package io.element.android.features.login.impl.util
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import androidx.core.net.toUri
import io.element.android.appconfig.AuthenticationConfig import io.element.android.appconfig.AuthenticationConfig
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
fun openLearnMorePage(context: Context) { fun openLearnMorePage(context: Context) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(AuthenticationConfig.SLIDING_SYNC_READ_MORE_URL)) val intent = Intent(Intent.ACTION_VIEW, AuthenticationConfig.SLIDING_SYNC_READ_MORE_URL.toUri())
tryOrNull { context.startActivity(intent) } tryOrNull { context.startActivity(intent) }
} }

View file

@ -7,7 +7,7 @@
package io.element.android.features.login.impl.web package io.element.android.features.login.impl.web
import android.net.Uri import androidx.core.net.toUri
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.AuthenticationConfig import io.element.android.appconfig.AuthenticationConfig
import io.element.android.features.login.impl.resolver.network.WellknownAPI import io.element.android.features.login.impl.resolver.network.WellknownAPI
@ -43,7 +43,7 @@ class DefaultWebClientUrlForAuthenticationRetriever @Inject constructor(
} }
val registrationHelperUrl = result.registrationHelperUrl val registrationHelperUrl = result.registrationHelperUrl
return if (registrationHelperUrl != null) { return if (registrationHelperUrl != null) {
Uri.parse(registrationHelperUrl) registrationHelperUrl.toUri()
.buildUpon() .buildUpon()
.appendQueryParameter("hs_url", homeServerUrl) .appendQueryParameter("hs_url", homeServerUrl)
.build() .build()

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_change_account_provider_other">"Arall"</string>
<string name="screen_create_account_title">"Creu cyfrif"</string>
<string name="screen_login_error_deactivated_account">"Mae\'r cyfrif hwn wedi\'i gau."</string>
<string name="screen_login_title">"Croeso nôl!"</string>
<string name="screen_qr_code_login_connection_note_secure_state_title">"Nid yw\'r cysylltiad yn ddiogel"</string>
<string name="screen_qr_code_login_error_cancelled_subtitle">"Cafodd y mewngofnodi ei ddiddymu ar y ddyfais arall."</string>
<string name="screen_qr_code_login_error_cancelled_title">"Cais mewngofnodi wedi\'i ddiddymu"</string>
<string name="screen_qr_code_login_error_declined_title">"Gwrthodwyd y mewngofnodi"</string>
<string name="screen_qr_code_login_error_expired_title">"Heb gwblhau\'r mewngofnodi mewn pryd"</string>
<string name="screen_qr_code_login_error_linking_not_suported_title">"Nid yw\'r cod QR yn cael ei gefnogi"</string>
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Ceisiwch eto"</string>
<string name="screen_qr_code_login_unknown_error_description">"Digwyddodd gwall annisgwyl. Ceisiwch eto."</string>
</resources>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_account_provider_change">"Keisti paskyros teikėją"</string>
<string name="screen_account_provider_signin_subtitle">"Čia bus saugomi Jūsų pokalbiai - panašiai kaip el. pašto paslaugų teikėjas saugo Jūsų el. laiškus."</string>
<string name="screen_account_provider_signup_subtitle">"Čia bus saugomi Jūsų pokalbiai - panašiai kaip el. pašto paslaugų teikėjas saugo Jūsų el. laiškus."</string>
<string name="screen_change_account_provider_title">"Keisti paskyros teikėją"</string>
<string name="screen_change_server_error_invalid_homeserver">"Nepavyko pasiekti šio serverio. Patikrinkite, ar teisingai įvedėte serverio URL. Jei URL yra teisingas, susisiekite su serverio administracija dėl tolimesnės pagalbos."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"Šiuo metu šis serveris nepalaiko slenkančios sinchronizacijos (sliding sync)."</string>
<string name="screen_change_server_form_header">"Serverio URL"</string>
<string name="screen_change_server_form_notice">"Galite prisijungti tik prie serverio, palaikančio slenkančią sinchronizaciją (sliding sync). Jūsų serverio administracija turėtų ją sukonfigūruoti. %1$s"</string>
<string name="screen_change_server_subtitle">"Koks yra Jūsų serverio adresas?"</string>
<string name="screen_create_account_title">"Sukurti paskyrą"</string>
<string name="screen_login_error_deactivated_account">"Ši paskyra buvo išjungta."</string>
<string name="screen_login_error_invalid_credentials">"Neteisingas vartotojo vardas ir (arba) slaptažodis"</string>
<string name="screen_login_error_invalid_user_id">"Tai nėra tinkamas vartotojo vardas. Reikalingas formatas: \'@vartotojas:serveris.org\'"</string>
<string name="screen_login_error_unsupported_authentication">"Pasirinktas serveris nepalaiko slaptažodžio ar OIDC prisijungimo. Susisiekite su serverio administracija arba pasirinkite kitą serverį."</string>
<string name="screen_login_form_header">"Įveskite savo duomenis"</string>
<string name="screen_login_subtitle">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
<string name="screen_login_title">"Sveiki sugrįžę!"</string>
<string name="screen_login_title_with_homeserver">"Prisijungti prie %1$s"</string>
<string name="screen_server_confirmation_change_server">"Keisti paskyros teikėją"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Privatus serveris “Element” darbuotojams."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
<string name="screen_server_confirmation_message_register">"Čia bus saugomi Jūsų pokalbiai - panašiai kaip el. pašto paslaugų teikėjas saugo Jūsų el. laiškus."</string>
<string name="screen_server_confirmation_title_login">"Jūs ruošiatės prisijungti prie %1$s"</string>
<string name="screen_server_confirmation_title_register">"Jūs ruošiatės susikurti paskyrą %1$s"</string>
</resources>

View file

@ -55,6 +55,7 @@ Prøv å logge på manuelt, eller skann QR-koden med en annen enhet."</string>
<string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Kontotilbyderen din støtter ikke %1$s."</string> <string name="screen_qr_code_login_error_sliding_sync_not_supported_subtitle">"Kontotilbyderen din støtter ikke %1$s."</string>
<string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s støttes ikke"</string> <string name="screen_qr_code_login_error_sliding_sync_not_supported_title">"%1$s støttes ikke"</string>
<string name="screen_qr_code_login_initial_state_button_title">"Klar til å skanne"</string> <string name="screen_qr_code_login_initial_state_button_title">"Klar til å skanne"</string>
<string name="screen_qr_code_login_initial_state_item_1">"Åpne %1$s på en datamaskin"</string>
<string name="screen_qr_code_login_initial_state_item_2">"Klikk på avataren din"</string> <string name="screen_qr_code_login_initial_state_item_2">"Klikk på avataren din"</string>
<string name="screen_qr_code_login_initial_state_item_3">"Velg %1$s"</string> <string name="screen_qr_code_login_initial_state_item_3">"Velg %1$s"</string>
<string name="screen_qr_code_login_initial_state_item_3_action">"«Koble til ny enhet»"</string> <string name="screen_qr_code_login_initial_state_item_3_action">"«Koble til ny enhet»"</string>

View file

@ -23,6 +23,7 @@
<string name="screen_login_error_deactivated_account">"Essa conta foi desativada."</string> <string name="screen_login_error_deactivated_account">"Essa conta foi desativada."</string>
<string name="screen_login_error_invalid_credentials">"Nome de usuário e/ou senha incorretos"</string> <string name="screen_login_error_invalid_credentials">"Nome de usuário e/ou senha incorretos"</string>
<string name="screen_login_error_invalid_user_id">"Esse não é um identificador de usuário válido. Formato esperado: \'@usuário:servidor.org\'"</string> <string name="screen_login_error_invalid_user_id">"Esse não é um identificador de usuário válido. Formato esperado: \'@usuário:servidor.org\'"</string>
<string name="screen_login_error_refresh_tokens">"Este servidor está configurado para usar tokens de atualização. Eles não são suportados ao usar login baseado em senha."</string>
<string name="screen_login_error_unsupported_authentication">"O servidor selecionado não suporta senha ou login no OIDC. Entre em contato com o administrador ou escolha outro servidor."</string> <string name="screen_login_error_unsupported_authentication">"O servidor selecionado não suporta senha ou login no OIDC. Entre em contato com o administrador ou escolha outro servidor."</string>
<string name="screen_login_form_header">"Insira seus dados"</string> <string name="screen_login_form_header">"Insira seus dados"</string>
<string name="screen_login_subtitle">"A Matrix é uma rede aberta para comunicação segura e descentralizada."</string> <string name="screen_login_subtitle">"A Matrix é uma rede aberta para comunicação segura e descentralizada."</string>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_submit">"Allgofnodi"</string>
<string name="screen_signout_confirmation_dialog_title">"Allgofnodi"</string>
<string name="screen_signout_preference_item">"Allgofnodi"</string>
</resources>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Ar tikrai norite atsijungti?"</string>
<string name="screen_signout_in_progress_dialog_content">"Atsijungiama…"</string>
</resources>

View file

@ -4,10 +4,15 @@
<string name="screen_signout_confirmation_dialog_submit">"Sair"</string> <string name="screen_signout_confirmation_dialog_submit">"Sair"</string>
<string name="screen_signout_confirmation_dialog_title">"Sair"</string> <string name="screen_signout_confirmation_dialog_title">"Sair"</string>
<string name="screen_signout_in_progress_dialog_content">"Saindo…"</string> <string name="screen_signout_in_progress_dialog_content">"Saindo…"</string>
<string name="screen_signout_key_backup_disabled_subtitle">"Você está prestes a sair da sua última sessão. Se você sair agora, perderá o acesso às suas mensagens criptografadas."</string>
<string name="screen_signout_key_backup_disabled_title">"Você desativou o backup"</string> <string name="screen_signout_key_backup_disabled_title">"Você desativou o backup"</string>
<string name="screen_signout_key_backup_offline_subtitle">"O backup das suas chaves ainda estava sendo feito quando você ficou off-line. Reconecte-se para que você possa fazer o backup de suas chaves antes de sair."</string>
<string name="screen_signout_key_backup_offline_title">"O backup das suas chaves ainda está em andamento"</string> <string name="screen_signout_key_backup_offline_title">"O backup das suas chaves ainda está em andamento"</string>
<string name="screen_signout_key_backup_ongoing_subtitle">"Aguarde até que isso seja concluído antes de sair."</string>
<string name="screen_signout_key_backup_ongoing_title">"O backup das suas chaves ainda está em andamento"</string> <string name="screen_signout_key_backup_ongoing_title">"O backup das suas chaves ainda está em andamento"</string>
<string name="screen_signout_preference_item">"Sair"</string> <string name="screen_signout_preference_item">"Sair"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Você está prestes a sair da sua última sessão. Se você sair agora, perderá o acesso às suas mensagens criptografadas."</string>
<string name="screen_signout_recovery_disabled_title">"A recuperação não está configurada"</string> <string name="screen_signout_recovery_disabled_title">"A recuperação não está configurada"</string>
<string name="screen_signout_save_recovery_key_subtitle">"Você está prestes a sair da sua última sessão. Se você sair agora, poderá perder o acesso às suas mensagens criptografadas."</string>
<string name="screen_signout_save_recovery_key_title">"Você salvou sua chave de recuperação?"</string> <string name="screen_signout_save_recovery_key_title">"Você salvou sua chave de recuperação?"</string>
</resources> </resources>

View file

@ -363,7 +363,9 @@ private fun MessagesViewContent(
}, },
content = { paddingValues -> content = { paddingValues ->
Box(modifier = Modifier.padding(paddingValues)) { Box(modifier = Modifier.padding(paddingValues)) {
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberExitOnScrollBehavior() val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberScrollBehavior(
pinnedMessagesCount = state.pinnedMessagesBannerState.pinnedMessagesCount(),
)
TimelineView( TimelineView(
state = state.timelineState, state = state.timelineState,
timelineProtectionState = state.timelineProtectionState, timelineProtectionState = state.timelineProtectionState,

View file

@ -39,6 +39,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.traversalIndex
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
@ -188,6 +190,7 @@ private fun ActionListViewContent(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.clearAndSetSemantics {},
) )
if (target.event.messageShield != null) { if (target.event.messageShield != null) {
MessageShieldView( MessageShieldView(
@ -347,12 +350,21 @@ private fun EmojiReactionsRow(
) )
for (emoji in defaultEmojis) { for (emoji in defaultEmojis) {
val isHighlighted = highlightedEmojis.contains(emoji) val isHighlighted = highlightedEmojis.contains(emoji)
EmojiButton(emoji, isHighlighted, onEmojiReactionClick) EmojiButton(
modifier = Modifier
// Make it appear after the more useful actions for the accessibility service
.semantics {
traversalIndex = 1f
},
emoji = emoji,
isHighlighted = isHighlighted,
onClick = onEmojiReactionClick
)
} }
Box( Box(
modifier = Modifier modifier = Modifier
.size(48.dp), .size(48.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center,
) { ) {
Icon( Icon(
imageVector = CompoundIcons.ReactionAdd(), imageVector = CompoundIcons.ReactionAdd(),
@ -366,6 +378,10 @@ private fun EmojiReactionsRow(
indication = ripple(bounded = false, radius = emojiRippleRadius), indication = ripple(bounded = false, radius = emojiRippleRadius),
interactionSource = remember { MutableInteractionSource() } interactionSource = remember { MutableInteractionSource() }
) )
// Make it appear after the more useful actions for the accessibility service
.semantics {
traversalIndex = 1f
}
) )
} }
} }
@ -413,6 +429,7 @@ private fun EmojiButton(
emoji: String, emoji: String,
isHighlighted: Boolean, isHighlighted: Boolean,
onClick: (String) -> Unit, onClick: (String) -> Unit,
modifier: Modifier = Modifier,
) { ) {
val backgroundColor = if (isHighlighted) { val backgroundColor = if (isHighlighted) {
ElementTheme.colors.bgActionPrimaryRest ElementTheme.colors.bgActionPrimaryRest
@ -425,10 +442,16 @@ private fun EmojiButton(
stringResource(id = CommonStrings.a11y_react_with, emoji) stringResource(id = CommonStrings.a11y_react_with, emoji)
} }
Box( Box(
modifier = Modifier modifier = modifier
.size(48.dp) .size(48.dp)
.background(backgroundColor, CircleShape) .background(backgroundColor, CircleShape)
.clearAndSetSemantics { .clickable(
enabled = true,
onClick = { onClick(emoji) },
indication = ripple(bounded = false, radius = emojiRippleRadius),
interactionSource = remember { MutableInteractionSource() }
)
.semantics {
contentDescription = description contentDescription = description
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
@ -436,13 +459,6 @@ private fun EmojiButton(
Text( Text(
emoji, emoji,
style = ElementTheme.typography.fontBodyLgRegular.copy(fontSize = 24.dp.toSp(), color = Color.White), style = ElementTheme.typography.fontBodyLgRegular.copy(fontSize = 24.dp.toSp(), color = Color.White),
modifier = Modifier
.clickable(
enabled = true,
onClick = { onClick(emoji) },
indication = ripple(bounded = false, radius = emojiRippleRadius),
interactionSource = remember { MutableInteractionSource() }
)
) )
} }
} }

View file

@ -48,6 +48,7 @@ internal fun DisabledComposerView(
IconColorButton( IconColorButton(
onClick = {}, onClick = {},
imageVector = CompoundIcons.Plus(), imageVector = CompoundIcons.Plus(),
contentDescription = null,
iconColorButtonStyle = IconColorButtonStyle.Disabled, iconColorButtonStyle = IconColorButtonStyle.Disabled,
) )

View file

@ -265,7 +265,7 @@ internal interface PinnedMessagesBannerViewScrollBehavior {
internal object PinnedMessagesBannerViewDefaults { internal object PinnedMessagesBannerViewDefaults {
@Composable @Composable
fun rememberExitOnScrollBehavior(): PinnedMessagesBannerViewScrollBehavior = remember { fun rememberScrollBehavior(pinnedMessagesCount: Int): PinnedMessagesBannerViewScrollBehavior = remember(pinnedMessagesCount) {
ExitOnScrollBehavior() ExitOnScrollBehavior()
} }
} }

View file

@ -8,7 +8,6 @@
package io.element.android.features.messages.impl.timeline package io.element.android.features.messages.impl.timeline
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.accessibility.AccessibilityManager
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
@ -75,6 +74,7 @@ import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.isTalkbackActive
import io.element.android.wysiwyg.link.Link import io.element.android.wysiwyg.link.Link
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
@ -126,10 +126,7 @@ fun TimelineView(
val context = LocalContext.current val context = LocalContext.current
val view = LocalView.current val view = LocalView.current
// Disable reverse layout when TalkBack is enabled to avoid incorrect ordering issues seen in the current Compose UI version // Disable reverse layout when TalkBack is enabled to avoid incorrect ordering issues seen in the current Compose UI version
val useReverseLayout = remember { val useReverseLayout = !isTalkbackActive()
val accessibilityManager = context.getSystemService(AccessibilityManager::class.java)
accessibilityManager.isTouchExplorationEnabled.not()
}
fun inReplyToClick(eventId: EventId) { fun inReplyToClick(eventId: EventId) {
state.eventSink(TimelineEvents.FocusOnEvent(eventId)) state.eventSink(TimelineEvents.FocusOnEvent(eventId))
@ -159,7 +156,7 @@ fun TimelineView(
.testTag(TestTags.timeline), .testTag(TestTags.timeline),
state = lazyListState, state = lazyListState,
reverseLayout = useReverseLayout, reverseLayout = useReverseLayout,
contentPadding = PaddingValues(vertical = 8.dp), contentPadding = PaddingValues(top = 64.dp, bottom = 8.dp),
) { ) {
items( items(
items = state.timelineItems, items = state.timelineItems,

View file

@ -49,6 +49,7 @@ import io.element.android.libraries.designsystem.theme.messageFromMeBackground
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.utils.time.isTalkbackActive
private val BUBBLE_RADIUS = 12.dp private val BUBBLE_RADIUS = 12.dp
private val avatarRadius = AvatarSize.TimelineSender.dp / 2 private val avatarRadius = AvatarSize.TimelineSender.dp / 2
@ -95,6 +96,17 @@ fun MessageEventBubble(
} }
} }
val clickableModifier = if (isTalkbackActive()) {
Modifier
} else {
Modifier.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
indication = ripple(),
interactionSource = interactionSource
)
}
// Ignore state.isHighlighted for now, we need a design decision on it. // Ignore state.isHighlighted for now, we need a design decision on it.
val backgroundBubbleColor = when { val backgroundBubbleColor = when {
state.isMine -> ElementTheme.colors.messageFromMeBackground state.isMine -> ElementTheme.colors.messageFromMeBackground
@ -137,12 +149,7 @@ fun MessageEventBubble(
.toDp() .toDp()
) )
.clip(bubbleShape) .clip(bubbleShape)
.combinedClickable( .then(clickableModifier),
onClick = onClick,
onLongClick = onLongClick,
indication = ripple(),
interactionSource = interactionSource
),
color = backgroundBubbleColor, color = backgroundBubbleColor,
shape = bubbleShape, shape = bubbleShape,
content = content content = content

View file

@ -18,6 +18,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
@ -77,7 +79,8 @@ fun TimelineEventTimestampView(
.size(15.dp, 18.dp) .size(15.dp, 18.dp)
.clickable(isVerifiedUserSendFailure) { .clickable(isVerifiedUserSendFailure) {
eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event))
}, }
.semantics { invisibleToUser() }
) )
} }
@ -91,7 +94,8 @@ fun TimelineEventTimestampView(
.size(15.dp) .size(15.dp)
.clickable { .clickable {
eventSink(TimelineEvents.ShowShieldDialog(shield)) eventSink(TimelineEvents.ShowShieldDialog(shield))
}, }
.semantics { invisibleToUser() },
tint = shield.toIconColor(), tint = shield.toIconColor(),
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))

View file

@ -37,13 +37,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.invisibleToUser import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.traversalIndex
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -96,6 +95,7 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.isTalkbackActive
import io.element.android.wysiwyg.link.Link import io.element.android.wysiwyg.link.Link
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.abs import kotlin.math.abs
@ -245,7 +245,7 @@ fun TimelineItemEventRow(
), ),
renderReadReceipts = renderReadReceipts, renderReadReceipts = renderReadReceipts,
onReadReceiptsClick = { onReadReceiptClick(event) }, onReadReceiptsClick = { onReadReceiptClick(event) },
modifier = Modifier.padding(top = 4.dp), modifier = Modifier.padding(top = 4.dp)
) )
} }
} }
@ -595,7 +595,10 @@ private fun MessageEventBubbleContent(
timestampPosition = timestampPosition, timestampPosition = timestampPosition,
eventSink = eventSink, eventSink = eventSink,
canShrinkContent = canShrinkContent, canShrinkContent = canShrinkContent,
modifier = timestampLayoutModifier, modifier = timestampLayoutModifier.semantics(mergeDescendants = false) {
isTraversalGroup = true
traversalIndex = -1f
},
content = { onContentLayoutChange -> content = { onContentLayoutChange ->
eventContentView(contentModifier, onContentLayoutChange) eventContentView(contentModifier, onContentLayoutChange)
} }
@ -607,17 +610,23 @@ private fun MessageEventBubbleContent(
val inReplyToModifier = Modifier val inReplyToModifier = Modifier
.padding(top = topPadding, start = 8.dp, end = 8.dp) .padding(top = topPadding, start = 8.dp, end = 8.dp)
.clip(RoundedCornerShape(6.dp)) .clip(RoundedCornerShape(6.dp))
// FIXME when a node is clickable, its contents won't be added to the semantics tree of its parent
.clickable(onClick = inReplyToClick) val talkbackCompatModifier = if (isTalkbackActive()) {
// Use z-index to make the replied to text being read after the message
// Usually, you'd use traversalIndex for that, but it's not working for some reason
inReplyToModifier.zIndex(1f)
} else {
inReplyToModifier.clickable(onClick = inReplyToClick)
}
InReplyToView( InReplyToView(
inReplyTo = inReplyTo, inReplyTo = inReplyTo,
hideImage = timelineProtectionState.hideMediaContent(inReplyTo.eventId()), hideImage = timelineProtectionState.hideMediaContent(inReplyTo.eventId()),
modifier = inReplyToModifier, modifier = talkbackCompatModifier,
) )
} }
if (inReplyToDetails != null) { if (inReplyToDetails != null) {
// Use SubComposeLayout only if necessary as it can have consequences on the performance. // Use SubComposeLayout only if necessary as it can have consequences on the performance.
EqualWidthColumn(modifier = modifier, spacing = 8.dp) { EqualWidthColumn(spacing = 8.dp) {
threadDecoration() threadDecoration()
inReplyTo(inReplyToDetails) inReplyTo(inReplyToDetails)
contentWithTimestamp() contentWithTimestamp()
@ -651,9 +660,7 @@ private fun MessageEventBubbleContent(
paddingBehaviour = paddingBehaviour, paddingBehaviour = paddingBehaviour,
inReplyToDetails = event.inReplyTo, inReplyToDetails = event.inReplyTo,
canShrinkContent = event.content is TimelineItemVoiceContent, canShrinkContent = event.content is TimelineItemVoiceContent,
modifier = bubbleModifier.semantics(mergeDescendants = true) { modifier = bubbleModifier,
contentDescription = event.safeSenderName
}
) )
} }

View file

@ -14,6 +14,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
@ -25,6 +27,7 @@ import io.element.android.features.messages.impl.timeline.components.layout.Cont
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
@ -140,7 +143,16 @@ private fun TimelineItemGroupedEventsRowContent(
}, },
) { ) {
Column(modifier = modifier.animateContentSize()) { Column(modifier = modifier.animateContentSize()) {
val groupedEventsTitle = pluralStringResource(
id = R.plurals.screen_room_timeline_state_changes,
count = timelineItem.events.size,
timelineItem.events.size
)
GroupHeaderView( GroupHeaderView(
modifier = Modifier.clearAndSetSemantics {
val groupedEventsContent = timelineItem.events.reversed().joinToString(separator = "\n") { (it.content as TimelineItemStateContent).body }
contentDescription = groupedEventsTitle + groupedEventsContent
},
text = pluralStringResource( text = pluralStringResource(
id = R.plurals.screen_room_timeline_state_changes, id = R.plurals.screen_room_timeline_state_changes,
count = timelineItem.events.size, count = timelineItem.events.size,

View file

@ -16,6 +16,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.R
@ -40,7 +42,9 @@ fun TimelineItemReactionsView(
) { ) {
var expanded: Boolean by rememberSaveable { mutableStateOf(false) } var expanded: Boolean by rememberSaveable { mutableStateOf(false) }
TimelineItemReactionsView( TimelineItemReactionsView(
modifier = modifier, modifier = modifier.semantics {
invisibleToUser()
},
reactions = reactionsState.reactions, reactions = reactionsState.reactions,
userCanSendReaction = userCanSendReaction, userCanSendReaction = userCanSendReaction,
expanded = expanded, expanded = expanded,

View file

@ -7,6 +7,8 @@
package io.element.android.features.messages.impl.timeline.components package io.element.android.features.messages.impl.timeline.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -17,6 +19,9 @@ import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
@ -28,6 +33,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
@ -37,8 +43,12 @@ import io.element.android.libraries.designsystem.theme.LocalBuildMeta
import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.isTalkbackActive
import io.element.android.wysiwyg.link.Link import io.element.android.wysiwyg.link.Link
import kotlin.time.DurationUnit
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
internal fun TimelineItemRow( internal fun TimelineItemRow(
timelineItem: TimelineItem, timelineItem: TimelineItem,
@ -120,7 +130,28 @@ internal fun TimelineItemRow(
) )
} }
else -> { else -> {
val a11yVoiceMessage = stringResource(CommonStrings.a11y_voice_message)
TimelineItemEventRow( TimelineItemEventRow(
modifier = Modifier
.semantics(mergeDescendants = true) {
contentDescription = if (timelineItem.content is TimelineItemVoiceContent) {
val voiceMessageText = String.format(a11yVoiceMessage, timelineItem.content.duration.toString(DurationUnit.MINUTES))
"${timelineItem.safeSenderName}, $voiceMessageText"
} else {
timelineItem.safeSenderName
}
}
// Custom clickable that applies over the whole item for accessibility
.then(
if (isTalkbackActive()) {
Modifier.combinedClickable(
onClick = { onContentClick(timelineItem) },
onLongClick = { onLongClick(timelineItem) }
)
} else {
Modifier
}
),
event = timelineItem, event = timelineItem,
timelineRoomInfo = timelineRoomInfo, timelineRoomInfo = timelineRoomInfo,
renderReadReceipts = renderReadReceipts, renderReadReceipts = renderReadReceipts,

View file

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate import androidx.compose.ui.draw.rotate
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
@ -21,6 +22,7 @@ import io.element.android.libraries.designsystem.icons.CompoundDrawables
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun TimelineItemFileView( fun TimelineItemFileView(
@ -37,7 +39,7 @@ fun TimelineItemFileView(
icon = { icon = {
Icon( Icon(
resourceId = CompoundDrawables.ic_compound_attachment, resourceId = CompoundDrawables.ic_compound_attachment,
contentDescription = null, contentDescription = stringResource(CommonStrings.common_file),
tint = ElementTheme.colors.iconPrimary, tint = ElementTheme.colors.iconPrimary,
modifier = Modifier modifier = Modifier
.size(16.dp) .size(16.dp)

View file

@ -33,8 +33,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
@ -55,6 +53,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.isTalkbackActive
import io.element.android.wysiwyg.compose.EditorStyledText import io.element.android.wysiwyg.compose.EditorStyledText
import io.element.android.wysiwyg.link.Link import io.element.android.wysiwyg.link.Link
@ -71,10 +70,9 @@ fun TimelineItemImageView(
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val description = stringResource(CommonStrings.common_image) val a11yLabel = stringResource(CommonStrings.common_image)
Column( val description = content.caption?.let { "$a11yLabel: $it" } ?: a11yLabel
modifier = modifier.semantics { contentDescription = description }, Column(modifier = modifier) {
) {
val containerModifier = if (content.showCaption) { val containerModifier = if (content.showCaption) {
Modifier.clip(RoundedCornerShape(10.dp)) Modifier.clip(RoundedCornerShape(10.dp))
} else { } else {
@ -93,7 +91,16 @@ fun TimelineItemImageView(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.then(if (isLoaded) Modifier.background(Color.White) else Modifier) .then(if (isLoaded) Modifier.background(Color.White) else Modifier)
.then(if (onContentClick != null) Modifier.combinedClickable(onClick = onContentClick, onLongClick = onLongClick) else Modifier), .then(
if (!isTalkbackActive() && onContentClick != null) {
Modifier.combinedClickable(
onClick = onContentClick,
onLongClick = onLongClick
)
} else {
Modifier
}
),
model = content.thumbnailMediaRequestData, model = content.thumbnailMediaRequestData,
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
alignment = Alignment.Center, alignment = Alignment.Center,

View file

@ -36,7 +36,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH
import io.element.android.libraries.matrix.ui.media.MediaRequestData import io.element.android.libraries.matrix.ui.media.MediaRequestData
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.isTalkbackActive
import io.element.android.wysiwyg.compose.EditorStyledText import io.element.android.wysiwyg.compose.EditorStyledText
import io.element.android.wysiwyg.link.Link import io.element.android.wysiwyg.link.Link
@ -79,10 +80,10 @@ fun TimelineItemVideoView(
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val description = stringResource(CommonStrings.common_video) val isTalkbackActive = isTalkbackActive()
Column( val a11yLabel = stringResource(CommonStrings.common_video)
modifier = modifier.semantics { contentDescription = description } val description = content.caption?.let { "$a11yLabel: $it" } ?: a11yLabel
) { Column(modifier = modifier) {
val containerModifier = if (content.showCaption) { val containerModifier = if (content.showCaption) {
Modifier Modifier
.padding(top = 6.dp) .padding(top = 6.dp)
@ -104,7 +105,16 @@ fun TimelineItemVideoView(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.then(if (isLoaded) Modifier.background(Color.White) else Modifier) .then(if (isLoaded) Modifier.background(Color.White) else Modifier)
.then(if (onContentClick != null) Modifier.combinedClickable(onClick = onContentClick, onLongClick = onLongClick) else Modifier), .then(
if (!isTalkbackActive && onContentClick != null) {
Modifier.combinedClickable(
onClick = onContentClick,
onLongClick = onLongClick
)
} else {
Modifier
}
),
model = MediaRequestData( model = MediaRequestData(
source = content.thumbnailSource, source = content.thumbnailSource,
kind = MediaRequestData.Kind.Thumbnail( kind = MediaRequestData.Kind.Thumbnail(
@ -126,6 +136,7 @@ fun TimelineItemVideoView(
imageVector = CompoundIcons.PlaySolid(), imageVector = CompoundIcons.PlaySolid(),
contentDescription = stringResource(id = CommonStrings.a11y_play), contentDescription = stringResource(id = CommonStrings.a11y_play),
colorFilter = ColorFilter.tint(Color.White), colorFilter = ColorFilter.tint(Color.White),
modifier = Modifier.semantics { invisibleToUser() }
) )
} }
} }

View file

@ -27,10 +27,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@ -40,7 +41,6 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentProvider import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContentProvider
import io.element.android.libraries.androidutils.accessibility.isScreenReaderEnabled
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -49,6 +49,7 @@ import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.isTalkbackActive
import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents
import io.element.android.libraries.voiceplayer.api.VoiceMessageState import io.element.android.libraries.voiceplayer.api.VoiceMessageState
import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider import io.element.android.libraries.voiceplayer.api.VoiceMessageStateProvider
@ -66,10 +67,27 @@ fun TimelineItemVoiceView(
} }
val a11y = stringResource(CommonStrings.common_voice_message) val a11y = stringResource(CommonStrings.common_voice_message)
val a11yActionLabel = stringResource(
when (state.button) {
VoiceMessageState.Button.Play -> CommonStrings.a11y_play
VoiceMessageState.Button.Pause -> CommonStrings.a11y_pause
VoiceMessageState.Button.Downloading -> CommonStrings.common_downloading
VoiceMessageState.Button.Retry -> CommonStrings.action_retry
VoiceMessageState.Button.Disabled -> CommonStrings.error_unknown
}
)
Row( Row(
modifier = modifier modifier = modifier
.semantics { .clearAndSetSemantics {
contentDescription = a11y contentDescription = a11y
if (state.button == VoiceMessageState.Button.Disabled) {
disabled()
} else if (state.button in listOf(VoiceMessageState.Button.Play, VoiceMessageState.Button.Pause)) {
onClick(label = a11yActionLabel) {
playPause()
true
}
}
} }
.onSizeChanged { .onSizeChanged {
onContentLayoutChange( onContentLayoutChange(
@ -81,12 +99,14 @@ fun TimelineItemVoiceView(
}, },
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
when (state.button) { if (!isTalkbackActive()) {
VoiceMessageState.Button.Play -> PlayButton(onClick = ::playPause) when (state.button) {
VoiceMessageState.Button.Pause -> PauseButton(onClick = ::playPause) VoiceMessageState.Button.Play -> PlayButton(onClick = ::playPause)
VoiceMessageState.Button.Downloading -> ProgressButton() VoiceMessageState.Button.Pause -> PauseButton(onClick = ::playPause)
VoiceMessageState.Button.Retry -> RetryButton(onClick = ::playPause) VoiceMessageState.Button.Downloading -> ProgressButton()
VoiceMessageState.Button.Disabled -> PlayButton(onClick = {}, enabled = false) VoiceMessageState.Button.Retry -> RetryButton(onClick = ::playPause)
VoiceMessageState.Button.Disabled -> PlayButton(onClick = {}, enabled = false)
}
} }
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
Text( Text(
@ -97,13 +117,12 @@ fun TimelineItemVoiceView(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
val context = LocalContext.current
WaveformPlaybackView( WaveformPlaybackView(
showCursor = state.showCursor, showCursor = state.showCursor,
playbackProgress = state.progress, playbackProgress = state.progress,
waveform = content.waveform, waveform = content.waveform,
modifier = Modifier.height(34.dp), modifier = Modifier.height(34.dp),
seekEnabled = !context.isScreenReaderEnabled(), seekEnabled = !isTalkbackActive(),
onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) }, onSeek = { state.eventSink(VoiceMessageEvents.Seek(it)) },
) )
} }

View file

@ -26,6 +26,7 @@ import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.invisibleToUser
import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -56,7 +57,11 @@ fun TimelineItemReadReceiptView(
) { ) {
if (state.receipts.isNotEmpty()) { if (state.receipts.isNotEmpty()) {
if (renderReadReceipts) { if (renderReadReceipts) {
ReadReceiptsRow(modifier = modifier) { ReadReceiptsRow(
modifier = modifier.clearAndSetSemantics {
invisibleToUser()
}
) {
ReadReceiptsAvatars( ReadReceiptsAvatars(
receipts = state.receipts, receipts = state.receipts,
modifier = Modifier modifier = Modifier

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_picker_category_activity">"Gweithgareddau"</string>
<string name="emoji_picker_category_flags">"Baneri"</string>
<string name="emoji_picker_category_foods">"Bwyd a Diod"</string>
<string name="emoji_picker_category_nature">"Anifeiliaid a Natur"</string>
<string name="emoji_picker_category_objects">"Gwrthrychau"</string>
<string name="emoji_picker_category_people">"Wynebau Hapus a Phobl"</string>
<string name="emoji_picker_category_places">"Teithio a Llefydd"</string>
<string name="emoji_picker_category_symbols">"Symbolau"</string>
<string name="screen_report_content_block_user">"Rhwystro defnyddiwr"</string>
<string name="screen_room_attachment_source_camera">"Camera"</string>
<string name="screen_room_attachment_source_camera_photo">"Cymryd llun"</string>
<string name="screen_room_attachment_source_files">"Atodiad"</string>
<string name="screen_room_attachment_source_location">"Lleoliad"</string>
<string name="screen_room_attachment_source_poll">"Pôl"</string>
<string name="screen_room_mentions_at_room_subtitle">"Rhowch wybod i\'r ystafell gyfan"</string>
<string name="screen_room_mentions_at_room_title">"Pawb"</string>
<string name="screen_room_timeline_less_reactions">"Dangos llai"</string>
<string name="screen_room_timeline_reactions_show_less">"Dangos llai"</string>
<string name="screen_room_timeline_reactions_show_more">"Dangos rhagor"</string>
<string name="screen_room_timeline_read_marker_title">"Newydd"</string>
<string name="screen_room_typing_two_members">"%1$s a %2$s"</string>
</resources>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_picker_category_activity">"Veikla"</string>
<string name="emoji_picker_category_flags">"Vėliavos"</string>
<string name="emoji_picker_category_foods">"Maistas ir Gėrimai"</string>
<string name="emoji_picker_category_nature">"Gyvūnai ir Gamta"</string>
<string name="emoji_picker_category_objects">"Objektai"</string>
<string name="emoji_picker_category_people">"Šypsenėlės ir Žmonės"</string>
<string name="emoji_picker_category_places">"Kelionės ir Vietovės"</string>
<string name="emoji_picker_category_symbols">"Simboliai"</string>
<string name="screen_report_content_block_user">"Blokuoti vartotoją"</string>
<string name="screen_report_content_block_user_hint">"Pažymėkite, jei norite paslėpti visas esamas ir būsimas šio vartotojo žinutes"</string>
<string name="screen_report_content_explanation">"Apie šią žinutę bus pranešta Jūsų serverio administracijai. Jie negalės perskaityti jokių užšifruotų žinučių."</string>
<string name="screen_report_content_hint">"Skundo dėl šio turinio priežastis"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Fotografuoti"</string>
<string name="screen_room_attachment_source_camera_video">"Įrašyti vaizdo įrašą"</string>
<string name="screen_room_attachment_source_files">"Priedas"</string>
<string name="screen_room_attachment_source_gallery">"Nuotraukų ir vaizdo įrašų biblioteka"</string>
<string name="screen_room_timeline_beginning_of_room">"Tai yra %1$s pradžia."</string>
<string name="screen_room_timeline_beginning_of_room_no_name">"Tai yra šio pokalbio pradžia."</string>
<string name="screen_room_timeline_read_marker_title">"Naujų"</string>
<plurals name="screen_room_timeline_state_changes">
<item quantity="one">"%1$d kambario pakeitimas"</item>
<item quantity="few">"%1$d kambario pakeitimai"</item>
<item quantity="other">"%1$d kambario pakeitimų"</item>
</plurals>
</resources>

View file

@ -21,6 +21,7 @@
<string name="screen_room_attachment_source_poll">"Enquete"</string> <string name="screen_room_attachment_source_poll">"Enquete"</string>
<string name="screen_room_attachment_text_formatting">"Formatação de texto"</string> <string name="screen_room_attachment_text_formatting">"Formatação de texto"</string>
<string name="screen_room_encrypted_history_banner">"O histórico de mensagens não está disponível no momento."</string> <string name="screen_room_encrypted_history_banner">"O histórico de mensagens não está disponível no momento."</string>
<string name="screen_room_encrypted_history_banner_unverified">"O histórico de mensagens não está disponível nesta sala. Verifique este dispositivo para ver seu histórico de mensagens."</string>
<string name="screen_room_invite_again_alert_message">"Gostaria de convidá-los de volta?"</string> <string name="screen_room_invite_again_alert_message">"Gostaria de convidá-los de volta?"</string>
<string name="screen_room_invite_again_alert_title">"Você está sozinho neste chat"</string> <string name="screen_room_invite_again_alert_title">"Você está sozinho neste chat"</string>
<string name="screen_room_mentions_at_room_subtitle">"Notificar a sala inteira"</string> <string name="screen_room_mentions_at_room_subtitle">"Notificar a sala inteira"</string>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_onboarding_sign_up">"Creu cyfrif"</string>
</resources>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_onboarding_sign_in_manually">"Prisijunkite rankiniu būdu"</string>
<string name="screen_onboarding_sign_in_with_qr_code">"Prisijunkite naudodami QR kodą"</string>
<string name="screen_onboarding_sign_up">"Sukurti paskyrą"</string>
<string name="screen_onboarding_welcome_subtitle">"Sveiki atvykę į %1$s. Įkrautas greitumui ir paprastumui."</string>
<string name="screen_onboarding_welcome_title">"Būkite savo elemente"</string>
</resources>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_create_poll_add_option_btn">"Ychwanegu dewis"</string>
<string name="screen_create_poll_cancel_confirmation_content_android">"Dyw eich newidiadau heb gael eu cadw. Ydych chi\'n siŵr eich bod am fynd nôl?"</string>
<string name="screen_create_poll_title">"Creu Pôl"</string>
<string name="screen_edit_poll_delete_confirmation_title">"Dileu Pôl"</string>
<string name="screen_edit_poll_title">"Golygu pôl"</string>
<string name="screen_polls_history_filter_ongoing">"Parhaus"</string>
<string name="screen_polls_history_title">"Polau"</string>
</resources>

View file

@ -12,8 +12,8 @@
<string name="screen_edit_poll_delete_confirmation_title">"Umfrage löschen"</string> <string name="screen_edit_poll_delete_confirmation_title">"Umfrage löschen"</string>
<string name="screen_edit_poll_title">"Umfrage bearbeiten"</string> <string name="screen_edit_poll_title">"Umfrage bearbeiten"</string>
<string name="screen_polls_history_empty_ongoing">"Keine laufenden Umfragen vorhanden."</string> <string name="screen_polls_history_empty_ongoing">"Keine laufenden Umfragen vorhanden."</string>
<string name="screen_polls_history_empty_past">"Keine vergangenen Umfragen vorhanden."</string> <string name="screen_polls_history_empty_past">"Keine beendeten Umfragen vorhanden."</string>
<string name="screen_polls_history_filter_ongoing">"Aktuell"</string> <string name="screen_polls_history_filter_ongoing">"Aktuell"</string>
<string name="screen_polls_history_filter_past">"Vergangene"</string> <string name="screen_polls_history_filter_past">"Vergangenheit"</string>
<string name="screen_polls_history_title">"Umfragen"</string> <string name="screen_polls_history_title">"Umfragen"</string>
</resources> </resources>

View file

@ -14,5 +14,6 @@
<string name="screen_polls_history_empty_ongoing">"Finner ingen pågående avstemninger."</string> <string name="screen_polls_history_empty_ongoing">"Finner ingen pågående avstemninger."</string>
<string name="screen_polls_history_empty_past">"Kan ikke finne noen tidligere avstemninger."</string> <string name="screen_polls_history_empty_past">"Kan ikke finne noen tidligere avstemninger."</string>
<string name="screen_polls_history_filter_ongoing">"Pågående"</string> <string name="screen_polls_history_filter_ongoing">"Pågående"</string>
<string name="screen_polls_history_filter_past">"Fortid"</string>
<string name="screen_polls_history_title">"Avstemninger"</string> <string name="screen_polls_history_title">"Avstemninger"</string>
</resources> </resources>

View file

@ -7,16 +7,13 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import io.element.android.compound.theme.Theme
import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.MediaPreviewValue
sealed interface AdvancedSettingsEvents { sealed interface AdvancedSettingsEvents {
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents
data object ChangeTheme : AdvancedSettingsEvents data class SetTheme(val theme: ThemeOption) : AdvancedSettingsEvents
data object CancelChangeTheme : AdvancedSettingsEvents
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents
data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvents data class SetTimelineMediaPreviewValue(val value: MediaPreviewValue) : AdvancedSettingsEvents
data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvents data class SetHideInviteAvatars(val value: Boolean) : AdvancedSettingsEvents
} }

View file

@ -9,11 +9,10 @@ package io.element.android.features.preferences.impl.advanced
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.compound.theme.Theme import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.mapToTheme import io.element.android.compound.theme.mapToTheme
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -39,11 +38,9 @@ class AdvancedSettingsPresenter @Inject constructor(
val doesCompressMedia by remember { val doesCompressMedia by remember {
sessionPreferencesStore.doesCompressMedia() sessionPreferencesStore.doesCompressMedia()
}.collectAsState(initial = true) }.collectAsState(initial = true)
val theme by remember { val theme = remember {
appPreferencesStore.getThemeFlow().mapToTheme() appPreferencesStore.getThemeFlow().mapToTheme()
}.collectAsState(initial = Theme.System) }.collectAsState(initial = Theme.System)
var showChangeThemeDialog by remember { mutableStateOf(false) }
val hideInviteAvatars by remember { val hideInviteAvatars by remember {
appPreferencesStore.getHideInviteAvatarsFlow() appPreferencesStore.getHideInviteAvatarsFlow()
}.collectAsState(false) }.collectAsState(false)
@ -52,6 +49,16 @@ class AdvancedSettingsPresenter @Inject constructor(
appPreferencesStore.getTimelineMediaPreviewValueFlow() appPreferencesStore.getTimelineMediaPreviewValueFlow()
}.collectAsState(initial = MediaPreviewValue.On) }.collectAsState(initial = MediaPreviewValue.On)
val themeOption by remember {
derivedStateOf {
when (theme.value) {
Theme.System -> ThemeOption.System
Theme.Dark -> ThemeOption.Dark
Theme.Light -> ThemeOption.Light
}
}
}
fun handleEvents(event: AdvancedSettingsEvents) { fun handleEvents(event: AdvancedSettingsEvents) {
when (event) { when (event) {
is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetDeveloperModeEnabled -> localCoroutineScope.launch {
@ -63,11 +70,12 @@ class AdvancedSettingsPresenter @Inject constructor(
is AdvancedSettingsEvents.SetCompressMedia -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetCompressMedia -> localCoroutineScope.launch {
sessionPreferencesStore.setCompressMedia(event.compress) sessionPreferencesStore.setCompressMedia(event.compress)
} }
AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false
AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true
is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch {
appPreferencesStore.setTheme(event.theme.name) when (event.theme) {
showChangeThemeDialog = false ThemeOption.System -> appPreferencesStore.setTheme(Theme.System.name)
ThemeOption.Dark -> appPreferencesStore.setTheme(Theme.Dark.name)
ThemeOption.Light -> appPreferencesStore.setTheme(Theme.Light.name)
}
} }
is AdvancedSettingsEvents.SetHideInviteAvatars -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetHideInviteAvatars -> localCoroutineScope.launch {
appPreferencesStore.setHideInviteAvatars(event.value) appPreferencesStore.setHideInviteAvatars(event.value)
@ -82,8 +90,7 @@ class AdvancedSettingsPresenter @Inject constructor(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia, doesCompressMedia = doesCompressMedia,
theme = theme, theme = themeOption,
showChangeThemeDialog = showChangeThemeDialog,
hideInviteAvatars = hideInviteAvatars, hideInviteAvatars = hideInviteAvatars,
timelineMediaPreviewValue = timelineMediaPreviewValue, timelineMediaPreviewValue = timelineMediaPreviewValue,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }

View file

@ -7,16 +7,33 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import io.element.android.compound.theme.Theme import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import io.element.android.libraries.designsystem.components.preferences.DropdownOption
import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings
data class AdvancedSettingsState( data class AdvancedSettingsState(
val isDeveloperModeEnabled: Boolean, val isDeveloperModeEnabled: Boolean,
val isSharePresenceEnabled: Boolean, val isSharePresenceEnabled: Boolean,
val doesCompressMedia: Boolean, val doesCompressMedia: Boolean,
val theme: Theme, val theme: ThemeOption,
val showChangeThemeDialog: Boolean,
val hideInviteAvatars: Boolean, val hideInviteAvatars: Boolean,
val timelineMediaPreviewValue: MediaPreviewValue, val timelineMediaPreviewValue: MediaPreviewValue,
val eventSink: (AdvancedSettingsEvents) -> Unit val eventSink: (AdvancedSettingsEvents) -> Unit
) )
enum class ThemeOption : DropdownOption {
System {
@Composable
override fun getText(): String = stringResource(CommonStrings.common_system)
},
Dark {
@Composable
override fun getText(): String = stringResource(CommonStrings.common_dark)
},
Light {
@Composable
override fun getText(): String = stringResource(CommonStrings.common_light)
}
}

View file

@ -8,7 +8,6 @@
package io.element.android.features.preferences.impl.advanced package io.element.android.features.preferences.impl.advanced
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.compound.theme.Theme
import io.element.android.libraries.matrix.api.media.MediaPreviewValue import io.element.android.libraries.matrix.api.media.MediaPreviewValue
open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> { open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSettingsState> {
@ -16,7 +15,6 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
get() = sequenceOf( get() = sequenceOf(
aAdvancedSettingsState(), aAdvancedSettingsState(),
aAdvancedSettingsState(isDeveloperModeEnabled = true), aAdvancedSettingsState(isDeveloperModeEnabled = true),
aAdvancedSettingsState(showChangeThemeDialog = true),
aAdvancedSettingsState(isSharePresenceEnabled = true), aAdvancedSettingsState(isSharePresenceEnabled = true),
aAdvancedSettingsState(doesCompressMedia = true), aAdvancedSettingsState(doesCompressMedia = true),
aAdvancedSettingsState(hideInviteAvatars = true), aAdvancedSettingsState(hideInviteAvatars = true),
@ -28,16 +26,15 @@ fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false,
isSharePresenceEnabled: Boolean = false, isSharePresenceEnabled: Boolean = false,
doesCompressMedia: Boolean = false, doesCompressMedia: Boolean = false,
showChangeThemeDialog: Boolean = false,
hideInviteAvatars: Boolean = false, hideInviteAvatars: Boolean = false,
theme: ThemeOption = ThemeOption.System,
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On, timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
eventSink: (AdvancedSettingsEvents) -> Unit = {}, eventSink: (AdvancedSettingsEvents) -> Unit = {},
) = AdvancedSettingsState( ) = AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia, doesCompressMedia = doesCompressMedia,
theme = Theme.System, theme = theme,
showChangeThemeDialog = showChangeThemeDialog,
hideInviteAvatars = hideInviteAvatars, hideInviteAvatars = hideInviteAvatars,
timelineMediaPreviewValue = timelineMediaPreviewValue, timelineMediaPreviewValue = timelineMediaPreviewValue,
eventSink = eventSink eventSink = eventSink

View file

@ -12,14 +12,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.theme.Theme
import io.element.android.compound.theme.themes
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.dialogs.ListOption
import io.element.android.libraries.designsystem.components.dialogs.SingleSelectionDialog
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory
import io.element.android.libraries.designsystem.components.preferences.PreferenceDropdown
import io.element.android.libraries.designsystem.components.preferences.PreferencePage import io.element.android.libraries.designsystem.components.preferences.PreferencePage
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
@ -34,8 +31,7 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.compose.LocalAnalyticsService import io.element.android.services.analytics.compose.LocalAnalyticsService
import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import io.element.android.services.analyticsproviders.api.trackers.captureInteraction
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
fun AdvancedSettingsView( fun AdvancedSettingsView(
@ -49,15 +45,12 @@ fun AdvancedSettingsView(
onBackClick = onBackClick, onBackClick = onBackClick,
title = stringResource(id = CommonStrings.common_advanced_settings) title = stringResource(id = CommonStrings.common_advanced_settings)
) { ) {
ListItem( PreferenceDropdown(
headlineContent = { title = stringResource(id = CommonStrings.common_appearance),
Text(text = stringResource(id = CommonStrings.common_appearance)) selectedOption = state.theme,
}, options = ThemeOption.entries.toPersistentList(),
trailingContent = ListItemContent.Text( onSelectOption = { logLevel ->
state.theme.toHumanReadable() state.eventSink(AdvancedSettingsEvents.SetTheme(logLevel))
),
onClick = {
state.eventSink(AdvancedSettingsEvents.ChangeTheme)
} }
) )
ListItem( ListItem(
@ -108,21 +101,6 @@ fun AdvancedSettingsView(
) )
ModerationAndSafety(state) ModerationAndSafety(state)
} }
if (state.showChangeThemeDialog) {
SingleSelectionDialog(
options = getOptions(),
initialSelection = themes.indexOf(state.theme),
onSelectOption = {
state.eventSink(
AdvancedSettingsEvents.SetTheme(
themes[it]
)
)
},
onDismissRequest = { state.eventSink(AdvancedSettingsEvents.CancelChangeTheme) },
)
}
} }
@Composable @Composable
@ -176,24 +154,6 @@ private fun ModerationAndSafety(
} }
} }
@Composable
private fun getOptions(): ImmutableList<ListOption> {
return themes.map {
ListOption(title = it.toHumanReadable())
}.toImmutableList()
}
@Composable
private fun Theme.toHumanReadable(): String {
return stringResource(
id = when (this) {
Theme.System -> CommonStrings.common_system
Theme.Dark -> CommonStrings.common_dark
Theme.Light -> CommonStrings.common_light
}
)
}
@PreviewWithLargeHeight @PreviewWithLargeHeight
@Composable @Composable
internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) = internal fun AdvancedSettingsViewLightPreview(@PreviewParameter(AdvancedSettingsStateProvider::class) state: AdvancedSettingsState) =

View file

@ -7,22 +7,28 @@
package io.element.android.features.preferences.impl.developer.tracing package io.element.android.features.preferences.impl.developer.tracing
import androidx.compose.runtime.Composable
import io.element.android.libraries.designsystem.components.preferences.DropdownOption import io.element.android.libraries.designsystem.components.preferences.DropdownOption
enum class LogLevelItem : DropdownOption { enum class LogLevelItem : DropdownOption {
ERROR { ERROR {
override val text: String = "Error" @Composable
override fun getText(): String = "Error"
}, },
WARN { WARN {
override val text: String = "Warn" @Composable
override fun getText(): String = "Warn"
}, },
INFO { INFO {
override val text: String = "Info" @Composable
override fun getText(): String = "Info"
}, },
DEBUG { DEBUG {
override val text: String = "Debug" @Composable
override fun getText(): String = "Debug"
}, },
TRACE { TRACE {
override val text: String = "Trace" @Composable
override fun getText(): String = "Trace"
} }
} }

View file

@ -58,7 +58,7 @@ class EditUserProfilePresenter @AssistedInject constructor(
@Composable @Composable
override fun present(): EditUserProfileState { override fun present(): EditUserProfileState {
val cameraPermissionState = cameraPermissionPresenter.present() val cameraPermissionState = cameraPermissionPresenter.present()
var userAvatarUri by rememberSaveable { mutableStateOf(matrixUser.avatarUrl?.let { Uri.parse(it) }) } var userAvatarUri by rememberSaveable { mutableStateOf(matrixUser.avatarUrl?.toUri()) }
var userDisplayName by rememberSaveable { mutableStateOf(matrixUser.displayName) } var userDisplayName by rememberSaveable { mutableStateOf(matrixUser.displayName) }
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker( val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker(
onResult = { uri -> onResult = { uri ->

View file

@ -19,6 +19,11 @@
<string name="screen_advanced_settings_send_read_receipts_description">"Pokud je vypnuto, potvrzení o přečtení se nikomu neodesílají. Stále budete dostávat potvrzení o přečtení od ostatních uživatelů."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Pokud je vypnuto, potvrzení o přečtení se nikomu neodesílají. Stále budete dostávat potvrzení o přečtení od ostatních uživatelů."</string>
<string name="screen_advanced_settings_share_presence">"Sdílejte přítomnost"</string> <string name="screen_advanced_settings_share_presence">"Sdílejte přítomnost"</string>
<string name="screen_advanced_settings_share_presence_description">"Pokud je tato funkce vypnutá, nebudete moci odesílat ani přijímat potvrzení o přečtení ani upozornění o psaní."</string> <string name="screen_advanced_settings_share_presence_description">"Pokud je tato funkce vypnutá, nebudete moci odesílat ani přijímat potvrzení o přečtení ani upozornění o psaní."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Vždy skrýt"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Vždy zobrazit"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"V soukromých místnostech"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Skryté médium lze vždy zobrazit klepnutím na něj"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Zobrazit média na časové ose"</string>
<string name="screen_advanced_settings_view_source_description">"Povolit možnost zobrazení zdroje zprávy na časové ose."</string> <string name="screen_advanced_settings_view_source_description">"Povolit možnost zobrazení zdroje zprávy na časové ose."</string>
<string name="screen_blocked_users_empty">"Nemáte žádné blokované uživatele"</string> <string name="screen_blocked_users_empty">"Nemáte žádné blokované uživatele"</string>
<string name="screen_blocked_users_unblock_alert_action">"Odblokovat"</string> <string name="screen_blocked_users_unblock_alert_action">"Odblokovat"</string>
@ -60,6 +65,7 @@ Pokud budete pokračovat, některá nastavení se mohou změnit."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"systémová nastavení"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"systémová nastavení"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Systémová oznámení byla vypnuta"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Systémová oznámení byla vypnuta"</string>
<string name="screen_notification_settings_title">"Oznámení"</string> <string name="screen_notification_settings_title">"Oznámení"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Historie push oznámení"</string>
<string name="troubleshoot_notifications_entry_point_section">"Odstraňování problémů"</string> <string name="troubleshoot_notifications_entry_point_section">"Odstraňování problémů"</string>
<string name="troubleshoot_notifications_entry_point_title">"Odstraňování problémů s upozorněními"</string> <string name="troubleshoot_notifications_entry_point_title">"Odstraňování problémů s upozorněními"</string>
</resources> </resources>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="full_screen_intent_banner_message">"Er mwyn sicrhau fyddwch chi ddim yn colli galwad bwysig, newidiwch eich gosodiadau i ganiatáu hysbysiadau sgrin lawn pan fydd eich ffôn wedi\'i gloi."</string>
<string name="full_screen_intent_banner_title">"Gwella profiad eich galwadau"</string>
<string name="screen_advanced_settings_element_call_base_url">"URL sylfaen Galwad Element Cyfaddas"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Gosod URL sylfaen cyfaddas ar gyfer Galwad Element."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"URL annilys, gwnewch yn siŵr eich bod yn cynnwys y protocol (http/https) a\'r cyfeiriad cywir."</string>
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Cuddio afatarau yn y ceisiadau gwahoddiad i ystafell"</string>
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Cuddio rhagolygon cyfryngau yn y llinell amser"</string>
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Cymedroli a Diogelwch"</string>
<string name="screen_advanced_settings_send_read_receipts">"Derbynebau darllen"</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Cuddio bob tro"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Dangos bob tro"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"Mewn ystafelloedd preifat"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Mae modd dangos cyfrwng cudd trwy dapio arno"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Dangos cyfryngau mewn llinell amser"</string>
<string name="screen_blocked_users_unblock_alert_action">"Dad-rwystro"</string>
<string name="screen_edit_profile_display_name">"Enw dangos"</string>
<string name="screen_edit_profile_title">"Golygu proffil"</string>
<string name="screen_notification_settings_additional_settings_section_title">"Gosodiadau ychwanegol"</string>
<string name="screen_notification_settings_mentions_section_title">"Crybwyll"</string>
<string name="screen_notification_settings_mode_all">"Y Cyfan"</string>
<string name="screen_notification_settings_mode_mentions">"Crybwyll"</string>
<string name="screen_notification_settings_title">"Hysbysiadau"</string>
</resources>

View file

@ -19,6 +19,11 @@
<string name="screen_advanced_settings_send_read_receipts_description">"Wenn diese Option deaktiviert ist, werden Ihre Lesebestätigungen an niemanden gesendet. Du erhältst weiterhin Lesebestätigungen von anderen Benutzern."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Wenn diese Option deaktiviert ist, werden Ihre Lesebestätigungen an niemanden gesendet. Du erhältst weiterhin Lesebestätigungen von anderen Benutzern."</string>
<string name="screen_advanced_settings_share_presence">"Präsenz teilen"</string> <string name="screen_advanced_settings_share_presence">"Präsenz teilen"</string>
<string name="screen_advanced_settings_share_presence_description">"Wenn diese Option deaktiviert ist, kannst du keine Lesebestätigungen oder Tipp-Benachrichtigungen senden oder empfangen."</string> <string name="screen_advanced_settings_share_presence_description">"Wenn diese Option deaktiviert ist, kannst du keine Lesebestätigungen oder Tipp-Benachrichtigungen senden oder empfangen."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Immer verstecken"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Immer anzeigen"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"In privaten Chatrooms"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Ein verstecktes Medium kann jederzeit durch Antippen angezeigt werden"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Medien in der Zeitleiste anzeigen"</string>
<string name="screen_advanced_settings_view_source_description">"Option aktiveren, um Nachrichtenquelle in der Zeitleiste anzuzeigen."</string> <string name="screen_advanced_settings_view_source_description">"Option aktiveren, um Nachrichtenquelle in der Zeitleiste anzuzeigen."</string>
<string name="screen_blocked_users_empty">"Du hast keine blockierten Nutzer"</string> <string name="screen_blocked_users_empty">"Du hast keine blockierten Nutzer"</string>
<string name="screen_blocked_users_unblock_alert_action">"Blockierung aufheben"</string> <string name="screen_blocked_users_unblock_alert_action">"Blockierung aufheben"</string>
@ -58,6 +63,7 @@ Wenn du fortfährst, können sich einige deiner Einstellungen ändern."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"Systemeinstellungen"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"Systemeinstellungen"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Systembenachrichtigungen deaktiviert"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Systembenachrichtigungen deaktiviert"</string>
<string name="screen_notification_settings_title">"Benachrichtigungen"</string> <string name="screen_notification_settings_title">"Benachrichtigungen"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Verlauf pushen"</string>
<string name="troubleshoot_notifications_entry_point_section">"Fehlerbehebung"</string> <string name="troubleshoot_notifications_entry_point_section">"Fehlerbehebung"</string>
<string name="troubleshoot_notifications_entry_point_title">"Fehlerbehebung für Benachrichtigungen"</string> <string name="troubleshoot_notifications_entry_point_title">"Fehlerbehebung für Benachrichtigungen"</string>
</resources> </resources>

View file

@ -19,6 +19,11 @@
<string name="screen_advanced_settings_send_read_receipts_description">"Kui lülitad selle valiku välja, siis mitte keegi enam ei saa sinult lugemisteatisi. Küll aga saad sina teiste kasutajate lugemisteatisi."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Kui lülitad selle valiku välja, siis mitte keegi enam ei saa sinult lugemisteatisi. Küll aga saad sina teiste kasutajate lugemisteatisi."</string>
<string name="screen_advanced_settings_share_presence">"Jaga oma olekut"</string> <string name="screen_advanced_settings_share_presence">"Jaga oma olekut"</string>
<string name="screen_advanced_settings_share_presence_description">"Kui see eelistus on välja lülitatud, siis sa ei saa ega saada ei lugemisteatisi ega kirjutamise teavitusi."</string> <string name="screen_advanced_settings_share_presence_description">"Kui see eelistus on välja lülitatud, siis sa ei saa ega saada ei lugemisteatisi ega kirjutamise teavitusi."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Peida alati"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Näita alati"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"Privaatsetes jututubades"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Peidetud meediumi saad alati näha temal klõpsides"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Näita ajajoonel meediat"</string>
<string name="screen_advanced_settings_view_source_description">"Selle eelistuse sisselülitamisel on võimalik ajajoonel vaadata sõnumite lähtekoodi."</string> <string name="screen_advanced_settings_view_source_description">"Selle eelistuse sisselülitamisel on võimalik ajajoonel vaadata sõnumite lähtekoodi."</string>
<string name="screen_blocked_users_empty">"Sa pole ühtegi kasutajat blokeerinud"</string> <string name="screen_blocked_users_empty">"Sa pole ühtegi kasutajat blokeerinud"</string>
<string name="screen_blocked_users_unblock_alert_action">"Eemalda blokeering"</string> <string name="screen_blocked_users_unblock_alert_action">"Eemalda blokeering"</string>
@ -58,6 +63,7 @@ Kui sa jätkad muutmist, siis võivad muutuda ka need peidetud eelistused."</str
<string name="screen_notification_settings_system_notifications_action_required_content_link">"süsteemi seadistusi"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"süsteemi seadistusi"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Süsteemi teavitused on välja lülitatud"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Süsteemi teavitused on välja lülitatud"</string>
<string name="screen_notification_settings_title">"Teavitused"</string> <string name="screen_notification_settings_title">"Teavitused"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Tõuketeadete ajalugu"</string>
<string name="troubleshoot_notifications_entry_point_section">"Veaotsing"</string> <string name="troubleshoot_notifications_entry_point_section">"Veaotsing"</string>
<string name="troubleshoot_notifications_entry_point_title">"Teavituste veaotsing"</string> <string name="troubleshoot_notifications_entry_point_title">"Teavituste veaotsing"</string>
</resources> </resources>

View file

@ -8,14 +8,22 @@
<string name="screen_advanced_settings_element_call_base_url">"Mukautettu Element Call URL-osoite"</string> <string name="screen_advanced_settings_element_call_base_url">"Mukautettu Element Call URL-osoite"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Aseta mukautettu URL-osoite Element Callille."</string> <string name="screen_advanced_settings_element_call_base_url_description">"Aseta mukautettu URL-osoite Element Callille."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"Virheellinen URL-osoite. Varmista, että sisällytät protokollan (http/https) ja oikean osoitteen."</string> <string name="screen_advanced_settings_element_call_base_url_validation_error">"Virheellinen URL-osoite. Varmista, että sisällytät protokollan (http/https) ja oikean osoitteen."</string>
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Piilota huoneiden avatarit kutsuista"</string>
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Piilota median esikatselu aikajanalla"</string>
<string name="screen_advanced_settings_media_compression_description">"Lähetä valokuvia ja videoita nopeammin ja vähennä datan käyttöä."</string> <string name="screen_advanced_settings_media_compression_description">"Lähetä valokuvia ja videoita nopeammin ja vähennä datan käyttöä."</string>
<string name="screen_advanced_settings_media_compression_title">"Optimoi median laatu"</string> <string name="screen_advanced_settings_media_compression_title">"Optimoi median laatu"</string>
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Moderointi ja Turvallisuus"</string>
<string name="screen_advanced_settings_push_provider_android">"Push-ilmoitusten tarjoaja"</string> <string name="screen_advanced_settings_push_provider_android">"Push-ilmoitusten tarjoaja"</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Ota rikastettu tekstieditori pois käytöstä, jotta voit kirjoittaa Markdownia manuaalisesti."</string> <string name="screen_advanced_settings_rich_text_editor_description">"Ota rikastettu tekstieditori pois käytöstä, jotta voit kirjoittaa Markdownia manuaalisesti."</string>
<string name="screen_advanced_settings_send_read_receipts">"Lukukuittaukset"</string> <string name="screen_advanced_settings_send_read_receipts">"Lukukuittaukset"</string>
<string name="screen_advanced_settings_send_read_receipts_description">"Jos tämä on poissa päältä, sinun lukukuittauksia ei lähetetä kenellekään. Vastaanotat silti lukukuittauksia muilta käyttäjiltä."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Jos tämä on poissa päältä, sinun lukukuittauksia ei lähetetä kenellekään. Vastaanotat silti lukukuittauksia muilta käyttäjiltä."</string>
<string name="screen_advanced_settings_share_presence">"Jaa läsnäolo"</string> <string name="screen_advanced_settings_share_presence">"Jaa läsnäolo"</string>
<string name="screen_advanced_settings_share_presence_description">"Jos tämä on poissa päältä, et lähetä tai vastaanota lukukuittauksia tai kirjoitusilmotuksia."</string> <string name="screen_advanced_settings_share_presence_description">"Jos tämä on poissa päältä, et lähetä tai vastaanota lukukuittauksia tai kirjoitusilmotuksia."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Piilota aina"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Näytä aina"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"Yksityisissä huoneissa"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Piilotetun median voi aina näyttää napauttamalla sitä"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Näytä media aikajanalla"</string>
<string name="screen_advanced_settings_view_source_description">"Ota käyttöön mahdollisuus tarkastella viestin lähdettä aikajanalla."</string> <string name="screen_advanced_settings_view_source_description">"Ota käyttöön mahdollisuus tarkastella viestin lähdettä aikajanalla."</string>
<string name="screen_blocked_users_empty">"Et ole estänyt ketään"</string> <string name="screen_blocked_users_empty">"Et ole estänyt ketään"</string>
<string name="screen_blocked_users_unblock_alert_action">"Poista esto"</string> <string name="screen_blocked_users_unblock_alert_action">"Poista esto"</string>
@ -55,6 +63,7 @@ Jos jatkat, jotkin asetukset saattavat muuttua."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"järjestelmäsi asetuksia"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"järjestelmäsi asetuksia"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Järjestelmän ilmoitukset on poissa päältä"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Järjestelmän ilmoitukset on poissa päältä"</string>
<string name="screen_notification_settings_title">"Ilmoitukset"</string> <string name="screen_notification_settings_title">"Ilmoitukset"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Push-historia"</string>
<string name="troubleshoot_notifications_entry_point_section">"Vianmääritys"</string> <string name="troubleshoot_notifications_entry_point_section">"Vianmääritys"</string>
<string name="troubleshoot_notifications_entry_point_title">"Ilmoitusten vianmääritys"</string> <string name="troubleshoot_notifications_entry_point_title">"Ilmoitusten vianmääritys"</string>
</resources> </resources>

View file

@ -19,6 +19,11 @@
<string name="screen_advanced_settings_send_read_receipts_description">"En cas de désactivation, vos accusés de lecture ne seront pas envoyés aux autres membres. Vous verrez toujours les accusés des autres membres."</string> <string name="screen_advanced_settings_send_read_receipts_description">"En cas de désactivation, vos accusés de lecture ne seront pas envoyés aux autres membres. Vous verrez toujours les accusés des autres membres."</string>
<string name="screen_advanced_settings_share_presence">"Partager la présence"</string> <string name="screen_advanced_settings_share_presence">"Partager la présence"</string>
<string name="screen_advanced_settings_share_presence_description">"Si cette option est désactivée, vous ne pourrez ni envoyer ni recevoir de confirmations de lecture ni de notifications de saisie."</string> <string name="screen_advanced_settings_share_presence_description">"Si cette option est désactivée, vous ne pourrez ni envoyer ni recevoir de confirmations de lecture ni de notifications de saisie."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Toujours cacher"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Toujours montrer"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"Dans les salons privés"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Un média caché peut toujours être affiché en cliquant dessus"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Afficher les médias dans les discussions."</string>
<string name="screen_advanced_settings_view_source_description">"Activer cette option pour pouvoir voir la source des messages dans la discussion."</string> <string name="screen_advanced_settings_view_source_description">"Activer cette option pour pouvoir voir la source des messages dans la discussion."</string>
<string name="screen_blocked_users_empty">"Vous navez bloqué personne"</string> <string name="screen_blocked_users_empty">"Vous navez bloqué personne"</string>
<string name="screen_blocked_users_unblock_alert_action">"Débloquer"</string> <string name="screen_blocked_users_unblock_alert_action">"Débloquer"</string>
@ -58,6 +63,7 @@ Si vous continuez, il est possible que certains de vos paramètres soient modifi
<string name="screen_notification_settings_system_notifications_action_required_content_link">"paramètres du système"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"paramètres du système"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Les notifications du système sont désactivées"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Les notifications du système sont désactivées"</string>
<string name="screen_notification_settings_title">"Notifications"</string> <string name="screen_notification_settings_title">"Notifications"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Historique des Push"</string>
<string name="troubleshoot_notifications_entry_point_section">"Dépannage"</string> <string name="troubleshoot_notifications_entry_point_section">"Dépannage"</string>
<string name="troubleshoot_notifications_entry_point_title">"Dépanner les notifications"</string> <string name="troubleshoot_notifications_entry_point_title">"Dépanner les notifications"</string>
</resources> </resources>

View file

@ -19,6 +19,11 @@
<string name="screen_advanced_settings_send_read_receipts_description">"Ha ki van kapcsolva, az olvasási visszaigazolások nem lesznek elküldve senkinek. A többi felhasználó olvasási visszaigazolását továbbra is meg fogja kapni."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Ha ki van kapcsolva, az olvasási visszaigazolások nem lesznek elküldve senkinek. A többi felhasználó olvasási visszaigazolását továbbra is meg fogja kapni."</string>
<string name="screen_advanced_settings_share_presence">"Jelenlét megosztása"</string> <string name="screen_advanced_settings_share_presence">"Jelenlét megosztása"</string>
<string name="screen_advanced_settings_share_presence_description">"Ha ki van kapcsolva, nem tud olvasási visszaigazolást vagy írási értesítést küldeni és fogadni"</string> <string name="screen_advanced_settings_share_presence_description">"Ha ki van kapcsolva, nem tud olvasási visszaigazolást vagy írási értesítést küldeni és fogadni"</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Elrejtés mindig"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Megjelenítés mindig"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"Privát szobákban"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"A rejtett médiatartalmak koppintással jeleníthetők meg"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Média megjelenítése az idővonalon"</string>
<string name="screen_advanced_settings_view_source_description">"Engedélyezze a beállítást az üzenet forrásának megjelenítéséhez az idővonalon."</string> <string name="screen_advanced_settings_view_source_description">"Engedélyezze a beállítást az üzenet forrásának megjelenítéséhez az idővonalon."</string>
<string name="screen_blocked_users_empty">"Nincsenek letiltott felhasználók"</string> <string name="screen_blocked_users_empty">"Nincsenek letiltott felhasználók"</string>
<string name="screen_blocked_users_unblock_alert_action">"Letiltás feloldása"</string> <string name="screen_blocked_users_unblock_alert_action">"Letiltás feloldása"</string>
@ -58,6 +63,7 @@ Ha folytatja, egyes beállítások megváltozhatnak."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"rendszerbeállításokat"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"rendszerbeállításokat"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"A rendszerértesítések ki vannak kapcsolva"</string> <string name="screen_notification_settings_system_notifications_turned_off">"A rendszerértesítések ki vannak kapcsolva"</string>
<string name="screen_notification_settings_title">"Értesítések"</string> <string name="screen_notification_settings_title">"Értesítések"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Leküldéses értesítés előzmények"</string>
<string name="troubleshoot_notifications_entry_point_section">"Hibaelhárítás"</string> <string name="troubleshoot_notifications_entry_point_section">"Hibaelhárítás"</string>
<string name="troubleshoot_notifications_entry_point_title">"Értesítések hibaelhárítása"</string> <string name="troubleshoot_notifications_entry_point_title">"Értesítések hibaelhárítása"</string>
</resources> </resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_advanced_settings_choose_distributor_dialog_title_android">"Pasirinkite, kaip gauti pranešimus"</string>
<string name="screen_blocked_users_unblock_alert_action">"Atblokuoti"</string>
<string name="screen_blocked_users_unblock_alert_description">"Vėl galėsite matyti visas iš jų gautas žinutes."</string>
<string name="screen_blocked_users_unblock_alert_title">"Atblokuoti vartotoją"</string>
</resources>

View file

@ -5,14 +5,25 @@
<string name="screen_advanced_settings_choose_distributor_dialog_title_android">"Velg hvordan du vil motta varsler"</string> <string name="screen_advanced_settings_choose_distributor_dialog_title_android">"Velg hvordan du vil motta varsler"</string>
<string name="screen_advanced_settings_developer_mode">"Utviklermodus"</string> <string name="screen_advanced_settings_developer_mode">"Utviklermodus"</string>
<string name="screen_advanced_settings_developer_mode_description">"Aktiver for å få tilgang til funksjoner og funksjonalitet for utviklere."</string> <string name="screen_advanced_settings_developer_mode_description">"Aktiver for å få tilgang til funksjoner og funksjonalitet for utviklere."</string>
<string name="screen_advanced_settings_element_call_base_url">"Egendefinert Element Call base URL"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Angi en egendefinert base URL for Element Call."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"Ugyldig URL. Pass på at du inkluderer protokollen (http/https) og riktig adresse."</string> <string name="screen_advanced_settings_element_call_base_url_validation_error">"Ugyldig URL. Pass på at du inkluderer protokollen (http/https) og riktig adresse."</string>
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Skjul avatarer i invitasjonsforespørsler til rom"</string>
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Skjul forhåndsvisninger av medier på tidslinjen"</string>
<string name="screen_advanced_settings_media_compression_description">"Last opp bilder og videoer raskere og reduser databruken"</string> <string name="screen_advanced_settings_media_compression_description">"Last opp bilder og videoer raskere og reduser databruken"</string>
<string name="screen_advanced_settings_media_compression_title">"Optimaliser mediekvaliteten"</string> <string name="screen_advanced_settings_media_compression_title">"Optimaliser mediekvaliteten"</string>
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Moderasjon og sikkerhet"</string>
<string name="screen_advanced_settings_push_provider_android">"Leverandør av pushvarsling"</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Deaktiver rik tekstredigering for å skrive Markdown manuelt."</string> <string name="screen_advanced_settings_rich_text_editor_description">"Deaktiver rik tekstredigering for å skrive Markdown manuelt."</string>
<string name="screen_advanced_settings_send_read_receipts">"Lesebekreftelser"</string> <string name="screen_advanced_settings_send_read_receipts">"Lesebekreftelser"</string>
<string name="screen_advanced_settings_send_read_receipts_description">"Hvis slått av, sendes ikke lesebekreftelsene dine til noen. Du vil fortsatt motta lesebekreftelser fra andre brukere."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Hvis slått av, sendes ikke lesebekreftelsene dine til noen. Du vil fortsatt motta lesebekreftelser fra andre brukere."</string>
<string name="screen_advanced_settings_share_presence">"Del tilstedeværelse"</string> <string name="screen_advanced_settings_share_presence">"Del tilstedeværelse"</string>
<string name="screen_advanced_settings_share_presence_description">"Hvis slått av, kan du ikke sende eller motta lesebekreftelser eller skrivevarsler."</string> <string name="screen_advanced_settings_share_presence_description">"Hvis slått av, kan du ikke sende eller motta lesebekreftelser eller skrivevarsler."</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Skjul alltid"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Vis alltid"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"I private rom"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Et skjult medium kan alltid vises ved å trykke på det"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Vis medier i tidslinjen"</string>
<string name="screen_advanced_settings_view_source_description">"Aktiver alternativet for å vise meldingskilden på tidslinjen."</string> <string name="screen_advanced_settings_view_source_description">"Aktiver alternativet for å vise meldingskilden på tidslinjen."</string>
<string name="screen_blocked_users_empty">"Du har ingen blokkerte brukere"</string> <string name="screen_blocked_users_empty">"Du har ingen blokkerte brukere"</string>
<string name="screen_blocked_users_unblock_alert_action">"Fjern blokkering"</string> <string name="screen_blocked_users_unblock_alert_action">"Fjern blokkering"</string>
@ -52,6 +63,7 @@ Hvis du fortsetter, kan noen av innstillingene dine endres."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"systeminnstillinger"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"systeminnstillinger"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Systemvarsler er slått av"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Systemvarsler er slått av"</string>
<string name="screen_notification_settings_title">"Varslinger"</string> <string name="screen_notification_settings_title">"Varslinger"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Push-historikk"</string>
<string name="troubleshoot_notifications_entry_point_section">"Feilsøk"</string> <string name="troubleshoot_notifications_entry_point_section">"Feilsøk"</string>
<string name="troubleshoot_notifications_entry_point_title">"Feilsøk varsler"</string> <string name="troubleshoot_notifications_entry_point_title">"Feilsøk varsler"</string>
</resources> </resources>

View file

@ -55,6 +55,7 @@ Niektóre ustawienia mogą ulec zmianie, jeśli kontynuujesz."</string>
<string name="screen_notification_settings_system_notifications_action_required_content_link">"ustawienia systemowe"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"ustawienia systemowe"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Powiadomienia systemowe wyłączone"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Powiadomienia systemowe wyłączone"</string>
<string name="screen_notification_settings_title">"Powiadomienia"</string> <string name="screen_notification_settings_title">"Powiadomienia"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"Historia powiadomień Push"</string>
<string name="troubleshoot_notifications_entry_point_section">"Rozwiązywanie problemów"</string> <string name="troubleshoot_notifications_entry_point_section">"Rozwiązywanie problemów"</string>
<string name="troubleshoot_notifications_entry_point_title">"Rozwiązywanie problemów powiadomień"</string> <string name="troubleshoot_notifications_entry_point_title">"Rozwiązywanie problemów powiadomień"</string>
</resources> </resources>

View file

@ -40,6 +40,7 @@ Se você continuar, algumas de suas configurações poderão mudar."</string>
<string name="screen_notification_settings_failed_fixing_configuration">"A configuração não foi corrigida, tente novamente."</string> <string name="screen_notification_settings_failed_fixing_configuration">"A configuração não foi corrigida, tente novamente."</string>
<string name="screen_notification_settings_group_chats">"Bate-papos em grupo"</string> <string name="screen_notification_settings_group_chats">"Bate-papos em grupo"</string>
<string name="screen_notification_settings_invite_for_me_label">"Convites"</string> <string name="screen_notification_settings_invite_for_me_label">"Convites"</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Seu servidor doméstico não suporta esta opção em salas criptografadas. Você pode não ser notificado em algumas salas."</string>
<string name="screen_notification_settings_mentions_section_title">"Menções"</string> <string name="screen_notification_settings_mentions_section_title">"Menções"</string>
<string name="screen_notification_settings_mode_all">"Todos"</string> <string name="screen_notification_settings_mode_all">"Todos"</string>
<string name="screen_notification_settings_mode_mentions">"Menções"</string> <string name="screen_notification_settings_mode_mentions">"Menções"</string>

View file

@ -8,14 +8,21 @@
<string name="screen_advanced_settings_element_call_base_url">"Базовый URL сервера звонков Element"</string> <string name="screen_advanced_settings_element_call_base_url">"Базовый URL сервера звонков Element"</string>
<string name="screen_advanced_settings_element_call_base_url_description">"Задайте свой сервер Element Call."</string> <string name="screen_advanced_settings_element_call_base_url_description">"Задайте свой сервер Element Call."</string>
<string name="screen_advanced_settings_element_call_base_url_validation_error">"Адрес указан неверно, удостоверьтесь, что вы указали протокол (http/https) и правильный адрес."</string> <string name="screen_advanced_settings_element_call_base_url_validation_error">"Адрес указан неверно, удостоверьтесь, что вы указали протокол (http/https) и правильный адрес."</string>
<string name="screen_advanced_settings_hide_invite_avatars_toggle_title">"Скрыть аватары в запросах на приглашение в комнату"</string>
<string name="screen_advanced_settings_hide_timeline_media_toggle_title">"Скрыть предварительный просмотр медиафайлов на временной шкале"</string>
<string name="screen_advanced_settings_media_compression_description">"Загружайте фотографии и видео быстрее и сокращайте потребление трафика"</string> <string name="screen_advanced_settings_media_compression_description">"Загружайте фотографии и видео быстрее и сокращайте потребление трафика"</string>
<string name="screen_advanced_settings_media_compression_title">"Оптимизировать качество мультимедиа"</string> <string name="screen_advanced_settings_media_compression_title">"Оптимизировать качество мультимедиа"</string>
<string name="screen_advanced_settings_moderation_and_safety_section_title">"Модерация и безопасность"</string>
<string name="screen_advanced_settings_push_provider_android">"Поставщик push-уведомлений"</string> <string name="screen_advanced_settings_push_provider_android">"Поставщик push-уведомлений"</string>
<string name="screen_advanced_settings_rich_text_editor_description">"Отключить редактор форматированного текста и включить Markdown."</string> <string name="screen_advanced_settings_rich_text_editor_description">"Отключить редактор форматированного текста и включить Markdown."</string>
<string name="screen_advanced_settings_send_read_receipts">"Уведомления о прочтении"</string> <string name="screen_advanced_settings_send_read_receipts">"Уведомления о прочтении"</string>
<string name="screen_advanced_settings_send_read_receipts_description">"Если этот параметр выключен, ваш статус о прочтении не будет отображаться. Вы по-прежнему будете видеть статус о прочтении от других пользователей."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Если этот параметр выключен, ваш статус о прочтении не будет отображаться. Вы по-прежнему будете видеть статус о прочтении от других пользователей."</string>
<string name="screen_advanced_settings_share_presence">"Поделиться присутствием"</string> <string name="screen_advanced_settings_share_presence">"Поделиться присутствием"</string>
<string name="screen_advanced_settings_share_presence_description">"Если выключено, вы не сможете отправлять, получать уведомления о прочтении и наборе текста"</string> <string name="screen_advanced_settings_share_presence_description">"Если выключено, вы не сможете отправлять, получать уведомления о прочтении и наборе текста"</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Всегда скрывать"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Всегда показывать"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"В личных комнатах"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Скрытый медиафайл всегда можно отобразить, нажав на него."</string>
<string name="screen_advanced_settings_view_source_description">"Включить опцию просмотра источника сообщения в ленте."</string> <string name="screen_advanced_settings_view_source_description">"Включить опцию просмотра источника сообщения в ленте."</string>
<string name="screen_blocked_users_empty">"У вас нет заблокированных пользователей"</string> <string name="screen_blocked_users_empty">"У вас нет заблокированных пользователей"</string>
<string name="screen_blocked_users_unblock_alert_action">"Разблокировать"</string> <string name="screen_blocked_users_unblock_alert_action">"Разблокировать"</string>

View file

@ -19,6 +19,11 @@
<string name="screen_advanced_settings_send_read_receipts_description">"Ak je táto funkcia vypnutá, vaše potvrdenia o prečítaní sa nebudú nikomu odosielať. Stále budete dostávať potvrdenia o prečítaní od ostatných používateľov."</string> <string name="screen_advanced_settings_send_read_receipts_description">"Ak je táto funkcia vypnutá, vaše potvrdenia o prečítaní sa nebudú nikomu odosielať. Stále budete dostávať potvrdenia o prečítaní od ostatných používateľov."</string>
<string name="screen_advanced_settings_share_presence">"Zdieľať prítomnosť"</string> <string name="screen_advanced_settings_share_presence">"Zdieľať prítomnosť"</string>
<string name="screen_advanced_settings_share_presence_description">"Ak je vypnuté, nebudete môcť odosielať ani prijímať potvrdenia o prečítaní alebo upozornenia o písaní"</string> <string name="screen_advanced_settings_share_presence_description">"Ak je vypnuté, nebudete môcť odosielať ani prijímať potvrdenia o prečítaní alebo upozornenia o písaní"</string>
<string name="screen_advanced_settings_show_media_timeline_always_hide">"Vždy skryť"</string>
<string name="screen_advanced_settings_show_media_timeline_always_show">"Vždy zobraziť"</string>
<string name="screen_advanced_settings_show_media_timeline_private_rooms">"V súkromných miestnostiach"</string>
<string name="screen_advanced_settings_show_media_timeline_subtitle">"Skryté médium sa dá vždy zobraziť ťuknutím naň"</string>
<string name="screen_advanced_settings_show_media_timeline_title">"Zobraziť médiá na časovej osi"</string>
<string name="screen_advanced_settings_view_source_description">"Povoliť možnosť zobrazenia zdroja správy na časovej osi."</string> <string name="screen_advanced_settings_view_source_description">"Povoliť možnosť zobrazenia zdroja správy na časovej osi."</string>
<string name="screen_blocked_users_empty">"Nemáte žiadnych blokovaných používateľov"</string> <string name="screen_blocked_users_empty">"Nemáte žiadnych blokovaných používateľov"</string>
<string name="screen_blocked_users_unblock_alert_action">"Odblokovať"</string> <string name="screen_blocked_users_unblock_alert_action">"Odblokovať"</string>
@ -60,6 +65,7 @@ Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť."</stri
<string name="screen_notification_settings_system_notifications_action_required_content_link">"nastavenia systému"</string> <string name="screen_notification_settings_system_notifications_action_required_content_link">"nastavenia systému"</string>
<string name="screen_notification_settings_system_notifications_turned_off">"Systémové oznámenia sú vypnuté"</string> <string name="screen_notification_settings_system_notifications_turned_off">"Systémové oznámenia sú vypnuté"</string>
<string name="screen_notification_settings_title">"Oznámenia"</string> <string name="screen_notification_settings_title">"Oznámenia"</string>
<string name="troubleshoot_notifications_entry_point_push_history_title">"História push oznámení"</string>
<string name="troubleshoot_notifications_entry_point_section">"Riešenie problémov"</string> <string name="troubleshoot_notifications_entry_point_section">"Riešenie problémov"</string>
<string name="troubleshoot_notifications_entry_point_title">"Oznámenia riešení problémov"</string> <string name="troubleshoot_notifications_entry_point_title">"Oznámenia riešení problémov"</string>
</resources> </resources>

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