diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index 72f9aee8f1..13e0c3b4ad 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -16,7 +16,7 @@ jobs: maestro-cloud: name: Maestro test suite runs-on: ubuntu-latest - if: github.event.review.state == 'approved' + if: github.event.review.state == 'approved' || github.event_name == 'workflow_dispatch' strategy: fail-fast: false # Allow one per PR. diff --git a/.github/workflows/triage-labelled.yml b/.github/workflows/triage-labelled.yml index 136acfe491..d7951a012b 100644 --- a/.github/workflows/triage-labelled.yml +++ b/.github/workflows/triage-labelled.yml @@ -12,22 +12,10 @@ jobs: if: > github.repository == 'vector-im/element-x-android' steps: - - uses: octokit/graphql-action@v2.x + - uses: actions/add-to-project@main with: - headers: '{"GraphQL-Features": "projects_next_graphql"}' - query: | - mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { - item { - id - } - } - } - projectid: ${{ env.PROJECT_ID }} - contentid: ${{ github.event.issue.node_id }} - env: - PROJECT_ID: "PVT_kwDOAM0swc4ABTXY" - GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + project-url: https://github.com/orgs/vector-im/projects/43 + github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} ex_plorers: name: Add labelled issues to X-Plorer project @@ -35,23 +23,10 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'Team: Element X Feature') steps: - - uses: octokit/graphql-action@v2.x - id: add_to_project + - uses: actions/add-to-project@main with: - headers: '{"GraphQL-Features": "projects_next_graphql"}' - query: | - mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { - item { - id - } - } - } - projectid: ${{ env.PROJECT_ID }} - contentid: ${{ github.event.issue.node_id }} - env: - PROJECT_ID: "PVT_kwDOAM0swc4ALoFY" - GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + project-url: https://github.com/orgs/vector-im/projects/73 + github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} verticals_feature: name: Add labelled issues to Verticals Feature project @@ -59,20 +34,18 @@ jobs: if: > contains(github.event.issue.labels.*.name, 'Team: Verticals Feature') steps: - - uses: octokit/graphql-action@v2.x - id: add_to_project + - uses: actions/add-to-project@main with: - headers: '{"GraphQL-Features": "projects_next_graphql"}' - query: | - mutation add_to_project($projectid:ID!,$contentid:ID!) { - addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { - item { - id - } - } - } - projectid: ${{ env.PROJECT_ID }} - contentid: ${{ github.event.issue.node_id }} - env: - PROJECT_ID: "PVT_kwDOAM0swc4AHJKW" - GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + project-url: https://github.com/orgs/vector-im/projects/57 + github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} + + qa: + name: Add labelled issues to QA project + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'Team: QA') + steps: + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/vector-im/projects/69 + github-token: ${{ secrets.ELEMENT_BOT_TOKEN }} diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 0fc3113136..69e86158ba 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.maestro/tests/account/logout.yaml b/.maestro/tests/account/logout.yaml index 3c9dd07972..a06ac25e2d 100644 --- a/.maestro/tests/account/logout.yaml +++ b/.maestro/tests/account/logout.yaml @@ -1,6 +1,7 @@ appId: ${APP_ID} --- -- tapOn: "Settings" +- tapOn: + id: "home_screen-settings" - tapOn: "Sign out" - takeScreenshot: build/maestro/900-SignOutDialg # Ensure cancel cancels diff --git a/.maestro/tests/settings/settings.yaml b/.maestro/tests/settings/settings.yaml index 397a0f70b5..ee3104024c 100644 --- a/.maestro/tests/settings/settings.yaml +++ b/.maestro/tests/settings/settings.yaml @@ -1,6 +1,7 @@ appId: ${APP_ID} --- -- tapOn: "Settings" +- tapOn: + id: "home_screen-settings" - assertVisible: "Rageshake to report bug" - takeScreenshot: build/maestro/600-Settings - tapOn: diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt index 7cd33c2958..2b33e37c7e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RoomFlowNode.kt @@ -18,6 +18,7 @@ package io.element.android.appnav import android.os.Parcelable import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.composable.Children @@ -133,8 +134,18 @@ class RoomFlowNode @AssistedInject constructor( object RoomDetails : NavTarget } + private val timeline = inputs.room.timeline() + @Composable override fun View(modifier: Modifier) { + + DisposableEffect(Unit) { + timeline.initialize() + onDispose { + timeline.dispose() + } + } + Children( navModel = backstack, modifier = modifier, diff --git a/changelog.d/339.feature b/changelog.d/339.feature new file mode 100644 index 0000000000..4cbf834b1c --- /dev/null +++ b/changelog.d/339.feature @@ -0,0 +1 @@ +Block & unblock users from room details screen. diff --git a/changelog.d/354.feature b/changelog.d/354.feature new file mode 100644 index 0000000000..7d6e15b545 --- /dev/null +++ b/changelog.d/354.feature @@ -0,0 +1 @@ +Improve room list search and general UI diff --git a/features/createroom/impl/src/main/res/values-de/translations.xml b/features/createroom/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..8a3c6cdeda --- /dev/null +++ b/features/createroom/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ + + + "Neuer Raum" + "Privater Raum (nur auf Einladung)" + "Raumname" + "Thema (optional)" + \ No newline at end of file diff --git a/features/createroom/impl/src/main/res/values-ro/translations.xml b/features/createroom/impl/src/main/res/values-ro/translations.xml index af6e3db1fa..a1ea3b31f0 100644 --- a/features/createroom/impl/src/main/res/values-ro/translations.xml +++ b/features/createroom/impl/src/main/res/values-ro/translations.xml @@ -3,6 +3,16 @@ "Cameră nouă" "Invitați persoane" "Adaugați persoane" + "A apărut o eroare la crearea camerei" + "Mesajele din această cameră sunt criptate. Criptarea nu poate fi dezactivată ulterior." + "Cameră privată (doar pe bază de invitație)" + "Mesajele nu sunt criptate și oricine le poate citi. Puteți activa criptarea la o dată ulterioară." + "Cameră publică (oricine)" + "Numele camerei" + "e.g. Mici și Cozonaci" + "Creați o cameră" + "Subiect (opțional)" + "Despre ce este această cameră?" "A apărut o eroare la încercarea începerii conversației" "Nu am putut valida ID-ul Matrix al acestui utilizator. Este posibil ca invitația să nu fi fost primită." \ No newline at end of file diff --git a/features/invitelist/impl/src/main/res/values-de/translations.xml b/features/invitelist/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..95e63cf5f2 --- /dev/null +++ b/features/invitelist/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,6 @@ + + + "Chat ablehnen" + "Keine Einladungen" + "%1$s hat dich eingeladen" + \ No newline at end of file diff --git a/features/invitelist/impl/src/main/res/values-es/translations.xml b/features/invitelist/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..49c32a9e49 --- /dev/null +++ b/features/invitelist/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,4 @@ + + + "%1$s te invitó." + \ No newline at end of file diff --git a/features/invitelist/impl/src/main/res/values-it/translations.xml b/features/invitelist/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..c0eccfcb7c --- /dev/null +++ b/features/invitelist/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,4 @@ + + + "%1$s ti ha invitato" + \ No newline at end of file diff --git a/features/invitelist/impl/src/main/res/values-ro/translations.xml b/features/invitelist/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..026485d102 --- /dev/null +++ b/features/invitelist/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,9 @@ + + + "Sigur doriți să refuzați alăturarea la %1$s?" + "Refuzați invitația" + "Sigur doriți să refuzați conversațiile cu %1$s?" + "Refuzați conversația" + "Nicio invitație" + "%1$s v-a invitat" + \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-de/translations.xml b/features/login/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..061f3453df --- /dev/null +++ b/features/login/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ + + + "Wie lautet die Adresse deines Servers?" + "Willkommen zurück!" + "Passwort" + "Benutzername" + \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-es/translations.xml b/features/login/impl/src/main/res/values-es/translations.xml index a299083994..284527c2f5 100644 --- a/features/login/impl/src/main/res/values-es/translations.xml +++ b/features/login/impl/src/main/res/values-es/translations.xml @@ -4,17 +4,17 @@ "Este servidor no soporta sliding sync." "Dirección del homeserver" "Solo puedes conectarte a un servidor que soporte sliding sync. El administrador de tu servidor tendrá que configurarlo. %1$s" - "Continuar" "¿Cuál es la dirección de tu servidor?" - "Selecciona tu servidor" "Esta cuenta ha sido desactivada." "Usuario y/o contraseña incorrectos" "Este no es un id de usuario válido. Formato esperado: \'@user:homeserver.org\'" "El servidor seleccionado no admite contraseñas ni inicio de sesión OIDC. Póngase en contacto con su administrador o elija otro homeserver." "Introduce tus datos" - "Contraseña" "Donde viven tus conversaciones" - "Continuar" "¡Hola de nuevo!" + "Continuar" + "Selecciona tu servidor" + "Contraseña" + "Continuar" "Usuario" \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-it/translations.xml b/features/login/impl/src/main/res/values-it/translations.xml index 429f156883..b11875a18e 100644 --- a/features/login/impl/src/main/res/values-it/translations.xml +++ b/features/login/impl/src/main/res/values-it/translations.xml @@ -4,17 +4,17 @@ "Questo server attualmente non supporta la sincronizzazione scorrevole." "URL dell\'homeserver" "Puoi connetterti solo a un server esistente che supporta la sincronizzazione scorrevole. L\'amministratore del tuo server domestico dovrà configurarlo. %1$s" - "Continua" "Qual è l\'indirizzo del tuo server?" - "Seleziona il tuo server" "Questo profilo è stato disattivato." "Nome utente e/o password errati" "Questo non è un identificatore utente valido. Formato previsto: \'@user:homeserver.org\'" "L\'homeserver selezionato non supporta la password o l\'accesso OIDC. Contatta il tuo amministratore o scegli un altro homeserver." "Inserisci i tuoi dati" - "Password" "Dove vivono le tue conversazioni" - "Continua" "Bentornato!" + "Continua" + "Seleziona il tuo server" + "Password" + "Continua" "Nome utente" \ No newline at end of file diff --git a/features/login/impl/src/main/res/values-ro/translations.xml b/features/login/impl/src/main/res/values-ro/translations.xml index 2b5cce6829..349e3ddc04 100644 --- a/features/login/impl/src/main/res/values-ro/translations.xml +++ b/features/login/impl/src/main/res/values-ro/translations.xml @@ -4,17 +4,17 @@ "Momentan acest server nu oferă suport pentru sliding sync." "Adresa URL a homeserver-ului" "Vă putețo conecta numai la un server existent care oferă suport pentru sliding sync. Administratorul homeserver-ului dumneavoastră va trebui să îl configureze. %1$s" - "Continuați" "Care este adresa serverului dumneavoastră?" - "Selectați serverul" "Acest cont a fost dezactivat." "Utilizator și/sau parolă incorecte" "Acesta nu este un identificator de utilizator valid. Format așteptat: „@user:homeserver.org”" "Homeserver-ul selectat nu acceptă autentificarea prin parola sau OIDC. Te rugăm să contactezi administratorul sau să alegi un alt homeserver." "Introduceți detaliile" - "Parolă" "Locul unde trăiesc conversațiile tale" - "Continuați" "Bine ați revenit!" + "Continuați" + "Selectați serverul" + "Parola" + "Continuați" "Utilizator" \ No newline at end of file diff --git a/features/login/impl/src/main/res/values/localazy.xml b/features/login/impl/src/main/res/values/localazy.xml index 82ea22e61b..6b0ecee43d 100644 --- a/features/login/impl/src/main/res/values/localazy.xml +++ b/features/login/impl/src/main/res/values/localazy.xml @@ -4,17 +4,17 @@ "This server currently doesn’t support sliding sync." "Homeserver URL" "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$s" - "Continue" "What is the address of your server?" - "Select your server" "This account has been deactivated." "Incorrect username and/or password" "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’" "The selected homeserver doesn\'t support password or OIDC login. Please contact your admin or choose another homeserver." "Enter your details" - "Password" "Where your conversations live" - "Continue" "Welcome back!" + "Continue" + "Select your server" + "Password" + "Continue" "Username" \ No newline at end of file diff --git a/features/logout/api/src/main/res/values-de/translations.xml b/features/logout/api/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..9fd4f6b083 --- /dev/null +++ b/features/logout/api/src/main/res/values-de/translations.xml @@ -0,0 +1,7 @@ + + + "Abmelden" + "Abmelden" + "Abmeldung läuft…" + "Abmelden" + \ No newline at end of file diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 1d931c76bb..9418e59edc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -80,13 +80,6 @@ class TimelinePresenter @Inject constructor( .launchIn(this) } - DisposableEffect(Unit) { - timeline.initialize() - onDispose { - timeline.dispose() - } - } - return TimelineState( highlightedEventId = highlightedEventId.value, paginationState = paginationState.value, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index 24e6f77d32..41f37158d9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -49,23 +49,6 @@ class TimelinePresenterTest { } } - @Test - fun `present - makes sure timeline is initialized and disposed`() = runTest { - val fakeTimeline = FakeMatrixTimeline() - val presenter = TimelinePresenter( - timelineItemsFactory = aTimelineItemsFactory(), - room = FakeMatrixRoom(matrixTimeline = fakeTimeline), - ) - assertThat(fakeTimeline.isInitialized).isFalse() - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - skipItems(2) - assertThat(fakeTimeline.isInitialized).isTrue() - } - assertThat(fakeTimeline.isInitialized).isFalse() - } - @Test fun `present - load more`() = runTest { val presenter = TimelinePresenter( diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index ba4d6c2775..25819c6eb3 100644 --- a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -71,8 +71,8 @@ class NetworkMonitorImpl @Inject constructor( private fun listenToConnectionChanges() { val request = NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) +// .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) +// .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .build() connectivityManager.registerNetworkCallback(request, callback) diff --git a/features/onboarding/impl/src/main/res/values-de/translations.xml b/features/onboarding/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..82e01aa522 --- /dev/null +++ b/features/onboarding/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,4 @@ + + + "Sei in deinem Element" + \ No newline at end of file diff --git a/features/rageshake/impl/src/main/res/values-de/translations.xml b/features/rageshake/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..e18f43d1de --- /dev/null +++ b/features/rageshake/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,6 @@ + + + "Beschreibe den Fehler…" + "Absturzprotokolle senden" + "Bildschirmfoto senden" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/blockuser/BlockUserSection.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/blockuser/BlockUserSection.kt new file mode 100644 index 0000000000..49daa15af3 --- /dev/null +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/blockuser/BlockUserSection.kt @@ -0,0 +1,89 @@ +/* + * 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.roomdetails.blockuser + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Block +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState +import io.element.android.features.roomdetails.impl.R +import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog +import io.element.android.libraries.designsystem.components.preferences.PreferenceCategory +import io.element.android.libraries.designsystem.components.preferences.PreferenceText +import io.element.android.libraries.designsystem.theme.LocalColors + +@Composable +internal fun BlockUserSection(state: RoomMemberDetailsState, modifier: Modifier = Modifier) { + PreferenceCategory(showDivider = false, modifier = modifier) { + if (state.isBlocked) { + PreferenceText( + title = stringResource(R.string.screen_dm_details_unblock_user), + icon = Icons.Outlined.Block, + onClick = { state.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true)) }, + ) + } else { + PreferenceText( + title = stringResource(R.string.screen_dm_details_block_user), + icon = Icons.Outlined.Block, + tintColor = LocalColors.current.textActionCritical, + onClick = { state.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true)) }, + ) + } + } +} + +@Composable +internal fun BlockUserDialogs(state: RoomMemberDetailsState) { + when (state.displayConfirmationDialog) { + null -> Unit + RoomMemberDetailsState.ConfirmationDialog.Block -> { + BlockConfirmationDialog( + onBlockAction = { state.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false)) }, + onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) } + ) + } + RoomMemberDetailsState.ConfirmationDialog.Unblock -> { + UnblockConfirmationDialog( + onUnblockAction = { state.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false)) }, + onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) } + ) + } + } +} + +@Composable +internal fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () -> Unit) { + ConfirmationDialog( + content = stringResource(R.string.screen_dm_details_block_alert_description), + submitText = stringResource(R.string.screen_dm_details_block_alert_action), + onSubmitClicked = onBlockAction, + onDismiss = onDismiss + ) +} + +@Composable +internal fun UnblockConfirmationDialog(onUnblockAction: () -> Unit, onDismiss: () -> Unit) { + ConfirmationDialog( + content = stringResource(R.string.screen_dm_details_unblock_alert_description), + submitText = stringResource(R.string.screen_dm_details_unblock_alert_action), + onSubmitClicked = onUnblockAction, + onDismiss = onDismiss + ) +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index 46e76a5f8a..37110e5192 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -63,7 +63,10 @@ class RoomDetailsNode @AssistedInject constructor( activityResultLauncher = null, chooserTitle = context.getString(R.string.screen_room_details_share_room_title), text = permalink, + noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found) ) + }.onFailure { + Timber.e(it) } } @@ -86,12 +89,21 @@ class RoomDetailsNode @AssistedInject constructor( override fun View(modifier: Modifier) { val context = LocalContext.current val state = presenter.present() + + fun onShareRoom() { + this.onShareRoom(context) + } + + fun onShareMember(roomMember: RoomMember) { + this.onShareMember(context, roomMember) + } + RoomDetailsView( state = state, modifier = modifier, - goBack = { navigateUp() }, - onShareRoom = { onShareRoom(context) }, - onShareMember = { onShareMember(context, it) }, + goBack = this::navigateUp, + onShareRoom = ::onShareRoom, + onShareMember = ::onShareMember, openRoomMemberList = ::openRoomMemberList, ) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 9b6a1916c7..bb4cae8423 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers @@ -41,6 +42,7 @@ class RoomDetailsPresenter @Inject constructor( private val room: MatrixRoom, private val roomMembershipObserver: RoomMembershipObserver, private val coroutineDispatchers: CoroutineDispatchers, + private val roomMembersDetailsPresenterFactory: RoomMemberDetailsPresenter.Factory, ) : Presenter { @Composable @@ -57,6 +59,8 @@ class RoomDetailsPresenter @Inject constructor( val dmMemberState by room.getDmMemberFlow() .collectAsState(initial = null, context = coroutineDispatchers.computation) + val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMemberState) + val roomType = getRoomType(dmMemberState) fun handleEvents(event: RoomDetailsEvent) { @@ -74,6 +78,8 @@ class RoomDetailsPresenter @Inject constructor( } } + val roomMemberDetailsState = roomMemberDetailsPresenter?.present() + return RoomDetailsState( roomId = room.roomId.value, roomName = room.name ?: room.displayName, @@ -85,10 +91,18 @@ class RoomDetailsPresenter @Inject constructor( displayLeaveRoomWarning = leaveRoomWarning.value, error = error.value, roomType = roomType.value, + roomMemberDetailsState = roomMemberDetailsState, eventSink = ::handleEvents, ) } + @Composable + private fun roomMemberDetailsPresenter(dmMemberState: RoomMember?) = remember(dmMemberState) { + dmMemberState?.let { roomMember -> + roomMembersDetailsPresenterFactory.create(roomMember) + } + } + @Composable private fun getRoomType(dmMember: RoomMember?): State = remember(dmMember) { derivedStateOf { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt index f8fed122de..173ba66ed0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsState.kt @@ -16,10 +16,8 @@ package io.element.android.features.roomdetails.impl +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState import io.element.android.libraries.architecture.Async -import io.element.android.libraries.architecture.isLoading - -import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember data class RoomDetailsState( @@ -33,6 +31,7 @@ data class RoomDetailsState( val displayLeaveRoomWarning: LeaveRoomWarning?, val error: RoomDetailsError?, val roomType: RoomDetailsType, + val roomMemberDetailsState: RoomMemberDetailsState?, val eventSink: (RoomDetailsEvent) -> Unit ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt index 61b9d310af..d30ea15f4a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsStateProvider.kt @@ -71,5 +71,6 @@ fun aRoomDetailsState() = RoomDetailsState( displayLeaveRoomWarning = null, error = null, roomType = RoomDetailsType.Room, + roomMemberDetailsState = null, eventSink = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index b9266cd8ca..9b3f83d456 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -42,7 +42,8 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import io.element.android.features.roomdetails.impl.members.details.BlockSection +import io.element.android.features.roomdetails.blockuser.BlockUserDialogs +import io.element.android.features.roomdetails.blockuser.BlockUserSection import io.element.android.features.roomdetails.impl.members.details.RoomMemberHeaderSection import io.element.android.features.roomdetails.impl.members.details.RoomMemberShareSection import io.element.android.libraries.architecture.Async @@ -135,10 +136,11 @@ fun RoomDetailsView( }) } is RoomDetailsType.Dm -> { - BlockSection( - isBlocked = state.roomType.roomMember.isIgnored, - onToggleBlock = { /*TODO*/ } - ) + if (state.roomMemberDetailsState != null) { + val roomMemberState = state.roomMemberDetailsState + BlockUserSection(roomMemberState) + BlockUserDialogs(roomMemberState) + } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt index aeb42893e8..b17cb9ab6a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt @@ -20,7 +20,6 @@ import com.squareup.anvil.annotations.ContributesTo import dagger.Binds import dagger.Module import dagger.Provides -import io.element.android.features.roomdetails.impl.RoomDetailsPresenter import io.element.android.features.roomdetails.impl.members.RoomUserListDataSource import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.userlist.api.UserListDataSource @@ -28,7 +27,6 @@ import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember -import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import javax.inject.Named @Module @@ -46,11 +44,12 @@ object RoomMemberProvidesModule { @Provides fun provideRoomMemberDetailsPresenterFactory( + matrixClient: MatrixClient, room: MatrixRoom, ): RoomMemberDetailsPresenter.Factory { return object : RoomMemberDetailsPresenter.Factory { override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { - return RoomMemberDetailsPresenter(room, roomMember) + return RoomMemberDetailsPresenter(matrixClient.sessionId, room, roomMember) } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt index ff2b1a0245..48743b66fa 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListNode.kt @@ -26,14 +26,12 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember @ContributesNode(RoomScope::class) class RoomMemberListNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, - private val room: MatrixRoom, private val presenter: RoomMemberListPresenter, ) : Node(buildContext, plugins = plugins) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt index 10d9d55c02..9133db7688 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt @@ -28,8 +28,6 @@ import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.ui.model.MatrixUser import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.skip -import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.withContext import javax.inject.Inject @@ -40,7 +38,7 @@ class RoomUserListDataSource @Inject constructor( override suspend fun search(query: String): List = withContext(coroutineDispatchers.io) { val roomMembers = room.membersStateFlow - .dropWhile { it !is MatrixRoomMembersState.Ready} + .dropWhile { it !is MatrixRoomMembersState.Ready } .first() .roomMembers() val filteredMembers = if (query.isBlank()) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt index 2c74caa8fd..5848561f3e 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt @@ -16,4 +16,8 @@ package io.element.android.features.roomdetails.impl.members.details -sealed interface RoomMemberDetailsEvents +sealed interface RoomMemberDetailsEvents { + data class BlockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents + data class UnblockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents + object ClearConfirmationDialog : RoomMemberDetailsEvents +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt index 5cd2544537..72e335c1d2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.androidutils.system.startSharePlainTextInten import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.room.RoomMember import timber.log.Timber @@ -52,7 +51,6 @@ class RoomMemberDetailsNode @AssistedInject constructor( @Composable override fun View(modifier: Modifier) { - val context = LocalContext.current fun onShareUser() { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index de24b5ee1b..8256453a08 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -17,15 +17,26 @@ package io.element.android.features.roomdetails.impl.members.details import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMember +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch class RoomMemberDetailsPresenter @AssistedInject constructor( + private val currentUserSessionId: SessionId, private val room: MatrixRoom, @Assisted private val roomMember: RoomMember, ) : Presenter { @@ -36,11 +47,31 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @Composable override fun present(): RoomMemberDetailsState { + val coroutineScope = rememberCoroutineScope() + var confirmationDialog by remember { mutableStateOf(null) } + val isBlocked = remember { mutableStateOf(roomMember.isIgnored) } -// fun handleEvents(event: RoomMemberDetailsEvents) { -// when (event) { -// } -// } + fun handleEvents(event: RoomMemberDetailsEvents) { + when (event) { + is RoomMemberDetailsEvents.BlockUser -> { + if (event.needsConfirmation) { + confirmationDialog = ConfirmationDialog.Block + } else { + confirmationDialog = null + coroutineScope.blockUser(roomMember.userId, isBlocked) + } + } + is RoomMemberDetailsEvents.UnblockUser -> { + if (event.needsConfirmation) { + confirmationDialog = ConfirmationDialog.Unblock + } else { + confirmationDialog = null + coroutineScope.unblockUser(roomMember.userId, isBlocked) + } + } + RoomMemberDetailsEvents.ClearConfirmationDialog -> confirmationDialog = null + } + } val userName by produceState(initialValue = roomMember.displayName) { room.userDisplayName(roomMember.userId).onSuccess { displayName -> @@ -58,8 +89,18 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( userId = roomMember.userId.value, userName = userName, avatarUrl = userAvatar, - isBlocked = roomMember.isIgnored, -// eventSink = ::handleEvents + isBlocked = isBlocked.value, + displayConfirmationDialog = confirmationDialog, + isCurrentUser = roomMember.userId == currentUserSessionId, + eventSink = ::handleEvents ) } + + private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState) = launch { + room.ignoreUser(userId).onSuccess { isBlockedState.value = true } + } + + private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState) = launch { + room.unignoreUser(userId).onSuccess { isBlockedState.value = false } + } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt index d9e3f949e7..0a2895db09 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt @@ -21,5 +21,11 @@ data class RoomMemberDetailsState( val userName: String?, val avatarUrl: String?, val isBlocked: Boolean, -// val eventSink: (RoomMemberDetailsEvents) -> Unit -) + val displayConfirmationDialog: ConfirmationDialog? = null, + val isCurrentUser: Boolean, + val eventSink: (RoomMemberDetailsEvents) -> Unit +) { + enum class ConfirmationDialog { + Block, Unblock + } +} diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt index c719ab7a26..d8e7ce5ad3 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt @@ -24,6 +24,8 @@ open class RoomMemberDetailsStateProvider : PreviewParameterProvider Unit, modifier: Modifier = } } -@Composable -internal fun BlockSection(isBlocked: Boolean, onToggleBlock: () -> Unit, modifier: Modifier = Modifier) { - PreferenceCategory(showDivider = false, modifier = modifier) { - if (isBlocked) { - PreferenceText( - title = stringResource(R.string.screen_dm_details_unblock_user), - icon = Icons.Outlined.Block, - ) - } else { - PreferenceText( - title = stringResource(R.string.screen_dm_details_block_user), - icon = Icons.Outlined.Block, - tintColor = LocalColors.current.textActionCritical, - ) - } - } -} - @Preview @Composable fun RoomMemberDetailsViewLightPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) = diff --git a/features/roomdetails/impl/src/main/res/values-de/translations.xml b/features/roomdetails/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..7581b585f1 --- /dev/null +++ b/features/roomdetails/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,15 @@ + + + + "1 Person" + "%1$d Personen" + + "Raum teilen" + "Blockieren" + "Nutzer blockieren" + "Blockierung aufheben" + "Nutzer entblockieren" + "Raum verlassen" + "Sicherheit" + "Thema" + \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values-es/translations.xml b/features/roomdetails/impl/src/main/res/values-es/translations.xml index ba4327000b..58c486d6c3 100644 --- a/features/roomdetails/impl/src/main/res/values-es/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-es/translations.xml @@ -4,18 +4,18 @@ "Una persona" "%1$d personas" + "Los mensajes están protegidos con \"candados\". Sólo tú y los destinatarios tenéis las llaves únicas para abrirlos." + "Cifrado de mensajes activado" + "Invitar a otras personas" + "Compartir sala" "Bloquear" - "Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puedes revertir esta acción en cualquier momento." + "Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puede revertir esta acción en cualquier momento." "Bloquear usuario" "Desbloquear" "Al desbloquear al usuario, podrás volver a ver todos sus mensajes." "Desbloquear usuario" - "Los mensajes están protegidos con \"candados\". Sólo tú y los destinatarios tenéis las llaves únicas para abrirlos." - "Cifrado de mensajes activado" - "Invitar a otras personas" "Salir de la sala" "Personas" "Seguridad" - "Compartir sala" "Tema" \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values-it/translations.xml b/features/roomdetails/impl/src/main/res/values-it/translations.xml index 9a980b79a9..a2e61a329c 100644 --- a/features/roomdetails/impl/src/main/res/values-it/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-it/translations.xml @@ -4,18 +4,18 @@ "1 persona" "%1$d persone" + "I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli." + "Crittografia messaggi abilitata" + "Invita persone" + "Condividi stanza" "Blocca" - "Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti i loro messaggi saranno nascosti. Potrai annullare questa azione in qualsiasi momento." + "Gli utenti bloccati non saranno in grado di inviarti nuovi messaggi e tutti quelli già esistenti saranno nascosti. Potrai annullare questa azione in qualsiasi momento." "Blocca utente" "Sblocca" "Dopo aver sbloccato l\'utente, potrai vedere nuovamente tutti i suoi messaggi." "Sblocca utente" - "I messaggi sono protetti da lucchetti. Solo tu e i destinatari avete le chiavi univoche per sbloccarli." - "Crittografia messaggi abilitata" - "Invita persone" "Esci dalla stanza" "Persone" "Sicurezza" - "Condividi stanza" "Oggetto" \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values-ro/translations.xml b/features/roomdetails/impl/src/main/res/values-ro/translations.xml index db6777fb7f..3525b06d8e 100644 --- a/features/roomdetails/impl/src/main/res/values-ro/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-ro/translations.xml @@ -5,18 +5,18 @@ "%1$d persoane" + "Mesajele sunt securizate cu încuietori. Doar dumneavoastră și destinatarii aveți cheile unice pentru a le debloca." + "Criptarea mesajelor este activată" + "Invitați persoane" + "Partajați camera" "Blocați" "Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând." "Blocați utilizatorul" "Deblocați" "La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta." "Deblocați utilizatorul" - "Mesajele sunt securizate cu încuietori. Doar dumneavoastră și destinatarii aveți cheile unice pentru a le debloca." - "Criptarea mesajelor este activată" - "Invitați persoane" "Părăsiți camera" "Persoane" "Securitate" - "Partajați camera" "Subiect" \ No newline at end of file diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index f63757a8e3..584f4322d3 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -4,18 +4,18 @@ "1 person" "%1$d people" + "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." + "Message encryption enabled" + "Invite people" + "Share room" "Block" "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime." "Block user" "Unblock" "On unblocking the user, you will be able to see all messages by them again." "Unblock user" - "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them." - "Message encryption enabled" - "Invite people" "Leave room" "People" "Security" - "Share room" "Topic" \ No newline at end of file diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 05617ebf1a..acac5e7ccc 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -23,9 +23,12 @@ import com.google.common.truth.Truth import io.element.android.features.roomdetails.impl.LeaveRoomWarning import io.element.android.features.roomdetails.impl.RoomDetailsEvent import io.element.android.features.roomdetails.impl.RoomDetailsPresenter +import io.element.android.features.roomdetails.impl.RoomDetailsType +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipObserver @@ -33,7 +36,9 @@ import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_USER_ID +import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -49,10 +54,19 @@ class RoomDetailsPresenterTests { private val roomMembershipObserver = RoomMembershipObserver() private val testCoroutineDispatchers = testCoroutineDispatchers() + private fun aRoomDetailsPresenter(room: MatrixRoom): RoomDetailsPresenter { + val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { + override fun create(roomMember: RoomMember): RoomMemberDetailsPresenter { + return RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) + } + } + return RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers, roomMemberDetailsPresenterFactory) + } + @Test fun `present - initial state is created from room info`() = runTest { val room = aMatrixRoom() - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -71,7 +85,7 @@ class RoomDetailsPresenterTests { @Test fun `present - room member count is calculated asynchronously`() = runTest { val room = aMatrixRoom() - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -86,7 +100,7 @@ class RoomDetailsPresenterTests { @Test fun `present - initial state with no room name`() = runTest { val room = aMatrixRoom(name = null) - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -97,12 +111,41 @@ class RoomDetailsPresenterTests { } } + @Test + fun `present - initial state with DM member sets custom DM roomType`() = runTest { + val room = aMatrixRoom( + isEncrypted = true, + isPublic = false, + name = null + ).apply { + val roomMembers = listOf( + aRoomMember(A_SESSION_ID), + aRoomMember(A_USER_ID_2), + ) + givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) + } + val presenter = aRoomDetailsPresenter(room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + // It's not configured yet in the first iteration + Truth.assertThat(initialState.roomType).isEqualTo(RoomDetailsType.Room) + + // Once updated, the RoomDetailsType becomes 'Dm' + val updatedState = awaitItem() + Truth.assertThat(updatedState.roomType).isEqualTo(RoomDetailsType.Dm(aRoomMember())) + + cancelAndIgnoreRemainingEvents() + } + } + @Test fun `present - can handle error while fetching member count`() = runTest { val room = aMatrixRoom(name = null).apply { givenRoomMembersState(MatrixRoomMembersState.Error(Throwable())) } - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -116,7 +159,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom(isPublic = false).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(emptyList())) } - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -132,7 +175,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(aRoomMember()))) } - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -148,7 +191,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(emptyList())) } - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -164,7 +207,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenRoomMembersState(MatrixRoomMembersState.Ready(emptyList())) } - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -185,7 +228,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenLeaveRoomError(Throwable()) } - val presenter = RoomDetailsPresenter(room, roomMembershipObserver, testCoroutineDispatchers) + val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index 68de573fae..a98ec1f971 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -22,7 +22,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth import io.element.android.features.roomdetails.aMatrixRoom import io.element.android.features.roomdetails.aRoomMember +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter +import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState +import io.element.android.libraries.matrix.test.A_SESSION_ID import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test @@ -37,7 +40,7 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.success("A custom avatar")) } val roomMember = aRoomMember(displayName = "Alice") - val presenter = RoomMemberDetailsPresenter(room, roomMember) + val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -60,7 +63,7 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.failure(Throwable())) } val roomMember = aRoomMember(displayName = "Alice") - val presenter = RoomMemberDetailsPresenter(room, roomMember) + val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -79,7 +82,7 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.success(null)) } val roomMember = aRoomMember(displayName = "Alice") - val presenter = RoomMemberDetailsPresenter(room, roomMember) + val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { @@ -90,4 +93,63 @@ class RoomMemberDetailsPresenterTests { ensureAllEventsConsumed() } } + + @Test + fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest { + val room = aMatrixRoom() + val roomMember = aRoomMember() + val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true)) + + val dialogState = awaitItem() + Truth.assertThat(dialogState.displayConfirmationDialog).isEqualTo(RoomMemberDetailsState.ConfirmationDialog.Block) + + dialogState.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) + Truth.assertThat(awaitItem().displayConfirmationDialog).isNull() + + ensureAllEventsConsumed() + } + } + + @Test + fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest { + val room = aMatrixRoom() + val roomMember = aRoomMember() + val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false)) + Truth.assertThat(awaitItem().isBlocked).isTrue() + + initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false)) + Truth.assertThat(awaitItem().isBlocked).isFalse() + } + } + + @Test + fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest { + val room = aMatrixRoom() + val roomMember = aRoomMember() + val presenter = RoomMemberDetailsPresenter(A_SESSION_ID, room, roomMember) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true)) + + val dialogState = awaitItem() + Truth.assertThat(dialogState.displayConfirmationDialog).isEqualTo(RoomMemberDetailsState.ConfirmationDialog.Unblock) + + dialogState.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) + Truth.assertThat(awaitItem().displayConfirmationDialog).isNull() + + ensureAllEventsConsumed() + } + } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt index 299c670eb4..684342bee8 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListEvents.kt @@ -20,4 +20,5 @@ sealed interface RoomListEvents { data class UpdateFilter(val newFilter: String) : RoomListEvents data class UpdateVisibleRange(val range: IntRange) : RoomListEvents object DismissRequestVerificationPrompt : RoomListEvents + object ToggleSearchResults : RoomListEvents } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 38c80734e2..f366c25326 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -79,6 +79,7 @@ class RoomListPresenter @Inject constructor( Timber.v("RoomSummaries size = ${roomSummaries.size}") + val mappedRoomSummaries: MutableState> = remember { mutableStateOf(persistentListOf()) } val filteredRoomSummaries: MutableState> = remember { mutableStateOf(persistentListOf()) } @@ -101,41 +102,51 @@ class RoomListPresenter @Inject constructor( derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified && !verificationPromptDismissed } } + var displaySearchResults by rememberSaveable { mutableStateOf(false) } + fun handleEvents(event: RoomListEvents) { when (event) { is RoomListEvents.UpdateFilter -> filter = event.newFilter is RoomListEvents.UpdateVisibleRange -> updateVisibleRange(event.range) RoomListEvents.DismissRequestVerificationPrompt -> verificationPromptDismissed = true + RoomListEvents.ToggleSearchResults -> { + if (displaySearchResults) { + filter = "" + } + displaySearchResults =! displaySearchResults + } } } LaunchedEffect(roomSummaries, filter) { - filteredRoomSummaries.value = updateFilteredRoomSummaries(roomSummaries, filter) + mappedRoomSummaries.value = if (roomSummaries.isEmpty()) { + RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList() + } else { + mapRoomSummaries(roomSummaries).toImmutableList() + } + filteredRoomSummaries.value = updateFilteredRoomSummaries(mappedRoomSummaries.value, filter) } val snackbarMessage = handleSnackbarMessage(snackbarDispatcher) return RoomListState( matrixUser = matrixUser.value, - roomList = filteredRoomSummaries.value, + roomList = mappedRoomSummaries.value, filter = filter, + filteredRoomList = filteredRoomSummaries.value, displayVerificationPrompt = displayVerificationPrompt, snackbarMessage = snackbarMessage, hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, displayInvites = invites.isNotEmpty(), + displaySearchResults = displaySearchResults, eventSink = ::handleEvents ) } - private suspend fun updateFilteredRoomSummaries(roomSummaries: List?, filter: String): ImmutableList { - if (roomSummaries.isNullOrEmpty()) { - return RoomListRoomSummaryPlaceholders.createFakeList(16).toImmutableList() - } - val mappedRoomSummaries = mapRoomSummaries(roomSummaries) - return if (filter.isEmpty()) { - mappedRoomSummaries - } else { - mappedRoomSummaries.filter { it.name.contains(filter, ignoreCase = true) } + private fun updateFilteredRoomSummaries(mappedRoomSummaries: ImmutableList, filter: String): ImmutableList { + return when { + filter.isEmpty() -> emptyList() + else -> mappedRoomSummaries.filter { it.name.contains(filter, ignoreCase = true) } }.toImmutableList() } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index 185c7c94a9..6fd629d67f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -26,10 +26,12 @@ import kotlinx.collections.immutable.ImmutableList data class RoomListState( val matrixUser: MatrixUser?, val roomList: ImmutableList, - val filter: String, + val filter: String?, + val filteredRoomList: ImmutableList, val displayVerificationPrompt: Boolean, val hasNetworkConnection: Boolean, val snackbarMessage: SnackbarMessage?, val displayInvites: Boolean, + val displaySearchResults: Boolean, val eventSink: (RoomListEvents) -> Unit ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 1dfa943660..a95456b6a4 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -36,6 +36,8 @@ open class RoomListStateProvider : PreviewParameterProvider { aRoomListState().copy(snackbarMessage = SnackbarMessage(StringR.string.common_verification_complete)), aRoomListState().copy(hasNetworkConnection = false), aRoomListState().copy(displayInvites = true), + aRoomListState().copy(displaySearchResults = true, filter = "", filteredRoomList = persistentListOf()), + aRoomListState().copy(displaySearchResults = true), ) } @@ -43,10 +45,12 @@ internal fun aRoomListState() = RoomListState( matrixUser = MatrixUser(id = UserId("@id:domain"), username = "User#1", avatarData = AvatarData("@id:domain", "U")), roomList = aRoomListRoomSummaryList(), filter = "filter", + filteredRoomList = aRoomListRoomSummaryList(), hasNetworkConnection = true, snackbarMessage = null, displayVerificationPrompt = false, displayInvites = false, + displaySearchResults = false, eventSink = {} ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index 8c591f2c2b..cd42548007 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -64,6 +64,8 @@ import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorVi import io.element.android.features.roomlist.impl.components.RoomListTopBar import io.element.android.features.roomlist.impl.components.RoomSummaryRow import io.element.android.features.roomlist.impl.model.RoomListRoomSummary +import io.element.android.features.roomlist.impl.search.RoomListSearchResultContent +import io.element.android.features.roomlist.impl.search.RoomListSearchResultView import io.element.android.libraries.designsystem.ElementTextStyles import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight @@ -91,15 +93,27 @@ fun RoomListView( onCreateRoomClicked: () -> Unit = {}, onInvitesClicked: () -> Unit = {}, ) { - RoomListContent( - state = state, - modifier = modifier, - onRoomClicked = onRoomClicked, - onOpenSettings = onOpenSettings, - onVerifyClicked = onVerifyClicked, - onCreateRoomClicked = onCreateRoomClicked, - onInvitesClicked = onInvitesClicked, - ) + Column(modifier = modifier) { + ConnectivityIndicatorView(isOnline = state.hasNetworkConnection) + Box { + RoomListContent( + state = state, + onRoomClicked = onRoomClicked, + onOpenSettings = onOpenSettings, + onVerifyClicked = onVerifyClicked, + onCreateRoomClicked = onCreateRoomClicked, + onInvitesClicked = onInvitesClicked, + ) + // This overlaid view will only be visible when state.displaySearchResults is true + RoomListSearchResultView( + state = state, + onRoomClicked = onRoomClicked, + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) + } + } } @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @@ -163,16 +177,14 @@ fun RoomListContent( Scaffold( modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - Column { - ConnectivityIndicatorView(isOnline = state.hasNetworkConnection) - RoomListTopBar( - matrixUser = state.matrixUser, - filter = state.filter, - onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) }, - onOpenSettings = onOpenSettings, - scrollBehavior = scrollBehavior, - ) - } + RoomListTopBar( + matrixUser = state.matrixUser, + areSearchResultsDisplayed = state.displaySearchResults, + onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) }, + onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) }, + onOpenSettings = onOpenSettings, + scrollBehavior = scrollBehavior, + ) }, content = { padding -> Column( @@ -306,7 +318,7 @@ internal fun PreviewRequestVerificationHeaderDark() { } } -private fun RoomListRoomSummary.contentType() = isPlaceholder +internal fun RoomListRoomSummary.contentType() = isPlaceholder @Preview @Composable @@ -322,3 +334,11 @@ internal fun RoomListViewDarkPreview(@PreviewParameter(RoomListStateProvider::cl private fun ContentToPreview(state: RoomListState) { RoomListView(state) } + +@Preview +@Composable +internal fun RoomListSearchResultContentPreview() { + ElementPreviewLight { + RoomListSearchResultContent(state = aRoomListState(), onRoomClicked = {}) + } +} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt index 1d1a4e7c10..11f0616e07 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt @@ -20,57 +20,42 @@ package io.element.android.features.roomlist.impl.components import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.ContentAlpha import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack -import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import io.element.android.features.roomlist.impl.R import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData -import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.MediumTopAppBar import io.element.android.libraries.designsystem.theme.components.Text -import io.element.android.libraries.designsystem.theme.components.TextField -import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.LogCompositions import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.ui.model.MatrixUser +import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.testtags.testTag import io.element.android.libraries.ui.strings.R as StringR +@OptIn(ExperimentalMaterial3Api::class) @Composable fun RoomListTopBar( matrixUser: MatrixUser?, - filter: String, + areSearchResultsDisplayed: Boolean, onFilterChanged: (String) -> Unit, + onToggleSearch: () -> Unit, onOpenSettings: () -> Unit, scrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier, @@ -79,124 +64,26 @@ fun RoomListTopBar( tag = "RoomListScreen", msg = "TopBar" ) - var searchWidgetStateIsOpened by rememberSaveable { mutableStateOf(false) } fun closeFilter() { onFilterChanged("") - searchWidgetStateIsOpened = false } - BackHandler(enabled = searchWidgetStateIsOpened) { + BackHandler(enabled = areSearchResultsDisplayed) { closeFilter() + onToggleSearch() } - if (searchWidgetStateIsOpened) { - SearchRoomListTopBar( - text = filter, - onFilterChanged = onFilterChanged, - onCloseClicked = ::closeFilter, - scrollBehavior = scrollBehavior, - modifier = modifier, - ) - } else { - DefaultRoomListTopBar( - matrixUser = matrixUser, - onOpenSettings = onOpenSettings, - onSearchClicked = { - searchWidgetStateIsOpened = true - }, - scrollBehavior = scrollBehavior, - modifier = modifier, - ) - } -} - -@Composable -fun SearchRoomListTopBar( - text: String, - scrollBehavior: TopAppBarScrollBehavior, - modifier: Modifier = Modifier, - onFilterChanged: (String) -> Unit = {}, - onCloseClicked: () -> Unit = {}, -) { - var filterState by textFieldState(stateValue = text) - val focusRequester = remember { FocusRequester() } - TopAppBar( - modifier = modifier - .nestedScroll(scrollBehavior.nestedScrollConnection), - title = { - TextField( - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), - value = filterState, - textStyle = TextStyle( - fontSize = 17.sp - ), - onValueChange = { - filterState = it - onFilterChanged(it) - }, - placeholder = { - Text( - text = stringResource(id = StringR.string.action_search), - color = MaterialTheme.colorScheme.onBackground.copy(alpha = ContentAlpha.medium) - ) - }, - singleLine = true, - trailingIcon = { - if (text.isNotEmpty()) { - IconButton( - onClick = { - onFilterChanged("") - } - ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = "clear", - tint = MaterialTheme.colorScheme.onBackground - ) - } - } - }, - ) - }, - navigationIcon = { - IconButton( - onClick = { - onCloseClicked() - } - ) { - Icon( - imageVector = Icons.Default.ArrowBack, - contentDescription = "close", - tint = MaterialTheme.colorScheme.onBackground - ) - } - }, - windowInsets = WindowInsets(0.dp) - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - } -} - -@Preview -@Composable -internal fun SearchRoomListTopBarLightPreview() = ElementPreviewLight { SearchRoomListTopBarPreview() } - -@Preview -@Composable -internal fun SearchRoomListTopBarDarkPreview() = ElementPreviewDark { SearchRoomListTopBarPreview() } - -@Composable -private fun SearchRoomListTopBarPreview() { - SearchRoomListTopBar( - text = "Hello", - scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()), + DefaultRoomListTopBar( + matrixUser = matrixUser, + onOpenSettings = onOpenSettings, + onSearchClicked = onToggleSearch, + scrollBehavior = scrollBehavior, + modifier = modifier, ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun DefaultRoomListTopBar( matrixUser: MatrixUser?, @@ -216,21 +103,19 @@ private fun DefaultRoomListTopBar( }, navigationIcon = { if (matrixUser != null) { - IconButton(onClick = {}) { - Avatar(matrixUser.avatarData) + IconButton( + modifier = Modifier.testTag(TestTags.homeScreenSettings), + onClick = onOpenSettings + ) { + Avatar(matrixUser.avatarData, contentDescription = stringResource(StringR.string.common_settings)) } } }, actions = { IconButton( - onClick = onSearchClicked + onClick = onSearchClicked, ) { - Icon(Icons.Default.Search, contentDescription = "search") - } - IconButton( - onClick = onOpenSettings - ) { - Icon(Icons.Default.Settings, contentDescription = "Settings") + Icon(Icons.Default.Search, contentDescription = stringResource(StringR.string.action_search)) } }, scrollBehavior = scrollBehavior, @@ -246,6 +131,7 @@ internal fun DefaultRoomListTopBarLightPreview() = ElementPreviewLight { Default @Composable internal fun DefaultRoomListTopBarDarkPreview() = ElementPreviewDark { DefaultRoomListTopBarPreview() } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun DefaultRoomListTopBarPreview() { DefaultRoomListTopBar( diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt new file mode 100644 index 0000000000..c2baca9a6b --- /dev/null +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt @@ -0,0 +1,201 @@ +/* + * 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.roomlist.impl.search + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.unit.dp +import io.element.android.features.roomlist.impl.RoomListEvents +import io.element.android.features.roomlist.impl.RoomListState +import io.element.android.features.roomlist.impl.components.RoomSummaryRow +import io.element.android.features.roomlist.impl.contentType +import io.element.android.features.roomlist.impl.model.RoomListRoomSummary +import io.element.android.libraries.designsystem.components.button.BackButton +import io.element.android.libraries.designsystem.modifiers.applyIf +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton +import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.TopAppBar +import io.element.android.libraries.designsystem.utils.copy +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.ui.strings.R + +@Composable +internal fun RoomListSearchResultView( + state: RoomListState, + onRoomClicked: (RoomId) -> Unit, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + visible = state.displaySearchResults, + enter = fadeIn(), + exit = fadeOut(), + ) { + Column( + modifier = modifier + .applyIf(state.displaySearchResults, ifTrue = { + // Disable input interaction to underlying views + pointerInput(Unit) {} + }) + ) { + if (state.displaySearchResults) { + RoomListSearchResultContent(state = state, onRoomClicked = onRoomClicked) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +internal fun RoomListSearchResultContent( + state: RoomListState, + onRoomClicked: (RoomId) -> Unit, + modifier: Modifier = Modifier, +) { + val borderColor = MaterialTheme.colorScheme.tertiary + val strokeWidth = 1.dp + fun onBackButtonPressed() { + state.eventSink(RoomListEvents.ToggleSearchResults) + } + fun onRoomClicked(room: RoomListRoomSummary) { + if (room.roomId == null) return + onRoomClicked(room.roomId) + } + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + modifier = Modifier.drawBehind { + drawLine( + color = borderColor, + start = Offset(0f, size.height), + end = Offset(size.width, size.height), + strokeWidth = strokeWidth.value + ) + }, + navigationIcon = { BackButton(onClick = ::onBackButtonPressed) }, + title = { + val filter = state.filter.orEmpty() + val focusRequester = FocusRequester() + TextField( + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + value = filter, + onValueChange = { state.eventSink(RoomListEvents.UpdateFilter(it)) }, + colors = TextFieldDefaults.textFieldColors( + containerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent + ), + trailingIcon = { + if (filter.isNotEmpty()) { + IconButton(onClick = { + state.eventSink(RoomListEvents.UpdateFilter("")) + }) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = stringResource(R.string.action_cancel) + ) + } + } + } + ) + + LaunchedEffect(state.displaySearchResults) { + if (state.displaySearchResults) { + focusRequester.requestFocus() + } + } + }, + windowInsets = TopAppBarDefaults.windowInsets.copy(top = 0) + ) + } + ) { padding -> + val lazyListState = rememberLazyListState() + val visibleRange by remember { + derivedStateOf { + val layoutInfo = lazyListState.layoutInfo + val firstItemIndex = layoutInfo.visibleItemsInfo.firstOrNull()?.index ?: 0 + val size = layoutInfo.visibleItemsInfo.size + firstItemIndex until firstItemIndex + size + } + } + val nestedScrollConnection = remember { + object : NestedScrollConnection { + override suspend fun onPostFling( + consumed: Velocity, + available: Velocity + ): Velocity { + state.eventSink(RoomListEvents.UpdateVisibleRange(visibleRange)) + return super.onPostFling(consumed, available) + } + } + } + Column( + modifier = Modifier + .padding(padding) + ) { + LazyColumn( + modifier = Modifier + .weight(1f) + .nestedScroll(nestedScrollConnection), + state = lazyListState, + ) { + items( + items = state.filteredRoomList, + contentType = { room -> room.contentType() }, + ) { room -> + RoomSummaryRow(room = room, onClick = ::onRoomClicked) + } + } + } + } +} diff --git a/features/roomlist/impl/src/main/res/values-de/translations.xml b/features/roomlist/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..00b1431f00 --- /dev/null +++ b/features/roomlist/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,40 @@ + + + "Alle Chats" + "(Avatar wurde ebenfalls geändert)" + "%1$s hat seinen Avatar geändert" + "Du hast deinen Avatar geändert" + "%1$s hat den Anzeigenamen von %2$s in %3$s geändert" + "Du hast deinen Anzeigenamen von %1$s in %2$s geändert" + "%1$s hat den Anzeigenamen entfernt (war %2$s)" + "Du hast deinen Anzeigenamen entfernt (war %1$s)" + "%1$s hat den Anzeigenamen auf %2$s gesetzt" + "Du hast deinen Anzeigenamen auf %1$s gesetzt" + "%1$s hat den Raum-Avatar geändert" + "Du hast den Raum-Avatar geändert" + "%1$s hat den Raum-Avatar entfernt" + "%1$s hat den Raum erstellt" + "Du hast den Raum erstellt" + "%1$s hat %2$s eingeladen" + "%1$s hat die Einladung angenommen" + "Du hast die Einladung angenommen" + "Du hast %1$s eingeladen" + "%1$s hat dich eingeladen" + "%1$s ist dem Raum beigetreten" + "Du bist dem Raum beigetreten" + "%1$s hat deine Beitrittsanfrage abgelehnt" + "%1$s hat den Raum verlassen" + "Du hast den Raum verlassen" + "%1$s hat den Raumnamen geändert in: %2$s" + "Sie haben den Raumnamen geändert in: %1$s" + "%1$s hat den Raumnamen entfernt" + "Du hast den Raumnamen entfernt" + "%1$s hat die Einladung abgelehnt" + "Du hast die Einladung abgelehnt" + "%1$s hat %2$s entfernt" + "Du hast %1$s entfernt" + "%1$s hat das Thema geändert zu: %2$s" + "Sie haben das Thema geändert zu: %1$s" + "%1$s hat das Raumthema entfernt" + "Du hast das Raumthema entfernt" + \ No newline at end of file diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index e28eb1e362..5dd73980f8 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -112,6 +112,8 @@ class RoomListPresenterTests { withUserState.eventSink.invoke(RoomListEvents.UpdateFilter("t")) val withFilterState = awaitItem() Truth.assertThat(withFilterState.filter).isEqualTo("t") + + cancelAndIgnoreRemainingEvents() } } @@ -168,17 +170,18 @@ class RoomListPresenterTests { val loadedState = awaitItem() // Test filtering with result loadedState.eventSink.invoke(RoomListEvents.UpdateFilter(A_ROOM_NAME.substring(0, 3))) + skipItems(1) // Filter update val withNotFilteredRoomState = awaitItem() Truth.assertThat(withNotFilteredRoomState.filter).isEqualTo(A_ROOM_NAME.substring(0, 3)) - Truth.assertThat(withNotFilteredRoomState.roomList.size).isEqualTo(1) - Truth.assertThat(withNotFilteredRoomState.roomList.first()) + Truth.assertThat(withNotFilteredRoomState.filteredRoomList.size).isEqualTo(1) + Truth.assertThat(withNotFilteredRoomState.filteredRoomList.first()) .isEqualTo(aRoomListRoomSummary) // Test filtering without result withNotFilteredRoomState.eventSink.invoke(RoomListEvents.UpdateFilter("tada")) skipItems(1) // Filter update val withFilteredRoomState = awaitItem() Truth.assertThat(withFilteredRoomState.filter).isEqualTo("tada") - Truth.assertThat(withFilteredRoomState.roomList).isEmpty() + Truth.assertThat(withFilteredRoomState.filteredRoomList).isEmpty() } } diff --git a/features/verifysession/impl/src/main/res/values-de/translations.xml b/features/verifysession/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..e3817c2507 --- /dev/null +++ b/features/verifysession/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,10 @@ + + + "Emojis vergleichen" + "Beweise, dass du es bist, um auf deinen verschlüsselten Nachrichtenverlauf zuzugreifen." + "Ich bin bereit" + "Warten auf Übereinstimmung" + "Sie stimmen nicht überein" + "Sie stimmen überein" + "Verifizierung abgebrochen" + \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values-es/translations.xml b/features/verifysession/impl/src/main/res/values-es/translations.xml index 839c945e24..ccc656e845 100644 --- a/features/verifysession/impl/src/main/res/values-es/translations.xml +++ b/features/verifysession/impl/src/main/res/values-es/translations.xml @@ -1,7 +1,6 @@ "Algo no fue bien. Se agotó el tiempo de espera de la solicitud o se rechazó." - "Verificación cancelada" "Confirma que los emojis que aparecen a continuación coinciden con los que aparecen en tu otra sesión." "Comparar emojis" "Tu nueva sesión ya está verificada. Tienes acceso a tus mensajes cifrados y otros usuarios lo considerarán de confianza." @@ -9,11 +8,12 @@ "Abrir una sesión existente" "Reintentar la verificación" "Estoy listo" - "Comenzar" "Esperando a que coincida" "Compara los emoji, asegurándote de que aparecen en el mismo orden." "No coinciden" "Coinciden" "Acepta la solicitud para iniciar el proceso de verificación en tu otra sesión para continuar." "A la espera de aceptar la solicitud" + "Verificación cancelada" + "Comenzar" \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values-it/translations.xml b/features/verifysession/impl/src/main/res/values-it/translations.xml index 3d8a46d581..1bf0e87ea9 100644 --- a/features/verifysession/impl/src/main/res/values-it/translations.xml +++ b/features/verifysession/impl/src/main/res/values-it/translations.xml @@ -1,7 +1,6 @@ "C\'è qualcosa che non va. La richiesta è scaduta o è stata rifiutata." - "Verifica annullata" "Verifica che gli emoji sottostanti corrispondano a quelli mostrati nell\'altra sessione." "Confronta le emoji" "La tua nuova sessione è ora verificata. Ha accesso ai tuoi messaggi crittografati e gli altri utenti la vedranno come attendibile." @@ -9,11 +8,12 @@ "Apri una sessione esistente" "Riprova la verifica" "Sono pronto" - "Inizia" "In attesa di un riscontro" "Confronta le emoji uniche, assicurandoti che appaiano nello stesso ordine." "Non corrispondono" "Corrispondono" "Accetta la richiesta di avviare il processo di verifica nell\'altra sessione per continuare." "In attesa di accettare la richiesta" + "Verifica annullata" + "Inizia" \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values-ro/translations.xml b/features/verifysession/impl/src/main/res/values-ro/translations.xml index f2bade56fc..3ad0de6e56 100644 --- a/features/verifysession/impl/src/main/res/values-ro/translations.xml +++ b/features/verifysession/impl/src/main/res/values-ro/translations.xml @@ -1,7 +1,6 @@ "Ceva nu este în regulă. Fie cererea a expirat, fie a fost respinsă." - "Verificare anulată" "Confirmați că emoticoanele de mai jos se potrivesc cu cele afișate în cealaltă sesiune." "Comparați emoticoanele" "Noua dumneavoastră sesiune este acum verificată. Are acces la mesajele dumneavoastră criptate, iar alți utilizatori vă vor vedea ca fiind de încredere." @@ -9,11 +8,12 @@ "Deschideți o sesiune existentă" "Reîncercați verificarea" "Sunt pregătit" - "Începeți" "Se așteaptă confirmarea" "Comparăți emoticoalene asigurându-vă că apar în aceeași ordine." "Nu se potrivesc" "Se potrivesc" "Acceptați solicitarea de a începe procesul de verificare în cealaltă sesiune pentru a continua." "Se așteptă acceptarea cererii" + "Verificare anulată" + "Începeți" \ No newline at end of file diff --git a/features/verifysession/impl/src/main/res/values/localazy.xml b/features/verifysession/impl/src/main/res/values/localazy.xml index fd81d104fb..c217f0d2a4 100644 --- a/features/verifysession/impl/src/main/res/values/localazy.xml +++ b/features/verifysession/impl/src/main/res/values/localazy.xml @@ -1,7 +1,6 @@ "Something doesn’t seem right. Either the request timed out or the request was denied." - "Verification cancelled" "Confirm that the emojis below match those shown on your other session." "Compare emojis" "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted." @@ -9,11 +8,12 @@ "Open an existing session" "Retry verification" "I am ready" - "Start" "Waiting to match" "Compare the unique emoji, ensuring they appear in the same order." "They don’t match" "They match" "Accept the request to start the verification process in your other session to continue." "Waiting to accept request" + "Verification cancelled" + "Start" \ No newline at end of file diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt new file mode 100644 index 0000000000..302978066c --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/ErrorFlow.kt @@ -0,0 +1,22 @@ +/* + * 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.libraries.core.coroutine + +import kotlinx.coroutines.flow.flow + +/** Create a Flow emitting a single error event. It should be useful for tests. */ +fun errorFlow(throwable: Throwable) = flow { throw throwable } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt index 05257ece7f..e5dcc0c5ff 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/Avatar.kt @@ -41,7 +41,11 @@ import io.element.android.libraries.designsystem.theme.components.Text import timber.log.Timber @Composable -fun Avatar(avatarData: AvatarData, modifier: Modifier = Modifier) { +fun Avatar( + avatarData: AvatarData, + modifier: Modifier = Modifier, + contentDescription: String? = null, +) { val commonModifier = modifier .size(avatarData.size.dp) .clip(CircleShape) @@ -54,6 +58,7 @@ fun Avatar(avatarData: AvatarData, modifier: Modifier = Modifier) { ImageAvatar( avatarData = avatarData, modifier = commonModifier, + contentDescription = contentDescription, ) } } @@ -62,13 +67,14 @@ fun Avatar(avatarData: AvatarData, modifier: Modifier = Modifier) { private fun ImageAvatar( avatarData: AvatarData, modifier: Modifier = Modifier, + contentDescription: String? = null, ) { AsyncImage( model = avatarData, onError = { Timber.e("TAG", "Error $it\n${it.result}", it.result.throwable) }, - contentDescription = null, + contentDescription = contentDescription, contentScale = ContentScale.Crop, placeholder = debugPlaceholderAvatar(), modifier = modifier @@ -89,7 +95,7 @@ private fun InitialsAvatar( end = Offset(100f, 0f) ) Box( - modifier.background(brush = initialsGradient) + modifier.background(brush = initialsGradient), ) { Text( modifier = Modifier.align(Alignment.Center), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt new file mode 100644 index 0000000000..a18d0ef3ed --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/ApplyIf.kt @@ -0,0 +1,45 @@ +/* + * 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.libraries.designsystem.modifiers + +import android.annotation.SuppressLint +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.platform.debugInspectorInfo + +/** + * Applies the [ifTrue] modifier when the [condition] is true, [ifFalse] otherwise. + */ +@SuppressLint("UnnecessaryComposedModifier") // It's actually necessary due to the `@Composable` lambdas +fun Modifier.applyIf( + condition: Boolean, + ifTrue: @Composable Modifier.() -> Modifier, + ifFalse: @Composable (Modifier.() -> Modifier)? = null +): Modifier = + composed( + inspectorInfo = debugInspectorInfo { + name = "applyIf" + value = condition + } + ) { + when { + condition -> then(ifTrue(Modifier)) + ifFalse != null -> then(ifFalse(Modifier)) + else -> this + } + } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt new file mode 100644 index 0000000000..9675c54a20 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/modifiers/CircularReveal.kt @@ -0,0 +1,106 @@ +/* + * 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.libraries.designsystem.modifiers + +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.updateTransition +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.drawscope.clipPath +import androidx.compose.ui.platform.debugInspectorInfo +import kotlin.math.sqrt + +// Note: these modifiers come from https://gist.github.com/darvld/eb3844474baf2f3fc6d3ab44a4b4b5f8 + +/** + * A modifier that clips the composable content using an animated circle. The circle will + * expand/shrink with an animation whenever [visible] changes. + * + * For more fine-grained control over the transition, see this method's overload, which allows passing + * a [State] object to control the progress of the reveal animation. + * + * By default, the circle is centered in the content, but custom positions may be specified using + * [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom).*/ +fun Modifier.circularReveal( + visible: Boolean, + showScrim: Boolean = false, + revealFrom: Offset = Offset(0.5f, 0.5f), +): Modifier = composed( + factory = { + val factor = updateTransition(visible, label = "Visibility") + .animateFloat(label = "revealFactor") { if (it) 1f else 0f } + + circularReveal(factor, showScrim, revealFrom) + }, + inspectorInfo = debugInspectorInfo { + name = "circularReveal" + properties["visible"] = visible + properties["revealFrom"] = revealFrom + } +) + +/** + * A modifier that clips the composable content using a circular shape. The radius of the circle + * will be determined by the [transitionProgress]. + * + * The values of the progress should be between 0 and 1. + * + * By default, the circle is centered in the content, but custom positions may be specified using + * [revealFrom]. Specified offsets should be between 0 (left/top) and 1 (right/bottom). + * */ +fun Modifier.circularReveal( + transitionProgress: State, + showScrim: Boolean = false, + revealFrom: Offset = Offset(0.5f, 0.5f) +): Modifier { + return drawWithCache { + val path = Path() + val center = revealFrom.mapTo(size) + val radius = calculateRadius(revealFrom, size) + val scrimColor = if (showScrim) + Color.Gray + else + Color.Transparent + + path.addOval(Rect(center, radius * transitionProgress.value)) + + onDrawWithContent { + if (showScrim) { + drawRect(scrimColor, alpha = transitionProgress.value * 0.75f) + } + clipPath(path) { this@onDrawWithContent.drawContent() } + } + } +} + +private fun Offset.mapTo(size: Size): Offset { + return Offset(x * size.width, y * size.height) +} + +private fun calculateRadius(normalizedOrigin: Offset, size: Size) = with(normalizedOrigin) { + val x = (if (x > 0.5f) x else 1 - x) * size.width + val y = (if (y > 0.5f) y else 1 - y) * size.height + + sqrt(x * x + y * y) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt new file mode 100644 index 0000000000..33baf19dce --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/WindowInsetsExtension.kt @@ -0,0 +1,39 @@ +/* + * 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.libraries.designsystem.utils + +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection + +@Composable +fun WindowInsets.copy( + top: Int? = null, + right: Int? = null, + bottom: Int? = null, + left: Int? = null +): WindowInsets { + val density = LocalDensity.current + val direction = LocalLayoutDirection.current + return WindowInsets( + top = top ?: this.getTop(density), + right = right ?: this.getRight(density, direction), + bottom = bottom ?: this.getBottom(density), + left = left ?: this.getLeft(density, direction) + ) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 41cd874098..87afee55da 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -68,6 +68,10 @@ interface MatrixRoom : Closeable { suspend fun redactEvent(eventId: EventId, reason: String? = null): Result + suspend fun ignoreUser(userId: UserId): Result + + suspend fun unignoreUser(userId: UserId): Result + suspend fun leave(): Result suspend fun acceptInvitation(): Result diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 4efe35f6b5..95094cfdc6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -38,6 +38,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.UpdateSummary import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown +import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember class RustMatrixRoom( override val sessionId: SessionId, @@ -53,6 +54,16 @@ class RustMatrixRoom( private var _membersStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) + private val timeline by lazy { + RustMatrixTimeline( + matrixRoom = this, + innerRoom = innerRoom, + slidingSyncRoom = slidingSyncRoom, + coroutineScope = coroutineScope, + coroutineDispatchers = coroutineDispatchers + ) + } + override fun syncUpdateFlow(): Flow { return slidingSyncUpdateFlow .filter { @@ -65,13 +76,7 @@ class RustMatrixRoom( } override fun timeline(): MatrixTimeline { - return RustMatrixTimeline( - matrixRoom = this, - innerRoom = innerRoom, - slidingSyncRoom = slidingSyncRoom, - coroutineScope = coroutineScope, - coroutineDispatchers = coroutineDispatchers - ) + return timeline } override fun close() { @@ -125,11 +130,11 @@ class RustMatrixRoom( _membersStateFlow.value = MatrixRoomMembersState.Pending runCatching { innerRoom.members().map(RoomMemberMapper::map) - }.onSuccess { + }.map { _membersStateFlow.value = MatrixRoomMembersState.Ready(it) }.onFailure { _membersStateFlow.value = MatrixRoomMembersState.Error(it) - }.map { } + } } override suspend fun userDisplayName(userId: UserId): Result = @@ -195,4 +200,19 @@ class RustMatrixRoom( } } + override suspend fun ignoreUser(userId: UserId): Result { + return runCatching { + getRustMember(userId)?.ignore() ?: error("No member with userId $userId exists in room $roomId") + } + } + + override suspend fun unignoreUser(userId: UserId): Result { + return runCatching { + getRustMember(userId)?.unignore() ?: error("No member with userId $userId exists in room $roomId") + } + } + + private fun getRustMember(userId: UserId): RustRoomMember? { + return innerRoom.members().find { it.userId() == userId.value } + } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 3222cda704..bffaa30886 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -151,9 +151,9 @@ class RustMatrixTimeline( requiredState = listOf( RequiredState(key = "m.room.canonical_alias", value = ""), RequiredState(key = "m.room.topic", value = ""), - RequiredState(key = "m.room.name", value = ""), - RequiredState(key = "m.room.join_rule", value = ""), + RequiredState(key = "m.room.join_rules", value = ""), ), + //TODO allow configuration timelineLimit = 20.toUInt() ) val result = slidingSyncRoom.subscribeAndAddTimelineListener(timelineListener, settings) diff --git a/libraries/matrix/test/build.gradle.kts b/libraries/matrix/test/build.gradle.kts index 9f1d35112f..6d9ca1eb8e 100644 --- a/libraries/matrix/test/build.gradle.kts +++ b/libraries/matrix/test/build.gradle.kts @@ -23,6 +23,7 @@ android { } dependencies { + api(projects.libraries.core) api(projects.libraries.matrix.api) api(libs.coroutines.core) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 0d3a9bb2eb..c0f4a7ee20 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -22,7 +22,6 @@ import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState -import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID @@ -48,6 +47,8 @@ class FakeMatrixRoom( private val matrixTimeline: MatrixTimeline = FakeMatrixTimeline(), ) : MatrixRoom { + private var ignoreResult: Result = Result.success(Unit) + private var unignoreResult: Result = Result.success(Unit) private var userDisplayNameResult = Result.success(null) private var userAvatarUrlResult = Result.success(null) private var updateMembersResult: Result = Result.success(Unit) @@ -116,6 +117,10 @@ class FakeMatrixRoom( return Result.success(Unit) } + override suspend fun ignoreUser(userId: UserId): Result = ignoreResult + + override suspend fun unignoreUser(userId: UserId): Result = unignoreResult + override suspend fun leave(): Result = leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit) override suspend fun acceptInvitation(): Result { isInviteAccepted = true @@ -157,4 +162,11 @@ class FakeMatrixRoom( rejectInviteResult = result } + fun givenIgnoreResult(result: Result) { + ignoreResult = result + } + + fun givenUnIgnoreResult(result: Result) { + unignoreResult = result + } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index 3c82496626..242ca6a67d 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -119,7 +119,7 @@ private fun NotificationData?.orDefault(roomId: RoomId, eventId: EventId): Notif isRemote = false, localSendState = null, reactions = emptyList(), - sender = UserId(""), + sender = UserId("@user:domain"), senderProfile = ProfileTimelineDetails.Unavailable, timestamp = System.currentTimeMillis(), content = MessageContent( diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..4d663c57aa --- /dev/null +++ b/libraries/push/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,30 @@ + + + "Laute Benachrichtigungen" + "Beitreten" + "Ablehnen" + "Neue Nachrichten" + "Als gelesen markieren" + "%1$s: %2$s" + "%1$s: %2$s %3$s" + "%1$s in %2$s und %3$s" + + "%1$s: %2$d Nachricht" + "%1$s: %2$d Nachrichten" + + + "%d Einladung" + "%d Einladungen" + + + "%d neue Nachricht" + "%d neue Nachrichten" + + + "%d Raum" + "%d Räume" + + "Google-Dienste" + "Keine gültigen Google Play-Dienste gefunden. Benachrichtigungen funktionieren möglicherweise nicht richtig." + "Schnellantwort" + \ No newline at end of file diff --git a/libraries/push/impl/src/main/res/values-es/translations.xml b/libraries/push/impl/src/main/res/values-es/translations.xml new file mode 100644 index 0000000000..31df508dc3 --- /dev/null +++ b/libraries/push/impl/src/main/res/values-es/translations.xml @@ -0,0 +1,4 @@ + + + "Respuesta rápida" + \ No newline at end of file diff --git a/libraries/push/impl/src/main/res/values-it/translations.xml b/libraries/push/impl/src/main/res/values-it/translations.xml new file mode 100644 index 0000000000..32957fe2ce --- /dev/null +++ b/libraries/push/impl/src/main/res/values-it/translations.xml @@ -0,0 +1,4 @@ + + + "Risposta rapida" + \ No newline at end of file diff --git a/libraries/push/impl/src/main/res/values-ro/translations.xml b/libraries/push/impl/src/main/res/values-ro/translations.xml new file mode 100644 index 0000000000..47280228a4 --- /dev/null +++ b/libraries/push/impl/src/main/res/values-ro/translations.xml @@ -0,0 +1,54 @@ + + + "Apel" + "Ascultare evenimente" + "Notificări zgomotoase" + "Notificări silențioase" + "** Trimiterea eșuată - vă rugăm să deschideți camera" + "Alăturați-vă" + "Respingeți" + "Mesaje noi" + "Marcați ca citit" + "Eu" + "Vizualizați o notificare! Faceți clic pe mine!" + "%1$s: %2$s" + "%1$s: %2$s %3$s" + "%1$s și %2$s" + "%1$s în %2$s" + "%1$s în %2$s și %3$s" + + "%1$s: %2$d mesaj" + + "%1$s: %2$d mesaje" + + + "%d notificare" + + "%d notificări" + + + "%d invitație" + + "%d invitații" + + + "%d mesaj nou" + + "%d mesaje noi" + + + "%d mesaj notificat necitit" + + "%d mesaje notificate necitite" + + + "%d cameră" + + "%d camere" + + "Alegeți modul de primire a notificărilor" + "Sincronizare în fundal" + "Servicii Google" + "Nu au fost găsite servicii Google Play valide. Este posibil ca notificările să nu funcționeze corect." + "Raspuns rapid" + diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index 3a11adb5d3..d38bf7d8dd 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -9,7 +9,6 @@ "Reject" "New Messages" "Mark as read" - "Quick reply" "Me" "You are viewing the notification! Click me!" "%1$s: %2$s" @@ -45,4 +44,5 @@ "Background synchronization" "Google Services" "No valid Google Play Services found. Notifications may not work properly." + "Quick reply" \ No newline at end of file diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index a254a636ef..df12c755e3 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -38,6 +38,11 @@ object TestTags { */ val changeServerServer = TestTag("change_server-server") val changeServerContinue = TestTag("change_server-continue") + + /** + * Room list / Home screen. + */ + val homeScreenSettings = TestTag("home_screen-settings") } diff --git a/libraries/textcomposer/src/main/res/values-de/translations.xml b/libraries/textcomposer/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..f016d4bdba --- /dev/null +++ b/libraries/textcomposer/src/main/res/values-de/translations.xml @@ -0,0 +1,5 @@ + + + "Nachricht…" + "Link setzen" + \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 22c60db481..4093aae962 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -1,5 +1,106 @@ + "Passwort ausblenden" + "Dateien senden" + "Passwort anzeigen" + "Benutzermenü" + "Zurück" + "Abbrechen" + "Foto auswählen" + "Schließen" + "Verifizierung abschließen" "Bestätigen" + "Kopieren" + "Link kopieren" + "Erstellen" + "Ablehnen" + "Deaktivieren" + "Fertig" + "Bearbeiten" + "Aktivieren" + "Einladen" + "Freunde zu %1$s einladen" + "Einladungen" + "Mehr erfahren" + "Verlassen" + "Raum verlassen" + "Weiter" + "Nein" + "OK" + "Schnellantwort" + "Zitieren" + "Entfernen" + "Fehler melden" + "Inhalt melden" + "Erneut versuchen" + "Entschlüsselung erneut versuchen" + "Speichern" + "Suchen" + "Senden" + "Nachricht senden" + "Teilen" + "Link teilen" + "Überspringen" + "Foto aufnehmen" + "Ja" + "Über" + "Analytik" + "Audio" + "Blasen" + "Entschlüsselungsfehler" + "Entwickleroptionen" + "(bearbeitet)" + "Verschlüsselung aktiviert" + "Fehler" + "Datei" + "GIF" + "Bild" + "Link in Zwischenablage kopiert" + "Nachricht" + "Modern" + "Offline" + "Passwort" + "Reaktionen" + "Sicherheit" + "Einstellungen" + "Sticker" + "Erfolg" + "Vorschläge" + "Thema" + "Entschlüsselung nicht möglich" + "Nicht unterstütztes Ereignis" + "Benutzername" + "Verifizierung abgebrochen" + "Verifizierung abgeschlossen" + "Video" + "Warten…" + "Warnung" + "Aktivitäten" + "Flaggen" + "Essen & Trinken" + "Tiere & Natur" + "Objekte" + "Smileys & Personen" + "Reisen & Orte" + "Symbole" + "Fehler beim Laden der Nachrichten" + "Entschuldigung, ein Fehler ist aufgetreten." + "%1$s Android" + + "%1$d Mitglied" + "%1$d Mitglieder" + + "Grund für die Meldung dieses Inhalts" + "Dies ist der Anfang von %1$s." + "Neu" + "Blockieren" + "Nutzer blockieren" + "Blockierung aufheben" + "Nutzer entblockieren" + "Erkennungsschwelle" + "Version: %1$s (%2$s)" "de" + "Fehler" + "Erfolg" + "Nutzer blockieren" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-es/translations.xml b/libraries/ui-strings/src/main/res/values-es/translations.xml index 564ede34a8..b430048f79 100644 --- a/libraries/ui-strings/src/main/res/values-es/translations.xml +++ b/libraries/ui-strings/src/main/res/values-es/translations.xml @@ -94,8 +94,6 @@ "Vídeo" "Esperando…" "Confirmar" - "Error" - "Terminado" "Atención" "Actividades" "Banderas" @@ -129,7 +127,6 @@ "Este es el principio de %1$s." "Este es el principio de esta conversación." "Nuevos" - "Bloquear usuario" "Marque si quieres ocultar todos los mensajes actuales y futuros de este usuario" "Bloquear" "Los usuarios bloqueados no podrán enviarte mensajes y se ocultarán todos sus mensajes. Puede revertir esta acción en cualquier momento." @@ -142,4 +139,7 @@ "General" "Versión: %1$s (%2$s)" "es" + "Error" + "Terminado" + "Bloquear usuario" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-it/translations.xml b/libraries/ui-strings/src/main/res/values-it/translations.xml index a8ec05115a..8fbe54dae2 100644 --- a/libraries/ui-strings/src/main/res/values-it/translations.xml +++ b/libraries/ui-strings/src/main/res/values-it/translations.xml @@ -94,8 +94,6 @@ "Video" "In attesa…" "Conferma" - "Errore" - "Operazione riuscita" "Attenzione" "Attività" "Bandiere" @@ -129,7 +127,6 @@ "Questo è l\'inizio di %1$s." "Questo è l\'inizio della conversazione." "Nuovo" - "Blocca utente" "Seleziona se vuoi nascondere tutti i messaggi attuali e futuri di questo utente" "Blocca" "Gli utenti bloccati non saranno in grado di inviarti nuovi messaggi e tutti quelli già esistenti saranno nascosti. Potrai annullare questa azione in qualsiasi momento." @@ -142,4 +139,7 @@ "Generali" "Versione: %1$s (%2$s)" "it" + "Errore" + "Operazione riuscita" + "Blocca utente" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values-ro/translations.xml b/libraries/ui-strings/src/main/res/values-ro/translations.xml index 091dd7f36d..2a9dc93490 100644 --- a/libraries/ui-strings/src/main/res/values-ro/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ro/translations.xml @@ -4,8 +4,10 @@ "Trimiteți fișiere" "Afișați parola" "Meniu utilizator" + "Acceptați" "Înapoi" "Anulați" + "Alegeți o fotografie" "Ștergeți" "Închideți" "Verificare completă" @@ -13,13 +15,16 @@ "Continuați" "Copiați" "Copiați linkul" + "Creați" "Creați o cameră" + "Refuzați" "Dezactivați" "Efectuat" "Editați" "Activați" "Invitați" "Invitați prieteni în %1$s" + "Invitații" "Aflați mai multe" "Părăsiți" "Părăsiți camera" @@ -38,15 +43,18 @@ "Salvați" "Căutați" "Trimiteți" + "Trimiteți mesajul" "Partajați" "Partajați linkul" "Omiteți" "Începeți" "Începeți discuția" "Începeți verificarea" + "Faceți o fotografie" "Vedeți sursă" "Da" "Despre" + "Analitice" "Audio" "Baloane" "Se creează camera…" @@ -94,8 +102,6 @@ "Video" "Se aşteaptă…" "Confirmare" - "Eroare" - "Succes" "Avertisment" "Activități" "Steaguri" @@ -131,7 +137,17 @@ "Acesta este începutul conversației %1$s." "Acesta este începutul acestei conversații." "Nou" - "Blocați utilizatorul" + "Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime." + "Nu"" înregistrăm sau profilăm datele contului" + "Ajutați-ne să identificăm problemele și să îmbunătățim %1$s prin partajarea datelor de utilizare anonime." + "Puteți citi toate condițiile noastre %1$s." + "aici" + "Puteți dezactiva această opțiune oricând din setări" + "Nu"" împărtășim informații cu terți" + "Ajutați la îmbunătățirea %1$s" + "Puteți citi toate condițiile noastre %1$s." + "aici" + "Partajați datele analitice" "Confirmați că doriți să ascundeți toate mesajele curente și viitoare de la acest utilizator" "Blocați" "Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând." @@ -144,4 +160,7 @@ "General" "Versiunea: %1$s (%2$s)" "ro" + "Eroare" + "Succes" + "Blocați utilizatorul" \ No newline at end of file diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index bfd2be2000..5df318b7ae 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -102,8 +102,6 @@ "Video" "Waiting…" "Confirmation" - "Error" - "Success" "Warning" "Activities" "Flags" @@ -122,60 +120,15 @@ "Are you sure that you want to leave this room? This room is not public and you will not be able to rejoin without an invite." "Are you sure that you want to leave the room?" "%1$s Android" - "Call" - "Listening for events" - "Noisy notifications" - "Silent notifications" - "** Failed to send - please open room" - "Join" - "Reject" - "New Messages" - "Mark as read" - "Quick reply" - "Me" - "You are viewing the notification! Click me!" - "%1$s: %2$s" - "%1$s: %2$s %3$s" - "%1$s and %2$s" - "%1$s in %2$s" - "%1$s in %2$s and %3$s" "%1$d member" "%1$d members" - - "%1$s: %2$d message" - "%1$s: %2$d messages" - - - "%d notification" - "%d notifications" - - - "%d invitation" - "%d invitations" - - - "%d new message" - "%d new messages" - - - "%d unread notified message" - "%d unread notified messages" - - - "%d room" - "%d rooms" - "%1$d room change" "%1$d room changes" "Rageshake to report bug" - "Choose how to receive notifications" - "Background synchronization" - "Google Services" - "No valid Google Play Services found. Notifications may not work properly." "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?" "This message will be reported to your homeserver’s administrator. They will not be able to read any encrypted messages." "Reason for reporting this content" @@ -193,7 +146,6 @@ "You can read all our terms %1$s." "here" "Share analytics data" - "Block user" "Check if you want to hide all current and future messages from this user" "Block" "Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime." @@ -207,4 +159,7 @@ "Version: %1$s (%2$s)" "en" "en" + "Error" + "Success" + "Block user" \ No newline at end of file diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..de77e469f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8c78efed997712873719636ff1f8479d38d317e443c5d4340346d4328de9c0d +size 28744 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..de77e469f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8c78efed997712873719636ff1f8479d38d317e443c5d4340346d4328de9c0d +size 28744 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewLightPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f269338d7c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f62537b4aa79f501908d1fff9d269139e88db5f6dbaedcf63670a4f71b47bff +size 28303 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewLightPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f269338d7c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members.details_null_DefaultGroup_RoomMemberDetailsViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f62537b4aa79f501908d1fff9d269139e88db5f6dbaedcf63670a4f71b47bff +size 28303 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_5,NEXUS_5,1.0,en].png index e9e352cb20..4ec6a0f362 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:354452861d1e006a8bfa744251ffdaf15088e0bb181a53043f121e606233d648 -size 67340 +oid sha256:a8a9186d741d251dc8bdf6bcf71d395e8c057c310585aeb8911609eaaacce842 +size 64550 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_6,NEXUS_5,1.0,en].png index 8dd1cd9116..4ec6a0f362 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsDarkPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1cb6bac9e72b956d0cffde807340e36a7a4d6873d4d7337995b53e82769c4f9 -size 68135 +oid sha256:a8a9186d741d251dc8bdf6bcf71d395e8c057c310585aeb8911609eaaacce842 +size 64550 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_5,NEXUS_5,1.0,en].png index 815e64ba9f..562eb63427 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc56e29c26250c6fef312ec4c5fdfaa2f63159fd0565bd37522b46c7ff67906a -size 61924 +oid sha256:94a589b556a750485fd61af6446457c98c5f112d0d013cd78016b88a8829e6a8 +size 58643 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_6,NEXUS_5,1.0,en].png index 687b034bee..562eb63427 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_6,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl_null_DefaultGroup_RoomDetailsLightPreview_0_null_6,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6faff25a1c187dee59d0dcf0affd5029251512efb6eff7fc2d41d34bace2061 -size 62356 +oid sha256:94a589b556a750485fd61af6446457c98c5f112d0d013cd78016b88a8829e6a8 +size 58643 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarDarkPreview_0_null,NEXUS_5,1.0,en].png index 167599e090..ec4ab9f3e7 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bca09418758a20493dae2e73a747449af8a448ad3a3cc4c5aae2e08a425f3fb -size 13464 +oid sha256:b5f2b24a19ca49b3e6e34ccd65d2bdba72d0384104931bda92e191959d58c5c3 +size 12697 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarLightPreview_0_null,NEXUS_5,1.0,en].png index 7efc250776..ddbf6df0b5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl.components_null_DefaultGroup_DefaultRoomListTopBarLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e50325c75193e47958862ea9cb515d7c84d2c47a00b01256fc244319780c107f -size 12425 +oid sha256:c4fa32eb24a0cc51b9b19c6f24a7d3d59aae65f1f30a43b1a6d70b3ed3e2154d +size 11716 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListSearchResultContentPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListSearchResultContentPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b113ba20f0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListSearchResultContentPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97b11203623c0c98da88dfedf85cb80d1f35cc55da570e12061b1385691bf1f0 +size 27758 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 4e36616412..e4b5f95b8d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fc272268179409483fc5fad89aa00714a6811c63e478b5c696d4ab0338ce6bf -size 37781 +oid sha256:3cdb131c68de1fce5a3319151e39148e9f3a71c7bc3984e89ec0a80abf0f7288 +size 37044 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 0d3bec9cf5..1dcb6593b1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d729c75d73d1837b365c8332b6e4203cb2492f6c5c4af06741a4bd2e818daebb -size 60667 +oid sha256:8b265978c4db7b266fd07d56364eccafba1cd765ed9bf6d5a03b1584e173ba6a +size 59936 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 4e36616412..e4b5f95b8d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0fc272268179409483fc5fad89aa00714a6811c63e478b5c696d4ab0338ce6bf -size 37781 +oid sha256:3cdb131c68de1fce5a3319151e39148e9f3a71c7bc3984e89ec0a80abf0f7288 +size 37044 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index c454377b9b..52c50a59a1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56a253a7981823ca0fc7b653f9e83d29d1f259fea43ca4cee5760fa863306f2c -size 39847 +oid sha256:ccf989dac7fad3cc70443d96e1ebd519463a6559ed0795ae2a1ffbaf91bdfe7c +size 39092 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index 2063e4eeee..e7c9ed7df3 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b1222e1ef2d0739caa410540bf74fea681ef2e05bb04b976e50f1fe5256d613 -size 39762 +oid sha256:c2a23141c6cc8aa6e7e5f0757bda4d1117bf7f752412ccc4649ea560113b3e3f +size 39030 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..069af4cecf --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9095546c30bb5bc9800c852456fa9cd82d14e873a5e1488d29496af088e951da +size 4882 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9861b7572e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cfe321535e1ce223a0460435123dc59e74c436bdd8696cf4bdb2169f511832b +size 28541 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 4a51d9cddc..2aa0933d88 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b69859fa3ee38d2b7f7415b87738db65dc6dac3d2fabddc1f1346b0b64932b -size 37329 +oid sha256:a2a261b30866af95b856ee1e7d6ac2cbe2d638cf80277645f361b043d2f94e60 +size 36658 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 92729ff9ed..2fcea72749 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f61d4fc4d5af166e1761f004e7d80934eef24a582c96c590c769ed1fb13041a4 -size 59489 +oid sha256:0cdd1fad4b3db78fb8599785334366e3bfbaf5dead7990f396f8752862ab9e98 +size 58987 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 4a51d9cddc..2aa0933d88 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:43b69859fa3ee38d2b7f7415b87738db65dc6dac3d2fabddc1f1346b0b64932b -size 37329 +oid sha256:a2a261b30866af95b856ee1e7d6ac2cbe2d638cf80277645f361b043d2f94e60 +size 36658 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index 1287c0ec4f..2664376698 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1bf9529823b4e04261b1528b2309c549f1d3aad80c542059f11c1b26a2979b04 -size 39359 +oid sha256:1ba3d8a5cfbd102dc6df8f511eb14663170a6d550ee5e65702bc1f3fce3efd14 +size 38657 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 273e7af0cf..5793650c81 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebb15a58e5d3497b2819f5a6b6fde88962f01cc2ae02d4b8cda0e45e1dde677f -size 39314 +oid sha256:d427b479f9eb6227bb92aecd997bab97a735ef85dd279a575d777e754effd258 +size 38639 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..6c14f8062c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d463ab6d045cc310973c5ff900cf4d9ae04e93cb1c7eac3f9b2aa0ea9b827cee +size 4815 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b113ba20f0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97b11203623c0c98da88dfedf85cb80d1f35cc55da570e12061b1385691bf1f0 +size 27758