Add forced logout flow when the proxy is no longer available (#3458)

* Add `MatrixClient.isSlidingSyncProxySupported` function

* Update localazy strings

* Modify `ErrorDialog` to have an `onSubmit` call, which will be used for the submit action.

Also make the title text optional and dismissing the dialog by tapping outside/going back configurable.

* Check if a forced migration to SSS is needed because the proxy is no longer available.

In that case, display the non-dismissable dialog and force the user to log out after enabling SSS.

* Enable native/simplified sliding sync by default.

* Refactor the login to make sure we:

1. Always try native/simplified sliding sync login first, if available.
2. Then, if it wasn't available or failed with an sliding sync not supported error, try with the proxy instead (either discovered proxy or forced custom one).

* Move logic to `LoggedInPresenter` and the UI to `LoggedInView`

* Update screenshots

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa
2024-09-16 11:13:02 +02:00
committed by GitHub
parent eae580422d
commit 03786c1fd2
79 changed files with 315 additions and 231 deletions

View File

@@ -59,6 +59,7 @@ dependencies {
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.oidc.impl)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.push.test)
testImplementation(projects.libraries.pushproviders.test)
testImplementation(projects.features.networkmonitor.test)

View File

@@ -9,4 +9,6 @@ package io.element.android.appnav.loggedin
sealed interface LoggedInEvents {
data class CloseErrorDialog(val doNotShowAgain: Boolean) : LoggedInEvents
data object CheckSlidingSyncProxyAvailability : LoggedInEvents
data object LogoutAndMigrateToNativeSlidingSync : LoggedInEvents
}

View File

@@ -16,6 +16,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import im.vector.app.features.analytics.plan.CryptoSessionStateChange
import im.vector.app.features.analytics.plan.UserProperties
import io.element.android.features.networkmonitor.api.NetworkMonitor
@@ -29,6 +30,7 @@ import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.pushproviders.api.RegistrationFailure
import io.element.android.services.analytics.api.AnalyticsService
@@ -48,6 +50,7 @@ class LoggedInPresenter @Inject constructor(
private val sessionVerificationService: SessionVerificationService,
private val analyticsService: AnalyticsService,
private val encryptionService: EncryptionService,
private val enableNativeSlidingSyncUseCase: EnableNativeSlidingSyncUseCase,
) : Presenter<LoggedInState> {
@Composable
override fun present(): LoggedInState {
@@ -78,6 +81,7 @@ class LoggedInPresenter @Inject constructor(
networkStatus == NetworkStatus.Online && syncIndicator == RoomListService.SyncIndicator.Show
}
}
var forceNativeSlidingSyncMigration by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
combine(
sessionVerificationService.sessionVerifiedStatus,
@@ -97,6 +101,18 @@ class LoggedInPresenter @Inject constructor(
}
}
}
LoggedInEvents.CheckSlidingSyncProxyAvailability -> coroutineScope.launch {
// Force the user to log out if they were using the proxy sliding sync and it's no longer available, but native sliding sync is.
forceNativeSlidingSyncMigration = !matrixClient.isUsingNativeSlidingSync() &&
matrixClient.isNativeSlidingSyncSupported() &&
!matrixClient.isSlidingSyncProxySupported()
}
LoggedInEvents.LogoutAndMigrateToNativeSlidingSync -> coroutineScope.launch {
// Enable native sliding sync if it wasn't already the case
enableNativeSlidingSyncUseCase()
// Then force the logout
matrixClient.logout(userInitiated = true, ignoreSdkError = true)
}
}
}
@@ -104,6 +120,7 @@ class LoggedInPresenter @Inject constructor(
showSyncSpinner = showSyncSpinner,
pusherRegistrationState = pusherRegistrationState.value,
ignoreRegistrationError = ignoreRegistrationError,
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
eventSink = ::handleEvent
)
}

View File

@@ -13,5 +13,6 @@ data class LoggedInState(
val showSyncSpinner: Boolean,
val pusherRegistrationState: AsyncData<Unit>,
val ignoreRegistrationError: Boolean,
val forceNativeSlidingSyncMigration: Boolean,
val eventSink: (LoggedInEvents) -> Unit,
)

View File

@@ -16,15 +16,18 @@ open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
aLoggedInState(),
aLoggedInState(showSyncSpinner = true),
aLoggedInState(pusherRegistrationState = AsyncData.Failure(PusherRegistrationFailure.NoDistributorsAvailable())),
aLoggedInState(forceNativeSlidingSyncMigration = true),
)
}
fun aLoggedInState(
showSyncSpinner: Boolean = false,
pusherRegistrationState: AsyncData<Unit> = AsyncData.Uninitialized,
forceNativeSlidingSyncMigration: Boolean = false,
) = LoggedInState(
showSyncSpinner = showSyncSpinner,
pusherRegistrationState = pusherRegistrationState,
ignoreRegistrationError = false,
forceNativeSlidingSyncMigration = forceNativeSlidingSyncMigration,
eventSink = {},
)

View File

@@ -15,10 +15,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.lifecycle.Lifecycle
import io.element.android.appnav.R
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogWithDoNotShowAgain
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.matrix.api.exception.isNetworkError
import io.element.android.libraries.ui.strings.CommonStrings
@@ -28,6 +32,11 @@ fun LoggedInView(
navigateToNotificationTroubleshoot: () -> Unit,
modifier: Modifier = Modifier
) {
OnLifecycleEvent { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
state.eventSink(LoggedInEvents.CheckSlidingSyncProxyAvailability)
}
}
Box(
modifier = modifier
.fillMaxSize()
@@ -61,6 +70,13 @@ fun LoggedInView(
}
}
}
// Set the force migration dialog here so it's always displayed over every screen
if (state.forceNativeSlidingSyncMigration) {
ForceNativeSlidingSyncMigrationDialog(onSubmit = {
state.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync)
})
}
}
private fun Throwable.getReason(): String? {
@@ -80,6 +96,19 @@ private fun Throwable.getReason(): String? {
}
}
@Composable
private fun ForceNativeSlidingSyncMigrationDialog(
onSubmit: () -> Unit,
) {
ErrorDialog(
title = null,
content = stringResource(R.string.banner_migrate_to_native_sliding_sync_force_logout_title),
submitText = stringResource(R.string.banner_migrate_to_native_sliding_sync_action),
onSubmit = onSubmit,
canDismiss = false,
)
}
@PreviewsDayNight
@Composable
internal fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview {

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="banner_migrate_to_native_sliding_sync_action">"Log Out &amp; Upgrade"</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app."</string>
</resources>

View File

@@ -29,6 +29,8 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.libraries.preferences.api.store.EnableNativeSlidingSyncUseCase
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.push.api.PushService
import io.element.android.libraries.push.test.FakePushService
import io.element.android.libraries.pushproviders.api.Distributor
@@ -42,6 +44,10 @@ import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -91,7 +97,8 @@ class LoggedInPresenterTest {
pushService = FakePushService(),
sessionVerificationService = verificationService,
analyticsService = analyticsService,
encryptionService = encryptionService
encryptionService = encryptionService,
enableNativeSlidingSyncUseCase = EnableNativeSlidingSyncUseCase(InMemoryAppPreferencesStore(), this),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
@@ -487,26 +494,103 @@ class LoggedInPresenterTest {
)
}
@Test
fun `present - CheckSlidingSyncProxyAvailability forces the sliding sync migration under the right circumstances`() = runTest {
// The migration will be forced if:
// - The user is not using the native sliding sync
// - The sliding sync proxy is no longer supported
// - The native sliding sync is supported
val matrixClient = FakeMatrixClient(
isUsingNativeSlidingSyncLambda = { false },
isSlidingSyncProxySupportedLambda = { false },
isNativeSlidingSyncSupportedLambda = { true },
)
val presenter = createLoggedInPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.forceNativeSlidingSyncMigration).isFalse()
initialState.eventSink(LoggedInEvents.CheckSlidingSyncProxyAvailability)
assertThat(awaitItem().forceNativeSlidingSyncMigration).isTrue()
}
}
@Test
fun `present - CheckSlidingSyncProxyAvailability will not force the migration if native sliding sync is not supported too`() = runTest {
val matrixClient = FakeMatrixClient(
isUsingNativeSlidingSyncLambda = { false },
isSlidingSyncProxySupportedLambda = { false },
isNativeSlidingSyncSupportedLambda = { false },
)
val presenter = createLoggedInPresenter(matrixClient = matrixClient)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.forceNativeSlidingSyncMigration).isFalse()
initialState.eventSink(LoggedInEvents.CheckSlidingSyncProxyAvailability)
expectNoEvents()
}
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `present - LogoutAndMigrateToNativeSlidingSync enables native sliding sync and logs out the user`() = runTest {
val logoutLambda = lambdaRecorder<Boolean, Boolean, String?> { userInitiated, ignoreSdkError ->
assertThat(userInitiated).isTrue()
assertThat(ignoreSdkError).isTrue()
null
}
val matrixClient = FakeMatrixClient().apply {
this.logoutLambda = logoutLambda
}
val appPreferencesStore = InMemoryAppPreferencesStore()
val enableNativeSlidingSyncUseCase = EnableNativeSlidingSyncUseCase(appPreferencesStore, this)
val presenter = createLoggedInPresenter(matrixClient = matrixClient, enableNativeSlidingSyncUseCase = enableNativeSlidingSyncUseCase)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()).isFalse()
initialState.eventSink(LoggedInEvents.LogoutAndMigrateToNativeSlidingSync)
advanceUntilIdle()
assertThat(appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first()).isTrue()
assertThat(logoutLambda.assertions().isCalledOnce())
}
}
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
skipItems(1)
return awaitItem()
}
private fun createLoggedInPresenter(
private fun TestScope.createLoggedInPresenter(
roomListService: RoomListService = FakeRoomListService(),
networkStatus: NetworkStatus = NetworkStatus.Offline,
analyticsService: AnalyticsService = FakeAnalyticsService(),
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
encryptionService: EncryptionService = FakeEncryptionService(),
pushService: PushService = FakePushService(),
enableNativeSlidingSyncUseCase: EnableNativeSlidingSyncUseCase = EnableNativeSlidingSyncUseCase(InMemoryAppPreferencesStore(), this),
matrixClient: MatrixClient = FakeMatrixClient(roomListService = roomListService),
): LoggedInPresenter {
return LoggedInPresenter(
matrixClient = FakeMatrixClient(roomListService = roomListService),
matrixClient = matrixClient,
networkMonitor = FakeNetworkMonitor(networkStatus),
pushService = pushService,
sessionVerificationService = sessionVerificationService,
analyticsService = analyticsService,
encryptionService = encryptionService
encryptionService = encryptionService,
enableNativeSlidingSyncUseCase = enableNativeSlidingSyncUseCase,
)
}
}

