Merge branch 'develop' into feature/fga/mark_room_as_favorite

This commit is contained in:
ganfra 2024-02-12 17:08:36 +01:00
commit a8bc0cb4ca
538 changed files with 4465 additions and 1639 deletions

View file

@ -38,7 +38,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Assemble debug APK

View file

@ -11,7 +11,8 @@ jobs:
welcome:
runs-on: ubuntu-latest
name: Welcome comment
if: github.event.pull_request.fork != null
# Only display it if base repo (upstream) is different from HEAD repo (possibly a fork)
if: github.event.pull_request.base.repo.full_name != github.event.pull_request.head.repo.full_name
steps:
- name: Add auto-generated commit warning
uses: actions/github-script@v7

View file

@ -62,7 +62,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Dependency analysis

View file

@ -40,7 +40,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Run code quality check suite

View file

@ -39,7 +39,7 @@ jobs:
java-version: '17'
# Add gradle cache, this should speed up the process
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: Record screenshots

View file

@ -25,7 +25,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
- name: Create app bundle
env:
ELEMENT_ANDROID_MAPTILER_API_KEY: ${{ secrets.MAPTILER_KEY }}

View file

@ -32,7 +32,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
- name: 🔊 Publish results to Sonar

View file

@ -44,7 +44,7 @@ jobs:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '17'
- name: Configure gradle
uses: gradle/gradle-build-action@v2.12.0
uses: gradle/actions/setup-gradle@v3
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
@ -81,6 +81,9 @@ jobs:
# https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov
if: always()
uses: codecov/codecov-action@v3
# with:
# files: build/reports/kover/xml/report.xml
uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
# with:
# files: build/reports/kover/xml/report.xml

View file