View File

@@ -111,7 +111,7 @@ internal fun CallScreenView(
is AsyncData.Failure ->
ErrorDialog(
content = state.urlState.error.message.orEmpty(),
onDismiss = { state.eventSink(CallScreenEvents.Hangup) },
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
)
is AsyncData.Success -> Unit
}

View File

@@ -105,7 +105,7 @@ private fun LeaveRoomErrorDialog(
is LeaveRoomState.Error.Hidden -> {}
is LeaveRoomState.Error.Shown -> ErrorDialog(
content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(LeaveRoomEvent.HideError) }
onSubmit = { state.eventSink(LeaveRoomEvent.HideError) }
)
}
}

View File

@@ -116,7 +116,7 @@ private fun SetupPinContent(
ErrorDialog(
title = state.setupPinFailure.title(),
content = state.setupPinFailure.content(),
onDismiss = {
onSubmit = {
state.eventSink(SetupPinEvents.ClearFailure)
}
)

View File

@@ -104,7 +104,7 @@ fun PinUnlockView(
if (state.showBiometricUnlockError) {
ErrorDialog(
content = state.biometricUnlockErrorMessage ?: "",
onDismiss = { state.eventSink(PinUnlockEvents.ClearBiometricError) }
onSubmit = { state.eventSink(PinUnlockEvents.ClearBiometricError) }
)
}
}
@@ -206,7 +206,7 @@ private fun SignOutPrompt(
ErrorDialog(
title = stringResource(id = R.string.screen_app_lock_signout_alert_title),
content = stringResource(id = R.string.screen_app_lock_signout_alert_message),
onDismiss = onSignOut,
onSubmit = onSignOut,
)
}
}

View File

@@ -36,7 +36,7 @@ fun ChangeServerView(
ErrorDialog(
modifier = modifier,
content = error.message(),
onDismiss = {
onSubmit = {
eventSink.invoke(ChangeServerEvents.ClearError)
}
)

View File

@@ -103,7 +103,7 @@ fun ConfirmAccountProviderView(
is ChangeServerError.Error -> {
ErrorDialog(
content = error.message(),
onDismiss = {
onSubmit = {
eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
}
)

View File

@@ -283,7 +283,7 @@ private fun LoginErrorDialog(error: Throwable, onDismiss: () -> Unit) {
ErrorDialog(
title = stringResource(id = CommonStrings.dialog_title_error),
content = stringResource(loginError(error)),
onDismiss = onDismiss
onSubmit = onDismiss
)
}

View File

@@ -78,10 +78,4 @@
<string name="screen_server_confirmation_message_register">"Тут будуць захоўвацца вашыя размовы - сапраўды гэтак жа, як вы выкарыстоўваеце паштовага правайдара для захоўвання сваіх лістоў."</string>
<string name="screen_server_confirmation_title_login">"Вы збіраецеся ўвайсці ў %1$s"</string>
<string name="screen_server_confirmation_title_register">"Вы збіраецеся стварыць уліковы запіс на %1$s"</string>
<string name="screen_waitlist_message">"Зараз існуе высокі попыт на %1$s на %2$s. Калі ласка, вярніцеся ў праграму праз некалькі дзён і паспрабуйце зноў.
Дзякуй за цярпенне!"</string>
<string name="screen_waitlist_message_success">"Вітаем у %1$s!"</string>
<string name="screen_waitlist_title">"Амаль гатова."</string>
<string name="screen_waitlist_title_success">"Вы зарэгістраваны."</string>
</resources>

View File

@@ -24,5 +24,4 @@
<string name="screen_server_confirmation_message_register">"Това е мястото, където ще живеят вашите разговори — точно както бихте използвали имейл доставчик, за да съхранявате вашите имейли."</string>
<string name="screen_server_confirmation_title_login">"На път сте да влезете в %1$s"</string>
<string name="screen_server_confirmation_title_register">"На път сте да създадете акаунт в %1$s"</string>
<string name="screen_waitlist_message_success">"Добре дошли в %1$s!"</string>
</resources>

View File

@@ -78,10 +78,4 @@ Zkuste se přihlásit ručně nebo naskenujte QR kód pomocí jiného zařízen
<string name="screen_server_confirmation_message_register">"Zde budou uloženy vaše konverzace - podobně jako u poskytovatele e-mailových služeb uchováváte své e-maily."</string>
<string name="screen_server_confirmation_title_login">"Chystáte se přihlásit do služby %1$s"</string>
<string name="screen_server_confirmation_title_register">"Chystáte se vytvořit účet na %1$s"</string>
<string name="screen_waitlist_message">"Na %2$s je momentálně vysoká poptávka po %1$s. Vraťte se do aplikace za pár dní a zkuste to znovu.
Díky za trpělivost!"</string>
<string name="screen_waitlist_message_success">"Vítá vás %1$s!"</string>
<string name="screen_waitlist_title">"Jste v pořadníku!"</string>
<string name="screen_waitlist_title_success">"Jdete do toho!"</string>
</resources>

View File

@@ -78,10 +78,4 @@ Versuche, dich manuell anzumelden, oder scanne den QR-Code mit einem anderen Ger
<string name="screen_server_confirmation_message_register">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würden."</string>
<string name="screen_server_confirmation_title_login">"Du bist dabei, dich bei %1$s anzumelden"</string>
<string name="screen_server_confirmation_title_register">"Du bist dabei, ein Konto auf %1$s zu erstellen"</string>
<string name="screen_waitlist_message">"Derzeit besteht eine hohe Nachfrage nach %1$s auf %2$s. Kehre in ein paar Tagen zur App zurück und versuche es erneut.
Danke für deine Geduld!"</string>
<string name="screen_waitlist_message_success">"Willkommen bei %1$s!"</string>
<string name="screen_waitlist_title">"Du bist fast am Ziel."</string>
<string name="screen_waitlist_title_success">"Du bist dabei."</string>
</resources>

View File

@@ -78,10 +78,4 @@
<string name="screen_server_confirmation_message_register">"Εδώ θα ζουν οι συνομιλίες σου - όπως θα χρησιμοποιούσες έναν πάροχο email για να διατηρήσεις τα email σου."</string>
<string name="screen_server_confirmation_title_login">"Πρόκειται να συνδεθείς στο %1$s"</string>
<string name="screen_server_confirmation_title_register">"Πρόκειται να δημιουργήσεις έναν λογαριασμό στο %1$s"</string>
<string name="screen_waitlist_message">"Υπάρχει μεγάλη ζήτηση για το %1$s στον %2$s αυτή τη στιγμή. Επέστρεψε στην εφαρμογή σε λίγες μέρες και δοκίμασε ξανά.
Ευχαριστώ για την υπομονή σου!"</string>
<string name="screen_waitlist_message_success">"Καλώς ήρθες στο %1$s!"</string>
<string name="screen_waitlist_title">"Σχεδόν τα κατάφερες."</string>
<string name="screen_waitlist_title_success">"Είσαι μέσα."</string>
</resources>

View File

@@ -37,10 +37,4 @@
<string name="screen_server_confirmation_message_register">"Aquí es donde se alojarán tus conversaciones — justo como utilizarías un proveedor de correo electrónico para guardar tus correos electrónicos."</string>
<string name="screen_server_confirmation_title_login">"Estás a punto de iniciar sesión en %1$s"</string>
<string name="screen_server_confirmation_title_register">"Estás a punto de crear una cuenta en %1$s"</string>
<string name="screen_waitlist_message">"Hay una gran demanda para %1$s en %2$s en este momento. Vuelve a la aplicación en unos días e inténtalo de nuevo.
¡Gracias por tu paciencia!"</string>
<string name="screen_waitlist_message_success">"¡Bienvenido a %1$s!"</string>
<string name="screen_waitlist_title">"Ya casi has terminado."</string>
<string name="screen_waitlist_title_success">"Estás dentro."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Proovi käsitsi sisselogimist või skaneeri QR-koodi mõne muu seadmega."</strin
<string name="screen_server_confirmation_message_register">"See on koht, kus sinu vestlused elavad just nagu kasutaksid oma e-kirjade säilitamiseks e-postitenuse pakkujat."</string>
<string name="screen_server_confirmation_title_login">"Sa oled sisselogimas koduserverisse %1$s"</string>
<string name="screen_server_confirmation_title_register">"Sa oled loomas kasutajakontot koduserveris %1$s"</string>
<string name="screen_waitlist_message">"%1$s kasutamiseks %2$s koduserveris on hetkel palju huvilisi. Proovi seda samast rakendusest mõne päeva pärast.
Täname kannatlikkuse eest!"</string>
<string name="screen_waitlist_message_success">"Tere tulemast rakendusse %1$s!"</string>
<string name="screen_waitlist_title">"Peaaegu olemas."</string>
<string name="screen_waitlist_title_success">"Oled nüüd jututoas."</string>
</resources>

View File

@@ -76,10 +76,4 @@
<string name="screen_server_confirmation_message_register">"Cest ici que vos conversations seront enregistrées, comme vous le feriez avec un fournisseur de messagerie pour conserver vos e-mails."</string>
<string name="screen_server_confirmation_title_login">"Vous êtes sur le point de vous connecter à %1$s"</string>
<string name="screen_server_confirmation_title_register">"Vous êtes sur le point de créer un compte sur %1$s"</string>
<string name="screen_waitlist_message">"Il y a une forte demande pour %1$s sur %2$s à lheure actuelle. Revenez sur lapplication dans quelques jours et réessayez.
Merci pour votre patience !"</string>
<string name="screen_waitlist_message_success">"Bienvenue dans %1$s !"</string>
<string name="screen_waitlist_title">"Vous y êtes presque."</string>
<string name="screen_waitlist_title_success">"Vous y êtes."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Próbáljon meg kézileg bejelentkezni, vagy olvassa be a QR-kódot egy másik e
<string name="screen_server_confirmation_message_register">"Itt lesznek a beszélgetései ahogyan egy e-mail-szolgáltatást is használna a levelei kezeléséhez."</string>
<string name="screen_server_confirmation_title_login">"Hamarosan bejelentkezik ebbe: %1$s"</string>
<string name="screen_server_confirmation_title_register">"Hamarosan létrehoz egy fiókot ezen: %1$s"</string>
<string name="screen_waitlist_message">"Jelenleg nagy a kereslet a(z) %2$s oldalon futó %1$s iránt. Térjen vissza néhány nap múlva az alkalmazáshoz, és próbálja újra.
Köszönjük a türelmét!"</string>
<string name="screen_waitlist_message_success">"Üdvözli az %1$s!"</string>
<string name="screen_waitlist_title">"Már majdnem kész van."</string>
<string name="screen_waitlist_title_success">"Bent van."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Coba masuk secara manual, atau pindai kode QR dengan perangkat lain."</string>
<string name="screen_server_confirmation_message_register">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_server_confirmation_title_login">"Anda akan masuk ke %1$s"</string>
<string name="screen_server_confirmation_title_register">"Anda akan membuat akun di %1$s"</string>
<string name="screen_waitlist_message">"Ada permintaan tinggi untuk %1$s di %2$s saat ini. Kembalilah ke aplikasi dalam beberapa hari dan coba lagi.
Terima kasih atas kesabaran Anda!"</string>
<string name="screen_waitlist_message_success">"Selamat datang di %1$s!"</string>
<string name="screen_waitlist_title">"Anda hampir selesai."</string>
<string name="screen_waitlist_title_success">"Anda sudah masuk."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Prova ad accedere manualmente o scansiona il codice QR con un altro dispositivo.
<string name="screen_server_confirmation_message_register">"Qui è dove vivranno le tue conversazioni — proprio come useresti un fornitore di posta elettronica per conservare le tue email."</string>
<string name="screen_server_confirmation_title_login">"Stai per accedere a %1$s"</string>
<string name="screen_server_confirmation_title_register">"Stai per creare un account su %1$s"</string>
<string name="screen_waitlist_message">"Al momento c\'è una grande richiesta per %1$s su %2$s. Torna a visitare l\'app tra qualche giorno e riprova.
Grazie per la pazienza!"</string>
<string name="screen_waitlist_message_success">"Benvenuti in %1$s!"</string>
<string name="screen_waitlist_title">"Ci sei quasi."</string>
<string name="screen_waitlist_title_success">"Sei dentro."</string>
</resources>

View File

@@ -34,10 +34,4 @@
<string name="screen_server_confirmation_message_register">"აქ იქნება თქვენი საუბრები - ისევე, როგორც თქვენ ელ. ფოსტაში ინახება თქვენი ელ.წერილები."</string>
<string name="screen_server_confirmation_title_login">"თქვენ აპირებთ შესვლას %1$s-ში"</string>
<string name="screen_server_confirmation_title_register">"თქვენ აპირებთ ანგარიშის შექმნას %1$s-ში"</string>
<string name="screen_waitlist_message">"ახლა დიდი მოთხოვნაა %1$s-ზე %2$s-ში. დაბრუნდით რამდენიმე დღეში და სცადეთ ერთხელაც.
მადლობა მოთმენისათვის!"</string>
<string name="screen_waitlist_message_success">"კეთილი იყოს თქვენი მობრძანება %1$s-ში!"</string>
<string name="screen_waitlist_title">"თითქმის მზადაა."</string>
<string name="screen_waitlist_title_success">"თქვენ შეხვედით."</string>
</resources>

View File

@@ -35,10 +35,4 @@
<string name="screen_server_confirmation_message_register">"Dit is waar je gesprekken zullen worden bewaard — net zoals je een e-mailprovider zou gebruiken om je e-mails te bewaren."</string>
<string name="screen_server_confirmation_title_login">"Je staat op het punt je aan te melden bij %1$s"</string>
<string name="screen_server_confirmation_title_register">"Je staat op het punt een account aan te maken op %1$s"</string>
<string name="screen_waitlist_message">"Er is momenteel veel vraag naar %1$s op %2$s. Kom over een paar dagen terug naar de app en probeer het opnieuw.
Bedankt voor je geduld!"</string>
<string name="screen_waitlist_message_success">"Welkom bij %1$s!"</string>
<string name="screen_waitlist_title">"Je bent er bijna."</string>
<string name="screen_waitlist_title_success">"Je bent binnen."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Spróbuj zalogować się ręcznie lub zeskanuj kod QR na innym urządzeniu."</st
<string name="screen_server_confirmation_message_register">"Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."</string>
<string name="screen_server_confirmation_title_login">"Zamierzasz się zalogować do %1$s"</string>
<string name="screen_server_confirmation_title_register">"Zamierzasz utworzyć konto na %1$s"</string>
<string name="screen_waitlist_message">"Obecnie istnieje duże zapotrzebowanie na %1$s na %2$s. Wróć do aplikacji za kilka dni i spróbuj ponownie.
Dziękujemy za Twoją cierpliwość!"</string>
<string name="screen_waitlist_message_success">"Witamy w %1$s!"</string>
<string name="screen_waitlist_title">"Już prawie gotowe!"</string>
<string name="screen_waitlist_title_success">"Witamy!"</string>
</resources>

View File

@@ -34,10 +34,4 @@
<string name="screen_server_confirmation_message_register">"Aqui é onde suas conversas vão ficar — assim como você usa um provedor de e-mails para manter seus e-mails."</string>
<string name="screen_server_confirmation_title_login">"Você está prestes a fazer login em %1$s"</string>
<string name="screen_server_confirmation_title_register">"Você está prestes a criar uma conta em %1$s"</string>
<string name="screen_waitlist_message">"Há uma grande demanda por %1$s sobre %2$s no momento. Volte ao aplicativo em alguns dias e tente novamente.
Obrigado pela sua paciência!"</string>
<string name="screen_waitlist_message_success">"Bem-vindo ao %1$s!"</string>
<string name="screen_waitlist_title">"Você está quase lá."</string>
<string name="screen_waitlist_title_success">"Você está dentro."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Tenta iniciar a sessão manualmente ou digitaliza o código QR com outro disposi
<string name="screen_server_confirmation_message_register">"É aqui que as tuas conversas vão ficar — tal como num serviço de e-mail."</string>
<string name="screen_server_confirmation_title_login">"Irás iniciar sessão em %1$s"</string>
<string name="screen_server_confirmation_title_register">"Irás criar uma conta em %1$s"</string>
<string name="screen_waitlist_message">"Há uma grande procura pela %1$s no %2$s, de momento. Volta à aplicação daqui a uns dias e tenta novamente.
Obrigado!"</string>
<string name="screen_waitlist_message_success">"Bem-vindo à %1$s!"</string>
<string name="screen_waitlist_title">"Estás quase lá."</string>
<string name="screen_waitlist_title_success">"Estás dentro."</string>
</resources>

View File

@@ -78,10 +78,4 @@
<string name="screen_server_confirmation_message_register">"Aici vor trăi conversațiile dvs. - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile."</string>
<string name="screen_server_confirmation_title_login">"Sunteți pe cale să vă conectați la %1$s"</string>
<string name="screen_server_confirmation_title_register">"Sunteți pe cale să creați un cont pe %1$s"</string>
<string name="screen_waitlist_message">"Există o cerere mare pentru %1$s pentru %2$s în acest moment. Reveniți la aplicație în câteva zile și încercați din nou.
Vă mulțumim pentru răbdare!"</string>
<string name="screen_waitlist_message_success">"Bun venit la%1$s!"</string>
<string name="screen_waitlist_title">"Sunteți pe lista de așteptare"</string>
<string name="screen_waitlist_title_success">"Sunteți conectat!"</string>
</resources>

View File

@@ -78,10 +78,4 @@
<string name="screen_server_confirmation_message_register">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_server_confirmation_title_login">"Вы собираетесь войти в %1$s"</string>
<string name="screen_server_confirmation_title_register">"Вы собираетесь создать учетную запись на %1$s"</string>
<string name="screen_waitlist_message">"В настоящее время существует высокий спрос на %1$s на %2$s. Вернитесь в приложение через несколько дней и попробуйте снова.
Спасибо за терпение!"</string>
<string name="screen_waitlist_message_success">"Добро пожаловать в %1$s!"</string>
<string name="screen_waitlist_title">"Почти готово."</string>
<string name="screen_waitlist_title_success">"Вы зарегистрированы."</string>
</resources>

View File

@@ -78,10 +78,4 @@ Skúste sa prihlásiť manuálne alebo naskenujte QR kód pomocou iného zariade
<string name="screen_server_confirmation_message_register">"Tu budú žiť vaše konverzácie - podobne ako používate poskytovateľa e-mailových služieb na uchovávanie e-mailov."</string>
<string name="screen_server_confirmation_title_login">"Chystáte sa prihlásiť do %1$s"</string>
<string name="screen_server_confirmation_title_register">"Chystáte sa vytvoriť účet na %1$s"</string>
<string name="screen_waitlist_message">"Momentálne je veľký dopyt po %1$s na %2$s. Vráťte sa do aplikácie za pár dní a skúste to znova.
Ďakujeme za trpezlivosť!"</string>
<string name="screen_waitlist_message_success">"Vitajte v %1$s!"</string>
<string name="screen_waitlist_title">"Ste na čakanej listine!"</string>
<string name="screen_waitlist_title_success">"Ste dnu!"</string>
</resources>

View File

@@ -78,10 +78,4 @@ Prova att logga in manuellt eller skanna QR-koden med en annan enhet."</string>
<string name="screen_server_confirmation_message_register">"Det är här dina konversationer kommer att sparas - precis som du skulle använda en e-postleverantör för att spara dina e-brev."</string>
<string name="screen_server_confirmation_title_login">"Du är på väg att logga in på %1$s"</string>
<string name="screen_server_confirmation_title_register">"Du är på väg att skapa ett konto på %1$s"</string>
<string name="screen_waitlist_message">"Det finns en stor efterfrågan på %1$s på %2$s just nu. Kom tillbaka till appen om några dagar och försök igen.
Tack för ditt tålamod!"</string>
<string name="screen_waitlist_message_success">"Välkommen till %1$s!"</string>
<string name="screen_waitlist_title">"Du är nästan framme."</string>
<string name="screen_waitlist_title_success">"Du är inne."</string>
</resources>

View File

@@ -78,10 +78,4 @@
<string name="screen_server_confirmation_message_register">"Тут будуть зберігатися Ваші розмови - так само, як Ви використовуєте поштову скриньку для зберігання своїх електронних листів."</string>
<string name="screen_server_confirmation_title_login">"Ви збираєтесь увійти в %1$s"</string>
<string name="screen_server_confirmation_title_register">"Ви збираєтеся створити обліковий запис на %1$s"</string>
<string name="screen_waitlist_message">"На цей момент існує високий попит на %1$s в %2$s. Поверніться до застосунку через кілька днів і спробуйте ще раз.
Дякуємо за терпіння!"</string>
<string name="screen_waitlist_message_success">"Ласкаво просимо до %1$s!"</string>
<string name="screen_waitlist_title">"Майже готово."</string>
<string name="screen_waitlist_title_success">"Готово."</string>
</resources>

View File

@@ -33,10 +33,4 @@
<string name="screen_server_confirmation_message_register">"Bu sizning suhbatlaringiz yashaydigan joy - xuddi siz elektron pochta xabarlaringizni saqlash uchun elektron pochta provayderidan foydalanganingiz kabi."</string>
<string name="screen_server_confirmation_title_login">"Siz tizimga kirmoqchisiz%1$s"</string>
<string name="screen_server_confirmation_title_register">"Hisob yaratmoqchisiz%1$s"</string>
<string name="screen_waitlist_message">"Hozirgi paytda %2$sga %1$sda talab yuqori. Bir necha kundan keyin ilovaga qayting va qaytadan urining.
Sabr-toqatingiz uchun rahmat!"</string>
<string name="screen_waitlist_message_success">"%1$sga Xush kelibsiz!"</string>
<string name="screen_waitlist_title">"Siz deyarli keldingiz."</string>
<string name="screen_waitlist_title_success">"Siz kirdingiz."</string>
</resources>

View File

@@ -29,5 +29,4 @@
<string name="screen_server_confirmation_message_register">"您的所有對話將保存於此,就如同您的電子郵件供應商會保存您的電子郵件一樣。"</string>
<string name="screen_server_confirmation_title_login">"您即將登入 %1$s"</string>
<string name="screen_server_confirmation_title_register">"您即將在 %1$s 建立帳號"</string>
<string name="screen_waitlist_message_success">"歡迎使用 %1$s"</string>
</resources>

View File

@@ -78,10 +78,4 @@
<string name="screen_server_confirmation_message_register">"这是您的对话将进行的地方,就像您使用电子邮件提供商来保存电子邮件一样。"</string>
<string name="screen_server_confirmation_title_login">"即将登录 %1$s"</string>
<string name="screen_server_confirmation_title_register">"即将在 %1$s 上创建一个账户"</string>
<string name="screen_waitlist_message">"目前 %1$s 上 %2$s 的负载很大。过几天再回来试试吧。
感谢您的耐心!"</string>
<string name="screen_waitlist_message_success">"欢迎使用 %1$s"</string>
<string name="screen_waitlist_title">"马上就好。"</string>
<string name="screen_waitlist_title_success">"您已加入。"</string>
</resources>

View File

@@ -78,10 +78,4 @@ Try signing in manually, or scan the QR code with another device."</string>
<string name="screen_server_confirmation_message_register">"This is where your conversations will live — just like you would use an email provider to keep your emails."</string>
<string name="screen_server_confirmation_title_login">"Youre about to sign in to %1$s"</string>
<string name="screen_server_confirmation_title_register">"Youre about to create an account on %1$s"</string>
<string name="screen_waitlist_message">"There\'s a high demand for %1$s on %2$s at the moment. Come back to the app in a few days and try again.
Thanks for your patience!"</string>
<string name="screen_waitlist_message_success">"Welcome to %1$s!"</string>
<string name="screen_waitlist_title">"Youre almost there."</string>
<string name="screen_waitlist_title_success">"You\'re in."</string>
</resources>

View File

@@ -108,7 +108,7 @@ private fun PinnedMessagesListContent(
ErrorDialog(
title = stringResource(id = CommonStrings.error_unknown),
content = stringResource(id = CommonStrings.error_failed_loading_messages),
onDismiss = onErrorDismiss
onSubmit = onErrorDismiss
)
}
PinnedMessagesListState.Empty -> PinnedMessagesListEmpty()

View File

@@ -36,7 +36,7 @@ fun FocusRequestStateView(
}
ErrorDialog(
content = errorMessage,
onDismiss = onClearFocusRequestState,
onSubmit = onClearFocusRequestState,
modifier = modifier,
)
}

View File

@@ -19,7 +19,7 @@ internal fun VoiceMessageSendingFailedDialog(
ErrorDialog(
title = stringResource(CommonStrings.common_error),
content = stringResource(CommonStrings.error_failed_uploading_voice_message),
onDismiss = onDismiss,
onSubmit = onDismiss,
submitText = stringResource(CommonStrings.action_ok),
)
}

View File

@@ -259,7 +259,7 @@ private fun InvalidNotificationSettingsView(
ErrorDialog(
title = stringResource(id = CommonStrings.dialog_title_error),
content = stringResource(id = R.string.screen_notification_settings_failed_fixing_configuration),
onDismiss = onDismissError
onSubmit = onDismissError
)
}
}

View File

@@ -124,7 +124,7 @@ fun RolesAndPermissionsView(
is AsyncAction.Failure -> {
ErrorDialog(
content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) }
onSubmit = { state.eventSink(RolesAndPermissionsEvents.CancelPendingAction) }
)
}
else -> Unit

View File

@@ -210,7 +210,7 @@ fun ChangeRolesView(
is AsyncAction.Failure -> {
ErrorDialog(
content = stringResource(CommonStrings.error_unknown),
onDismiss = { state.eventSink(ChangeRolesEvent.ClearError) }
onSubmit = { state.eventSink(ChangeRolesEvent.ClearError) }
)
}
is AsyncAction.Success -> {

View File

@@ -2,6 +2,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="banner_migrate_to_native_sliding_sync_action">"Log Out &amp; Upgrade"</string>
<string name="banner_migrate_to_native_sliding_sync_description">"Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later."</string>
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app."</string>
<string name="banner_migrate_to_native_sliding_sync_title">"Upgrade available"</string>
<string name="banner_set_up_recovery_content">"Generate a new recovery key that can be used to restore your encrypted message history in case you lose access to your devices."</string>
<string name="banner_set_up_recovery_title">"Set up recovery"</string>

View File

@@ -38,7 +38,6 @@
<string name="screen_recovery_key_confirm_error_content">"Паўтарыце спробу, каб пацвердзіць доступ да рэзервовай копіі чата."</string>
<string name="screen_recovery_key_confirm_error_title">"Няправільны ключ аднаўлення"</string>
<string name="screen_recovery_key_confirm_key_description">"Калі ў вас ёсць ключ аднаўлення або парольная фраза, гэта таксама будзе працаваць."</string>
<string name="screen_recovery_key_confirm_key_label">"Ключ аднаўлення або код доступу"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Увесці…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Страцілі ключ аднаўлення?"</string>
<string name="screen_recovery_key_confirm_success">"Ключ аднаўлення пацверджаны"</string>

View File

@@ -39,7 +39,6 @@
<string name="screen_recovery_key_confirm_error_content">"Zkuste prosím znovu potvrdit přístup k záloze chatu."</string>
<string name="screen_recovery_key_confirm_error_title">"Nesprávný klíč pro obnovení"</string>
<string name="screen_recovery_key_confirm_key_description">"Pokud máte bezpečnostní klíč nebo bezpečnostní frázi, bude to fungovat také."</string>
<string name="screen_recovery_key_confirm_key_label">"Klíč pro obnovení nebo přístupový kód"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Zadejte…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Ztratili jste klíč pro obnovení?"</string>
<string name="screen_recovery_key_confirm_success">"Klíč pro obnovení potvrzen"</string>

View File

@@ -51,10 +51,6 @@ Das bedeutet:"</string>
<string name="screen_recovery_key_confirm_error_content">"Bitte versuche es noch einmal, um den Zugriff auf dein Chat-Backup zu bestätigen."</string>
<string name="screen_recovery_key_confirm_error_title">"Falscher Wiederherstellungsschlüssel"</string>
<string name="screen_recovery_key_confirm_key_description">"Dies funktioniert auch mit einem Sicherheitsschlüssel oder Sicherheitsphrase."</string>
<string name="screen_recovery_key_confirm_key_label">
<b>"Wiederherstellungsschlüssel"</b>
" oder Passcode"
</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Eingeben…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Hast du deinen Wiederherstellungschlüssel vergessen?"</string>
<string name="screen_recovery_key_confirm_success">"Wiederherstellungsschlüssel bestätigt"</string>

View File

@@ -33,7 +33,6 @@
<string name="screen_recovery_key_confirm_error_content">"Προσπάθησε ξανά για να επιβεβαιώσεις την πρόσβαση στο αντίγραφο ασφαλείας της συνομιλίας σου."</string>
<string name="screen_recovery_key_confirm_error_title">"Λανθασμένο κλειδί ανάκτησης"</string>
<string name="screen_recovery_key_confirm_key_description">"Εάν έχεις ένα κλειδί ασφαλείας ή μια φράση ασφαλείας, θα λειτουργήσει επίσης."</string>
<string name="screen_recovery_key_confirm_key_label">"Κλειδί ανάκτησης ή κωδικός πρόσβασης"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Εισαγωγή…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Έχασες το κλειδί ανάκτησης;"</string>
<string name="screen_recovery_key_confirm_success">"Επιβεβαιώθηκε το κλειδί ανάκτησης"</string>

View File

@@ -39,7 +39,6 @@
<string name="screen_recovery_key_confirm_error_content">"Kinnitamaks ligipääsu sinu vestluse varukoopiale, palun proovi uuesti"</string>
<string name="screen_recovery_key_confirm_error_title">"Vigane taastevõti"</string>
<string name="screen_recovery_key_confirm_key_description">"Kui sul on turvavõti või turvafraas, siis need toimivad ka."</string>
<string name="screen_recovery_key_confirm_key_label">"Taastevõti või turvafraas"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Sisesta…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Kas sa oled taastevõtme kaotanud?"</string>
<string name="screen_recovery_key_confirm_success">"Taastevõti on kinnitatud"</string>

View File

@@ -37,7 +37,6 @@
<string name="screen_recovery_key_confirm_error_content">"Veuillez réessayer afin de pouvoir accéder à vos anciens messages."</string>
<string name="screen_recovery_key_confirm_error_title">"Clé de récupération incorrecte"</string>
<string name="screen_recovery_key_confirm_key_description">"Si vous avez une clé de sécurité ou une phrase de sécurité, cela fonctionnera également."</string>
<string name="screen_recovery_key_confirm_key_label">"Clé de récupération"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Saisissez la clé ici…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Clé de récupération perdue?"</string>
<string name="screen_recovery_key_confirm_success">"Clé de récupération confirmée"</string>

View File

@@ -39,7 +39,6 @@
<string name="screen_recovery_key_confirm_error_content">"Próbálja meg újra megerősíteni a csevegés biztonsági mentéséhez való hozzáférését."</string>
<string name="screen_recovery_key_confirm_error_title">"Helytelen helyreállítási kulcs"</string>
<string name="screen_recovery_key_confirm_key_description">"Ha van biztonsági kulcsa vagy biztonsági jelmondata, akkor ez is fog működni."</string>
<string name="screen_recovery_key_confirm_key_label">"Helyreállítási kulcs vagy jelkód"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Megadás…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Elvesztette a helyreállítási kulcsát?"</string>
<string name="screen_recovery_key_confirm_success">"Helyreállítási kulcs megerősítve"</string>

View File

@@ -33,7 +33,6 @@
<string name="screen_recovery_key_confirm_error_content">"Silakan coba lagi untuk mengonfirmasi akses ke cadangan percakapan Anda."</string>
<string name="screen_recovery_key_confirm_error_title">"Kunci pemulihan salah"</string>
<string name="screen_recovery_key_confirm_key_description">"Jika Anda memiliki kunci keamanan atau frasa keamanan, ini juga bisa digunakan."</string>
<string name="screen_recovery_key_confirm_key_label">"Kunci pemulihan atau kode sandi"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Masukkan…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Kehilangan kunci pemulihan Anda?"</string>
<string name="screen_recovery_key_confirm_success">"Kunci pemulihan dikonfirmasi"</string>

View File

@@ -38,7 +38,6 @@
<string name="screen_recovery_key_confirm_error_content">"Riprova per confermare l\'accesso al backup della chat."</string>
<string name="screen_recovery_key_confirm_error_title">"Chiave di recupero errata"</string>
<string name="screen_recovery_key_confirm_key_description">"Se hai una chiave di sicurezza o una password, andrà bene anche questo."</string>
<string name="screen_recovery_key_confirm_key_label">"Chiave di recupero o codice di accesso"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Inserisci…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Hai perso la chiave di recupero?"</string>
<string name="screen_recovery_key_confirm_success">"Chiave di recupero confermata"</string>

View File

@@ -38,7 +38,6 @@
<string name="screen_recovery_key_confirm_error_content">"Spróbuj ponownie, aby potwierdzić dostęp do backupu czatu."</string>
<string name="screen_recovery_key_confirm_error_title">"Nieprawidłowy klucz przywracania"</string>
<string name="screen_recovery_key_confirm_key_description">"To też zadziała, jeśli posiadasz klucz lub frazę bezpieczeństwa."</string>
<string name="screen_recovery_key_confirm_key_label">"Klucz przywracania lub hasło"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Wprowadź…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Zgubiłeś swój kod przywracania?"</string>
<string name="screen_recovery_key_confirm_success">"Potwierdzono klucz przywracania"</string>

View File

@@ -38,7 +38,6 @@
<string name="screen_recovery_key_confirm_error_content">"Por favor, tenta novamente para confirmar o acesso à tua cópia de segurança das conversas."</string>
<string name="screen_recovery_key_confirm_error_title">"Chave de recuperação incorreta"</string>
<string name="screen_recovery_key_confirm_key_description">"Também funciona se tiveres uma chave ou frase de segurança."</string>
<string name="screen_recovery_key_confirm_key_label">"Chave ou código de recuperação"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Inserir…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Perdeste a tua chave?"</string>
<string name="screen_recovery_key_confirm_success">"Chave de recuperação confirmada"</string>

View File

@@ -33,7 +33,6 @@
<string name="screen_recovery_key_confirm_error_content">"Vă rugăm să încercați din nou să confirmați accesul la backup."</string>
<string name="screen_recovery_key_confirm_error_title">"Cheie de recuperare incorectă"</string>
<string name="screen_recovery_key_confirm_key_description">"Dacă aveți o cheie de securitate sau o frază de securitate, aceasta va funcționa și ea."</string>
<string name="screen_recovery_key_confirm_key_label">"Cheie de recuperare sau cod de acces"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Introduceți…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Ați pierdut cheia de recuperare?"</string>
<string name="screen_recovery_key_confirm_success">"Cheia de recuperare confirmată"</string>

View File

@@ -60,10 +60,6 @@
<b>"ключ восстановления"</b>
</string>
<string name="screen_recovery_key_confirm_key_description">"Если у вас есть пароль для восстановления или секретный пароль/ключ, это тоже сработает."</string>
<string name="screen_recovery_key_confirm_key_label">
<b>"Ключ восстановления"</b>
" или пароль"
</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Вход…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Потеряли ключ восстановления?"</string>
<string name="screen_recovery_key_confirm_success">

View File

@@ -39,7 +39,6 @@
<string name="screen_recovery_key_confirm_error_content">"Skúste prosím znova potvrdiť prístup k vašej zálohe konverzácie."</string>
<string name="screen_recovery_key_confirm_error_title">"Nesprávny kľúč na obnovenie"</string>
<string name="screen_recovery_key_confirm_key_description">"Ak máte bezpečnostný kľúč alebo bezpečnostnú frázu, bude to fungovať tiež."</string>
<string name="screen_recovery_key_confirm_key_label">"Kľúč na obnovenie alebo prístupový kód"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Zadať…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Stratili ste kľúč na obnovenie?"</string>
<string name="screen_recovery_key_confirm_success">"Kľúč na obnovu potvrdený"</string>

View File

@@ -39,7 +39,6 @@
<string name="screen_recovery_key_confirm_error_content">"Vänligen pröva igen för att bekräfta åtkomsten till din chattsäkerhetskopia."</string>
<string name="screen_recovery_key_confirm_error_title">"Felaktig återställningsnyckel"</string>
<string name="screen_recovery_key_confirm_key_description">"Om du har en säkerhetsnyckel eller säkerhetsfras så funkar den också."</string>
<string name="screen_recovery_key_confirm_key_label">"Återställningsnyckel eller lösenkod"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Ange …"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Blivit av med din återställningsnyckel?"</string>
<string name="screen_recovery_key_confirm_success">"Återställningsnyckel bekräftad"</string>

View File

@@ -38,7 +38,6 @@
<string name="screen_recovery_key_confirm_error_content">"Будь ласка, спробуйте ще раз, щоб підтвердити доступ до резервної копії чату."</string>
<string name="screen_recovery_key_confirm_error_title">"Неправильний ключ відновлення"</string>
<string name="screen_recovery_key_confirm_key_description">"Якщо у вас є ключ безпеки або фраза безпеки, це теж спрацює."</string>
<string name="screen_recovery_key_confirm_key_label">"Ключ відновлення або код допуску"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Ввести…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Загубили ключ відновлення?"</string>
<string name="screen_recovery_key_confirm_success">"Ключ відновлення підтверджено"</string>

View File

@@ -38,7 +38,6 @@
<string name="screen_recovery_key_confirm_error_content">"请重试以访问您的聊天备份。"</string>
<string name="screen_recovery_key_confirm_error_title">"恢复密钥不正确"</string>
<string name="screen_recovery_key_confirm_key_description">"如果您有安全密钥或安全短语,也可以用。"</string>
<string name="screen_recovery_key_confirm_key_label">"恢复密钥或密码"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"输入……"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"丢失了恢复密钥?"</string>
<string name="screen_recovery_key_confirm_success">"恢复密钥已确认"</string>

View File

@@ -39,7 +39,6 @@
<string name="screen_recovery_key_confirm_error_content">"Please try again to confirm access to your chat backup."</string>
<string name="screen_recovery_key_confirm_error_title">"Incorrect recovery key"</string>
<string name="screen_recovery_key_confirm_key_description">"If you have a security key or security phrase, this will work too."</string>
<string name="screen_recovery_key_confirm_key_label">"Recovery key or passcode"</string>
<string name="screen_recovery_key_confirm_key_placeholder">"Enter…"</string>
<string name="screen_recovery_key_confirm_lost_recovery_key">"Lost your recovery key?"</string>
<string name="screen_recovery_key_confirm_success">"Recovery key confirmed"</string>

View File

@@ -48,7 +48,7 @@ fun <T> AsyncActionView(
ErrorDialog(
title = errorTitle(async.error),
content = errorMessage(async.error),
onDismiss = onErrorDismiss
onSubmit = onErrorDismiss
)
} else {
RetryDialog(

View File

@@ -13,6 +13,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.window.DialogProperties
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@@ -25,17 +26,23 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun ErrorDialog(
content: String,
onDismiss: () -> Unit,
onSubmit: () -> Unit,
modifier: Modifier = Modifier,
title: String = ErrorDialogDefaults.title,
title: String? = ErrorDialogDefaults.title,
submitText: String = ErrorDialogDefaults.submitText,
onDismiss: () -> Unit = onSubmit,
canDismiss: Boolean = true,
) {
BasicAlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
BasicAlertDialog(
modifier = modifier,
onDismissRequest = onDismiss,
properties = DialogProperties(dismissOnClickOutside = canDismiss, dismissOnBackPress = canDismiss)
) {
ErrorDialogContent(
title = title,
content = content,
submitText = submitText,
onSubmitClick = onDismiss,
onSubmitClick = onSubmit,
)
}
}
@@ -44,7 +51,7 @@ fun ErrorDialog(
private fun ErrorDialogContent(
content: String,
onSubmitClick: () -> Unit,
title: String = ErrorDialogDefaults.title,
title: String? = ErrorDialogDefaults.title,
submitText: String = ErrorDialogDefaults.submitText,
) {
SimpleAlertDialogContent(
@@ -78,6 +85,6 @@ internal fun ErrorDialogContentPreview() {
internal fun ErrorDialogPreview() = ElementPreview {
ErrorDialog(
content = "Content",
onDismiss = {},
onSubmit = {},
)
}

View File

@@ -131,6 +131,9 @@ interface MatrixClient : Closeable {
/** Returns `true` if the home server supports native sliding sync. */
suspend fun isNativeSlidingSyncSupported(): Boolean
/** Returns `true` if the current session is using native sliding sync. */
/** Returns `true` if the home server supports sliding sync using a proxy. */
suspend fun isSlidingSyncProxySupported(): Boolean
/** Returns `true` if the current session is using native sliding sync, `false` if it's using a proxy. */
fun isUsingNativeSlidingSync(): Boolean
}

View File

@@ -534,6 +534,10 @@ class RustMatrixClient(
return client.availableSlidingSyncVersions().contains(SlidingSyncVersion.Native)
}
override suspend fun isSlidingSyncProxySupported(): Boolean {
return client.availableSlidingSyncVersions().any { it is SlidingSyncVersion.Proxy }
}
override fun isUsingNativeSlidingSync(): Boolean {
return client.session().slidingSyncVersion == SlidingSyncVersion.Native
}

View File

@@ -7,7 +7,6 @@
package io.element.android.libraries.matrix.impl
import io.element.android.appconfig.AuthenticationConfig
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.featureflag.api.FeatureFlagService
@@ -19,12 +18,10 @@ import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.sessionstorage.api.SessionData
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.Session
@@ -47,7 +44,6 @@ class RustMatrixClientFactory @Inject constructor(
private val proxyProvider: ProxyProvider,
private val clock: SystemClock,
private val utdTracker: UtdTracker,
private val appPreferencesStore: AppPreferencesStore,
private val featureFlagService: FeatureFlagService,
) {
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
@@ -55,7 +51,7 @@ class RustMatrixClientFactory @Inject constructor(
val client = getBaseClientBuilder(
sessionPaths = sessionData.getSessionPaths(),
passphrase = sessionData.passphrase,
restore = true,
slidingSyncType = ClientBuilderSlidingSync.Restored,
)
.homeserverUrl(sessionData.homeserverUrl)
.username(sessionData.userId)
@@ -88,16 +84,8 @@ class RustMatrixClientFactory @Inject constructor(
internal suspend fun getBaseClientBuilder(
sessionPaths: SessionPaths,
passphrase: String?,
restore: Boolean,
slidingSyncType: ClientBuilderSlidingSync,
): ClientBuilder {
val slidingSync = when {
// Always check restore first, since otherwise other values could accidentally override the already persisted config
restore -> ClientBuilderSlidingSync.Restored
AuthenticationConfig.SLIDING_SYNC_PROXY_URL != null -> ClientBuilderSlidingSync.CustomProxy(AuthenticationConfig.SLIDING_SYNC_PROXY_URL!!)
appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first() -> ClientBuilderSlidingSync.Simplified
else -> ClientBuilderSlidingSync.Discovered
}
return ClientBuilder()
.sessionPaths(
dataPath = sessionPaths.fileDirectory.absolutePath,
@@ -117,9 +105,9 @@ class RustMatrixClientFactory @Inject constructor(
)
.run {
// Apply sliding sync version settings
when (slidingSync) {
when (slidingSyncType) {
ClientBuilderSlidingSync.Restored -> this
is ClientBuilderSlidingSync.CustomProxy -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Proxy(slidingSync.url))
is ClientBuilderSlidingSync.CustomProxy -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Proxy(slidingSyncType.url))
ClientBuilderSlidingSync.Discovered -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.DiscoverProxy)
ClientBuilderSlidingSync.Simplified -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.DiscoverNative)
ClientBuilderSlidingSync.ForcedSimplified -> slidingSyncVersionBuilder(SlidingSyncVersionBuilder.Native)

View File

@@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.auth
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.AuthenticationConfig
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.di.AppScope
@@ -19,6 +20,7 @@ import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.impl.ClientBuilderSlidingSync
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
import io.element.android.libraries.matrix.impl.auth.qrlogin.QrErrorMapper
import io.element.android.libraries.matrix.impl.auth.qrlogin.SdkQrCodeLoginData
@@ -28,6 +30,7 @@ import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator
import io.element.android.libraries.matrix.impl.mapper.toSessionData
import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.sessionstorage.api.LoggedInState
import io.element.android.libraries.sessionstorage.api.LoginType
import io.element.android.libraries.sessionstorage.api.SessionStore
@@ -35,9 +38,13 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.HumanQrLoginException
import org.matrix.rustcomponents.sdk.OidcConfiguration
import org.matrix.rustcomponents.sdk.QrCodeData
import org.matrix.rustcomponents.sdk.QrCodeDecodeException
import org.matrix.rustcomponents.sdk.QrLoginProgress
import org.matrix.rustcomponents.sdk.QrLoginProgressListener
@@ -55,6 +62,7 @@ class RustMatrixAuthenticationService @Inject constructor(
private val rustMatrixClientFactory: RustMatrixClientFactory,
private val passphraseGenerator: PassphraseGenerator,
private val oidcConfigurationProvider: OidcConfigurationProvider,
private val appPreferencesStore: AppPreferencesStore,
) : MatrixAuthenticationService {
// Passphrase which will be used for new sessions. Existing sessions will use the passphrase
// stored in the SessionData.
@@ -117,9 +125,10 @@ class RustMatrixAuthenticationService @Inject constructor(
withContext(coroutineDispatchers.io) {
val emptySessionPath = rotateSessionPath()
runCatching {
val client = getBaseClientBuilder(emptySessionPath)
.serverNameOrHomeserverUrl(homeserver)
.build()
val client = makeClient(sessionPaths = emptySessionPath) {
serverNameOrHomeserverUrl(homeserver)
}
currentClient = client
val homeServerDetails = client.homeserverLoginDetails().map()
currentHomeserver.value = homeServerDetails.copy(url = homeserver)
@@ -207,23 +216,24 @@ class RustMatrixAuthenticationService @Inject constructor(
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) =
withContext(coroutineDispatchers.io) {
val sdkQrCodeLoginData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData
val emptySessionPaths = rotateSessionPath()
val oidcConfiguration = oidcConfigurationProvider.get()
val progressListener = object : QrLoginProgressListener {
override fun onUpdate(state: QrLoginProgress) {
Timber.d("QR Code login progress: $state")
progress(state.toStep())
}
}
runCatching {
val client = rustMatrixClientFactory.getBaseClientBuilder(
val client = makeQrCodeLoginClient(
sessionPaths = emptySessionPaths,
passphrase = pendingPassphrase,
restore = false,
qrCodeData = sdkQrCodeLoginData,
oidcConfiguration = oidcConfiguration,
progressListener = progressListener,
)
.buildWithQrCode(
qrCodeData = (qrCodeData as SdkQrCodeLoginData).rustQrCodeData,
oidcConfiguration = oidcConfigurationProvider.get(),
progressListener = object : QrLoginProgressListener {
override fun onUpdate(state: QrLoginProgress) {
Timber.d("QR Code login progress: $state")
progress(state.toStep())
}
}
)
client.use { rustClient ->
val sessionData = rustClient.session()
.toSessionData(
@@ -249,14 +259,80 @@ class RustMatrixAuthenticationService @Inject constructor(
}
}
private suspend fun getBaseClientBuilder(
private suspend fun makeClient(
sessionPaths: SessionPaths,
) = rustMatrixClientFactory
.getBaseClientBuilder(
sessionPaths = sessionPaths,
passphrase = pendingPassphrase,
restore = false,
)
config: suspend ClientBuilder.() -> ClientBuilder,
): Client {
val slidingSyncType = getSlidingSyncType()
if (slidingSyncType is ClientBuilderSlidingSync.Simplified) {
Timber.d("Creating client with simplified sliding sync")
try {
return rustMatrixClientFactory
.getBaseClientBuilder(
sessionPaths = sessionPaths,
passphrase = pendingPassphrase,
slidingSyncType = slidingSyncType,
)
.run { config() }
.build()
} catch (e: HumanQrLoginException.SlidingSyncNotAvailable) {
Timber.e(e, "Failed to create client with simplified sliding sync, trying with Proxy now")
}
}
Timber.d("Creating client with Proxy sliding sync")
return rustMatrixClientFactory
.getBaseClientBuilder(
sessionPaths = sessionPaths,
passphrase = pendingPassphrase,
slidingSyncType = getSlidingSyncProxy(),
)
.run { config() }
.build()
}
private suspend fun makeQrCodeLoginClient(
sessionPaths: SessionPaths,
passphrase: String?,
qrCodeData: QrCodeData,
oidcConfiguration: OidcConfiguration,
progressListener: QrLoginProgressListener,
): Client {
val slidingSyncType = getSlidingSyncType()
if (slidingSyncType is ClientBuilderSlidingSync.Simplified) {
Timber.d("Creating client for QR Code login with simplified sliding sync")
try {
return rustMatrixClientFactory
.getBaseClientBuilder(
sessionPaths = sessionPaths,
passphrase = pendingPassphrase,
slidingSyncType = slidingSyncType,
)
.passphrase(passphrase)
.buildWithQrCode(qrCodeData, oidcConfiguration, progressListener)
} catch (e: HumanQrLoginException.SlidingSyncNotAvailable) {
Timber.e(e, "Failed to create client with simplified sliding sync, trying with Proxy now")
}
}
Timber.d("Creating client for QR Code login with Proxy sliding sync")
return rustMatrixClientFactory
.getBaseClientBuilder(
sessionPaths = sessionPaths,
passphrase = pendingPassphrase,
slidingSyncType = getSlidingSyncProxy(),
)
.passphrase(passphrase)
.buildWithQrCode(qrCodeData, oidcConfiguration, progressListener)
}
private suspend fun getSlidingSyncType(nativeSlidingSyncFailed: Boolean = false) = when {
appPreferencesStore.isSimplifiedSlidingSyncEnabledFlow().first() && !nativeSlidingSyncFailed -> ClientBuilderSlidingSync.Simplified
else -> getSlidingSyncProxy()
}
private fun getSlidingSyncProxy() = when {
AuthenticationConfig.SLIDING_SYNC_PROXY_URL != null -> ClientBuilderSlidingSync.CustomProxy(AuthenticationConfig.SLIDING_SYNC_PROXY_URL!!)
else -> ClientBuilderSlidingSync.Discovered
}
private fun clear() {
currentClient?.close()

View File

@@ -79,6 +79,7 @@ class FakeMatrixClient(
private val userIdServerNameLambda: () -> String = { lambdaError() },
private val getUrlLambda: (String) -> Result<String> = { lambdaError() },
var isNativeSlidingSyncSupportedLambda: suspend () -> Boolean = { true },
var isSlidingSyncProxySupportedLambda: suspend () -> Boolean = { true },
var isUsingNativeSlidingSyncLambda: () -> Boolean = { true },
) : MatrixClient {
var setDisplayNameCalled: Boolean = false
@@ -324,6 +325,10 @@ class FakeMatrixClient(
return isNativeSlidingSyncSupportedLambda()
}
override suspend fun isSlidingSyncProxySupported(): Boolean {
return isSlidingSyncProxySupportedLambda()
}
override fun isUsingNativeSlidingSync(): Boolean {
return isUsingNativeSlidingSyncLambda()
}

View File

@@ -87,7 +87,7 @@ class DefaultAppPreferencesStore @Inject constructor(
override fun isSimplifiedSlidingSyncEnabledFlow(): Flow<Boolean> {
return store.data.map { prefs ->
prefs[simplifiedSlidingSyncKey] ?: false
prefs[simplifiedSlidingSyncKey] ?: true
}
}

View File

@@ -36,6 +36,7 @@
<string name="action_back">"Back"</string>
<string name="action_call">"Call"</string>
<string name="action_cancel">"Cancel"</string>
<string name="action_cancel_for_now">"Cancel for now"</string>
<string name="action_choose_photo">"Choose photo"</string>
<string name="action_clear">"Clear"</string>
<string name="action_close">"Close"</string>
@@ -283,6 +284,12 @@ Reason: %1$s."</string>
<string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string>
<string name="screen_reset_identity_confirmation_subtitle">"You\'re about to go to your %1$s account to reset your identity. Afterwards you\'ll be taken back to the app."</string>
<string name="screen_reset_identity_confirmation_title">"Can\'t confirm? Go to your account to reset your identity."</string>
<string name="screen_resolve_send_failure_changed_identity_primary_button_title">"Withdraw verification and send"</string>
<string name="screen_resolve_send_failure_changed_identity_subtitle">"You can withdraw your verification and send this message anyway, or you can cancel for now and try again later after reverifying %1$s."</string>
<string name="screen_resolve_send_failure_changed_identity_title">"Your message was not sent because %1$ss verified identity has changed"</string>
<string name="screen_resolve_send_failure_unsigned_device_primary_button_title">"Send message anyway"</string>
<string name="screen_resolve_send_failure_unsigned_device_subtitle">"%1$s is using one or more unverified devices. You can send the message anyway, or you can cancel for now and try again later after %2$s has verified all their devices."</string>
<string name="screen_resolve_send_failure_unsigned_device_title">"Your message was not sent because %1$s has not verified one or more devices"</string>
<string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string>
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
@@ -304,6 +311,8 @@ Reason: %1$s."</string>
<string name="screen_share_open_google_maps">"Open in Google Maps"</string>
<string name="screen_share_open_osm_maps">"Open in OpenStreetMap"</string>
<string name="screen_share_this_location_action">"Share this location"</string>
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Message not sent because %1$ss verified identity has changed."</string>
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Message not sent because %1$s has not verified one or more devices."</string>
<string name="screen_view_location_title">"Location"</string>
<string name="settings_version_number">"Version: %1$s (%2$s)"</string>
<string name="test_language_identifier">"en"</string>

View File

@@ -55,11 +55,11 @@ class MainActivity : ComponentActivity() {
proxyProvider = proxyProvider,
clock = DefaultSystemClock(),
utdTracker = UtdTracker(NoopAnalyticsService()),
appPreferencesStore = InMemoryAppPreferencesStore(),
featureFlagService = AlwaysEnabledFeatureFlagService(),
),
passphraseGenerator = NullPassphraseGenerator(),
oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory),
appPreferencesStore = InMemoryAppPreferencesStore(),
)
}

View File

@@ -36,7 +36,7 @@ private fun AppErrorViewContent(
ErrorDialog(
title = title,
content = body,
onDismiss = onDismiss,
onSubmit = onDismiss,
)
}

View File

@@ -1,5 +1,12 @@
{
"modules" : [
{
"name" : ":appnav",
"includeRegex" : [
"banner\\.migrate_to_native_sliding_sync\\.force_logout.title",
"banner\\.migrate_to_native_sliding_sync\\.action"
]
},
{
"name" : ":features:rageshake:impl",
"includeRegex" : [