@ -31,7 +31,7 @@
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md
Element X Android support can be found in this room: [![Element Android Matrix room #element-android:matrix.org](https://img.shields.io/matrix/element-android:matrix.org.svg?label=%23element-android:matrix.org&logo=matrix&server_fqdn=matrix.org)](https://matrix.to/#/#element-android:matrix.org).
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

View file

@ -3,7 +3,7 @@
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-x-android&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=vector-im_element-x-android&metric=bugs)](https://sonarcloud.io/summary/new_code?id=vector-im_element-x-android)
[![codecov](https://codecov.io/github/element-hq/element-x-android/branch/develop/graph/badge.svg?token=ecwvia7amV)](https://codecov.io/github/vector-im/element-x-android)
[![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)
[![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)
[![Localazy](https://img.shields.io/endpoint?url=https%3A%2F%2Fconnect.localazy.com%2Fstatus%2Felement%2Fdata%3Fcontent%3Dall%26title%3Dlocalazy%26logo%3Dtrue)](https://localazy.com/p/element)
# Element X Android

View file

@ -35,10 +35,8 @@ import io.element.android.x.R
@Preview
@Composable
internal fun IconPreview(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
internal fun IconPreview() {
Box {
Image(painter = painterResource(id = R.mipmap.ic_launcher_background), contentDescription = null)
Image(painter = painterResource(id = R.mipmap.ic_launcher_foreground), contentDescription = null)
}
@ -46,10 +44,8 @@ internal fun IconPreview(
@Preview
@Composable
internal fun RoundIconPreview(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.clip(shape = CircleShape)) {
internal fun RoundIconPreview() {
Box(modifier = Modifier.clip(shape = CircleShape)) {
Image(painter = painterResource(id = R.mipmap.ic_launcher_background), contentDescription = null)
Image(painter = painterResource(id = R.mipmap.ic_launcher_foreground), contentDescription = null)
}
@ -57,11 +53,9 @@ internal fun RoundIconPreview(
@Preview
@Composable
internal fun MonochromeIconPreview(
modifier: Modifier = Modifier,
) {
internal fun MonochromeIconPreview() {
Box(
modifier = modifier
modifier = Modifier
.size(108.dp)
.background(Color(0xFF2F3133))
.clip(shape = RoundedCornerShape(32.dp)),

View file

@ -84,10 +84,8 @@ fun LoadingRoomNodeView(
@Composable
private fun LoadingRoomTopBar(
onBackClicked: () -> Unit,
modifier: Modifier = Modifier
) {
TopAppBar(
modifier = modifier,
navigationIcon = {
BackButton(onClick = onBackClicked)
},

View file

@ -28,6 +28,7 @@ import io.element.android.features.rageshake.test.crash.FakeCrashDataStore
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.services.apperror.api.AppErrorState
import io.element.android.services.apperror.api.AppErrorStateService
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
@ -83,6 +84,7 @@ class RootPresenterTest {
val rageshake = FakeRageShake()
val screenshotHolder = FakeScreenshotHolder()
val crashDetectionPresenter = DefaultCrashDetectionPresenter(
buildMeta = aBuildMeta(),
crashDataStore = crashDataStore
)
val rageshakeDetectionPresenter = DefaultRageshakeDetectionPresenter(

View file

@ -0,0 +1 @@
Fix message forwarding after SDK API change related to Timeline intitialization.

View file

@ -0,0 +1 @@
Remove Compose Foundation version pinning workaround. This was done to avoid a bug introduced in the default foundation version used by the material3 library, but that has already been fixed.

View file

@ -0,0 +1,2 @@
Remove `FilterHiddenStateEventsProcessor`, as this is already handled by the Rust SDK.

View file

@ -0,0 +1 @@
Remove session preferences on user log out.

2
changelog.d/2241.feature Normal file
View file

@ -0,0 +1,2 @@
Change "Read receipts" advanced setting used to send private Read Receipt to "Share presence" settings.
When disabled, private Read Receipts will be sent, and no typing notification will be sent. Also Read Receipts and typing notifications will not be rendered in the timeline.

1
changelog.d/2242.feature Normal file
View file

@ -0,0 +1 @@
Rendering typing notification

1
changelog.d/2261.feature Normal file
View file

@ -0,0 +1 @@
Manually mark a room as unread

1
changelog.d/2304.bugfix Normal file
View file

@ -0,0 +1 @@
Fix crash after unregistering UnifiedPush distributor

1
changelog.d/2310.misc Normal file
View file

@ -0,0 +1 @@
Move migration screen to within the room list

1
changelog.d/2330.feature Normal file
View file

@ -0,0 +1 @@
Add empty state to the room list.

1
changelog.d/2333.feature Normal file
View file

@ -0,0 +1 @@
Allow joining unencrypted video calls in non encrypted rooms.

1
changelog.d/825.misc Normal file
View file

@ -0,0 +1 @@
Adjusted the login flow buttons so the continue button is always at the same height

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_share_data">"Дзяліцеся дадзенымі аналітыкі"</string>
<string name="screen_analytics_settings_help_us_improve">"Даваць ананімныя дадзеныя аб выкарыстанні, каб дапамагчы нам выявіць праблемы."</string>
<string name="screen_analytics_settings_read_terms">"Вы можаце азнаёміцца з усімі нашымі ўмовамі %1$s."</string>
<string name="screen_analytics_settings_read_terms_content_link">"тут"</string>
</resources>

View file

@ -57,7 +57,6 @@ import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.persistentListOf
@ -67,7 +66,6 @@ fun AnalyticsOptInView(
onClickTerms: () -> Unit,
modifier: Modifier = Modifier,
) {
LogCompositions(tag = "Analytics", msg = "Root")
val eventSink = state.eventSink
fun onTermsAccepted() {
@ -101,10 +99,8 @@ private const val LINK_TAG = "link"
private fun AnalyticsOptInHeader(
state: AnalyticsOptInState,
onClickTerms: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconTitleSubtitleMolecule(
@ -141,9 +137,9 @@ private fun AnalyticsOptInHeader(
}
@Composable
private fun CheckIcon(modifier: Modifier = Modifier) {
private fun CheckIcon() {
Icon(
modifier = modifier
modifier = Modifier
.size(20.dp)
.background(color = MaterialTheme.colorScheme.background, shape = CircleShape)
.padding(2.dp),
@ -154,11 +150,9 @@ private fun CheckIcon(modifier: Modifier = Modifier) {
}
@Composable
private fun AnalyticsOptInContent(
modifier: Modifier = Modifier,
) {
private fun AnalyticsOptInContent() {
Box(
modifier = modifier.fillMaxSize(),
modifier = Modifier.fillMaxSize(),
contentAlignment = BiasAlignment(
horizontalBias = 0f,
verticalBias = -0.4f
@ -190,11 +184,8 @@ private fun AnalyticsOptInContent(
private fun AnalyticsOptInFooter(
onTermsAccepted: () -> Unit,
onTermsDeclined: () -> Unit,
modifier: Modifier = Modifier,
) {
ButtonColumnMolecule(
modifier = modifier,
) {
ButtonColumnMolecule {
Button(
text = stringResource(id = CommonStrings.action_ok),
onClick = onTermsAccepted,

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">"Мы не будзем запісваць або прафіляваць любыя асабістыя даныя"</string>
<string name="screen_analytics_prompt_help_us_improve">"Даваць ананімныя дадзеныя аб выкарыстанні, каб дапамагчы нам выявіць праблемы."</string>
<string name="screen_analytics_prompt_read_terms">"Вы можаце азнаёміцца з усімі нашымі ўмовамі %1$s."</string>
<string name="screen_analytics_prompt_read_terms_content_link">"тут"</string>
<string name="screen_analytics_prompt_settings">"Вы можаце адключыць гэта ў любы час"</string>
<string name="screen_analytics_prompt_third_party_sharing">"Мы не будзем перадаваць вашыя дадзеныя трэцім асобам"</string>
<string name="screen_analytics_prompt_title">"Дапамажыце палепшыць %1$s"</string>
</resources>

View file

@ -43,7 +43,7 @@ class DefaultCallWidgetProvider @Inject constructor(
): Result<Pair<MatrixWidgetDriver, String>> = runCatching {
val room = matrixClientsProvider.getOrRestore(sessionId).getOrThrow().getRoom(roomId) ?: error("Room not found")
val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull() ?: ElementCallConfig.DEFAULT_BASE_URL
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl)
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = room.isEncrypted)
val callUrl = room.generateWidgetWebViewUrl(widgetSettings, clientId, languageTag, theme).getOrThrow()
room.getWidgetDriver(widgetSettings).getOrThrow() to callUrl
}

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">"Бягучы выклік"</string>
<string name="call_foreground_service_message_android">"Націсніце, каб вярнуцца да выкліка"</string>
<string name="call_foreground_service_title_android">"☎️ Выконваецца выклік"</string>
</resources>

View file

@ -88,10 +88,8 @@ private fun AddPeopleViewTopBar(
hasSelectedUsers: Boolean,
onBackPressed: () -> Unit,
onNextPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
title = {
Text(
text = stringResource(id = R.string.screen_create_room_add_people_title),

View file

@ -18,7 +18,6 @@ package io.element.android.features.createroom.impl.configureroom
import android.net.Uri
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@ -38,8 +37,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization
@ -52,6 +49,7 @@ import io.element.android.libraries.designsystem.components.LabelledTextField
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
@ -173,10 +171,8 @@ private fun ConfigureRoomToolbar(
isNextActionEnabled: Boolean,
onBackPressed: () -> Unit,
onNextPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
title = {
Text(
text = stringResource(R.string.screen_create_room_title),
@ -259,13 +255,6 @@ private fun RoomPrivacyOptions(
}
}
private fun Modifier.clearFocusOnTap(focusManager: FocusManager): Modifier =
pointerInput(Unit) {
detectTapGestures(onTap = {
focusManager.clearFocus()
})
}
@PreviewsDayNight
@Composable
internal fun ConfigureRoomViewPreview(@PreviewParameter(ConfigureRoomStateProvider::class) state: ConfigureRoomState) = ElementPreview {

View file

@ -117,10 +117,8 @@ fun CreateRoomRootView(
@Composable
private fun CreateRoomRootViewTopBar(
onClosePressed: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
title = {
Text(
text = stringResource(id = CommonStrings.action_start_chat),
@ -141,9 +139,8 @@ private fun CreateRoomActionButtonsList(
state: CreateRoomRootState,
onNewRoomClicked: () -> Unit,
onInvitePeopleClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
Column {
CreateRoomActionButton(
iconRes = CompoundDrawables.ic_plus,
text = stringResource(id = R.string.screen_create_room_action_create_room),
@ -162,10 +159,9 @@ private fun CreateRoomActionButton(
@DrawableRes iconRes: Int,
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.clickable { onClick() }

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_create_room_action_create_room">"Новы пакой"</string>
<string name="screen_create_room_action_invite_people">"Запрасіце сяброў у Element"</string>
<string name="screen_create_room_add_people_title">"Запрасіць людзей"</string>
<string name="screen_create_room_error_creating_room">"Пры стварэнні пакоя адбылася памылка"</string>
<string name="screen_create_room_private_option_description">"Паведамленні ў гэтым пакоі зашыфраваны. Гэта шыфраванне нельга адключыць."</string>
<string name="screen_create_room_private_option_title">"Прыватны пакой (толькі па запрашэнні)"</string>
<string name="screen_create_room_public_option_description">"Паведамленні не зашыфраваны, і кожны можа іх прачытаць. Вы можаце ўключыць шыфраванне пазней."</string>
<string name="screen_create_room_public_option_title">"Адкрыты пакой (для ўсіх)"</string>
<string name="screen_create_room_topic_label">"Тэма (неабавязкова)"</string>
<string name="screen_start_chat_error_starting_chat">"Пры спробе пачаць чат адбылася памылка"</string>
<string name="screen_create_room_room_name_label">"Назва пакоя"</string>
<string name="screen_create_room_title">"Стварыце пакой"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Soukromá místnost (jen pro pozvané)"</string>
<string name="screen_create_room_public_option_description">"Zprávy nejsou šifrované a může si je přečíst kdokoli. Šifrování můžete povolit později."</string>
<string name="screen_create_room_public_option_title">"Veřejná místnost (kdokoli)"</string>
<string name="screen_create_room_room_name_label">"Název místnosti"</string>
<string name="screen_create_room_topic_label">"Téma (nepovinné)"</string>
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
<string name="screen_create_room_room_name_label">"Název místnosti"</string>
<string name="screen_create_room_title">"Vytvořit místnost"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Privater Raum (nur auf Einladung)"</string>
<string name="screen_create_room_public_option_description">"Die Nachrichten sind nicht verschlüsselt und können von jedem gelesen werden. Die Verschlüsselung kann zu einem späteren Zeitpunkt aktiviert werden."</string>
<string name="screen_create_room_public_option_title">"Öffentlicher Raum (für alle)"</string>
<string name="screen_create_room_room_name_label">"Raumname"</string>
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
<string name="screen_start_chat_error_starting_chat">"Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten"</string>
<string name="screen_create_room_room_name_label">"Raumname"</string>
<string name="screen_create_room_title">"Raum erstellen"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Sala privada (sólo con invitación)"</string>
<string name="screen_create_room_public_option_description">"Los mensajes no están cifrados y cualquiera puede leerlos. Puedes activar la encriptación más adelante."</string>
<string name="screen_create_room_public_option_title">"Sala pública (cualquiera)"</string>
<string name="screen_create_room_room_name_label">"Nombre de la sala"</string>
<string name="screen_create_room_topic_label">"Tema (opcional)"</string>
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
<string name="screen_create_room_room_name_label">"Nombre de la sala"</string>
<string name="screen_create_room_title">"Crear una sala"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Salon privé (sur invitation seulement)"</string>
<string name="screen_create_room_public_option_description">"Les messages ne sont pas chiffrés et nimporte qui peut les lire. Vous pouvez activer le chiffrement ultérieurement."</string>
<string name="screen_create_room_public_option_title">"Salon public (tout le monde)"</string>
<string name="screen_create_room_room_name_label">"Nom du salon"</string>
<string name="screen_create_room_topic_label">"Sujet (facultatif)"</string>
<string name="screen_start_chat_error_starting_chat">"Une erreur sest produite lors de la tentative de création de la discussion"</string>
<string name="screen_create_room_room_name_label">"Nom du salon"</string>
<string name="screen_create_room_title">"Créer un salon"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Privát szoba (csak meghívással)"</string>
<string name="screen_create_room_public_option_description">"Az üzenetek nincsenek titkosítva, és bárki elolvashatja őket. A titkosítást később is engedélyezheti."</string>
<string name="screen_create_room_public_option_title">"Nyilvános szoba (bárki)"</string>
<string name="screen_create_room_room_name_label">"Szoba neve"</string>
<string name="screen_create_room_topic_label">"Téma (nem kötelező)"</string>
<string name="screen_start_chat_error_starting_chat">"Hiba történt a csevegés indításakor"</string>
<string name="screen_create_room_room_name_label">"Szoba neve"</string>
<string name="screen_create_room_title">"Szoba létrehozása"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Ruangan pribadi (hanya undangan)"</string>
<string name="screen_create_room_public_option_description">"Pesan tidak dienkripsi dan siapa pun dapat membacanya. Anda dapat mengaktifkan enkripsi di kemudian hari."</string>
<string name="screen_create_room_public_option_title">"Ruang publik (siapa saja)"</string>
<string name="screen_create_room_room_name_label">"Nama ruangan"</string>
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
<string name="screen_start_chat_error_starting_chat">"Terjadi kesalahan saat mencoba memulai obrolan"</string>
<string name="screen_create_room_room_name_label">"Nama ruangan"</string>
<string name="screen_create_room_title">"Buat ruangan"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Stanza privata (solo su invito)"</string>
<string name="screen_create_room_public_option_description">"I messaggi non sono cifrati e chiunque può leggerli. Puoi attivare la crittografia in un secondo momento."</string>
<string name="screen_create_room_public_option_title">"Stanza pubblica (chiunque)"</string>
<string name="screen_create_room_room_name_label">"Nome stanza"</string>
<string name="screen_create_room_topic_label">"Argomento (facoltativo)"</string>
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
<string name="screen_create_room_room_name_label">"Nome stanza"</string>
<string name="screen_create_room_title">"Crea una stanza"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Cameră privată (doar pe bază de invitație)"</string>
<string name="screen_create_room_public_option_description">"Mesajele nu sunt criptate și oricine le poate citi. Puteți activa criptarea la o dată ulterioară."</string>
<string name="screen_create_room_public_option_title">"Cameră publică (oricine)"</string>
<string name="screen_create_room_room_name_label">"Numele camerei"</string>
<string name="screen_create_room_topic_label">"Subiect (opțional)"</string>
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
<string name="screen_create_room_room_name_label">"Numele camerei"</string>
<string name="screen_create_room_title">"Creați o cameră"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Приватная комната (только по приглашению)"</string>
<string name="screen_create_room_public_option_description">"Сообщения не зашифрованы, каждый может их прочитать. Вы можете включить шифрование позже."</string>
<string name="screen_create_room_public_option_title">"Публичная комната (любой)"</string>
<string name="screen_create_room_room_name_label">"Название комнаты"</string>
<string name="screen_create_room_topic_label">"Тема (необязательно)"</string>
<string name="screen_start_chat_error_starting_chat">"Произошла ошибка при попытке открытия комнаты"</string>
<string name="screen_create_room_room_name_label">"Название комнаты"</string>
<string name="screen_create_room_title">"Создать комнату"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Súkromná miestnosť (len pre pozvaných)"</string>
<string name="screen_create_room_public_option_description">"Správy nie sú šifrované a môže si ich prečítať ktokoľvek. Šifrovanie môžete zapnúť neskôr."</string>
<string name="screen_create_room_public_option_title">"Verejná miestnosť (ktokoľvek)"</string>
<string name="screen_create_room_room_name_label">"Názov miestnosti"</string>
<string name="screen_create_room_topic_label">"Téma (voliteľné)"</string>
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
<string name="screen_create_room_room_name_label">"Názov miestnosti"</string>
<string name="screen_create_room_title">"Vytvoriť miestnosť"</string>
</resources>

View file

@ -8,7 +8,7 @@
<string name="screen_create_room_private_option_title">"私密聊天室(僅限邀請)"</string>
<string name="screen_create_room_public_option_description">"訊息未加密,任何人都可以查看。您可以在之後啟用加密功能。"</string>
<string name="screen_create_room_public_option_title">"公開聊天室(任何人)"</string>
<string name="screen_create_room_room_name_label">"聊天室名稱"</string>
<string name="screen_create_room_topic_label">"主題(非必填)"</string>
<string name="screen_create_room_room_name_label">"聊天室名稱"</string>
<string name="screen_create_room_title">"建立聊天室"</string>
</resources>

View file

@ -8,8 +8,8 @@
<string name="screen_create_room_private_option_title">"Private room (invite only)"</string>
<string name="screen_create_room_public_option_description">"Messages are not encrypted and anyone can read them. You can enable encryption at a later date."</string>
<string name="screen_create_room_public_option_title">"Public room (anyone)"</string>
<string name="screen_create_room_room_name_label">"Room name"</string>
<string name="screen_create_room_topic_label">"Topic (optional)"</string>
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
<string name="screen_create_room_room_name_label">"Room name"</string>
<string name="screen_create_room_title">"Create a room"</string>
</resources>

View file

@ -33,7 +33,6 @@ import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.analytics.api.AnalyticsEntryPoint
import io.element.android.features.ftue.api.FtueEntryPoint
import io.element.android.features.ftue.impl.migration.MigrationScreenNode
import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode
import io.element.android.features.ftue.impl.state.DefaultFtueState
import io.element.android.features.ftue.impl.state.FtueStep
@ -74,9 +73,6 @@ class FtueFlowNode @AssistedInject constructor(
@Parcelize
data object Placeholder : NavTarget
@Parcelize
data object MigrationScreen : NavTarget
@Parcelize
data object WelcomeScreen : NavTarget
@ -114,14 +110,6 @@ class FtueFlowNode @AssistedInject constructor(
NavTarget.Placeholder -> {
createNode<PlaceholderNode>(buildContext)
}
NavTarget.MigrationScreen -> {
val callback = object : MigrationScreenNode.Callback {
override fun onMigrationFinished() {
lifecycleScope.launch { moveToNextStep() }
}
}
createNode<MigrationScreenNode>(buildContext, listOf(callback))
}
NavTarget.WelcomeScreen -> {
val callback = object : WelcomeNode.Callback {
override fun onContinueClicked() {
@ -158,9 +146,6 @@ class FtueFlowNode @AssistedInject constructor(
private fun moveToNextStep() {
when (ftueState.getNextStep()) {
FtueStep.MigrationScreen -> {
backstack.newRoot(NavTarget.MigrationScreen)
}
FtueStep.WelcomeScreen -> {
backstack.newRoot(NavTarget.WelcomeScreen)
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.ftue.impl.migration
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class)
class MigrationScreenNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: MigrationScreenPresenter,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun onMigrationFinished()
}
private fun onMigrationFinished() {
plugins.filterIsInstance<Callback>().forEach { it.onMigrationFinished() }
}
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
MigrationScreenView(
state,
onMigrationFinished = ::onMigrationFinished,
modifier = modifier
)
}
}

View file

@ -67,7 +67,7 @@ fun NotificationsOptInView(
header = { NotificationsOptInHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) },
footer = { NotificationsOptInFooter(state) },
) {
NotificationsOptInContent(modifier = Modifier.fillMaxWidth())
NotificationsOptInContent()
}
}
@ -104,10 +104,8 @@ private fun NotificationsOptInFooter(state: NotificationsOptInState) {
}
@Composable
private fun NotificationsOptInContent(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
private fun NotificationsOptInContent() {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(
verticalArrangement = Arrangement.spacedBy(
16.dp,
@ -144,10 +142,8 @@ private fun NotificationRow(
avatarColorsId: String,
firstRowPercent: Float,
secondRowPercent: Float,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
color = ElementTheme.colors.bgCanvasDisabled,
shape = RoundedCornerShape(14.dp),
shadowElevation = 2.dp,

View file

@ -21,11 +21,9 @@ import android.os.Build
import androidx.annotation.VisibleForTesting
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.ftue.api.state.FtueState
import io.element.android.features.ftue.impl.migration.MigrationScreenStore
import io.element.android.features.ftue.impl.welcome.state.WelcomeScreenState
import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.permissions.api.PermissionStateProvider
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
@ -43,17 +41,14 @@ class DefaultFtueState @Inject constructor(
coroutineScope: CoroutineScope,
private val analyticsService: AnalyticsService,
private val welcomeScreenState: WelcomeScreenState,
private val migrationScreenStore: MigrationScreenStore,
private val permissionStateProvider: PermissionStateProvider,
private val lockScreenService: LockScreenService,
private val matrixClient: MatrixClient,
) : FtueState {
override val shouldDisplayFlow = MutableStateFlow(isAnyStepIncomplete())
override suspend fun reset() {
welcomeScreenState.reset()
analyticsService.reset()
migrationScreenStore.reset()
if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
permissionStateProvider.resetPermission(Manifest.permission.POST_NOTIFICATIONS)
}
@ -67,12 +62,7 @@ class DefaultFtueState @Inject constructor(
fun getNextStep(currentStep: FtueStep? = null): FtueStep? =
when (currentStep) {
null -> if (shouldDisplayMigrationScreen()) {
FtueStep.MigrationScreen
} else {
getNextStep(FtueStep.MigrationScreen)
}
FtueStep.MigrationScreen -> if (shouldDisplayWelcomeScreen()) {
null -> if (shouldDisplayWelcomeScreen()) {
FtueStep.WelcomeScreen
} else {
getNextStep(FtueStep.WelcomeScreen)
@ -97,7 +87,6 @@ class DefaultFtueState @Inject constructor(
private fun isAnyStepIncomplete(): Boolean {
return listOf(
{ shouldDisplayMigrationScreen() },
{ shouldDisplayWelcomeScreen() },
{ shouldAskNotificationPermissions() },
{ needsAnalyticsOptIn() },
@ -105,10 +94,6 @@ class DefaultFtueState @Inject constructor(
).any { it() }
}
private fun shouldDisplayMigrationScreen(): Boolean {
return migrationScreenStore.isMigrationScreenNeeded(matrixClient.sessionId)
}
private fun needsAnalyticsOptIn(): Boolean {
// We need this function to not be suspend, so we need to load the value through runBlocking
return runBlocking { analyticsService.didAskUserConsent().first().not() }
@ -147,7 +132,6 @@ class DefaultFtueState @Inject constructor(
}
sealed interface FtueStep {
data object MigrationScreen : FtueStep
data object WelcomeScreen : FtueStep
data object NotificationsOptIn : FtueStep
data object AnalyticsOptIn : FtueStep

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_notification_optin_subtitle">"Вы можаце змяніць налады пазней."</string>
<string name="screen_notification_optin_title">"Дазвольце апавяшчэнні і ніколі не прапускайце іх"</string>
<string name="screen_welcome_bullet_1">"Званкі, апытанні, пошук і многае іншае будзе дададзена пазней у гэтым годзе."</string>
<string name="screen_welcome_bullet_2">"Гісторыя паведамленняў для зашыфраваных пакояў пакуль недаступна."</string>
<string name="screen_welcome_bullet_3">"Мы будзем рады пачуць вашае меркаванне, паведаміце нам аб гэтым праз старонку налад."</string>
<string name="screen_welcome_button">"Пачнём!"</string>
<string name="screen_welcome_subtitle">"Вось што вам трэба ведаць:"</string>
<string name="screen_welcome_title">"Вітаем у %1$s!"</string>
</resources>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Jedná se o jednorázový proces, prosíme o strpení."</string>
<string name="screen_migration_title">"Nastavení vašeho účtu"</string>
<string name="screen_notification_optin_subtitle">"Nastavení můžete později změnit."</string>
<string name="screen_notification_optin_title">"Povolte oznámení a nezmeškejte žádnou zprávu"</string>
<string name="screen_welcome_bullet_1">"Hovory, hlasování, vyhledávání a další budou přidány koncem tohoto roku."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Dies ist ein einmaliger Vorgang, danke fürs Warten."</string>
<string name="screen_migration_title">"Dein Konto wird eingerichtet."</string>
<string name="screen_notification_optin_subtitle">"Du kannst deine Einstellungen später ändern."</string>
<string name="screen_notification_optin_title">"Erlaube Benachrichtigungen und verpasse keine Nachricht"</string>
<string name="screen_welcome_bullet_1">"Anrufe, Umfragen, Suchfunktionen und mehr werden im Laufe des Jahres hinzugefügt."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Este proceso solo se hace una vez, gracias por esperar."</string>
<string name="screen_migration_title">"Configura tu cuenta"</string>
<string name="screen_notification_optin_subtitle">"Puedes cambiar la configuración más tarde."</string>
<string name="screen_notification_optin_title">"Activa las notificaciones y nunca te pierdas un mensaje"</string>
<string name="screen_welcome_bullet_1">"Las llamadas, las encuestas, la búsqueda y más se agregarán más adelante este año."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Il sagit dune opération ponctuelle, merci dattendre quelques instants."</string>
<string name="screen_migration_title">"Configuration de votre compte."</string>
<string name="screen_notification_optin_subtitle">"Vous pourrez modifier vos paramètres ultérieurement."</string>
<string name="screen_notification_optin_title">"Autorisez les notifications et ne manquez aucun message"</string>
<string name="screen_welcome_bullet_1">"Les appels, les sondages, les recherches et plus encore seront ajoutés plus tard cette année."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Ez egy egyszeri folyamat, köszönjük a türelmét."</string>
<string name="screen_migration_title">"A fiók beállítása."</string>
<string name="screen_notification_optin_subtitle">"A beállításokat később is módosíthatja."</string>
<string name="screen_notification_optin_title">"Értesítések engedélyezése, hogy soha ne maradjon le egyetlen üzenetről sem"</string>
<string name="screen_welcome_bullet_1">"A hívások, szavazások, keresések és egyebek az év további részében kerülnek hozzáadásra."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Ini adalah proses satu kali, terima kasih telah menunggu."</string>
<string name="screen_migration_title">"Menyiapkan akun Anda."</string>
<string name="screen_notification_optin_subtitle">"Anda dapat mengubah pengaturan Anda nanti."</string>
<string name="screen_notification_optin_title">"Izinkan pemberitahuan dan jangan pernah melewatkan pesan"</string>
<string name="screen_welcome_bullet_1">"Panggilan, pemungutan suara, pencarian, dan lainnya akan ditambahkan di tahun ini."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Si tratta di una procedura che si effettua una sola volta, grazie per l\'attesa."</string>
<string name="screen_migration_title">"Configurazione del tuo account."</string>
<string name="screen_notification_optin_subtitle">"Potrai modificare le tue impostazioni in seguito."</string>
<string name="screen_notification_optin_title">"Consenti le notifiche e non perdere mai un messaggio"</string>
<string name="screen_welcome_bullet_1">"Chiamate, sondaggi, ricerche e altro ancora saranno aggiunti nel corso dell\'anno."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Acesta este un proces care se desfășoară o singură dată, vă mulțumim pentru așteptare."</string>
<string name="screen_migration_title">"Contul dumneavoastră se configurează"</string>
<string name="screen_welcome_bullet_1">"Apelurile, sondajele, căutare și multe altele vor fi adăugate în cursul acestui an."</string>
<string name="screen_welcome_bullet_2">"Istoricul mesajelor pentru camerele criptate nu va fi disponibil în această actualizare."</string>
<string name="screen_welcome_bullet_3">"Ne-ar plăcea să auzim de la dumneavoastră, spuneți-ne ce părere aveți prin intermediul paginii de setări."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Это одноразовый процесс, спасибо, что подождали."</string>
<string name="screen_migration_title">"Настройка учетной записи."</string>
<string name="screen_notification_optin_subtitle">"Вы можете изменить настройки позже."</string>
<string name="screen_notification_optin_title">"Разрешите уведомления и никогда не пропустите сообщение"</string>
<string name="screen_welcome_bullet_1">"Звонки, опросы, поиск и многое другое будут добавлены позже в этом году."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"Ide o jednorazový proces, ďakujeme za trpezlivosť."</string>
<string name="screen_migration_title">"Nastavenie vášho účtu."</string>
<string name="screen_notification_optin_subtitle">"Svoje nastavenia môžete neskôr zmeniť."</string>
<string name="screen_notification_optin_title">"Povoľte oznámenia a nikdy nezmeškajte žiadnu správu"</string>
<string name="screen_welcome_bullet_1">"Hovory, ankety, vyhľadávanie a ďalšie funkcie pribudnú neskôr v tomto roku."</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"這是一次性的程序,感謝您耐心等候。"</string>
<string name="screen_migration_title">"正在設定您的帳號。"</string>
<string name="screen_welcome_bullet_1">"通話、投票、搜尋等更多功能將在今年登場。"</string>
<string name="screen_welcome_bullet_2">"在這次的更新,您無法查看聊天室內被加密的歷史訊息。"</string>
<string name="screen_welcome_bullet_3">"我們很樂意聽取您的意見,請到設定頁面告訴我們您的想法。"</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_migration_message">"This is a one time process, thanks for waiting."</string>
<string name="screen_migration_title">"Setting up your account."</string>
<string name="screen_notification_optin_subtitle">"You can change your settings later."</string>
<string name="screen_notification_optin_title">"Allow notifications and never miss a message"</string>
<string name="screen_welcome_bullet_1">"Calls, polls, search and more will be added later this year."</string>

View file

@ -18,16 +18,11 @@ package io.element.android.features.ftue.impl
import android.os.Build
import com.google.common.truth.Truth.assertThat
import io.element.android.features.ftue.impl.migration.InMemoryMigrationScreenStore
import io.element.android.features.ftue.impl.migration.MigrationScreenStore
import io.element.android.features.ftue.impl.state.DefaultFtueState
import io.element.android.features.ftue.impl.state.FtueStep
import io.element.android.features.ftue.impl.welcome.state.FakeWelcomeState
import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.lockscreen.test.FakeLockScreenService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analytics.test.FakeAnalyticsService
@ -54,7 +49,6 @@ class DefaultFtueStateTests {
fun `given all checks being true, should display flow is false`() = runTest {
val welcomeState = FakeWelcomeState()
val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore()
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
val lockScreenService = FakeLockScreenService()
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
@ -63,14 +57,12 @@ class DefaultFtueStateTests {
coroutineScope = coroutineScope,
welcomeState = welcomeState,
analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
)
welcomeState.setWelcomeScreenShown()
analyticsService.setDidAskUserConsent()
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
permissionStateProvider.setPermissionGranted()
lockScreenService.setIsPinSetup(true)
state.updateState()
@ -85,7 +77,6 @@ class DefaultFtueStateTests {
fun `traverse flow`() = runTest {
val welcomeState = FakeWelcomeState()
val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore()
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
val lockScreenService = FakeLockScreenService()
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
@ -94,29 +85,24 @@ class DefaultFtueStateTests {
coroutineScope = coroutineScope,
welcomeState = welcomeState,
analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
)
val steps = mutableListOf<FtueStep?>()
// First step, migration screen
steps.add(state.getNextStep(steps.lastOrNull()))
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
// Second step, welcome screen
// First step, welcome screen
steps.add(state.getNextStep(steps.lastOrNull()))
welcomeState.setWelcomeScreenShown()
// Third step, notifications opt in
// Second step, notifications opt in
steps.add(state.getNextStep(steps.lastOrNull()))
permissionStateProvider.setPermissionGranted()
// Fourth step, entering PIN code
// Third step, entering PIN code
steps.add(state.getNextStep(steps.lastOrNull()))
lockScreenService.setIsPinSetup(true)
// Fifth step, analytics opt in
// Fourth step, analytics opt in
steps.add(state.getNextStep(steps.lastOrNull()))
analyticsService.setDidAskUserConsent()
@ -124,7 +110,6 @@ class DefaultFtueStateTests {
steps.add(state.getNextStep(steps.lastOrNull()))
assertThat(steps).containsExactly(
FtueStep.MigrationScreen,
FtueStep.WelcomeScreen,
FtueStep.NotificationsOptIn,
FtueStep.LockscreenSetup,
@ -141,19 +126,16 @@ class DefaultFtueStateTests {
fun `if a check for a step is true, start from the next one`() = runTest {
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore()
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
val lockScreenService = FakeLockScreenService()
val state = createState(
coroutineScope = coroutineScope,
analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
)
// Skip first 4 steps
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
// Skip first 3 steps
state.setWelcomeScreenShown()
permissionStateProvider.setPermissionGranted()
lockScreenService.setIsPinSetup(true)
@ -171,18 +153,15 @@ class DefaultFtueStateTests {
fun `if version is older than 13 we don't display the notification opt in screen`() = runTest {
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
val analyticsService = FakeAnalyticsService()
val migrationScreenStore = InMemoryMigrationScreenStore()
val lockScreenService = FakeLockScreenService()
val state = createState(
sdkIntVersion = Build.VERSION_CODES.M,
coroutineScope = coroutineScope,
analyticsService = analyticsService,
migrationScreenStore = migrationScreenStore,
lockScreenService = lockScreenService,
)
migrationScreenStore.setMigrationScreenShown(A_SESSION_ID)
assertThat(state.getNextStep()).isEqualTo(FtueStep.WelcomeScreen)
state.setWelcomeScreenShown()
lockScreenService.setIsPinSetup(true)
@ -200,9 +179,7 @@ class DefaultFtueStateTests {
coroutineScope: CoroutineScope,
welcomeState: FakeWelcomeState = FakeWelcomeState(),
analyticsService: AnalyticsService = FakeAnalyticsService(),
migrationScreenStore: MigrationScreenStore = InMemoryMigrationScreenStore(),
permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
matrixClient: MatrixClient = FakeMatrixClient(),
lockScreenService: LockScreenService = FakeLockScreenService(),
// First version where notification permission is required
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
@ -211,9 +188,7 @@ class DefaultFtueStateTests {
coroutineScope = coroutineScope,
analyticsService = analyticsService,
welcomeScreenState = welcomeState,
migrationScreenStore = migrationScreenStore,
permissionStateProvider = permissionStateProvider,
lockScreenService = lockScreenService,
matrixClient = matrixClient,
)
}

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">"Вы ўпэўненыя, што жадаеце адхіліць запрашэнне ў %1$s?"</string>
<string name="screen_invites_decline_chat_title">"Адхіліць запрашэнне"</string>
<string name="screen_invites_decline_direct_chat_message">"Вы ўпэўненыя, што жадаеце адмовіцца ад прыватных зносін з %1$s?"</string>
<string name="screen_invites_decline_direct_chat_title">"Адхіліць чат"</string>
<string name="screen_invites_empty_list">"Няма запрашэнняў"</string>
<string name="screen_invites_invited_you">"%1$s (%2$s) запрасіў вас"</string>
</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">"Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Вы тут адзіны карыстальнік. Калі вы выйдзеце, ніхто не зможа далучыцца ў будучыні, у тым ліку і вы."</string>
<string name="leave_room_alert_private_subtitle">"Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Гэты пакой не агульнадаступны, і вы не зможаце далучыцца да яго зноў без запрашэння."</string>
<string name="leave_room_alert_subtitle">"Вы ўпэўнены, што жадаеце пакінуць пакой?"</string>
</resources>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Bist du sicher, dass du diese Unterhaltung verlassen willst? Diese Unterhaltung ist nicht öffentlich und du kannst ihr ohne Einladung nicht wieder beitreten."</string>
<string name="leave_room_alert_empty_subtitle">"Bist du sicher, dass du diesen Raum verlassen möchtest? Du bist die einzige Person hier. Wenn du austritst, kann in Zukunft niemand mehr eintreten, auch du nicht."</string>
<string name="leave_room_alert_private_subtitle">"Bist du sicher, dass du diesen Raum verlassen möchtest? Dieser Raum ist nicht öffentlich und du kannst ihm ohne Einladung nicht erneut beitreten."</string>
<string name="leave_room_alert_subtitle">"Bist du sicher, dass du den Raum verlassen willst?"</string>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="leave_conversation_alert_subtitle">"Biztos, hogy elhagyja ezt a beszélgetést? Ez a beszélgetés nem nyilvános, és meghívás nélkül nem fog tudni visszacsatlakozni."</string>
<string name="leave_room_alert_empty_subtitle">"Biztos, hogy elhagyja ezt a szobát? Ön az egyedüli ember itt. Ha kilép, akkor senki sem fog tudni csatlakozni a jövőben, Önt is beleértve."</string>
<string name="leave_room_alert_private_subtitle">"Biztos, hogy elhagyod ezt a szobát? Ez a szoba nem nyilvános, és meghívó nélkül nem fogsz tudni újra belépni."</string>
<string name="leave_room_alert_subtitle">"Biztos, hogy elhagyod a szobát?"</string>

View file

@ -66,10 +66,8 @@ fun PinEntryTextField(
private fun PinEntryRow(
pinEntry: PinEntry,
isSecured: Boolean,
modifier: Modifier = Modifier,
) {
FlowRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.CenterHorizontally),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
@ -83,7 +81,6 @@ private fun PinEntryRow(
private fun PinDigitView(
digit: PinDigit,
isSecured: Boolean,
modifier: Modifier = Modifier,
) {
val shape = RoundedCornerShape(8.dp)
val appearanceModifier = when (digit) {
@ -95,7 +92,7 @@ private fun PinDigitView(
}
}
Box(
modifier = modifier
modifier = Modifier
.size(48.dp)
.then(appearanceModifier),
contentAlignment = Alignment.Center,

View file

@ -57,13 +57,12 @@ fun SetupBiometricView(
}
@Composable
private fun SetupBiometricHeader(modifier: Modifier = Modifier) {
private fun SetupBiometricHeader() {
val biometricAuth = stringResource(id = R.string.screen_app_lock_biometric_authentication)
IconTitleSubtitleMolecule(
iconImageVector = Icons.Default.Fingerprint,
title = stringResource(id = R.string.screen_app_lock_settings_enable_biometric_unlock),
subTitle = stringResource(id = R.string.screen_app_lock_setup_biometric_unlock_subtitle, biometricAuth),
modifier = modifier
)
}
@ -71,11 +70,8 @@ private fun SetupBiometricHeader(modifier: Modifier = Modifier) {
private fun SetupBiometricFooter(
onAllowClicked: () -> Unit,
onSkipClicked: () -> Unit,
modifier: Modifier = Modifier
) {
ButtonColumnMolecule(
modifier = modifier,
) {
ButtonColumnMolecule {
val biometricAuth = stringResource(id = R.string.screen_app_lock_biometric_authentication)
Button(
text = stringResource(id = R.string.screen_app_lock_setup_biometric_unlock_allow_title, biometricAuth),

View file

@ -86,10 +86,8 @@ fun SetupPinView(
private fun SetupPinHeader(
isValidationStep: Boolean,
appName: String,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
IconTitleSubtitleMolecule(
@ -107,7 +105,6 @@ private fun SetupPinHeader(
@Composable
private fun SetupPinContent(
state: SetupPinState,
modifier: Modifier = Modifier,
) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
@ -119,14 +116,13 @@ private fun SetupPinContent(
onValueChange = { entry ->
state.eventSink(SetupPinEvents.OnPinEntryChanged(entry, state.isConfirmationStep))
},
modifier = modifier
modifier = Modifier
.focusRequester(focusRequester)
.padding(top = 36.dp)
.fillMaxWidth()
)
if (state.setupPinFailure != null) {
ErrorDialog(
modifier = modifier,
title = state.setupPinFailure.title(),
content = state.setupPinFailure.content(),
onDismiss = {

View file

@ -107,10 +107,9 @@ fun PinUnlockView(
private fun PinUnlockPage(
state: PinUnlockState,
isInAppUnlock: Boolean,
modifier: Modifier = Modifier
) {
BoxWithConstraints {
val commonModifier = modifier
val commonModifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding()
@ -188,7 +187,6 @@ private fun SignOutPrompt(
isCancellable: Boolean,
onSignOut: () -> Unit,
onDismiss: () -> Unit,
modifier: Modifier = Modifier
) {
if (isCancellable) {
ConfirmationDialog(
@ -196,14 +194,12 @@ private fun SignOutPrompt(
content = stringResource(id = R.string.screen_app_lock_signout_alert_message),
onSubmitClicked = onSignOut,
onDismiss = onDismiss,
modifier = modifier,
)
} else {
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,
modifier = modifier,
)
}
}
@ -258,9 +254,11 @@ private fun PinUnlockExpandedView(
@Composable
private fun PinDotsRow(
pinEntry: PinEntry,
modifier: Modifier = Modifier,
) {
Row(modifier, horizontalArrangement = spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
Row(
horizontalArrangement = spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
for (digit in pinEntry.digits) {
PinDot(isFilled = digit is PinDigit.Filled)
}
@ -270,7 +268,6 @@ private fun PinDotsRow(
@Composable
private fun PinDot(
isFilled: Boolean,
modifier: Modifier = Modifier,
) {
val backgroundColor = if (isFilled) {
ElementTheme.colors.iconPrimary
@ -278,7 +275,7 @@ private fun PinDot(
ElementTheme.colors.bgSubtlePrimary
}
Box(
modifier = modifier
modifier = Modifier
.size(14.dp)
.background(backgroundColor, CircleShape)
)
@ -290,7 +287,10 @@ private fun PinUnlockHeader(
isInAppUnlock: Boolean,
modifier: Modifier = Modifier,
) {
Column(modifier, horizontalAlignment = Alignment.CenterHorizontally) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (isInAppUnlock) {
RoundedIconAtom(imageVector = Icons.Filled.Lock)
} else {

View file

@ -108,14 +108,13 @@ private fun PinKeypadRow(
models: ImmutableList<PinKeypadModel>,
onClick: (PinKeypadModel) -> Unit,
pinKeySize: Dp,
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
) {
Row(
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment,
modifier = modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
) {
val commonModifier = Modifier.size(pinKeySize)
for (model in models) {

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<plurals name="screen_app_lock_subtitle">
<item quantity="one">"У вас %1$d спроба разблакіроўкі"</item>
<item quantity="few">"У вас %1$d спроб разблакіроўкі"</item>
<item quantity="many">"У вас %1$d спроб разблакіроўкі"</item>
</plurals>
<plurals name="screen_app_lock_subtitle_wrong_pin">
<item quantity="one">"Няправільны PIN-код. У вас застаўся %1$d шанец"</item>
<item quantity="few">"Няправільны PIN-код. У вас застаўася %1$d шанцаў"</item>
<item quantity="many">"Няправільны PIN-код. У вас застаўася %1$d шанцаў"</item>
</plurals>
<string name="screen_app_lock_biometric_authentication">"біяметрычная аўтэнтыфікацыя"</string>
<string name="screen_app_lock_biometric_unlock">"біяметрычная разблакіроўка"</string>
<string name="screen_app_lock_biometric_unlock_title_android">"Разблакіроўка з дапамогай біяметрыі"</string>
<string name="screen_app_lock_forgot_pin">"Забыліся PIN-код?"</string>
<string name="screen_app_lock_settings_change_pin">"Змяніць PIN-код"</string>
<string name="screen_app_lock_settings_enable_biometric_unlock">"Дазволіць біяметрычную разблакіроўку"</string>
<string name="screen_app_lock_settings_remove_pin">"Выдаліць PIN-код"</string>
<string name="screen_app_lock_settings_remove_pin_alert_message">"Вы ўпэўнены, што жадаеце выдаліць PIN-код?"</string>
<string name="screen_app_lock_settings_remove_pin_alert_title">"Выдаліць PIN-код?"</string>
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Дазволіць %1$s"</string>
<string name="screen_app_lock_setup_biometric_unlock_skip">"Я хацеў бы выкарыстоўваць PIN-код"</string>
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Эканомце час і выкарыстоўвайце %1$s для разблакіроўкі праграмы"</string>
<string name="screen_app_lock_setup_choose_pin">"Выберыце PIN-код"</string>
<string name="screen_app_lock_setup_confirm_pin">"Пацвярджэнне PIN-кода"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Вы не можаце выбраць гэты PIN-код з меркаванняў бяспекі"</string>
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"Выберыце іншы PIN-код"</string>
<string name="screen_app_lock_setup_pin_context">"Заблакіруйце %1$s, каб павялічыць бяспеку вашых чатаў.
Абярыце што-небудзь незабыўнае. Калі вы забудзецеся гэты PIN-код, вы выйдзеце з праграмы."</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Увядзіце адзін і той жа PIN двойчы"</string>
<string name="screen_app_lock_setup_pin_mismatch_dialog_title">"PIN-коды не супадаюць"</string>
<string name="screen_app_lock_signout_alert_message">"Каб працягнуць, вам неабходна паўторна ўвайсці ў сістэму і стварыць новы PIN-код"</string>
<string name="screen_app_lock_signout_alert_title">"Вы выходзіце з сістэмы"</string>
<string name="screen_app_lock_use_biometric_android">"Выкарыстоўваць біяметрыю"</string>
<string name="screen_app_lock_use_pin_android">"Выкарыстоўваць PIN-код"</string>
<string name="screen_signout_in_progress_dialog_content">"Выхад…"</string>
</resources>

View file

@ -16,6 +16,7 @@
package io.element.android.features.login.impl.screens.loginpassword
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
@ -55,6 +56,7 @@ import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.error.isWaitListError
import io.element.android.features.login.impl.error.loginError
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
@ -114,7 +116,7 @@ fun LoginPasswordView(
.padding(padding)
.consumeWindowInsets(padding)
.verticalScroll(state = scrollState)
.padding(horizontal = 16.dp),
.padding(start = 20.dp, end = 20.dp, bottom = 20.dp),
) {
// Title
IconTitleSubtitleMolecule(
@ -137,16 +139,23 @@ fun LoginPasswordView(
// Flexible spacing to keep the submit button at the bottom
Spacer(modifier = Modifier.weight(1f))
// Submit
Button(
text = stringResource(CommonStrings.action_continue),
showProgress = isLoading,
onClick = ::submit,
enabled = state.submitEnabled || isLoading,
Box(
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginContinue)
)
Spacer(modifier = Modifier.height(60.dp))
.padding(horizontal = 16.dp)
) {
ButtonColumnMolecule {
Button(
text = stringResource(CommonStrings.action_continue),
showProgress = isLoading,
onClick = ::submit,
enabled = state.submitEnabled || isLoading,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginContinue)
)
Spacer(modifier = Modifier.height(48.dp))
}
}
if (state.loginAction is AsyncData.Failure) {
when {
@ -170,7 +179,6 @@ private fun LoginForm(
state: LoginPasswordState,
isLoading: Boolean,
onSubmit: () -> Unit,
modifier: Modifier = Modifier
) {
var loginFieldState by textFieldState(stateValue = state.formState.login)
var passwordFieldState by textFieldState(stateValue = state.formState.password)
@ -178,7 +186,7 @@ private fun LoginForm(
val focusManager = LocalFocusManager.current
val eventSink = state.eventSink
Column(modifier) {
Column {
Text(
text = stringResource(R.string.screen_login_form_header),
modifier = Modifier.padding(start = 16.dp),

View file

@ -25,11 +25,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.error.isWaitListError
import io.element.android.features.login.impl.error.loginError
@ -119,11 +119,10 @@ private fun WaitListContent(
private fun OverallContent(
state: WaitListState,
onCancelClicked: () -> Unit,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxSize()) {
if (state.loginAction !is AsyncData.Success) {
CompositionLocalProvider(LocalContentColor provides Color.Black) {
CompositionLocalProvider(LocalContentColor provides ElementTheme.colors.textOnSolidPrimary) {
TextButton(
text = stringResource(CommonStrings.action_cancel),
onClick = onCancelClicked,

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_account_provider_change">"Змяніць правайдара ўліковага запісу"</string>
<string name="screen_account_provider_form_hint">"Адрас хатняга сервера"</string>
<string name="screen_account_provider_form_notice">"Увядзіце пошукавы запыт або адрас дамена."</string>
<string name="screen_account_provider_form_subtitle">"Пошук кампаніі, супольнасці або прыватнага сервера."</string>
<string name="screen_account_provider_form_title">"Знайдзіце правайдара ўліковых запісаў"</string>
<string name="screen_account_provider_signin_title">"Вы збіраецеся ўвайсці ў %s"</string>
<string name="screen_account_provider_signup_title">"Вы збіраецеся стварыць уліковы запіс на %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org - гэта вялікі бясплатны сервер у агульнадаступнай сетцы Matrix для бяспечнай дэцэнтралізаванай сувязі, якім кіруе фонд Matrix.org."</string>
<string name="screen_change_account_provider_other">"Іншае"</string>
<string name="screen_change_account_provider_subtitle">"Выкарыстоўвайце іншага правайдара ўліковых запісаў, напрыклад, уласны прыватны сервер або працоўны ўліковы запіс."</string>
<string name="screen_change_account_provider_title">"Змяніць правайдара ўліковага запісу"</string>
<string name="screen_change_server_error_invalid_homeserver">"Нам не ўдалося звязацца з гэтым хатнім серверам. Упэўніцеся, што вы правільна ўвялі URL-адрас хатняга сервера. Калі URL-адрас пазначаны правільна, звярніцеся да адміністратара хатняга сервера за дадатковай дапамогай."</string>
<string name="screen_change_server_error_no_sliding_sync_message">"На жаль, гэты сервер не падтрымлівае sliding sync."</string>
<string name="screen_change_server_form_header">"URL хатняга сервера"</string>
<string name="screen_change_server_form_notice">"Вы можаце падключыцца толькі да існуючага сервера, які падтрымлівае sliding sync. Адміністратару хатняга сервера запатрабуецца наладзіць яго. %1$s"</string>
<string name="screen_change_server_subtitle">"Які адрас вашага сервера?"</string>
<string name="screen_change_server_title">"Выберыце свой сервер"</string>
<string name="screen_login_error_deactivated_account">"Гэты ўліковы запіс быў дэактываваны."</string>
<string name="screen_login_error_invalid_credentials">"Няправільнае імя карыстальніка і/або пароль"</string>
<string name="screen_login_error_invalid_user_id">"Гэта несапраўдны ідэнтыфікатар карыстальніка. Чаканы фармат: @user:homeserver.org"</string>
<string name="screen_login_error_unsupported_authentication">"Выбраны хатні сервер не падтрымлівае пароль або ўваход у OIDC. Калі ласка, звярніцеся да адміністратара або абярыце іншы хатні сервер."</string>
<string name="screen_login_form_header">"Увядзіце свае даныя"</string>
<string name="screen_login_title">"Сардэчна запрашаем!"</string>
<string name="screen_login_title_with_homeserver">"Увайдзіце ў %1$s"</string>
<string name="screen_server_confirmation_change_server">"Змяніць правайдара ўліковага запісу"</string>
<string name="screen_server_confirmation_message_login_element_dot_io">"Прыватны сервер для супрацоўнікаў Element."</string>
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
<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_title">"Амаль гатова."</string>
<string name="screen_account_provider_signin_subtitle">"Тут будуць захоўвацца вашыя размовы - сапраўды гэтак жа, як вы выкарыстоўваеце паштовага правайдара для захоўвання сваіх лістоў."</string>
<string name="screen_account_provider_signup_subtitle">"Тут будуць захоўвацца вашыя размовы - сапраўды гэтак жа, як вы выкарыстоўваеце паштовага правайдара для захоўвання сваіх лістоў."</string>
<string name="screen_login_subtitle">"Matrix - гэта адкрытая сетка для бяспечнай, дэцэнтралізаванай сувязі."</string>
<string name="screen_waitlist_message_success">"Вітаем у %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Zadejte hledaný výraz nebo adresu domény."</string>
<string name="screen_account_provider_form_subtitle">"Vyhledejte společnost, komunitu nebo soukromý server."</string>
<string name="screen_account_provider_form_title">"Najít poskytovatele účtu"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signin_title">"Chystáte se přihlásit do %s"</string>
<string name="screen_account_provider_signup_subtitle">"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_account_provider_signup_title">"Chystáte se vytvořit účet na %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org je velký bezplatný server ve veřejné síti Matrix pro bezpečnou decentralizovanou komunikaci, který provozuje nadace Matrix.org."</string>
<string name="screen_change_account_provider_other">"Jiný"</string>
@ -37,6 +35,8 @@
Díky za trpělivost!"</string>
<string name="screen_waitlist_title">"Jste v pořadníku!"</string>
<string name="screen_waitlist_title_success">"Jdete do toho!"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signup_subtitle">"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_login_subtitle">"Matrix je otevřená síť pro bezpečnou a decentralizovanou komunikaci."</string>
<string name="screen_waitlist_message_success">"Vítá vás %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Gib einen Suchbegriff oder eine Domainadresse ein."</string>
<string name="screen_account_provider_form_subtitle">"Suche nach einem Unternehmen, einer Community oder einem privaten Server."</string>
<string name="screen_account_provider_form_title">"Kontoanbieter finden"</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Gespräche gespeichert genau so, wie du einen E-Mail-Anbieter nutzen würdest, um deine E-Mails aufzubewahren."</string>
<string name="screen_account_provider_signin_title">"Du bist dabei, dich bei %s anzumelden"</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Gespräche gespeichert genau so, wie du einen E-Mail-Anbieter nutzen würdest, um deine E-Mails aufzubewahren."</string>
<string name="screen_account_provider_signup_title">"Du bist dabei, ein Konto bei %s zu erstellen"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org ist ein großer, kostenloser Server im öffentlichen Matrix-Netzwerk für eine sichere, dezentralisierte Kommunikation, der von der Matrix.org Foundation betrieben wird."</string>
<string name="screen_change_account_provider_other">"Sonstige"</string>
@ -37,6 +35,8 @@
Danke für deine Geduld!"</string>
<string name="screen_waitlist_title">"Du bist fast am Ziel."</string>
<string name="screen_waitlist_title_success">"Du bist dabei."</string>
<string name="screen_account_provider_signin_subtitle">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würden."</string>
<string name="screen_account_provider_signup_subtitle">"Hier werden deine Gespräche gespeichert - so wie du deine E-Mails bei einem E-Mail-Anbieter aufbewahren würden."</string>
<string name="screen_login_subtitle">"Matrix ist ein offenes Netzwerk für eine sichere, dezentrale Kommunikation."</string>
<string name="screen_waitlist_message_success">"Willkommen bei %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Introduzca un término de búsqueda o una dirección de dominio."</string>
<string name="screen_account_provider_form_subtitle">"Busca una empresa, comunidad o servidor privado."</string>
<string name="screen_account_provider_form_title">"Encontrar un proveedor de cuenta"</string>
<string name="screen_account_provider_signin_subtitle">"Aquí es donde se alojarán tus conversaciones — justo como si utilizaras un proveedor de correo electrónico para guardar tus correos electrónicos."</string>
<string name="screen_account_provider_signin_title">"Estás a punto de iniciar sesión en %s"</string>
<string name="screen_account_provider_signup_subtitle">"Aquí es donde se alojarán tus conversaciones — justo como si utilizaras un proveedor de correo electrónico para guardar tus correos electrónicos."</string>
<string name="screen_account_provider_signup_title">"Estás a punto de crear una cuenta en %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org es un servidor grande y gratuito en la red pública Matrix para una comunicación segura y descentralizada, administrado por la Fundación Matrix.org."</string>
<string name="screen_change_account_provider_other">"Otro"</string>
@ -37,6 +35,8 @@
¡Gracias por tu paciencia!"</string>
<string name="screen_waitlist_title">"Ya casi has terminado."</string>
<string name="screen_waitlist_title_success">"Estás dentro."</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signup_subtitle">"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_login_subtitle">"Matrix es una red abierta para una comunicación segura y descentralizada."</string>
<string name="screen_waitlist_message_success">"¡Bienvenido a %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Entrez un terme de recherche ou une adresse de domaine."</string>
<string name="screen_account_provider_form_subtitle">"Recherchez une entreprise, une communauté ou un serveur privé."</string>
<string name="screen_account_provider_form_title">"Trouver un fournisseur de comptes"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signin_title">"Vous êtes sur le point de vous connecter à %s"</string>
<string name="screen_account_provider_signup_subtitle">"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_account_provider_signup_title">"Vous êtes sur le point de créer un compte sur %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org est un grand serveur gratuit sur le réseau public Matrix pour une communication sécurisée et décentralisée, géré par la Fondation Matrix.org."</string>
<string name="screen_change_account_provider_other">"Autres"</string>
@ -37,6 +35,8 @@
Merci pour votre patience !"</string>
<string name="screen_waitlist_title">"Vous y êtes presque."</string>
<string name="screen_waitlist_title_success">"Vous y êtes."</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signup_subtitle">"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_login_subtitle">"Matrix est un réseau ouvert pour une communication sécurisée et décentralisée."</string>
<string name="screen_waitlist_message_success">"Bienvenue dans %1$s !"</string>
</resources>

View file

@ -5,10 +5,8 @@
<string name="screen_account_provider_form_notice">"Adjon meg egy keresési kifejezést vagy egy tartománycímet."</string>
<string name="screen_account_provider_form_subtitle">"Keresés egy cégre, közösségre vagy privát kiszolgálóra."</string>
<string name="screen_account_provider_form_title">"Fiókszolgáltató keresése"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signin_title">"Hamarosan bejelentkezik ide: %s"</string>
<string name="screen_account_provider_signup_subtitle">"Itt lesznek a beszélgetéseid ahogyan egy e-mail-szolgáltatást is használnál a leveleid kezeléséhez."</string>
<string name="screen_account_provider_signup_title">"Hamarosan létrehoz egy fiókot itt: %s"</string>
<string name="screen_account_provider_signup_title">"Hamarosan létrehozol egy fiókot itt: %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"A Matrix.org egy nagy, ingyenes kiszolgáló a nyilvános Matrix-hálózaton, a biztonságos, decentralizált kommunikáció érdekében, amelyet a Matrix.org Alapítvány üzemeltet."</string>
<string name="screen_change_account_provider_other">"Egyéb"</string>
<string name="screen_change_account_provider_subtitle">"Másik fiókszolgáltató, például a saját privát kiszolgáló vagy egy munkahelyi fiók használata."</string>
@ -37,6 +35,8 @@
Köszönjük a türelmét!"</string>
<string name="screen_waitlist_title">"Már majdnem kész van."</string>
<string name="screen_waitlist_title_success">"Bent van."</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signup_subtitle">"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_login_subtitle">"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."</string>
<string name="screen_waitlist_message_success">"Üdvözli az %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Masukkan istilah pencarian atau alamat domain."</string>
<string name="screen_account_provider_form_subtitle">"Cari perusahaan, komunitas, atau server pribadi."</string>
<string name="screen_account_provider_form_title">"Cari penyedia akun"</string>
<string name="screen_account_provider_signin_subtitle">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_account_provider_signin_title">"Anda akan masuk ke %s"</string>
<string name="screen_account_provider_signup_subtitle">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_account_provider_signup_title">"Anda akan membuat akun di %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org adalah server besar dan gratis di jaringan Matrix publik untuk komunikasi yang aman dan terdesentralisasi, disediakan oleh Yayasan Matrix.org."</string>
<string name="screen_change_account_provider_other">"Lainnya"</string>
@ -37,6 +35,8 @@
Terima kasih atas kesabaran Anda!"</string>
<string name="screen_waitlist_title">"Anda hampir selesai."</string>
<string name="screen_waitlist_title_success">"Anda sudah masuk."</string>
<string name="screen_account_provider_signin_subtitle">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_account_provider_signup_subtitle">"Di sinilah percakapan Anda akan berlangsung — sama seperti Anda menggunakan penyedia surel untuk menyimpan surel Anda."</string>
<string name="screen_login_subtitle">"Matrix adalah jaringan terbuka untuk komunikasi yang aman dan terdesentralisasi."</string>
<string name="screen_waitlist_message_success">"Selamat datang di %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Inserisci un termine di ricerca o un indirizzo di dominio."</string>
<string name="screen_account_provider_form_subtitle">"Cerca un\' azienda, una comunità o un server privato."</string>
<string name="screen_account_provider_form_title">"Trova un fornitore di account"</string>
<string name="screen_account_provider_signin_subtitle">"Qui è dove vivranno le tue conversazioni - proprio come useresti un fornitore di posta elettronica per conservare le tue email."</string>
<string name="screen_account_provider_signin_title">"Stai per accedere a %s"</string>
<string name="screen_account_provider_signup_subtitle">"Qui è dove vivranno le tue conversazioni - proprio come useresti un fornitore di posta elettronica per conservare le tue email."</string>
<string name="screen_account_provider_signup_title">"Stai per creare un account su %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org è un grande server gratuito nella rete pubblica Matrix per una comunicazione sicura e decentralizzata, gestito dalla Fondazione Matrix.org."</string>
<string name="screen_change_account_provider_other">"Altro"</string>
@ -37,6 +35,8 @@
Grazie per la pazienza!"</string>
<string name="screen_waitlist_title">"Ci sei quasi."</string>
<string name="screen_waitlist_title_success">"Sei dentro."</string>
<string name="screen_account_provider_signin_subtitle">"Qui è dove vivranno le tue conversazioni — proprio come useresti un fornitore di posta elettronica per conservare le tue email."</string>
<string name="screen_account_provider_signup_subtitle">"Qui è dove vivranno le tue conversazioni — proprio come useresti un fornitore di posta elettronica per conservare le tue email."</string>
<string name="screen_login_subtitle">"Matrix è una rete aperta per comunicazioni sicure e decentralizzate."</string>
<string name="screen_waitlist_message_success">"Benvenuti in %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Introduceţi un termen de căutare sau o adresă de domeniu."</string>
<string name="screen_account_provider_form_subtitle">"Căutați o companie, o comunitate sau un server privat."</string>
<string name="screen_account_provider_form_title">"Găsiți un furnizor de cont"</string>
<string name="screen_account_provider_signin_subtitle">"Aici vor trăi conversațiile dumneavoastră - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile."</string>
<string name="screen_account_provider_signin_title">"Sunteți pe cale să vă conectați la %s"</string>
<string name="screen_account_provider_signup_subtitle">"Aici vor trăi conversațiile dumneavoastră - la fel cum ați folosi un furnizor de e-mail pentru a vă păstra e-mailurile."</string>
<string name="screen_account_provider_signup_title">"Sunteți pe cale să creați un cont pe %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org este un server mare și gratuit din rețeaua publică Matrix pentru comunicații sigure și descentralizate, administrat de Fundația Matrix.org."</string>
<string name="screen_change_account_provider_other">"Altul"</string>
@ -36,6 +34,8 @@
Vă mulțumim pentru răbdare!"</string>
<string name="screen_waitlist_title">"Sunteți pe lista de așteptare"</string>
<string name="screen_waitlist_title_success">"Sunteți conectat!"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signup_subtitle">"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_login_subtitle">"Matrix este o rețea deschisă pentru o comunicare sigură și descentralizată."</string>
<string name="screen_waitlist_message_success">"Bun venit la%1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Введите поисковый запрос или адрес домена."</string>
<string name="screen_account_provider_form_subtitle">"Поиск компании, сообщества или частного сервера."</string>
<string name="screen_account_provider_form_title">"Поиск сервера учетной записи"</string>
<string name="screen_account_provider_signin_subtitle">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_account_provider_signin_title">"Вы собираетесь войти в %s"</string>
<string name="screen_account_provider_signup_subtitle">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_account_provider_signup_title">"Вы собираетесь создать учетную запись на %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org — это большой бесплатный сервер в общедоступной сети Matrix для безопасной децентрализованной связи, управляемый Matrix.org Foundation."</string>
<string name="screen_change_account_provider_other">"Другое"</string>
@ -37,6 +35,8 @@
Спасибо за терпение!"</string>
<string name="screen_waitlist_title">"Почти готово."</string>
<string name="screen_waitlist_title_success">"Вы зарегистрированы."</string>
<string name="screen_account_provider_signin_subtitle">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_account_provider_signup_subtitle">"Здесь будут храниться ваши разговоры - точно так же, как вы используете почтового провайдера для хранения своих писем."</string>
<string name="screen_login_subtitle">"Matrix — это открытая сеть для безопасной децентрализованной связи."</string>
<string name="screen_waitlist_message_success">"Добро пожаловать в %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Zadajte hľadaný výraz alebo adresu domény."</string>
<string name="screen_account_provider_form_subtitle">"Vyhľadať spoločnosť, komunitu alebo súkromný server."</string>
<string name="screen_account_provider_form_title">"Nájsť poskytovateľa účtu"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signin_title">"Chystáte sa prihlásiť do %s"</string>
<string name="screen_account_provider_signup_subtitle">"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_account_provider_signup_title">"Chystáte sa vytvoriť účet na %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org je veľký bezplatný server vo verejnej sieti Matrix na bezpečnú, decentralizovanú komunikáciu, ktorý prevádzkuje nadácia Matrix.org."</string>
<string name="screen_change_account_provider_other">"Iný"</string>
@ -37,6 +35,8 @@
Ďakujeme za trpezlivosť!"</string>
<string name="screen_waitlist_title">"Ste na čakanej listine!"</string>
<string name="screen_waitlist_title_success">"Ste dnu!"</string>
<string name="screen_account_provider_signin_subtitle">"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_account_provider_signup_subtitle">"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_login_subtitle">"Matrix je otvorená sieť pre bezpečnú a decentralizovanú komunikáciu."</string>
<string name="screen_waitlist_message_success">"Vitajte v %1$s!"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"輸入關鍵字或網域名稱。"</string>
<string name="screen_account_provider_form_subtitle">"搜尋公司、社群、私有伺服器。"</string>
<string name="screen_account_provider_form_title">"尋找帳號提供者"</string>
<string name="screen_account_provider_signin_subtitle">"您的所有對話將保存於此,就如同您的電子郵件供應商會保存您的電子郵件一樣。"</string>
<string name="screen_account_provider_signin_title">"您即將登入 %s"</string>
<string name="screen_account_provider_signup_subtitle">"您的所有對話將保存於此,就如同您的電子郵件供應商會保存您的電子郵件一樣。"</string>
<string name="screen_account_provider_signup_title">"您即將在 %s 建立帳號"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org 由 Matrix.org 基金會營運,是用於安全、去中心化通訊的公共 Matrix 網路上的大型免費伺服器。"</string>
<string name="screen_change_account_provider_other">"其他"</string>
@ -27,6 +25,8 @@
<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_account_provider_signin_subtitle">"您的所有對話將保存於此,就如同您的電子郵件供應商會保存您的電子郵件一樣。"</string>
<string name="screen_account_provider_signup_subtitle">"您的所有對話將保存於此,就如同您的電子郵件供應商會保存您的電子郵件一樣。"</string>
<string name="screen_login_subtitle">"Matrix 是一個開放網路,為了安全且去中心化的通訊而生。"</string>
<string name="screen_waitlist_message_success">"歡迎使用 %1$s"</string>
</resources>

View file

@ -5,9 +5,7 @@
<string name="screen_account_provider_form_notice">"Enter a search term or a domain address."</string>
<string name="screen_account_provider_form_subtitle">"Search for a company, community, or private server."</string>
<string name="screen_account_provider_form_title">"Find an account provider"</string>
<string name="screen_account_provider_signin_subtitle">"This is where your conversations will live — just like you would use an email provider to keep your emails."</string>
<string name="screen_account_provider_signin_title">"Youre about to sign in to %s"</string>
<string name="screen_account_provider_signup_subtitle">"This is where your conversations will live — just like you would use an email provider to keep your emails."</string>
<string name="screen_account_provider_signup_title">"Youre about to create an account on %s"</string>
<string name="screen_change_account_provider_matrix_org_subtitle">"Matrix.org is a large, free server on the public Matrix network for secure, decentralised communication, run by the Matrix.org Foundation."</string>
<string name="screen_change_account_provider_other">"Other"</string>
@ -37,6 +35,8 @@
Thanks for your patience!"</string>
<string name="screen_waitlist_title">"Youre almost there."</string>
<string name="screen_waitlist_title_success">"You\'re in."</string>
<string name="screen_account_provider_signin_subtitle">"This is where your conversations will live — just like you would use an email provider to keep your emails."</string>
<string name="screen_account_provider_signup_subtitle">"This is where your conversations will live — just like you would use an email provider to keep your emails."</string>
<string name="screen_login_subtitle">"Matrix is an open network for secure, decentralised communication."</string>
<string name="screen_waitlist_message_success">"Welcome to %1$s!"</string>
</resources>

View file

@ -86,7 +86,7 @@ fun LogoutView(
onForceLogoutClicked = {
eventSink(LogoutEvents.Logout(ignoreSdkError = true))
},
onDismissError = {
onDismissDialog = {
eventSink(LogoutEvents.CloseDialogs)
},
onSuccessLogout = {

View file

@ -41,7 +41,7 @@ class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView {
onForceLogoutClicked = {
eventSink(DirectLogoutEvents.Logout(ignoreSdkError = true))
},
onDismissError = {
onDismissDialog = {
eventSink(DirectLogoutEvents.CloseDialogs)
},
onSuccessLogout = {

View file

@ -32,8 +32,7 @@ fun LogoutActionDialog(
state: AsyncAction<String?>,
onConfirmClicked: () -> Unit,
onForceLogoutClicked: () -> Unit,
// TODO Rename
onDismissError: () -> Unit,
onDismissDialog: () -> Unit,
onSuccessLogout: (String?) -> Unit,
) {
when (state) {
@ -42,7 +41,7 @@ fun LogoutActionDialog(
AsyncAction.Confirming ->
LogoutConfirmationDialog(
onSubmitClicked = onConfirmClicked,
onDismiss = onDismissError
onDismiss = onDismissDialog
)
is AsyncAction.Loading ->
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
@ -52,7 +51,7 @@ fun LogoutActionDialog(
content = stringResource(id = CommonStrings.error_unknown),
retryText = stringResource(id = CommonStrings.action_signout_anyway),
onRetry = onForceLogoutClicked,
onDismiss = onDismissError,
onDismiss = onDismissDialog,
)
is AsyncAction.Success -> {
val latestOnSuccessLogout by rememberUpdatedState(onSuccessLogout)

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_signout_confirmation_dialog_content">"Вы ўпэўнены, што жадаеце выйсці?"</string>
<string name="screen_signout_in_progress_dialog_content">"Выхад…"</string>
<string name="screen_signout_key_backup_disabled_subtitle">"Вы збіраецеся выйсці з апошняга сеанса. Калі вы выйдзеце з сістэмы зараз, вы страціце доступ да зашыфраваных паведамленняў."</string>
<string name="screen_signout_key_backup_disabled_title">"Вы адключылі рэзервовае капіраванне"</string>
<string name="screen_signout_key_backup_offline_subtitle">"Вашы ключы ўсё яшчэ захоўваліся, калі вы выйшлі з сеткі. Паўторна падключыцеся, каб можна было стварыць рэзервовую копію вашых ключоў перад выхадам."</string>
<string name="screen_signout_key_backup_offline_title">"Рэзервовае капіраванне ключоў усё яшчэ працягваецца"</string>
<string name="screen_signout_key_backup_ongoing_subtitle">"Калі ласка, дачакайцеся завяршэння працэсу, перш чым выходзіць з сістэмы."</string>
<string name="screen_signout_key_backup_ongoing_title">"Вашы ключы ўсё яшчэ ствараюцца"</string>
<string name="screen_signout_recovery_disabled_subtitle">"Вы збіраецеся выйсці з апошняга сеанса. Калі вы выйдзеце з сістэмы зараз, вы страціце доступ да зашыфраваных паведамленняў."</string>
<string name="screen_signout_recovery_disabled_title">"Аднаўленне не наладжана"</string>
<string name="screen_signout_save_recovery_key_subtitle">"Вы збіраецеся выйсці з апошняга сеанса. Калі вы выйдзеце з сістэмы зараз, вы страціце доступ да зашыфраваных паведамленняў."</string>
<string name="screen_signout_save_recovery_key_title">"Вы захавалі свой ключ аднаўлення?"</string>
<string name="screen_signout_confirmation_dialog_submit">"Выйсці"</string>
<string name="screen_signout_confirmation_dialog_title">"Выйсці"</string>
<string name="screen_signout_preference_item">"Выйсці"</string>
</resources>

View file

@ -62,6 +62,7 @@ dependencies {
implementation(projects.libraries.voicerecorder.api)
implementation(projects.libraries.mediaplayer.api)
implementation(projects.libraries.uiUtils)
implementation(projects.libraries.testtags)
implementation(projects.features.networkmonitor.api)
implementation(projects.services.analytics.api)
implementation(libs.coil.compose)
@ -94,6 +95,7 @@ dependencies {
testImplementation(projects.libraries.voicerecorder.test)
testImplementation(projects.libraries.mediaplayer.test)
testImplementation(projects.libraries.mediaviewer.test)
testImplementation(projects.libraries.testtags)
testImplementation(libs.test.mockk)
testImplementation(libs.test.junitext)
testImplementation(libs.test.robolectric)

View file

@ -59,6 +59,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
import io.element.android.features.messages.impl.utils.messagesummary.MessageSummaryFormatter
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
import io.element.android.features.networkmonitor.api.NetworkMonitor
@ -97,6 +98,7 @@ class MessagesPresenter @AssistedInject constructor(
private val composerPresenter: MessageComposerPresenter,
private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter,
timelinePresenterFactory: TimelinePresenter.Factory,
private val typingNotificationPresenter: TypingNotificationPresenter,
private val actionListPresenter: ActionListPresenter,
private val customReactionPresenter: CustomReactionPresenter,
private val reactionSummaryPresenter: ReactionSummaryPresenter,
@ -129,6 +131,7 @@ class MessagesPresenter @AssistedInject constructor(
val composerState = composerPresenter.present()
val voiceMessageComposerState = voiceMessageComposerPresenter.present()
val timelineState = timelinePresenter.present()
val typingNotificationState = typingNotificationPresenter.present()
val actionListState = actionListPresenter.present()
val customReactionState = customReactionPresenter.present()
val reactionSummaryState = reactionSummaryPresenter.present()
@ -139,7 +142,7 @@ class MessagesPresenter @AssistedInject constructor(
val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value)
val userHasPermissionToRedactOwn by room.canRedactOwnAsState(updateKey = syncUpdateFlow.value)
val userHasPermissionToRedactOther by room.canRedactOtherAsState(updateKey = syncUpdateFlow.value)
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION_SENT, updateKey = syncUpdateFlow.value)
val userHasPermissionToSendReaction by room.canSendMessageAsState(type = MessageEventType.REACTION, updateKey = syncUpdateFlow.value)
val roomName: AsyncData<String> by remember {
derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized }
}
@ -155,6 +158,14 @@ class MessagesPresenter @AssistedInject constructor(
mutableStateOf(false)
}
LaunchedEffect(Unit) {
// Mark the room as read on entering but don't send read receipts
// as those will be handled by the timeline.
withContext(dispatchers.io) {
room.markAsRead(null)
}
}
LaunchedEffect(syncUpdateFlow.value) {
withContext(dispatchers.io) {
canJoinCall = room.canUserJoinCall(room.sessionId).getOrDefault(false)
@ -225,6 +236,7 @@ class MessagesPresenter @AssistedInject constructor(
composerState = composerState,
voiceMessageComposerState = voiceMessageComposerState,
timelineState = timelineState,
typingNotificationState = typingNotificationState,
actionListState = actionListState,
customReactionState = customReactionState,
reactionSummaryState = reactionSummaryState,

View file

@ -24,6 +24,7 @@ import io.element.android.features.messages.impl.timeline.components.customreact
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuState
import io.element.android.features.messages.impl.typing.TypingNotificationState
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData
@ -42,6 +43,7 @@ data class MessagesState(
val composerState: MessageComposerState,
val voiceMessageComposerState: VoiceMessageComposerState,
val timelineState: TimelineState,
val typingNotificationState: TypingNotificationState,
val actionListState: ActionListState,
val customReactionState: CustomReactionState,
val reactionSummaryState: ReactionSummaryState,

View file

@ -17,16 +17,22 @@
package io.element.android.features.messages.impl
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.messagecomposer.AttachmentsState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
import io.element.android.features.messages.impl.timeline.aTimelineItemList
import io.element.android.features.messages.impl.timeline.aTimelineState
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
import io.element.android.features.messages.impl.timeline.components.retrysendmenu.RetrySendMenuState
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.features.messages.impl.typing.aTypingNotificationState
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState
import io.element.android.libraries.architecture.AsyncData
@ -42,62 +48,77 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
override val values: Sequence<MessagesState>
get() = sequenceOf(
aMessagesState(),
aMessagesState().copy(hasNetworkConnection = false),
aMessagesState().copy(composerState = aMessageComposerState().copy(showAttachmentSourcePicker = true)),
aMessagesState().copy(userHasPermissionToSendMessage = false),
aMessagesState().copy(showReinvitePrompt = true),
aMessagesState().copy(
aMessagesState(hasNetworkConnection = false),
aMessagesState(composerState = aMessageComposerState(showAttachmentSourcePicker = true)),
aMessagesState(userHasPermissionToSendMessage = false),
aMessagesState(showReinvitePrompt = true),
aMessagesState(
roomName = AsyncData.Uninitialized,
roomAvatar = AsyncData.Uninitialized,
),
aMessagesState().copy(composerState = aMessageComposerState().copy(showTextFormatting = true)),
aMessagesState().copy(
aMessagesState(composerState = aMessageComposerState(showTextFormatting = true)),
aMessagesState(
enableVoiceMessages = true,
voiceMessageComposerState = aVoiceMessageComposerState(showPermissionRationaleDialog = true),
),
aMessagesState().copy(
composerState = aMessageComposerState().copy(
aMessagesState(
composerState = aMessageComposerState(
attachmentsState = AttachmentsState.Sending.Processing(persistentListOf())
),
),
aMessagesState().copy(
composerState = aMessageComposerState().copy(
aMessagesState(
composerState = aMessageComposerState(
attachmentsState = AttachmentsState.Sending.Uploading(0.33f)
),
),
aMessagesState().copy(
aMessagesState(
callState = RoomCallState.ONGOING,
),
aMessagesState().copy(
aMessagesState(
enableVoiceMessages = true,
voiceMessageComposerState = aVoiceMessageComposerState(
voiceMessageState = aVoiceMessagePreviewState(),
showSendFailureDialog = true
),
),
aMessagesState().copy(
aMessagesState(
callState = RoomCallState.DISABLED,
),
)
}
fun aMessagesState() = MessagesState(
roomId = RoomId("!id:domain"),
roomName = AsyncData.Success("Room name"),
roomAvatar = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
userHasPermissionToSendMessage = true,
userHasPermissionToRedactOwn = false,
userHasPermissionToRedactOther = false,
userHasPermissionToSendReaction = true,
composerState = aMessageComposerState().copy(
fun aMessagesState(
roomName: AsyncData<String> = AsyncData.Success("Room name"),
roomAvatar: AsyncData<AvatarData> = AsyncData.Success(AvatarData("!id:domain", "Room name", size = AvatarSize.TimelineRoom)),
userHasPermissionToSendMessage: Boolean = true,
composerState: MessageComposerState = aMessageComposerState(
richTextEditorState = RichTextEditorState("Hello", initialFocus = true),
isFullScreen = false,
mode = MessageComposerMode.Normal,
),
voiceMessageComposerState = aVoiceMessageComposerState(),
timelineState = aTimelineState().copy(
voiceMessageComposerState: VoiceMessageComposerState = aVoiceMessageComposerState(),
actionListState: ActionListState = anActionListState(),
customReactionState: CustomReactionState = aCustomReactionState(),
reactionSummaryState: ReactionSummaryState = aReactionSummaryState(),
hasNetworkConnection: Boolean = true,
showReinvitePrompt: Boolean = false,
enableVoiceMessages: Boolean = true,
callState: RoomCallState = RoomCallState.ENABLED,
eventSink: (MessagesEvents) -> Unit = {},
) = MessagesState(
roomId = RoomId("!id:domain"),
roomName = roomName,
roomAvatar = roomAvatar,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
userHasPermissionToRedactOwn = false,
userHasPermissionToRedactOther = false,
userHasPermissionToSendReaction = true,
composerState = composerState,
voiceMessageComposerState = voiceMessageComposerState,
timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()),
),
typingNotificationState = aTypingNotificationState(),
retrySendMenuState = RetrySendMenuState(
selectedEvent = null,
eventSink = {},
@ -106,23 +127,32 @@ fun aMessagesState() = MessagesState(
selectedEvent = null,
eventSink = {},
),
actionListState = anActionListState(),
customReactionState = CustomReactionState(
target = CustomReactionState.Target.None,
eventSink = {},
selectedEmoji = persistentSetOf(),
),
reactionSummaryState = ReactionSummaryState(
target = null,
eventSink = {},
),
hasNetworkConnection = true,
actionListState = actionListState,
customReactionState = customReactionState,
reactionSummaryState = reactionSummaryState,
hasNetworkConnection = hasNetworkConnection,
snackbarMessage = null,
inviteProgress = AsyncData.Uninitialized,
showReinvitePrompt = false,
showReinvitePrompt = showReinvitePrompt,
enableTextFormatting = true,
enableVoiceMessages = true,
callState = RoomCallState.ENABLED,
enableVoiceMessages = enableVoiceMessages,
callState = callState,
appName = "Element",
eventSink = {}
eventSink = eventSink,
)
fun aReactionSummaryState(
target: ReactionSummaryState.Summary? = null,
eventSink: (ReactionSummaryEvents) -> Unit = {}
) = ReactionSummaryState(
target = target,
eventSink = eventSink,
)
fun aCustomReactionState(
eventSink: (CustomReactionEvents) -> Unit = {},
) = CustomReactionState(
target = CustomReactionState.Target.None,
selectedEmoji = persistentSetOf(),
eventSink = eventSink,
)

View file

@ -102,7 +102,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.KeepScreenOn
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
@ -126,9 +125,8 @@ fun MessagesView(
onCreatePollClicked: () -> Unit,
onJoinCallClicked: () -> Unit,
modifier: Modifier = Modifier,
forceJumpToBottomVisibility: Boolean = false
) {
LogCompositions(tag = "MessagesScreen", msg = "Root")
OnLifecycleEvent { _, event ->
state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.LifecycleEvent(event))
}
@ -146,8 +144,6 @@ fun MessagesView(
// This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose
val localView = LocalView.current
LogCompositions(tag = "MessagesScreen", msg = "Content")
fun onMessageClicked(event: TimelineItem.Event) {
Timber.v("OnMessageClicked= ${event.id}")
val hideKeyboard = onEventClicked(event)
@ -229,6 +225,7 @@ fun MessagesView(
onSwipeToReply = { targetEvent ->
state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent))
},
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
)
},
snackbarHost = {
@ -329,6 +326,7 @@ private fun MessagesViewContent(
onTimestampClicked: (TimelineItem.Event) -> Unit,
onSendLocationClicked: () -> Unit,
onCreatePollClicked: () -> Unit,
forceJumpToBottomVisibility: Boolean,
modifier: Modifier = Modifier,
onSwipeToReply: (TimelineItem.Event) -> Unit,
) {
@ -389,6 +387,7 @@ private fun MessagesViewContent(
modifier = Modifier.padding(paddingValues),
state = state.timelineState,
roomName = state.roomName.dataOrNull(),
typingNotificationState = state.typingNotificationState,
onMessageClicked = onMessageClicked,
onMessageLongClicked = onMessageLongClicked,
onUserDataClicked = onUserDataClicked,
@ -398,6 +397,7 @@ private fun MessagesViewContent(
onMoreReactionsClicked = onMoreReactionsClicked,
onReadReceiptClick = onReadReceiptClick,
onSwipeToReply = onSwipeToReply,
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
)
},
sheetContent = { subcomposing: Boolean ->
@ -417,10 +417,9 @@ private fun MessagesViewContent(
private fun MessagesViewComposerBottomSheetContents(
subcomposing: Boolean,
state: MessagesState,
modifier: Modifier = Modifier,
) {
if (state.userHasPermissionToSendMessage) {
Column(modifier = modifier.fillMaxWidth()) {
Column(modifier = Modifier.fillMaxWidth()) {
MentionSuggestionsPickerView(
modifier = Modifier
.heightIn(max = 230.dp)
@ -448,7 +447,7 @@ private fun MessagesViewComposerBottomSheetContents(
)
}
} else {
CantSendMessageBanner(modifier = modifier)
CantSendMessageBanner()
}
}
@ -461,10 +460,8 @@ private fun MessagesViewTopBar(
onRoomDetailsClicked: () -> Unit,
onJoinCallClicked: () -> Unit,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
navigationIcon = {
BackButton(onClick = onBackPressed)
},
@ -502,7 +499,6 @@ private fun MessagesViewTopBar(
@Composable
private fun JoinCallMenuItem(
modifier: Modifier = Modifier,
onJoinCallClicked: () -> Unit,
) {
Material3Button(
@ -512,7 +508,7 @@ private fun JoinCallMenuItem(
containerColor = ElementTheme.colors.iconAccentTertiary
),
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
modifier = modifier.heightIn(min = 36.dp),
modifier = Modifier.heightIn(min = 36.dp),
) {
Icon(
modifier = Modifier.size(20.dp),
@ -550,11 +546,9 @@ private fun RoomAvatarAndNameRow(
}
@Composable
private fun CantSendMessageBanner(
modifier: Modifier = Modifier,
) {
private fun CantSendMessageBanner() {
Row(
modifier = modifier
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.secondary)
.padding(16.dp),
@ -584,5 +578,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
onSendLocationClicked = {},
onCreatePollClicked = {},
onJoinCallClicked = {},
forceJumpToBottomVisibility = true,
)
}

View file

@ -122,9 +122,12 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
}
}
fun anActionListState() = ActionListState(
target = ActionListState.Target.None,
eventSink = {}
fun anActionListState(
target: ActionListState.Target = ActionListState.Target.None,
eventSink: (ActionListEvents) -> Unit = {},
) = ActionListState(
target = target,
eventSink = eventSink
)
fun aTimelineItemActionList(): ImmutableList<TimelineItemAction> {

View file

@ -337,7 +337,6 @@ private fun EmojiButton(
emoji: String,
isHighlighted: Boolean,
onClicked: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val backgroundColor = if (isHighlighted) {
ElementTheme.colors.bgActionPrimaryRest
@ -350,7 +349,7 @@ private fun EmojiButton(
stringResource(id = CommonStrings.a11y_react_with, emoji)
}
Box(
modifier = modifier
modifier = Modifier
.size(48.dp)
.background(backgroundColor, CircleShape)
.clearAndSetSemantics {

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